This commit is contained in:
2026-01-27 12:11:00 -08:00
parent 474e591f58
commit 34704eaf84
43 changed files with 3603 additions and 217 deletions
+5 -1
View File
@@ -1,3 +1,7 @@
specs/
specs--completed/
CLAUDE.md
dist/
CLAUDE.md
plan2code-loop/dist
plan2code-loop/node_modules
plan2code-loop/package-lock.json
+179
View File
@@ -0,0 +1,179 @@
# AGENTS.md
This file provides guidance to AI coding agents like Claude Code (claude.ai/code), Cursor AI, Codex, Gemini CLI, GitHub Copilot, and other AI coding assistants when working with code in this repository.
## Project Overview
Plan2Code is a structured 4-step workflow methodology for AI-assisted software development. It provides prompt templates that can be installed globally or per-project for various AI coding tools (Claude Code, Cursor, Copilot, Continue, Windsurf, Codeium).
**Version:** Check `version.json` for current version
**Author:** Justin Parker
**License:** MIT
## Architecture
```
plan2code/
├── src/ # Source workflow prompts (8 markdown files)
├── plan2code-loop/ # Autonomous loop CLI tool (Node.js/TypeScript)
│ ├── src/ # TypeScript source
│ └── dist/ # Built output (tsup)
├── dist/ # Generated distribution files (auto-generated)
│ ├── global-commands/ # For global installation (~/.claude/, etc.)
│ └── local-commands/ # For per-project installation (.claude/, etc.)
├── docs/ # Documentation and assets
├── specs/ # Feature specs (if any in-progress)
├── install.js # Interactive installer (Node.js)
├── version.json # Version metadata
└── README.md # User documentation
```
## Key Files
| File | Purpose |
|------|---------|
| `install.js` | Main installer - generates and installs workflow files to AI tool directories |
| `src/plan2code-*.md` | Source workflow prompts (the "source of truth") |
| `version.json` | Version metadata (name, version, description) |
| `QUICK-REFERENCE.md` | User quick-reference card |
## Workflow Prompts (in `src/`)
| File | Step | Purpose |
|------|------|---------|
| `plan2code---init.md` | Init | Generate AGENTS.md for projects |
| `plan2code---init-update.md` | Update | Update AGENTS.md with learnings |
| `plan2code---quick-task.md` | 0 | Lightweight planning for small tasks |
| `plan2code-1--plan.md` | 1 | Requirements analysis & architecture |
| `plan2code-1b--revise-plan.md` | 1b | Mid-implementation revisions |
| `plan2code-2--document.md` | 2 | Create implementation specs |
| `plan2code-3--implement.md` | 3 | Execute implementation (phase by phase) |
| `plan2code-4--finalize.md` | 4 | Validate, summarize, archive |
## Plan2Code Loop (`plan2code-loop/`)
A separate Node.js CLI tool that autonomously implements specs by looping through tasks.
### Loop Architecture
The loop uses an **LLM-driven discovery** approach:
- Node app just orchestrates iterations and parses completion markers
- The LLM reads spec files (`overview.md`, `phase-X.md`) to discover tasks
- The LLM finds unchecked checkboxes, implements ONE task per iteration, marks it complete
- No regex parsing of markdown in Node - the AI handles all task discovery
### Loop Commands
```bash
# Build the loop CLI
cd plan2code-loop && npm run build
# Run the loop (after linking) - fully interactive
plan2code-loop
```
The CLI auto-detects specs in `./specs/`, prompts for selection if multiple found, and handles session continuation interactively. Session state is stored per-spec in `specs/<feature>/.plan2code-loop/`.
### Completion Markers
The LLM must output one of these formats:
- `TASK_COMPLETE: 1.1 - Task description` - Task done successfully
- `TASK_BLOCKED: 1.1 - Reason` - Cannot complete task
- `LOOP_COMPLETE` - All phases finished
## Development Commands
```bash
# Run the interactive installer
node install.js
# Install to specific platform only
node install.js --platform claude
node install.js --platform cursor
node install.js --platform copilot
node install.js --platform continue
node install.js --platform windsurf
node install.js --platform codeium
node install.js --platform vscode-copilot
# Preview changes without installing
node install.js --dry-run
# Show local (per-project) installation instructions
node install.js --local
# Uninstall all Plan2Code files
node install.js --uninstall
# Plan2Code Loop
cd plan2code-loop && npm install # First time setup
cd plan2code-loop && npm run build # Build the CLI
```
### Installer Menu Options
| Option | Action |
|--------|--------|
| `A` | Install to ALL platforms + build & link loop CLI |
| `O` | Build & link plan2code-loop CLI only |
| `U` | Uninstall prompts from all platforms + unlink loop CLI |
| `L` | Show local (per-project) install instructions |
| `1-7` | Install to specific platform |
## How the Installer Works
1. **Reads source prompts** from `src/plan2code-*.md`
2. **Generates platform-specific files** with appropriate headers (YAML frontmatter for some platforms)
3. **Writes to `dist/`** subdirectories organized by destination type
4. **Copies to target directories** (global: `~/.claude/commands/`, etc.)
### Platform-Specific File Formats
| Platform | Extension | Header |
|----------|-----------|--------|
| Claude Code | `.md` | None |
| Cursor | `.md` | None |
| Copilot CLI | `.md` | YAML frontmatter |
| Continue | `.prompt.md` | YAML frontmatter |
| Windsurf | `.md` | YAML frontmatter |
| VS Code Copilot | `.prompt.md` | YAML frontmatter |
| Codeium | `.md` | YAML frontmatter |
## Naming Convention
Workflow files follow a strict naming pattern:
- **Utilities:** `plan2code---<name>.md` (triple dash)
- **Numbered steps:** `plan2code-<N>--<name>.md` (single dash, number, double dash)
Examples:
- `plan2code---init.md` (utility)
- `plan2code-1--plan.md` (step 1)
- `plan2code-1b--revise-plan.md` (step 1b)
## Editing Workflow Prompts
When modifying workflow prompts in `src/`:
1. Edit the source file in `src/`
2. Run `node install.js` to regenerate distribution files
3. Test the workflow in your AI tool of choice
4. The `dist/` folder is regenerated automatically - don't edit files there directly
## Code Style
- **JavaScript:** CommonJS modules (Node.js built-ins only - no external dependencies)
- **Console output:** Uses ANSI color codes via the `COLORS` constant
- **User interaction:** Readline-based interactive prompts
- **File operations:** Synchronous fs operations for simplicity
## Gotchas/Pitfalls
- **Version sync:** When adding a new version to `CHANGELOG.md`, also update `version.json` to match. The installer displays the version from `version.json` in its header.
## Mascot
The project has a mascot - an ASCII art robot that appears in installer output and workflow prompts. Mascot variants are defined in `MASCOT` constant in `install.js` and appear in workflow markdown files.
```
╭───╮
│ ● │
│ ◡ │
╰───╯
```
+86
View File
@@ -2,6 +2,92 @@
All notable changes to Plan2Code will be documented in this file.
## v1.5.2
### ✨ Added
- **AI Agent File Sync** - Init and Init-Update workflows now detect and sync other AI agent config files
- Detects 6 file types: CLAUDE.md, GEMINI.md, .cursorrules, .github/copilot-instructions.md, .cursor/rules/, .windsurf/rules/
- Offers to replace with references to AGENTS.md as single source of truth
- User confirmation required before any modifications
- Correct relative paths for each file location (./AGENTS.md, ../AGENTS.md, ../../AGENTS.md)
- **Knowledge Transfer** - Init mode now uses existing CLAUDE.md content as context when creating new AGENTS.md
- Preserves project knowledge during migration to AGENTS.md
## v1.5.1
### ✨ Added
- **Prerequisite verification workflow** - Agents now verify/complete prerequisites before starting phase tasks
- Implementation mode processes prerequisites in order: verify, complete, or mark assumed
- Loop prompt treats prerequisites as "Task 0.X" - one per iteration before tasks
- New completion markers: `PREREQ_COMPLETE` and `PREREQ_ASSUMED`
- **New checkbox state `[?]`** - "Assumed complete, couldn't verify" for prerequisites that can't be validated
- Use when prerequisite cannot be programmatically verified (e.g., "Design approved by stakeholder")
- Agents skip `[?]` items like `[x]` items
- **Re-opened phase handling in Revision mode** - Properly handle adding tasks to completed phases
- New "Re-opening" impact type (Medium-High risk) in impact assessment
- New tasks in completed phases get `🆕 ADDED` flag
- Phase checkbox changes from `[x]` to `[ ]` in overview.md when new tasks added
- "Phases Re-opened" section in revision summary
- Consistency check now verifies phase completion status matches task completion
### 🔧 Changed
- **Installer UI refresh** - Cleaner, narrower layout for better terminal compatibility
- Narrower menu boxes (65 characters instead of 76)
- Smaller mascot display at end of installation
- Added first-time user documentation link after successful install
- Updated menu descriptions to show "+ loop CLI" for relevant options
- **Planning workflow guardrails** - Prevent users from skipping the documentation step
- Added critical reminder after Phase 7 to direct to `/plan2code-2--document`
- Added workflow order reminder in Session End section: Plan → Document → Implement → Finalize
- Updated example closing message to emphasize documentation as next step
- Added workflow order to Important Reminders section
- **AGENTS.md pre-flight message** - Improved guidance for new projects
- Message now explains that new projects can continue without AGENTS.md
- Suggests creating basic AGENTS.md first with rules can still be valuable
## v1.5.0 - 2026-01-22
### ✨ Added
- **Plan2Code Loop** - New autonomous CLI tool for hands-off spec implementation
- Separate Node.js/TypeScript tool in `plan2code-loop/` directory
- LLM-driven task discovery - AI reads spec files and finds unchecked tasks
- Iterates through tasks one at a time, marking checkboxes as complete
- Structured completion markers: `TASK_COMPLETE: 1.1 - description`
- Session persistence with scratchpad and iteration logging
- Supports Claude Code and GitHub Copilot CLI agents
- **Installer integration** for loop CLI
- Option `A` now installs prompts to all platforms AND builds/links the loop CLI
- Option `O` builds and links plan2code-loop CLI only
- Option `U` uninstalls prompts AND unlinks the loop CLI
### 📝 Documentation
- Updated README.md with "Autonomous Loop" section explaining when to use loop vs manual Step 3
- Updated QUICK-REFERENCE.md with loop commands and decision tree
- Updated AGENTS.md with loop architecture, commands, and completion markers
## v1.4.0 - 2026-01-09
### ✨ Added
- **Parallel Phase Execution** - Run multiple implementation phases simultaneously in separate agent instances
- Documentation Mode auto-detects parallel-eligible phases based on file conflicts and dependencies
- Implementation Mode presents phase selection UI when parallel options are available
- New "Parallel Execution Groups" section in `overview.md` tracks which phases can run together
- Conflict detection criteria: file overlap, prerequisite dependencies, data/output dependencies, shared state
- Users can start multiple `/plan2code-3--implement` sessions to work on different parallel phases
- **In-Progress Phase Tracking** - Track which phases are actively being worked on
- New `[/]` checkbox status indicates a phase is in-progress (between `[ ]` pending and `[x]` complete)
- Phases marked `[/]` when an agent starts working, `[x]` when user approves completion
- Aborted phases stay `[/]` to enable resume - never reset back to `[ ]`
- Parallel selection UI shows `[IN PROGRESS]` vs `[AVAILABLE]` status for each phase
- Single in-progress phase prompts user to confirm resume (prevents accidental overlap)
- Supports multiple agent sessions on parallel phases with clear visibility of what's active
### 🔧 Changed
- **Documentation Mode process** - Added step 6 "Analyze phases for parallel execution eligibility"
- **Implementation Mode detection** - Now checks for parallel siblings before starting phase
- **Implementation Mode phase selection** - 4-case decision logic for parallel, resume, auto-start scenarios
- **Session end summaries** - Documentation Mode now reports parallel execution groups
- **Abort handling** - Phases remain `[/]` on abort with clear resume instructions
## v1.3.3 - 2025-12-30
### ✨ Added
+26 -2
View File
@@ -29,21 +29,34 @@ specs--completed/ # After Step 4
## Key Rules
- Start NEW conversation for each step (and each implementation phase)
- ONE phase per conversation
- ONE phase per conversation (but parallel phases can run in separate instances)
- Reply "approved" to complete phases
- 90% confidence required before planning completes
- Never look in `specs--completed/` (it's archived specs)
## Phase Status
| Checkbox | Status | Meaning |
|----------|--------|---------|
| `[ ]` | Pending | Not started |
| `[/]` | In Progress | Agent working (or paused) |
| `[x]` | Complete | Approved |
## Parallel Execution
When phases have no file conflicts or dependencies, they can run simultaneously:
1. Documentation Mode auto-detects parallel-eligible phases
2. Implementation Mode shows selection UI with status for each phase
3. Run multiple `/plan2code-3--implement` instances on different phases
4. `[/]` status shows which phases are actively being worked on
## Quick Troubleshooting
| Issue | Solution |
| ---------------------- | ----------------------------------------------- |
| ---------------------- | ------------------------------------------------- |
| Lost context mid-phase | Attach spec files, say "resume from Task X.Y" |
| Wrong phase started | Say "abort", start correct phase |
| Need to change plan | Use `/plan2code-1b--revise-plan` |
| Multiple spec folders | Specify which: "Continue with specs/user-auth/" |
| Need AGENTS.md file | Use `/plan2code---init` to generate one |
| Update AGENTS.md | Use `/plan2code---init-update` after sessions |
| Run phases in parallel | Check Parallel Execution Groups in overview.md |
## Workflow Decision
@@ -59,8 +72,19 @@ Is it a quick, small task?
└── No → /plan2code-1--plan (full workflow)
├── /plan2code-2--document
├── /plan2code-3--implement (repeat per phase)
│ └── OR: plan2code-loop (autonomous alternative)
└── /plan2code-4--finalize
Need to revise mid-implementation?
└── /plan2code-1b--revise-plan
```
## Autonomous Loop (Alternative)
The `plan2code-loop` CLI is an **alternative** to Step 3, not a replacement.
| Approach | Use When |
|----------|----------|
| `/plan2code-3--implement` | You want interactive control per phase |
| `plan2code-loop` | You want hands-off autonomous execution |
```bash
plan2code-loop # Fully interactive - auto-detects specs, prompts for options
```
Session state stored per-spec in `specs/<feature>/.plan2code-loop/`
+343 -95
View File
@@ -38,118 +38,357 @@ See [QUICK-REFERENCE.md](QUICK-REFERENCE.md) for full reference card.
## Installation
Plan2Code includes an interactive installer that generates and installs workflow files for all major AI coding assistants.
<img src="docs/install-script.jpg" width="582">
### Prerequisites
**Node.js** (v14 or later) is required. If you don't have it:
- Download from [nodejs.org](https://nodejs.org/)
- Or: `brew install node` (macOS) | `winget install OpenJS.NodeJS` (Windows) | `sudo apt install nodejs` (Linux)
The install script requires **Node.js** (v14 or later). If you don't have Node.js installed:
1. Download from [nodejs.org](https://nodejs.org/)
2. Or use a package manager:
- **macOS:** `brew install node`
- **Windows:** `winget install OpenJS.NodeJS` or `choco install nodejs`
- **Linux:** `sudo apt install nodejs` (Debian/Ubuntu) or `sudo dnf install nodejs` (Fedora)
### Supported Platforms
| Platform | Global Directory | Invocation |
| ---------------- | -------------------------------------- | ---------------------------- |
| Claude Code | `~/.claude/commands/` | `/plan2code-1--plan`, etc. |
| Copilot CLI | `~/.copilot/agents/` | `--agent=plan2code-1--plan` |
| Cursor | `~/.cursor/commands/` | `/plan2code-1--plan`, etc. |
| Continue | `~/.continue/prompts/` | `/plan2code-1--plan`, etc. |
| Windsurf | `~/.codeium/windsurf/global_workflows/`| `/plan2code-1--plan`, etc. |
| Codeium (IJ) | `~/.codeium/global_workflows/` | `/plan2code-1--plan`, etc. |
| VS Code Copilot | Platform-specific (see below) | Slash commands in chat |
**VS Code Copilot paths:**
- Windows: `%APPDATA%\Code\User\prompts\`
- macOS: `~/Library/Application Support/Code/User/prompts/`
- Linux: `~/.config/Code/User/prompts/`
### Quick Start
```bash
git clone https://github.com/jparkerweb/plan2code.git
# Clone the repository
git clone https://github.com/plan/plan2code.git
cd plan2code
# Run the interactive installer
node install.js
```
The interactive installer will guide you through:
- **Global installation** (recommended) - commands available in all projects
- **Local/project installation** - commands for a specific project only
- **Uninstall** - remove previously installed files
The installer will display an interactive menu:
<img src="docs/install-script.jpg" width="600">
```
Available platforms:
### Supported Platforms
1. Claude Code (~/.claude/commands/)
2. Copilot CLI (~/.copilot/agents/)
3. Cursor (~/.cursor/commands/)
4. Continue (~/.continue/prompts/)
5. Windsurf (~/.codeium/windsurf/global_workflows/)
6. Codeium (IJ) (~/.codeium/global_workflows/)
7. VS Code Copilot (%APPDATA%\Code\User\prompts\)
A. Install ALL platforms + loop CLI
O. Build/link plan2code-loop CLI only
L. Show local (project) install instructions
U. Uninstall Plan2Code files + unlink loop CLI
Q. Quit
| Platform | Global Location | Invocation |
|----------|-----------------|------------|
| Claude Code | `~/.claude/commands/` | `/plan2code-1--plan` |
| Copilot CLI | `~/.copilot/agents/` | `--agent=plan2code-1--plan` |
| VS Code Copilot | `~/Library/Application Support/Code/User/prompts/` | Slash commands |
| Windsurf | `~/.codeium/windsurf/global_workflows/` | `/plan2code-1--plan` |
| Cursor | `~/.cursor/commands/` | `/plan2code-1--plan` |
| Continue | `~/.continue/prompts/` | `/plan2code-1--plan` |
| Antigravity | `.agent/workflows/` (project only) | `/plan2code-1--plan` |
Enter choice (1-7, A, O, L, U, Q, or comma-separated like 1,3,5):
```
### CLI Options
### Installer Options
| Command | Description |
|---------|-------------|
| `node install.js` | Interactive mode - select platforms from menu |
| `node install.js --platform <id>` | Install to specific platform only |
| `node install.js --dry-run` | Preview what would be installed without making changes |
| `node install.js --local` | Show instructions for project-level installation |
| `node install.js --uninstall` | Remove installed files and unlink loop CLI |
| `node install.js --help` | Display help information |
**Valid platform IDs:** `claude`, `copilot`, `cursor`, `continue`, `windsurf`, `codeium`, `vscode-copilot`
**Examples:**
```bash
node install.js # Interactive menu
node install.js --platform claude # Install specific platform
node install.js --local # Install to current project instead of global
node install.js --dry-run # Preview what would be installed
node install.js --uninstall # Remove installed files
node install.js --help # Show all options
# Install to Claude Code only
node install.js --platform claude
# Install to multiple specific platforms
node install.js # Then enter "1,3,5" for Claude, Cursor, Windsurf
# Preview installation without making changes
node install.js --dry-run
# Remove all installed files
node install.js --uninstall
```
### Per-Project Installation
If you prefer project-specific configuration instead of global installation:
```bash
# Run the installer with --local flag
node install.js --local
```
This will generate the distribution files and display instructions for copying them to your project. The generated files will be in `dist/local-commands/` organized by platform:
```
dist/local-commands/
├── .claude/commands/ # Claude Code
├── .cursor/commands/ # Cursor
├── .github/prompts/ # VS Code GitHub Copilot
├── .github/agents/ # GitHub Copilot CLI (agents)
├── .continue/prompts/ # Continue
├── .windsurf/workflows/ # Windsurf
└── .agent/workflows/ # Google Antigravity
```
> **Note:** Some AI tools (Windsurf, Cursor, Continue) may not recognize workflows in gitignored directories. If your project's `.gitignore` includes patterns like `.windsurf/`, consider using global installation instead.
---
## Platform-Specific Notes
<details>
<summary>Claude Code CLI</summary>
**Installation:**
```bash
node install.js --platform claude
# Or use interactive mode and select option 1
```
**Global location:** `~/.claude/commands/`
**Usage:**
```bash
/plan2code-1--plan # Start planning a new feature
/plan2code-2--document # Create implementation docs from plan
/plan2code-3--implement # Begin/continue implementation
/plan2code-4--finalize # Wrap up after all phases complete
```
Restart Claude Code or start a new session after installation.
**Documentation:** [Claude Code Slash Commands](https://docs.anthropic.com/en/docs/claude-code/slash-commands)
</details>
---
<details>
<summary>Platform Details</summary>
### Claude Code CLI
Type `/plan2code-1--plan` in chat. Restart Claude Code after installation.
**Docs:** [Claude Code Slash Commands](https://code.claude.com/docs/en/slash-commands)
### GitHub Copilot CLI
<summary>GitHub Copilot CLI</summary>
**Installation:**
```bash
node install.js --platform copilot
# Or use interactive mode and select option 2
```
**Global location:** `~/.copilot/agents/`
Ensure GitHub Copilot CLI is installed: `npm install -g @github/copilot@latest`
**Usage:**
```bash
# Using --agent flag
copilot --agent=plan2code-1--plan --prompt "I want to build a REST API"
# Using slash commands in interactive mode
copilot
> /agent plan2code-1--plan
```
Requires: `npm install -g @github/copilot@latest`
**Docs:** [GitHub Copilot CLI Custom Agents](https://docs.github.com/en/copilot/concepts/agents/about-copilot-cli)
### VS Code GitHub Copilot
Open Copilot Chat (`Ctrl+Shift+I`), type `/` to see prompts. Requires per-project install via `node install.js --local`.
**Docs:** [VS Code Copilot Prompt Files](https://code.visualstudio.com/docs/copilot/customization/prompt-files)
### Windsurf IDE
Type `/plan2code-1--plan` in Cascade. Note: 12,000 character limit per workflow.
**Docs:** [Windsurf Workflows](https://docs.windsurf.com/windsurf/cascade/workflows)
### Cursor AI
Type `/` in chat, select command from dropdown.
**Docs:** [Cursor Commands](https://docs.cursor.com/agent/chat/commands)
### Google Antigravity
Type `/plan2code-1--plan` in chat. Requires per-project install via `node install.js --local`.
**Docs:** [Customize Antigravity](https://atamel.dev/posts/2025/11-25_customize_antigravity_rules_workflows/)
### Continue (VS Code/JetBrains)
Type `/plan2code-1--plan` in chat. Install the Continue extension first.
**Docs:** [Continue Prompts](https://docs.continue.dev/customize/deep-dives/prompts)
**Documentation:** [GitHub Copilot CLI Custom Agents](https://docs.github.com/en/copilot/concepts/agents/about-copilot-cli)
</details>
---
<details>
<summary>Manual Installation</summary>
<summary>VS Code GitHub Copilot</summary>
If your AI tool isn't listed or you prefer manual setup:
**Installation:**
```bash
node install.js --platform vscode-copilot
# Or use interactive mode and select option 7
```
1. **Copy/Paste:** Copy contents from `src/plan2code-*.md` files into your conversation
2. **File Reference:** Tell the AI: `Please follow the instructions in src/plan2code-1--plan.md`
3. **Custom Integration:** Adapt files from `dist/` directories to your tool's format
**Global location:** Platform-specific (Windows: `%APPDATA%\Code\User\prompts\`, macOS: `~/Library/Application Support/Code/User/prompts/`, Linux: `~/.config/Code/User/prompts/`)
**Usage:**
- Open Copilot Chat (Ctrl+Shift+I or Cmd+Shift+I)
- Type `/` to see available prompts
- Select the desired workflow step
**Documentation:** [VS Code Copilot Prompt Files](https://code.visualstudio.com/docs/copilot/customization/prompt-files)
</details>
---
<details>
<summary>Windsurf IDE</summary>
**Installation:**
```bash
node install.js --platform windsurf
# Or use interactive mode and select option 5
```
**Global location:** `~/.codeium/windsurf/global_workflows/`
> **Note:** Windsurf has a 12,000 character limit per workflow file.
**Usage:**
- In Cascade, type `/plan2code-1--plan` to invoke the planning workflow
**Documentation:** [Windsurf Workflows](https://docs.windsurf.com/windsurf/cascade/workflows)
</details>
---
<details>
<summary>Cursor AI</summary>
**Installation:**
```bash
node install.js --platform cursor
# Or use interactive mode and select option 3
```
**Global location:** `~/.cursor/commands/`
**Usage:**
- Type `/` in Cursor chat to see available commands
- Select `plan2code-1--plan` from the dropdown
- Commands from both project and global directories appear automatically
**Documentation:** [Cursor Commands](https://docs.cursor.com/agent/chat/commands)
</details>
---
<details>
<summary>Continue (VS Code/JetBrains)</summary>
**Installation:**
```bash
node install.js --platform continue
# Or use interactive mode and select option 4
```
**Global location:** `~/.continue/prompts/`
Install the Continue extension for VS Code or JetBrains. Prompts are automatically recognized.
**Usage:**
- In Continue chat, type `/plan2code-1--plan` to invoke the planning workflow
**Documentation:** [Continue Prompts](https://docs.continue.dev/customize/deep-dives/prompts)
</details>
---
<details>
<summary>Codeium (IntelliJ)</summary>
**Installation:**
```bash
node install.js --platform codeium
# Or use interactive mode and select option 6
```
**Global location:** `~/.codeium/global_workflows/`
**Usage:**
- Type `/plan2code-1--plan` in the Codeium chat to invoke workflows
</details>
---
<details>
<summary>Google Antigravity</summary>
**Installation:** Use the `--local` option and copy files to your project:
```bash
node install.js --local
# Then copy dist/local-commands/.agent to your project
```
**Project location:** `.agent/workflows/` (per-project only)
> **Note:** Antigravity requires per-project installation.
**Usage:**
- Type `/plan2code-1--plan` in the agent chat to invoke the planning workflow
**Documentation:** [Customize Antigravity](https://atamel.dev/posts/2025/11-25_customize_antigravity_rules_workflows/)
</details>
---
## Autonomous Loop (Alternative to Step 3)
For hands-off implementation, Plan2Code includes an optional autonomous loop CLI that iterates through your spec tasks automatically.
> **Note:** The loop is an **alternative** to `/plan2code-3--implement`, not a replacement. Use the manual Step 3 workflow when you want direct control over each phase, or use the loop when you prefer autonomous execution.
### When to Use Each
| Approach | Best For |
|----------|----------|
| `/plan2code-3--implement` | Interactive control, reviewing each phase, complex logic requiring human judgment |
| `plan2code-loop` | Straightforward implementations, batch processing, overnight runs |
### Installing the Loop
```bash
# Option 1: Install everything (prompts + loop)
node install.js # Select option A
# Option 2: Install loop only
node install.js # Select option O
```
### Using the Loop
```bash
# Run the loop - fully interactive
plan2code-loop
```
The CLI will:
1. Auto-detect specs in `./specs/` directory
2. Let you select a spec if multiple are found
3. Prompt to continue if an existing session is found
4. Ask for JIRA ticket ID, agent selection, and max iterations
Session state is stored per-spec in `specs/<feature>/.plan2code-loop/`, keeping each feature's progress isolated.
The loop will:
1. Read your `overview.md` and phase files
2. Find the first unchecked task
3. Implement it and mark the checkbox complete
4. Repeat until all tasks are done or max iterations reached
See [plan2code-loop/](plan2code-loop/) for full documentation.
---
### Manual Usage (No Installation)
If you prefer not to install, you can use Plan2Code prompts directly:
1. **Copy/Paste Method:** Copy the contents of the appropriate `src/plan2code-*.md` file and paste it at the start of your conversation.
2. **File Reference Method:** Reference the file directly in your prompt:
```
Please follow the instructions in src/plan2code-1--plan.md
I want to build a user authentication system.
```
---
## Important: Start Fresh Conversations
**Start a new conversation/chat session before each step.** This includes:
@@ -202,12 +441,14 @@ Fresh conversations prevent context pollution and ensure the AI focuses on the c
```
specs/
└── <feature-name>/
├── overview.md # High-level overview with phase checkboxes
├── overview.md # High-level overview with phase checkboxes and parallel groups
├── Phase 1.md # Detailed tasks for Phase 1
├── Phase 2.md # Detailed tasks for Phase 2
└── Phase N.md # ...additional phases
```
The `overview.md` includes a "Parallel Execution Groups" section that identifies which phases can be run simultaneously in separate agent instances.
**Document Format:**
- Each phase file contains detailed one-story-point tasks
@@ -228,11 +469,14 @@ specs/
**Workflow:**
1. Identify the next uncompleted phase (unchecked in `overview.md`)
2. Implement ALL tasks in that phase exactly as specified
3. Update `Phase X.md` checkboxes as tasks complete `[x]`
4. Update `overview.md` phase checkbox when phase completes
5. Perform code review to ensure nothing was missed
6. Add completion summary to the phase document
2. Check for parallel execution options (if phases can run simultaneously)
3. Implement ALL tasks in that phase exactly as specified
4. Update `Phase X.md` checkboxes as tasks complete `[x]`
5. Update `overview.md` phase checkbox when phase completes
6. Perform code review to ensure nothing was missed
7. Add completion summary to the phase document
**Parallel Execution:** If the next phase is part of a parallel-eligible group, you'll be prompted to choose which phase to implement. This allows running multiple agent instances simultaneously on different phases that don't conflict with each other.
**Key Rules:**
@@ -264,15 +508,13 @@ specs/
### Option 1: Platform-Specific Slash Commands (Recommended)
This repository includes pre-configured workflow files for all major AI coding assistants. See the [Installation](#installation) section above for platform-specific setup instructions.
**Quick commands after setup:**
After running `node install.js`, use the slash commands directly in your AI tool:
```
/plan # Start planning a new feature
/document # Create implementation docs from plan
/implement # Begin/continue implementation
/finalize # Wrap up after all phases complete
/plan2code-1--plan # Start planning a new feature
/plan2code-2--document # Create implementation docs from plan
/plan2code-3--implement # Begin/continue implementation
/plan2code-4--finalize # Wrap up after all phases complete
```
### Option 2: Direct File Reference
@@ -372,18 +614,24 @@ AI: 🧹 [SPEC CLEANUP]
The checkbox system enables seamless progress tracking across multiple sessions:
**overview.md:**
**Phase Status (overview.md):**
| Checkbox | Status | Meaning |
|----------|--------|---------|
| `[ ]` | Pending | Not yet started |
| `[/]` | In Progress | Agent actively working (or paused/aborted) |
| `[x]` | Complete | Finished and approved |
```markdown
## Phases
- [x] Phase 1: Project Setup
- [x] Phase 2: Database Models
- [ ] Phase 3: API Endpoints <- Next phase to implement
- [ ] Phase 4: Authentication
- [/] Phase 3: API Endpoints <- In progress (agent working)
- [ ] Phase 4: Authentication <- Next available
```
**Phase 3.md:**
**Task Status (phase-X.md):**
```markdown
## Tasks
@@ -395,6 +643,8 @@ The checkbox system enables seamless progress tracking across multiple sessions:
- [ ] Implement DELETE /tasks/:id
```
The `[/]` status enables parallel execution - multiple agents can work on different phases simultaneously, and you can see which phases are actively being worked on.
---
## Best Practices
@@ -456,9 +706,9 @@ Feel free to modify these prompts to fit your workflow:
**Slash commands/workflows not recognized:**
- Check if your project's `.gitignore` includes patterns like `.windsurf/`, `.cursor/`, `.continue/`, or `.agent/`
- Many AI tools don't recognize workflows in gitignored directories
- Solution: Use global installation by copying the directories to your home directory (e.g., `cp -r .windsurf ~/`)
- Ensure you ran `node install.js` and selected the appropriate platform
- Restart your AI tool after installation
- For per-project installation, ensure the directory isn't in `.gitignore`
**AI jumps ahead to implementation during planning:**
@@ -481,5 +731,3 @@ Feel free to modify these prompts to fit your workflow:
**Too many/few phases:**
- Adjust during Step 2 (Documentation) - phases should represent logical groupings of work
Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 147 KiB

+385 -21
View File
@@ -84,6 +84,32 @@ const SYMBOLS = {
TEE_LEFT: '╣',
};
// Plan2Code Mascot - appears during user interactions
const MASCOT = {
// Full mascot for headers
full: [
' ╭───╮ ',
' │ ● │ ',
' │ ◡ │ ',
' ╰───╯ ',
],
// Mini mascot for inline use
mini: '(◉‿◉)',
// Waving mascot for greetings
wave: [
' ╭───╮ ',
' │ ● │ ',
' │ ◡ │ ',
' ╰───╯ ',
],
// Thinking mascot for prompts
thinking: [
' ╭───╮ ',
' │ ● │ ?',
' │ ~ │ ',
' ╰───╯ ',
],
};
// Source directory containing pre-formatted global installation files
const SOURCE_BASE = 'dist/global-commands';
@@ -434,6 +460,21 @@ const hasArgs = args.length > 0;
// DISPLAY FUNCTIONS
// ============================================================================
/**
* Display mascot with optional message
*/
function displayMascot(variant = 'full', message = '') {
const mascotLines = MASCOT[variant] || MASCOT.full;
console.log('');
mascotLines.forEach(line => {
console.log(`${COLORS.MAGENTA}${line}${COLORS.RESET}`);
});
if (message) {
console.log(`${COLORS.CYAN}${message}${COLORS.RESET}`);
}
console.log('');
}
/**
* Display header
*/
@@ -441,6 +482,10 @@ function displayHeader() {
console.log('');
console.log(`${COLORS.GREEN}${COLORS.BRIGHT}`);
console.log('╔═════════════════════════════════════════════════════════════════════════════════╗');
console.log('║ ╭───╮ ║');
console.log('║ │ ● │ Hi! ║');
console.log('║ │ ◡ │ Nice to meet you ║');
console.log('║ ╰───╯ ║');
console.log('║ ║');
console.log('║ ██████╗ ██╗ █████╗ ███╗ ██╗ ██████╗ ██████╗ ██████╗ ██████╗ ███████╗ ║');
console.log('║ ██╔══██╗██║ ██╔══██╗████╗ ██║ ╚════██╗ ██╔════╝██╔═══██╗██╔══██╗██╔════╝ ║');
@@ -529,6 +574,12 @@ function displayHelp() {
`${COLORS.CYAN}${COLORS.RESET} ${COLORS.BRIGHT}node install.js --uninstall${COLORS.RESET}`,
` ${COLORS.DIM}Remove all installed Plan2Code files${COLORS.RESET}`,
'',
`${COLORS.CYAN}${COLORS.RESET} ${COLORS.BRIGHT}node install.js --loop${COLORS.RESET}`,
` ${COLORS.DIM}Build and install plan2code-loop CLI tool${COLORS.RESET}`,
'',
`${COLORS.CYAN}${COLORS.RESET} ${COLORS.BRIGHT}node install.js --uninstall-loop${COLORS.RESET}`,
` ${COLORS.DIM}Remove plan2code-loop global symlink${COLORS.RESET}`,
'',
`${COLORS.CYAN}${COLORS.RESET} ${COLORS.BRIGHT}node install.js --help${COLORS.RESET}`,
` ${COLORS.DIM}Display this help information${COLORS.RESET}`,
]);
@@ -919,7 +970,18 @@ function install(targets = null) {
console.log(`${COLORS.CYAN}${COLORS.RESET}${' '.repeat(75)}${COLORS.CYAN}${COLORS.RESET}`);
console.log(`${COLORS.CYAN}╚═══════════════════════════════════════════════════════════════════════════╝${COLORS.RESET}`);
if (dryRun) {
// Show celebratory or sad mascot based on result
console.log('');
if (totalErrors === 0 && !dryRun) {
console.log(`${COLORS.GREEN} ╭───╮${COLORS.RESET}`);
console.log(`${COLORS.GREEN}${COLORS.CYAN}${COLORS.GREEN}${COLORS.RESET} ${COLORS.BRIGHT}All done! Happy coding!${COLORS.RESET}`);
console.log(`${COLORS.GREEN}${COLORS.BRIGHT}${COLORS.GREEN}${COLORS.RESET} ${COLORS.BRIGHT}If this is your first time using Plan2Code, read the docs here:${COLORS.RESET}`);
console.log(`${COLORS.GREEN} ╰───╯${COLORS.RESET} ${COLORS.BRIGHT}https://github.com/jparkerweb/plan2code${COLORS.RESET}`);
} else if (dryRun) {
console.log(`${COLORS.YELLOW} ╭───╮${COLORS.RESET}`);
console.log(`${COLORS.YELLOW}${COLORS.CYAN}${COLORS.YELLOW}${COLORS.RESET}`);
console.log(`${COLORS.YELLOW} │ ○ │${COLORS.RESET} ${COLORS.DIM}That was just a preview!${COLORS.RESET}`);
console.log(`${COLORS.YELLOW} ╰───╯${COLORS.RESET}`);
console.log('');
console.log(`${COLORS.YELLOW}${SYMBOLS.INFO} Run without --dry-run to install files${COLORS.RESET}`);
}
@@ -1085,12 +1147,9 @@ function displayLocalInstructions() {
{ name: 'Claude Code', dir: '.claude/commands/', desc: 'Slash commands for Claude Code' },
{ name: 'Cursor', dir: '.cursor/commands/', desc: 'Slash commands for Cursor IDE' },
{ name: 'GitHub Copilot', dir: '.github/prompts/', desc: 'Prompts for GitHub Copilot' },
{ name: 'GitHub Agents', dir: '.github/agents/', desc: 'Agent definitions for Copilot' },
{ name: 'Continue', dir: '.continue/prompts/', desc: 'Prompts for Continue extension' },
{ name: 'Windsurf', dir: '.windsurf/workflows/', desc: 'Workflows for Windsurf' },
{ name: 'Windsurf Global', dir: '.codeium/windsurf/global_workflows/', desc: 'Global workflows' },
{ name: 'Agent', dir: '.agent/workflows/', desc: 'Agent workflows' },
{ name: 'Codeium', dir: '.codeium/global_workflows/', desc: 'Codeium global workflows' },
{ name: 'Codeium (IntelliJ)', dir: '.codeium/global_workflows/', desc: 'Codeium global workflows' },
];
localDirs.forEach((item, i) => {
@@ -1146,9 +1205,9 @@ function runInteractive() {
console.log(`${COLORS.BLUE}${COLORS.BRIGHT}Version Info:${COLORS.RESET} ${projectVersion.name} ${projectVersion.version}`);
console.log(`${COLORS.GREEN}${SYMBOLS.ACTIVE} SELECT PLATFORMS${COLORS.RESET}\n`);
console.log(`${COLORS.CYAN}╔═══════════════════════════════════════════════════════════════════════════${COLORS.RESET}`);
console.log(padEndVisible(`${COLORS.CYAN}${COLORS.RESET} ${COLORS.BRIGHT}AVAILABLE PLATFORMS${COLORS.RESET}`, 76) + `${COLORS.CYAN}${COLORS.RESET}`);
console.log(`${COLORS.CYAN}╠═══════════════════════════════════════════════════════════════════════════${COLORS.RESET}`);
console.log(`${COLORS.CYAN}╔════════════════════════════════════════════════════════════════╗${COLORS.RESET}`);
console.log(padEndVisible(`${COLORS.CYAN}${COLORS.RESET} ${COLORS.BRIGHT}AVAILABLE PLATFORMS${COLORS.RESET}`, 65) + `${COLORS.CYAN}${COLORS.RESET}`);
console.log(`${COLORS.CYAN}╠════════════════════════════════════════════════════════════════╣${COLORS.RESET}`);
INSTALL_TARGETS.forEach((target, i) => {
const num = `${COLORS.BRIGHT}${i + 1}.${COLORS.RESET}`;
@@ -1156,18 +1215,25 @@ function runInteractive() {
const name = `${COLORS.BRIGHT}${target.name.padEnd(14)}${COLORS.RESET}`;
const targetPath = `${COLORS.DIM}${getDisplayPath(target)}${COLORS.RESET}`;
console.log(padEndVisible(`${COLORS.CYAN}${COLORS.RESET} ${num} ${icon} ${name} ${targetPath}`, 76) + `${COLORS.CYAN}${COLORS.RESET}`);
console.log(padEndVisible(`${COLORS.CYAN}${COLORS.RESET} ${num} ${icon} ${name} ${targetPath}`, 65) + `${COLORS.CYAN}${COLORS.RESET}`);
});
console.log(`${COLORS.CYAN}╠═══════════════════════════════════════════════════════════════════════════${COLORS.RESET}`);
console.log(padEndVisible(`${COLORS.CYAN}${COLORS.RESET} ${COLORS.BRIGHT}A.${COLORS.RESET} ${COLORS.MAGENTA}◉ ALL${COLORS.RESET} ${COLORS.BRIGHT}Install to ALL platforms${COLORS.RESET}`, 76) + `${COLORS.CYAN}${COLORS.RESET}`);
console.log(padEndVisible(`${COLORS.CYAN}${COLORS.RESET} ${COLORS.BRIGHT}L.${COLORS.RESET} ${COLORS.BLUE}◉ LOCAL${COLORS.RESET} ${COLORS.BRIGHT}Show local (project) install instructions${COLORS.RESET}`, 76) + `${COLORS.CYAN}${COLORS.RESET}`);
console.log(padEndVisible(`${COLORS.CYAN}${COLORS.RESET} ${COLORS.BRIGHT}U.${COLORS.RESET} ${COLORS.RED}UNINSTALL${COLORS.RESET} ${COLORS.BRIGHT}Remove Plan2Code files${COLORS.RESET}`, 76) + `${COLORS.CYAN}${COLORS.RESET}`);
console.log(padEndVisible(`${COLORS.CYAN}${COLORS.RESET} ${COLORS.BRIGHT}Q.${COLORS.RESET} ${COLORS.DIM}QUIT${COLORS.RESET} ${COLORS.BRIGHT}Exit${COLORS.RESET}`, 76) + `${COLORS.CYAN}${COLORS.RESET}`);
console.log(`${COLORS.CYAN}╚═══════════════════════════════════════════════════════════════════════════╝${COLORS.RESET}`);
console.log(`${COLORS.CYAN}╠════════════════════════════════════════════════════════════════╣${COLORS.RESET}`);
console.log(padEndVisible(`${COLORS.CYAN}${COLORS.RESET} ${COLORS.BRIGHT}A.${COLORS.RESET} ${COLORS.MAGENTA}◉ ALL${COLORS.RESET} ${COLORS.BRIGHT}Install to ALL platforms + loop CLI${COLORS.RESET}`, 65) + `${COLORS.CYAN}${COLORS.RESET}`);
console.log(padEndVisible(`${COLORS.CYAN}${COLORS.RESET} ${COLORS.BRIGHT}L.${COLORS.RESET} ${COLORS.BLUE}◉ LOCAL${COLORS.RESET} ${COLORS.BRIGHT}Show local (project) install instructions${COLORS.RESET}`, 65) + `${COLORS.CYAN}${COLORS.RESET}`);
console.log(padEndVisible(`${COLORS.CYAN}${COLORS.RESET} ${COLORS.BRIGHT}O.${COLORS.RESET} ${COLORS.GREEN}LOOP${COLORS.RESET} ${COLORS.BRIGHT}Install plan2code-loop CLI only${COLORS.RESET}`, 65) + `${COLORS.CYAN}${COLORS.RESET}`);
console.log(padEndVisible(`${COLORS.CYAN}${COLORS.RESET} ${COLORS.BRIGHT}U.${COLORS.RESET} ${COLORS.RED}UNINSTALL${COLORS.RESET} ${COLORS.BRIGHT}Remove Plan2Code files + loop CLI${COLORS.RESET}`, 65) + `${COLORS.CYAN}${COLORS.RESET}`);
console.log(padEndVisible(`${COLORS.CYAN}${COLORS.RESET} ${COLORS.BRIGHT}Q.${COLORS.RESET} ${COLORS.DIM}◉ QUIT${COLORS.RESET} ${COLORS.BRIGHT}Exit${COLORS.RESET}`, 65) + `${COLORS.CYAN}${COLORS.RESET}`);
console.log(`${COLORS.CYAN}╚════════════════════════════════════════════════════════════════╝${COLORS.RESET}`);
console.log('');
const answer = await question(`${COLORS.CYAN}${SYMBOLS.SELECT} SELECT OPTION${COLORS.RESET} (1-7, A, L, U, Q, or 1,3,5): `);
// Show thinking mascot for input prompt
console.log(`${COLORS.MAGENTA} ╭───╮${COLORS.RESET}`);
console.log(`${COLORS.MAGENTA}${COLORS.CYAN}${COLORS.MAGENTA}${COLORS.RESET} ${COLORS.DIM}What would you like to do?${COLORS.RESET}`);
console.log(`${COLORS.MAGENTA}${COLORS.YELLOW}~${COLORS.MAGENTA}${COLORS.RESET} ${COLORS.GREEN}I suggest A: 'Install to ALL platforms + loop CLI'${COLORS.RESET}`);
console.log(`${COLORS.MAGENTA} ╰───╯${COLORS.RESET}`);
console.log('');
const answer = await question(`${COLORS.CYAN}${SYMBOLS.SELECT} SELECT OPTION${COLORS.RESET} (1-7, A, L, O, U, Q, or 1,3,5): `);
const input = answer.trim().toUpperCase();
if (input === 'Q' || input === '') {
@@ -1181,6 +1247,11 @@ function runInteractive() {
rl.close();
process.exit(displayLocalInstructions());
}
if (input === 'O') {
// Install plan2code-loop
rl.close();
process.exit(installPlan2CodeLoop());
}
if (input === 'U') {
// Uninstall flow
@@ -1199,11 +1270,17 @@ function runInteractive() {
});
console.log(`${COLORS.CYAN}╠═══════════════════════════════════════════════════════════════════════════╣${COLORS.RESET}`);
console.log(padEndVisible(`${COLORS.CYAN}${COLORS.RESET} ${COLORS.BRIGHT}A.${COLORS.RESET} ${COLORS.RED}◉ ALL${COLORS.RESET} ${COLORS.BRIGHT}Uninstall from ALL platforms${COLORS.RESET}`, 76) + `${COLORS.CYAN}${COLORS.RESET}`);
console.log(padEndVisible(`${COLORS.CYAN}${COLORS.RESET} ${COLORS.BRIGHT}A.${COLORS.RESET} ${COLORS.RED}◉ ALL${COLORS.RESET} ${COLORS.BRIGHT}Uninstall from ALL platforms + loop CLI${COLORS.RESET}`, 76) + `${COLORS.CYAN}${COLORS.RESET}`);
console.log(padEndVisible(`${COLORS.CYAN}${COLORS.RESET} ${COLORS.BRIGHT}Q.${COLORS.RESET} ${COLORS.DIM}◉ CANCEL${COLORS.RESET} ${COLORS.BRIGHT}Cancel${COLORS.RESET}`, 76) + `${COLORS.CYAN}${COLORS.RESET}`);
console.log(`${COLORS.CYAN}╚═══════════════════════════════════════════════════════════════════════════╝${COLORS.RESET}`);
console.log('');
// Show worried mascot for uninstall prompt
console.log(`${COLORS.RED} ╭───╮${COLORS.RESET}`);
console.log(`${COLORS.RED}${COLORS.YELLOW}${COLORS.RED}${COLORS.RESET}`);
console.log(`${COLORS.RED}${COLORS.YELLOW}~${COLORS.RED}${COLORS.RESET} ${COLORS.DIM}Are you sure about this?${COLORS.RESET}`);
console.log(`${COLORS.RED} ╰───╯${COLORS.RESET}`);
console.log('');
const uninstallAnswer = await question(`${COLORS.RED}${SYMBOLS.SELECT} SELECT PLATFORMS TO UNINSTALL${COLORS.RESET} (1-7, A, Q, or 1,3,5): `);
const uninstallInput = uninstallAnswer.trim().toUpperCase();
@@ -1239,7 +1316,13 @@ function runInteractive() {
}
rl.close();
process.exit(uninstallFiles(targets));
const uninstallResult = uninstallFiles(targets);
// If uninstalling from ALL platforms, also unlink plan2code-loop
if (uninstallInput === 'A') {
console.log('');
uninstallPlan2CodeLoop();
}
process.exit(uninstallResult);
}
// Install flow
@@ -1260,7 +1343,13 @@ function runInteractive() {
}
const platformNames = targets.map(t => `${COLORS.YELLOW}${t.name}${COLORS.RESET}`).join(', ');
const confirm = await question(`\n${COLORS.GREEN}${SYMBOLS.SELECT} CONFIRM INSTALL${COLORS.RESET} to ${targets.length} platform(s): ${platformNames}? (Y/n): `);
// Show happy mascot for install confirmation
console.log('');
console.log(`${COLORS.GREEN} ╭───╮${COLORS.RESET}`);
console.log(`${COLORS.GREEN}${COLORS.CYAN}${COLORS.GREEN}${COLORS.RESET}`);
console.log(`${COLORS.GREEN}${COLORS.BRIGHT}${COLORS.GREEN}${COLORS.RESET} ${COLORS.DIM}Ready to install!${COLORS.RESET}`);
console.log(`${COLORS.GREEN} ╰───╯${COLORS.RESET}`);
const confirm = await question(`\n${COLORS.GREEN}${SYMBOLS.SELECT} CONFIRM INSTALL${COLORS.RESET}? (Y/n): `);
if (confirm.trim().toLowerCase() === 'n') {
console.log(`\n${COLORS.YELLOW}${SYMBOLS.WARNING} CANCELLED${COLORS.RESET}\n`);
@@ -1269,7 +1358,17 @@ function runInteractive() {
}
rl.close();
process.exit(install(targets));
// If installing ALL platforms, install plan2code-loop first
let loopResult = 0;
if (input === 'A') {
loopResult = installPlan2CodeLoop();
console.log('');
}
const installResult = install(targets);
if (input === 'A') {
process.exit(installResult === 0 && loopResult === 0 ? 0 : 1);
}
process.exit(installResult);
}
main().catch(err => {
@@ -1279,12 +1378,277 @@ function runInteractive() {
});
}
// ============================================================================
// PLAN2CODE-LOOP INSTALLATION
// ============================================================================
const { execSync, spawn } = require('child_process');
/**
* Build plan2code-loop package
*/
function buildPlan2CodeLoop() {
const loopDir = path.join(__dirname, 'plan2code-loop');
// Check if directory exists
if (!fs.existsSync(loopDir)) {
console.log(`${COLORS.YELLOW}${SYMBOLS.WARNING}${COLORS.RESET} plan2code-loop directory not found`);
return false;
}
console.log(`${COLORS.CYAN}${SYMBOLS.ACTIVE} Building plan2code-loop...${COLORS.RESET}`);
try {
// Install dependencies
console.log(` ${COLORS.DIM}Installing dependencies...${COLORS.RESET}`);
execSync('npm install', { cwd: loopDir, stdio: 'pipe' });
// Build
console.log(` ${COLORS.DIM}Running build...${COLORS.RESET}`);
execSync('npm run build', { cwd: loopDir, stdio: 'pipe' });
console.log(` ${COLORS.GREEN}${SYMBOLS.SUCCESS}${COLORS.RESET} Build complete`);
return true;
} catch (error) {
console.log(` ${COLORS.RED}${SYMBOLS.ERROR}${COLORS.RESET} Build failed: ${error.message}`);
return false;
}
}
/**
* Clean up existing plan2code-loop symlinks/files before creating new ones
* This prevents the Windows lstat error with stale symlinks
*/
function cleanupExistingSymlinks() {
console.log(` ${COLORS.DIM}Cleaning up existing symlinks...${COLORS.RESET}`);
try {
// Get npm global prefix - more reliable than npm bin -g on Windows
const npmPrefix = execSync('npm config get prefix', { encoding: 'utf8' }).trim();
// On Windows, bin files are directly in prefix; on Unix they're in prefix/bin
const npmBinGlobal = process.platform === 'win32' ? npmPrefix : path.join(npmPrefix, 'bin');
const npmRootGlobal = path.join(npmPrefix, 'node_modules');
// Files/symlinks to clean up in npm bin directory
const binFiles = [
path.join(npmBinGlobal, 'plan2code-loop'),
path.join(npmBinGlobal, 'plan2code-loop.cmd'),
path.join(npmBinGlobal, 'plan2code-loop.ps1')
];
// Symlink in node_modules - this is the main culprit for the lstat error
const nodeModulesLink = path.join(npmRootGlobal, 'plan2code-loop');
// Remove bin files first
for (const file of binFiles) {
try {
const stats = fs.lstatSync(file);
if (stats) {
fs.unlinkSync(file);
console.log(` ${COLORS.DIM}Removed: ${path.basename(file)}${COLORS.RESET}`);
}
} catch (err) {
if (err.code !== 'ENOENT') {
// File exists but can't be removed normally, try rmSync
try {
fs.rmSync(file, { force: true, recursive: true });
console.log(` ${COLORS.DIM}Removed: ${path.basename(file)}${COLORS.RESET}`);
} catch (rmErr) {
console.log(` ${COLORS.DIM}Could not remove: ${path.basename(file)}${COLORS.RESET}`);
}
}
}
}
// Remove node_modules symlink - this is critical for fixing the lstat error
try {
const stats = fs.lstatSync(nodeModulesLink);
if (stats) {
// On Windows, junction points need special handling
if (process.platform === 'win32') {
// Try using rmdir for junction points
try {
fs.rmdirSync(nodeModulesLink);
console.log(` ${COLORS.DIM}Removed: node_modules/plan2code-loop (junction)${COLORS.RESET}`);
} catch (rmdirErr) {
// Fall back to rmSync
fs.rmSync(nodeModulesLink, { force: true, recursive: true });
console.log(` ${COLORS.DIM}Removed: node_modules/plan2code-loop${COLORS.RESET}`);
}
} else {
fs.rmSync(nodeModulesLink, { force: true, recursive: true });
console.log(` ${COLORS.DIM}Removed: node_modules/plan2code-loop${COLORS.RESET}`);
}
}
} catch (err) {
if (err.code !== 'ENOENT') {
console.log(` ${COLORS.DIM}Could not remove node_modules symlink: ${err.message}${COLORS.RESET}`);
}
}
return true;
} catch (error) {
// Non-fatal - npm link might still work
console.log(` ${COLORS.DIM}Cleanup skipped: ${error.message}${COLORS.RESET}`);
return true;
}
}
/**
* Create global symlink for plan2code-loop
*/
function createGlobalSymlink() {
const loopDir = path.join(__dirname, 'plan2code-loop');
const distBin = path.join(loopDir, 'dist', 'bin', 'plan2code-loop.js');
// Check if built binary exists
if (!fs.existsSync(distBin)) {
console.log(`${COLORS.YELLOW}${SYMBOLS.WARNING}${COLORS.RESET} plan2code-loop binary not found. Run build first.`);
return false;
}
console.log(`${COLORS.CYAN}${SYMBOLS.ACTIVE} Creating global symlink...${COLORS.RESET}`);
// Clean up existing symlinks first to prevent Windows lstat errors
cleanupExistingSymlinks();
// On Windows, skip npm link entirely and create batch file directly
// npm link has persistent issues with junctions and lstat errors on Windows
if (process.platform === 'win32') {
try {
const npmPrefix = execSync('npm config get prefix', { encoding: 'utf8' }).trim();
const symlinkCmd = path.join(npmPrefix, 'plan2code-loop.cmd');
const symlinkPs1 = path.join(npmPrefix, 'plan2code-loop.ps1');
// Create batch file wrapper for cmd.exe
const batchContent = `@echo off\r\nnode "${distBin}" %*\r\n`;
fs.writeFileSync(symlinkCmd, batchContent);
console.log(` ${COLORS.GREEN}${SYMBOLS.SUCCESS}${COLORS.RESET} Created: plan2code-loop.cmd`);
// Create PowerShell wrapper for better shell support
const ps1Content = `#!/usr/bin/env pwsh\r\nnode "${distBin}" $args\r\n`;
fs.writeFileSync(symlinkPs1, ps1Content);
console.log(` ${COLORS.GREEN}${SYMBOLS.SUCCESS}${COLORS.RESET} Created: plan2code-loop.ps1`);
console.log(` ${COLORS.DIM}Run 'plan2code-loop --help' from any directory${COLORS.RESET}`);
return true;
} catch (error) {
console.log(` ${COLORS.RED}${SYMBOLS.ERROR}${COLORS.RESET} Failed to create wrappers: ${error.message}`);
return false;
}
}
// On Unix systems, use npm link as it works reliably
try {
execSync('npm link', { cwd: loopDir, stdio: 'pipe' });
console.log(` ${COLORS.GREEN}${SYMBOLS.SUCCESS}${COLORS.RESET} Global symlink created`);
console.log(` ${COLORS.DIM}Run 'plan2code-loop --help' from any directory${COLORS.RESET}`);
return true;
} catch (error) {
console.log(` ${COLORS.RED}${SYMBOLS.ERROR}${COLORS.RESET} Symlink failed: ${error.message}`);
return false;
}
}
/**
* Remove global symlink for plan2code-loop
*/
function removeGlobalSymlink() {
console.log(`${COLORS.CYAN}${SYMBOLS.ACTIVE} Removing global symlink...${COLORS.RESET}`);
let removedCount = 0;
try {
// Get npm global prefix
const npmPrefix = execSync('npm config get prefix', { encoding: 'utf8' }).trim();
// On Windows, bin files are directly in prefix; on Unix they're in prefix/bin
const npmBinGlobal = process.platform === 'win32' ? npmPrefix : path.join(npmPrefix, 'bin');
const npmRootGlobal = path.join(npmPrefix, 'node_modules');
// Files to remove
const filesToRemove = [
{ path: path.join(npmBinGlobal, 'plan2code-loop'), name: 'plan2code-loop' },
{ path: path.join(npmBinGlobal, 'plan2code-loop.cmd'), name: 'plan2code-loop.cmd' },
{ path: path.join(npmBinGlobal, 'plan2code-loop.ps1'), name: 'plan2code-loop.ps1' },
{ path: path.join(npmRootGlobal, 'plan2code-loop'), name: 'node_modules/plan2code-loop' }
];
for (const file of filesToRemove) {
try {
fs.lstatSync(file.path);
// File exists, try to remove it
try {
// Try rmdirSync first for junctions/symlinks
fs.rmdirSync(file.path);
console.log(` ${COLORS.GREEN}${SYMBOLS.SUCCESS}${COLORS.RESET} Removed: ${file.name}`);
removedCount++;
} catch (rmdirErr) {
// Fall back to unlinkSync for regular files
try {
fs.unlinkSync(file.path);
console.log(` ${COLORS.GREEN}${SYMBOLS.SUCCESS}${COLORS.RESET} Removed: ${file.name}`);
removedCount++;
} catch (unlinkErr) {
// Last resort: rmSync
fs.rmSync(file.path, { force: true, recursive: true });
console.log(` ${COLORS.GREEN}${SYMBOLS.SUCCESS}${COLORS.RESET} Removed: ${file.name}`);
removedCount++;
}
}
} catch (err) {
// File doesn't exist, skip it
}
}
if (removedCount === 0) {
console.log(` ${COLORS.DIM}${SYMBOLS.INFO} No files found to remove${COLORS.RESET}`);
}
return true;
} catch (error) {
console.log(` ${COLORS.RED}${SYMBOLS.ERROR}${COLORS.RESET} Uninstall failed: ${error.message}`);
return false;
}
}
/**
* Install plan2code-loop (build + symlink)
*/
function installPlan2CodeLoop() {
displaySectionHeader('PLAN2CODE-LOOP', '[ BUILD & INSTALL ]');
const buildSuccess = buildPlan2CodeLoop();
if (!buildSuccess) {
return 1;
}
const symlinkSuccess = createGlobalSymlink();
if (!symlinkSuccess) {
return 1;
}
console.log('');
console.log(`${COLORS.GREEN}${SYMBOLS.SUCCESS} plan2code-loop installed successfully!${COLORS.RESET}`);
console.log('');
console.log(`${COLORS.CYAN}Usage:${COLORS.RESET}`);
console.log(` ${COLORS.BRIGHT}plan2code-loop${COLORS.RESET} Start the autonomous loop`);
console.log(` ${COLORS.BRIGHT}plan2code-loop -c${COLORS.RESET} Resume previous session`);
console.log(` ${COLORS.BRIGHT}plan2code-loop -v${COLORS.RESET} Verbose mode`);
console.log(` ${COLORS.BRIGHT}plan2code-loop --help${COLORS.RESET} Show all options`);
console.log('');
return 0;
}
/**
* Uninstall plan2code-loop
*/
function uninstallPlan2CodeLoop() {
displaySectionHeader('PLAN2CODE-LOOP', '[ UNINSTALL ]');
removeGlobalSymlink();
console.log('');
console.log(`${COLORS.GREEN}${SYMBOLS.SUCCESS} plan2code-loop uninstalled${COLORS.RESET}`);
console.log('');
return 0;
}
// ============================================================================
// MAIN
// ============================================================================
// Additional CLI arguments for plan2code-loop
const installLoop = args.includes('--loop');
const uninstallLoop = args.includes('--uninstall-loop');
// Run the appropriate function
if (!hasArgs) {
if (installLoop) {
process.exit(installPlan2CodeLoop());
} else if (uninstallLoop) {
process.exit(uninstallPlan2CodeLoop());
} else if (!hasArgs) {
// No arguments - run interactive menu
runInteractive();
} else if (localInstall) {
+195
View File
@@ -0,0 +1,195 @@
# Plan2Code Loop
An autonomous CLI tool that implements Plan2Code specs by looping through tasks automatically.
> **Note:** This is an **alternative** to `/plan2code-3--implement`, not a replacement. Use the manual Step 3 workflow when you want interactive control over each phase, or use this loop when you prefer hands-off autonomous execution.
## Installation
```bash
# From the plan2code root directory:
# Option 1: Install everything (recommended)
node install.js # Select option A
# Option 2: Install loop only
node install.js # Select option O
# Option 3: Manual build and link
cd plan2code-loop
npm install
npm run build
npm link
```
## Usage
Simply run the command - everything is interactive:
```bash
plan2code-loop
```
The CLI will:
1. Auto-detect specs in `./specs/` directory
2. Let you select a spec if multiple are found
3. Prompt to continue if an existing session is found
4. Ask for JIRA ticket ID, agent selection, and max iterations
## How It Works
The loop uses an **LLM-driven discovery** approach:
1. **Spec Selection** - Interactive menu to select from discovered specs
2. **Task Discovery** - The AI reads `overview.md` and phase files to find unchecked tasks
3. **Implementation** - The AI implements ONE task per iteration
4. **Checkbox Update** - The AI marks the task complete in the markdown file
5. **Scratchpad Update** - The AI appends notes to the per-spec scratchpad
6. **Completion Marker** - The AI outputs a structured marker (e.g., `TASK_COMPLETE: 1.1 - description`)
7. **Loop** - Repeat until all tasks done or max iterations reached
### Why LLM-Driven?
The Node app does NOT parse markdown to find tasks. Instead, the AI reads the spec files directly and decides what to work on. This is:
- **More flexible** - Works with any reasonable markdown format
- **Smarter** - AI can handle edge cases and ambiguity
- **Simpler** - Node code is just orchestration, not parsing
## Completion Markers
The AI must output one of these markers at the end of each iteration:
```
TASK_COMPLETE: 1.1 - Initialize project structure
TASK_BLOCKED: 2.3 - Missing API credentials
LOOP_COMPLETE
```
| Marker | Meaning |
|--------|---------|
| `TASK_COMPLETE: X.X - desc` | Task implemented and marked complete |
| `TASK_BLOCKED: X.X - reason` | Cannot complete task (explains why) |
| `LOOP_COMPLETE` | All phases finished |
## Session Files
Session state is stored **per-spec** inside the spec directory:
```
specs/my-feature/
├── overview.md
├── phase-1.md
├── phase-2.md
└── .plan2code-loop/ # Per-spec session state
├── config.json # Session configuration
├── scratchpad.md # LLM-managed progress notes
├── iteration.log # JSON log of each iteration
└── spec.hash # Hash for detecting spec changes
```
The scratchpad is managed by the LLM itself - after each task, the AI appends notes about what was done, decisions made, and files changed. This helps future iterations skip exploration.
## Supported Agents
| Agent | Status |
|-------|--------|
| Claude Code | Supported |
| GitHub Copilot CLI | Supported |
The loop uses your configured default model for each agent.
## Example Session
```
$ plan2code-loop
╭──────────────────────────────────────╮
│ │
│ 🔮 Plany's Loop │
│ Autonomous Implementation │
│ │
╰──────────────────────────────────────╯
Found spec: specs/todo-app
Feature: Todo App
Phases: 0/3
Tasks: 0/15
══════════════════════════════════════════════
Spec: Todo App
══════════════════════════════════════════════
Phases: 0/3
Tasks: 0/15
? JIRA Ticket ID (optional): PROJ-123
? Select AI agent: Claude Code
? Maximum iterations: 100
Starting Plan2Code Loop
══════════════════════════════════════════════
Agent: Claude Code
Model: default
Spec: C:\projects\my-app\specs\todo-app
Max iterations: 100
Iteration 1/100
[1/100] Task 1.1: Initialize project with Vite (45s)
✓ Completed: Task 1.1: Initialize project with Vite
Iteration 2/100
[2/100] Task 1.2: Configure TypeScript (32s)
✓ Completed: Task 1.2: Configure TypeScript
...
Session Summary
══════════════════════════════════════════════
Total iterations: 12
Tasks completed: 12
Exit reason: all_complete
Session files saved to specs/todo-app/.plan2code-loop:
- config.json (session configuration)
- scratchpad.md (LLM-managed notes)
- iteration.log (history)
```
## Development
```bash
# Install dependencies
npm install
# Build
npm run build
# Link for global usage
npm link
# Unlink
npm unlink
```
## Architecture
```
src/
├── bin/plan2code-loop.ts # CLI entry point
├── index.ts # Main exports
├── controller.ts # Loop orchestration
├── cli.ts # Interactive prompts
├── agents/ # Agent implementations
│ ├── claude-code.ts
│ └── copilot-cli.ts
├── prompt/ # Prompt building
│ ├── templates.ts
│ └── builder.ts
├── state/ # Session state management
│ └── manager.ts
├── spec/ # Spec utilities (for CLI display)
│ └── utils.ts
└── utils/ # Utilities
├── completion.ts # Marker parsing
└── logger.ts
```
+45
View File
@@ -0,0 +1,45 @@
{
"name": "plan2code-loop",
"version": "1.0.0",
"description": "Plan2Code Loop - Autonomous spec-driven implementation CLI",
"type": "module",
"main": "dist/index.js",
"bin": {
"plan2code-loop": "./dist/bin/plan2code-loop.js"
},
"files": [
"dist"
],
"engines": {
"node": ">=18.0.0"
},
"keywords": [
"cli",
"ai",
"agent",
"automation",
"claude",
"copilot",
"spec-driven"
],
"license": "MIT",
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"start": "node dist/bin/plan2code-loop.js",
"prepublishOnly": "npm run build"
},
"dependencies": {
"@inquirer/prompts": "^8.1.0",
"chalk": "^5.6.2",
"execa": "^9.6.1",
"fs-extra": "^11.3.3",
"ora": "^9.0.0"
},
"devDependencies": {
"@types/fs-extra": "^11.0.4",
"@types/node": "^25.0.3",
"tsup": "^8.5.1",
"typescript": "^5.9.3"
}
}
+82
View File
@@ -0,0 +1,82 @@
import type { Agent, AgentConfig, AgentExecutionOptions, AgentExecutionResult } from './types.js';
import { executeCommand } from '../utils/process.js';
import { writeFileSync, unlinkSync } from 'fs';
import { join } from 'path';
import { tmpdir } from 'os';
const claudeCodeConfig: AgentConfig = {
name: 'claude-code',
displayName: 'Claude Code',
command: 'claude',
models: [
{ value: 'default', label: 'Default (use Claude config)' },
],
defaultModel: 'default',
flags: {
prompt: '--print',
model: '--model',
skipPermissions: '--dangerously-skip-permissions',
},
};
class ClaudeCodeAgent implements Agent {
readonly config = claudeCodeConfig;
async execute(options: AgentExecutionOptions): Promise<AgentExecutionResult> {
// Write prompt to temp file - more reliable than stdin on Windows
const tempFile = join(tmpdir(), `plan2code-prompt-${Date.now()}.txt`);
writeFileSync(tempFile, options.prompt, 'utf-8');
try {
// Build args: flags first, then read prompt from temp file via shell
const args: string[] = [
this.config.flags.prompt, // --print for non-interactive mode
this.config.flags.skipPermissions,
];
// Only add --model if not using default
if (options.model && options.model !== 'default') {
args.push(this.config.flags.model, options.model);
}
// Use stdin from the temp file
const result = await executeCommand({
command: this.config.command,
args,
cwd: options.cwd,
timeout: options.timeout,
signal: options.signal,
stdinFile: tempFile,
});
return {
stdout: result.stdout,
stderr: result.stderr,
exitCode: result.exitCode,
timedOut: result.timedOut,
cancelled: result.cancelled,
duration: result.duration,
};
} finally {
// Clean up temp file
try {
unlinkSync(tempFile);
} catch {
// Ignore cleanup errors
}
}
}
async isAvailable(): Promise<boolean> {
// Run claude --version to verify it's actually installed and working
const result = await executeCommand({
command: this.config.command,
args: ['--version'],
cwd: process.cwd(),
timeout: 5000,
});
return result.exitCode === 0;
}
}
export const claudeCodeAgent = new ClaudeCodeAgent();
+70
View File
@@ -0,0 +1,70 @@
import type { Agent, AgentConfig, AgentExecutionOptions, AgentExecutionResult } from './types.js';
import { executeCommand } from '../utils/process.js';
const copilotCliConfig: AgentConfig = {
name: 'copilot-cli',
displayName: 'GitHub Copilot CLI',
command: 'copilot',
models: [
{ value: 'claude-sonnet-4', label: 'Claude Sonnet 4 (Default)' },
{ value: 'claude-sonnet-4.5', label: 'Claude Sonnet 4.5' },
{ value: 'claude-opus-4.5', label: 'Claude Opus 4.5' },
{ value: 'gpt-5', label: 'GPT-5' },
{ value: 'gpt-5-mini', label: 'GPT-5 Mini' },
{ value: 'gemini-3-pro-preview', label: 'Gemini 3 Pro' },
],
defaultModel: 'claude-sonnet-4',
flags: {
prompt: '-p',
model: '--model',
skipPermissions: '--allow-all-tools',
silent: '-s',
},
};
class CopilotCliAgent implements Agent {
readonly config = copilotCliConfig;
async execute(options: AgentExecutionOptions): Promise<AgentExecutionResult> {
// Use stdin for prompt to handle multi-line text properly
const args: string[] = [];
// Only add --model if not using default
if (options.model && options.model !== 'default') {
args.push(this.config.flags.model, options.model);
}
args.push(this.config.flags.skipPermissions, this.config.flags.silent!);
const result = await executeCommand({
command: this.config.command,
args,
cwd: options.cwd,
timeout: options.timeout,
signal: options.signal,
stdin: options.prompt,
});
return {
stdout: result.stdout,
stderr: result.stderr,
exitCode: result.exitCode,
timedOut: result.timedOut,
cancelled: result.cancelled,
duration: result.duration,
};
}
async isAvailable(): Promise<boolean> {
// Run copilot --version to verify it's installed
const result = await executeCommand({
command: this.config.command,
args: ['--version'],
cwd: process.cwd(),
timeout: 5000,
});
return result.exitCode === 0;
}
}
export const copilotCliAgent = new CopilotCliAgent();
+19
View File
@@ -0,0 +1,19 @@
export type {
Agent,
AgentConfig,
AgentExecutionOptions,
AgentExecutionResult,
ModelOption,
} from './types.js';
export { agentRegistry } from './registry.js';
export { claudeCodeAgent } from './claude-code.js';
export { copilotCliAgent } from './copilot-cli.js';
// Register all agents
import { agentRegistry } from './registry.js';
import { claudeCodeAgent } from './claude-code.js';
import { copilotCliAgent } from './copilot-cli.js';
agentRegistry.register(claudeCodeAgent);
agentRegistry.register(copilotCliAgent);
+34
View File
@@ -0,0 +1,34 @@
import type { Agent } from './types.js';
class AgentRegistry {
private agents: Map<string, Agent> = new Map();
register(agent: Agent): void {
this.agents.set(agent.config.name, agent);
}
get(name: string): Agent | undefined {
return this.agents.get(name);
}
getAll(): Agent[] {
return Array.from(this.agents.values());
}
getAvailable(): Promise<Agent[]> {
return Promise.all(
this.getAll().map(async (agent) => ({
agent,
available: await agent.isAvailable(),
}))
).then((results) =>
results.filter((r) => r.available).map((r) => r.agent)
);
}
getNames(): string[] {
return Array.from(this.agents.keys());
}
}
export const agentRegistry = new AgentRegistry();
+42
View File
@@ -0,0 +1,42 @@
export interface ModelOption {
value: string;
label: string;
}
export interface AgentConfig {
name: string;
displayName: string;
command: string;
models: ModelOption[];
defaultModel: string;
flags: {
prompt: string;
model: string;
skipPermissions: string;
silent?: string;
};
}
export interface AgentExecutionOptions {
prompt: string;
model: string;
timeout: number; // milliseconds
verbose: boolean;
cwd: string;
signal?: AbortSignal; // For cancellation
}
export interface AgentExecutionResult {
stdout: string;
stderr: string;
exitCode: number;
timedOut: boolean;
cancelled: boolean;
duration: number; // milliseconds
}
export interface Agent {
config: AgentConfig;
execute(options: AgentExecutionOptions): Promise<AgentExecutionResult>;
isAvailable(): Promise<boolean>;
}
+34
View File
@@ -0,0 +1,34 @@
import { run } from '../index.js';
import { logger } from '../utils/index.js';
async function main() {
try {
const result = await run();
if (!result) {
process.exit(0);
}
// Exit codes per spec
switch (result.exitReason) {
case 'all_complete':
logger.success('Loop completed successfully - all tasks done!');
process.exit(0);
case 'max_iterations':
logger.warning('Loop ended: max iterations reached');
process.exit(1);
case 'interrupted':
logger.info('Loop interrupted by user');
process.exit(2);
case 'error':
logger.error('Loop ended with error');
process.exit(3);
}
} catch (error) {
logger.error(error instanceof Error ? error.message : String(error));
process.exit(3);
}
}
main();
+228
View File
@@ -0,0 +1,228 @@
import path from 'path';
import { confirm, input, select } from '@inquirer/prompts';
import { agentRegistry } from './agents/index.js';
import { StateManager, type SessionConfig } from './state/index.js';
import { detectSpecDirectories, getSpecProgress } from './spec/utils.js';
import { logger } from './utils/index.js';
export interface SessionSetupResult {
config: SessionConfig;
isResume: boolean;
}
/**
* Detect and select a spec directory
*/
async function selectSpec(cwd: string = process.cwd()): Promise<string | null> {
// Auto-detect spec directories
const specDirs = await detectSpecDirectories(cwd);
if (specDirs.length === 0) {
logger.error('No spec directories found!');
logger.info('Expected: specs/<feature>/overview.md');
logger.info('');
logger.info('To get started:');
logger.info('1. Create a spec directory: mkdir -p specs/my-feature');
logger.info('2. Create overview.md with phases listed as checkboxes');
logger.info('3. Create phase-1.md, phase-2.md, etc. with task checkboxes');
return null;
}
if (specDirs.length === 1) {
const spec = specDirs[0];
const progress = await getSpecProgress(spec);
logger.info(`Found spec: ${path.relative(cwd, spec)}`);
logger.dim(` Feature: ${progress.featureName}`);
logger.dim(` Phases: ${progress.totalPhases}`);
return spec;
}
// Multiple specs - let user choose
const choices = await Promise.all(
specDirs.map(async (spec) => {
const progress = await getSpecProgress(spec);
const relativePath = path.relative(cwd, spec);
return {
name: `${progress.featureName} (${progress.totalPhases} phases) - ${relativePath}`,
value: spec,
};
})
);
const selectedSpec = await select({
message: 'Select spec to implement:',
choices,
});
return selectedSpec;
}
/**
* Select AI agent
*/
async function selectAgent(): Promise<string> {
const availableAgents = await agentRegistry.getAvailable();
if (availableAgents.length === 0) {
logger.error('No AI agents detected on your system!');
console.log();
logger.info('Please install at least one of the following:');
console.log();
logger.bold('Claude Code:');
logger.dim(' npm install -g @anthropic-ai/claude-code');
console.log();
logger.bold('GitHub Copilot CLI:');
logger.dim(' gh extension install github/gh-copilot');
console.log();
throw new Error('No agents available. Please install an AI agent and try again.');
}
if (availableAgents.length === 1) {
const agent = availableAgents[0];
logger.info(`Using ${agent.config.displayName} (only available agent)`);
return agent.config.name;
}
const agentName = await select({
message: 'Select AI agent:',
choices: availableAgents.map((agent) => ({
name: agent.config.displayName,
value: agent.config.name,
})),
});
return agentName;
}
/**
* Prompt for JIRA ticket ID
*/
async function promptJiraTicketId(): Promise<string | undefined> {
const ticketId = await input({
message: 'JIRA Ticket ID (optional, for commit messages):',
});
return ticketId.trim() || undefined;
}
/**
* Select maximum iterations
*/
async function selectMaxIterations(): Promise<number> {
const choices = [15, 30, 50, 75, 100, 125, 150, 200];
const max = await select({
message: 'Maximum iterations:',
choices: choices.map((n) => ({
name: n.toString(),
value: n,
})),
default: 100,
});
return max;
}
/**
* Handle existing session - returns action to take
*/
async function handleExistingSession(
stateManager: StateManager,
specPath: string
): Promise<'continue' | 'fresh' | 'new'> {
const state = await stateManager.detectSessionState(specPath);
if (state === 'new') {
return 'new';
}
if (state === 'continue') {
const config = await stateManager.readConfig();
const lastIter = config?.currentIteration ?? 0;
logger.info(`Found existing session at iteration ${lastIter}`);
const continueSession = await confirm({
message: 'Continue previous session?',
default: true,
});
return continueSession ? 'continue' : 'fresh';
}
if (state === 'changed') {
logger.warning('Spec has changed since last session.');
const startFresh = await confirm({
message: 'Start fresh? (This will clear previous progress)',
default: false,
});
return startFresh ? 'fresh' : 'continue';
}
return 'new';
}
/**
* Setup session via interactive prompts
*/
export async function setupSession(
stateManager: StateManager
): Promise<SessionSetupResult | null> {
// Show welcome
logger.welcome();
// Select spec
const specPath = await selectSpec(process.cwd());
if (!specPath) {
return null;
}
// Set spec path on state manager for per-spec state directory
stateManager.setSpecPath(specPath);
// Show spec progress
const progress = await getSpecProgress(specPath);
console.log();
logger.header(`Spec: ${progress.featureName}`);
logger.info(`Phases: ${progress.totalPhases}`);
console.log();
// Check for existing session
const sessionAction = await handleExistingSession(stateManager, specPath);
if (sessionAction === 'continue') {
const existingConfig = await stateManager.readConfig();
if (existingConfig) {
logger.info('Resuming previous session...');
return { config: existingConfig, isResume: true };
}
}
if (sessionAction === 'fresh') {
await stateManager.clearState();
}
// Collect new session configuration
const jiraTicketId = await promptJiraTicketId();
const agent = await selectAgent();
const maxIterations = await selectMaxIterations();
const config: SessionConfig = {
agent,
model: 'default',
maxIterations,
specPath,
timeout: 30,
verbose: false,
startedAt: new Date().toISOString(),
currentIteration: 0,
jiraTicketId,
};
// Initialize session
await stateManager.initializeNewSession(config);
return { config, isResume: false };
}
+314
View File
@@ -0,0 +1,314 @@
import { agentRegistry, type Agent, type AgentExecutionResult } from './agents/index.js';
import { StateManager, type SessionConfig, type IterationLogEntry } from './state/index.js';
import { buildLoopPrompt } from './prompt/index.js';
import { checkForCompletion, logger, type CompletionCheckResult } from './utils/index.js';
export interface TaskCompleteInfo {
marker: string;
taskId?: string;
taskName?: string;
}
export interface ControllerOptions {
config: SessionConfig;
stateManager: StateManager;
onIteration?: (iteration: number, max: number) => void;
onTaskComplete?: (info: TaskCompleteInfo) => void | Promise<void>;
onLoopComplete?: () => void;
}
export interface LoopResult {
completed: boolean;
iterations: number;
finalMarker?: string;
exitReason: 'all_complete' | 'max_iterations' | 'interrupted' | 'error';
tasksCompleted: number;
error?: Error;
}
export class Controller {
private readonly config: SessionConfig;
private readonly stateManager: StateManager;
private readonly agent: Agent;
private readonly onIteration?: (iteration: number, max: number) => void;
private readonly onTaskComplete?: (info: TaskCompleteInfo) => void | Promise<void>;
private readonly onLoopComplete?: () => void;
private interrupted = false;
private abortController: AbortController | null = null;
private tasksCompleted = 0;
constructor(options: ControllerOptions) {
this.config = options.config;
this.stateManager = options.stateManager;
this.onIteration = options.onIteration;
this.onTaskComplete = options.onTaskComplete;
this.onLoopComplete = options.onLoopComplete;
const agent = agentRegistry.get(this.config.agent);
if (!agent) {
throw new Error(`Agent not found: ${this.config.agent}`);
}
this.agent = agent;
}
private async buildPrompt(): Promise<string> {
return buildLoopPrompt({
specPath: this.config.specPath,
iteration: this.config.currentIteration + 1,
maxIterations: this.config.maxIterations,
stateManager: this.stateManager,
});
}
private async executeIteration(prompt: string): Promise<AgentExecutionResult> {
const timeoutMs = this.config.timeout * 60 * 1000;
// Create new AbortController for this iteration
this.abortController = new AbortController();
const result = await this.agent.execute({
prompt,
model: this.config.model,
timeout: timeoutMs,
verbose: this.config.verbose,
cwd: process.cwd(),
signal: this.abortController.signal,
});
this.abortController = null;
return result;
}
private createLogEntry(
result: AgentExecutionResult,
status: IterationLogEntry['status'],
marker?: string
): IterationLogEntry {
return {
iteration: this.config.currentIteration + 1,
timestamp: new Date().toISOString(),
duration: result.duration,
exitCode: result.exitCode,
status,
completionMarker: marker,
};
}
private formatTaskDisplay(completion: CompletionCheckResult): string {
if (completion.taskId && completion.taskName) {
return `Task ${completion.taskId}: ${completion.taskName}`;
} else if (completion.taskId) {
return `Task ${completion.taskId}`;
}
return '';
}
private displayIterationResult(result: AgentExecutionResult, iterNum: number, completion: CompletionCheckResult): void {
const duration = Math.round(result.duration / 1000);
const taskDisplay = this.formatTaskDisplay(completion);
// Show iteration completion with task info if available
if (taskDisplay) {
logger.iteration(iterNum, this.config.maxIterations, `${taskDisplay} (${duration}s)`);
} else {
logger.iteration(iterNum, this.config.maxIterations, `completed in ${duration}s`);
}
// Verbose mode: show full output
if (this.config.verbose) {
console.log();
logger.dim('--- Agent Output ---');
console.log(result.stdout);
if (result.stderr) {
logger.dim('--- Agent Stderr ---');
console.log(result.stderr);
}
logger.dim('--- End Output ---');
console.log();
} else if (result.exitCode !== 0 && result.stderr.trim()) {
logger.error(` ${result.stderr.trim().split('\n')[0]}`);
}
}
async run(): Promise<LoopResult> {
logger.header('Starting Plan2Code Loop');
logger.info(`Agent: ${this.agent.config.displayName}`);
logger.info(`Model: ${this.config.model}`);
logger.info(`Spec: ${this.config.specPath}`);
logger.info(`Max iterations: ${this.config.maxIterations}`);
console.log();
while (this.config.currentIteration < this.config.maxIterations) {
if (this.interrupted) {
return {
completed: false,
iterations: this.config.currentIteration,
exitReason: 'interrupted',
tasksCompleted: this.tasksCompleted,
};
}
const iterNum = this.config.currentIteration + 1;
this.onIteration?.(iterNum, this.config.maxIterations);
console.log();
logger.info(`Iteration ${iterNum}/${this.config.maxIterations}`);
// Build prompt - simple, just spec path and iteration info
const prompt = await this.buildPrompt();
const spinner = logger.spinner('Waiting for agent response');
const startTime = Date.now();
// Update spinner with elapsed time every second
const elapsedInterval = setInterval(() => {
const elapsed = Math.round((Date.now() - startTime) / 1000);
spinner.text = `Waiting for agent response (${elapsed}s)`;
}, 1000);
try {
const result = await this.executeIteration(prompt);
clearInterval(elapsedInterval);
spinner.stop();
// Check if cancelled
if (result.cancelled || this.interrupted) {
logger.info('Agent process cancelled');
const entry: IterationLogEntry = {
iteration: iterNum,
timestamp: new Date().toISOString(),
duration: result.duration,
exitCode: -1,
status: 'interrupted',
};
await this.stateManager.appendIterationLog(entry);
return {
completed: false,
iterations: this.config.currentIteration,
exitReason: 'interrupted',
tasksCompleted: this.tasksCompleted,
};
}
// Check for completion markers in output (now includes task info)
const completion = checkForCompletion(result.stdout + result.stderr);
// Determine status
let status: IterationLogEntry['status'] = 'running';
if (completion.completed) {
if (completion.marker === 'TASK_BLOCKED') {
status = 'blocked';
} else {
status = 'completed';
}
} else if (result.timedOut) {
status = 'timeout';
} else if (result.exitCode !== 0) {
status = 'error';
}
// Log iteration
const logEntry = this.createLogEntry(result, status, completion.marker);
await this.stateManager.appendIterationLog(logEntry);
// Display iteration result with task info from completion marker
this.displayIterationResult(result, iterNum, completion);
// Note: Scratchpad updates are now handled by the LLM directly
// Handle completion markers
if (completion.completed) {
const taskDisplay = this.formatTaskDisplay(completion);
if (completion.marker === 'TASK_COMPLETE') {
this.tasksCompleted++;
await this.onTaskComplete?.({
marker: completion.marker,
taskId: completion.taskId,
taskName: completion.taskName,
});
logger.success(taskDisplay ? `Completed: ${taskDisplay}` : 'Task completed!');
} else if (completion.marker === 'TASK_BLOCKED') {
const blockInfo = completion.taskId
? `Task ${completion.taskId} blocked: ${completion.reason || 'Unknown reason'}`
: `Task blocked: ${completion.reason || 'Unknown reason'}`;
logger.warning(blockInfo);
} else if (completion.marker === 'LOOP_COMPLETE') {
// Commit any final changes before completing
this.tasksCompleted++;
await this.onTaskComplete?.({
marker: completion.marker,
taskId: completion.taskId,
taskName: completion.taskName || 'Final implementation complete',
});
this.onLoopComplete?.();
logger.success('All tasks complete!');
return {
completed: true,
iterations: iterNum,
finalMarker: 'LOOP_COMPLETE',
exitReason: 'all_complete',
tasksCompleted: this.tasksCompleted,
};
}
}
// Increment iteration and update spec hash (so resume doesn't see false changes)
await this.stateManager.incrementIteration();
await this.stateManager.updateSpecHash(this.config.specPath);
this.config.currentIteration++;
// Handle timeout
if (result.timedOut) {
logger.warning(`Iteration ${iterNum} timed out, continuing...`);
}
// Handle error (but continue - LLM might recover)
if (result.exitCode !== 0 && !result.timedOut && !completion.completed) {
logger.warning(`Iteration ${iterNum} exited with code ${result.exitCode}, continuing...`);
}
} catch (error) {
clearInterval(elapsedInterval);
spinner.stop();
logger.error(`Iteration ${iterNum} failed: ${error}`);
// Log the error
const entry: IterationLogEntry = {
iteration: iterNum,
timestamp: new Date().toISOString(),
duration: 0,
exitCode: -1,
status: 'error',
};
await this.stateManager.appendIterationLog(entry);
return {
completed: false,
iterations: this.config.currentIteration,
exitReason: 'error',
tasksCompleted: this.tasksCompleted,
error: error instanceof Error ? error : new Error(String(error)),
};
}
}
// Max iterations reached
logger.warning('Max iterations reached');
return {
completed: false,
iterations: this.config.currentIteration,
exitReason: 'max_iterations',
tasksCompleted: this.tasksCompleted,
};
}
interrupt(): void {
this.interrupted = true;
if (this.abortController) {
this.abortController.abort();
}
}
}
+93
View File
@@ -0,0 +1,93 @@
import path from 'path';
import { StateManager } from './state/index.js';
import { Controller, type LoopResult, type TaskCompleteInfo } from './controller.js';
import { setupSession } from './cli.js';
import { logger, createTaskCommit } from './utils/index.js';
export async function run(): Promise<LoopResult | null> {
// Ensure agents are registered
await import('./agents/index.js');
const stateManager = new StateManager();
const result = await setupSession(stateManager);
if (!result) {
return null;
}
const { config, isResume } = result;
if (isResume) {
logger.info(`Resuming from iteration ${config.currentIteration}`);
}
const controller = new Controller({
config,
stateManager,
onIteration: (iter, max) => {
// Could add git checkpoint logic here if needed
},
onTaskComplete: async (info: TaskCompleteInfo) => {
// Create git commit for completed task
const taskName = info.taskName || info.taskId || 'Task completed';
await createTaskCommit({
taskName,
jiraTicketId: config.jiraTicketId,
cwd: process.cwd(),
});
},
onLoopComplete: () => {
// All tasks completed callback
},
});
// Setup interrupt handler
const handleInterrupt = () => {
logger.warning('\nInterrupt received, saving state...');
controller.interrupt();
};
process.on('SIGINT', handleInterrupt);
process.on('SIGTERM', handleInterrupt);
try {
const loopResult = await controller.run();
// Display summary
console.log();
logger.header('Session Summary');
logger.info(`Total iterations: ${loopResult.iterations}`);
logger.info(`Tasks completed: ${loopResult.tasksCompleted}`);
logger.info(`Exit reason: ${loopResult.exitReason}`);
if (loopResult.finalMarker) {
logger.info(`Completion marker: ${loopResult.finalMarker}`);
}
if (loopResult.error) {
logger.error(`Error: ${loopResult.error.message}`);
}
// Show completion celebration and finalize reminder when all phases complete
if (loopResult.exitReason === 'all_complete') {
logger.allPhasesComplete();
}
// Show state file locations (now per-spec)
console.log();
logger.dim(`Session files saved to ${path.relative(process.cwd(), stateManager.getStateDir())}:`);
logger.dim(' - config.json (session configuration)');
logger.dim(' - scratchpad.md (LLM-managed notes)');
logger.dim(' - iteration.log (history)');
return loopResult;
} finally {
process.off('SIGINT', handleInterrupt);
process.off('SIGTERM', handleInterrupt);
}
}
// Re-export types and classes
export { Controller, type ControllerOptions, type LoopResult, type TaskCompleteInfo } from './controller.js';
export { StateManager } from './state/index.js';
export { setupSession } from './cli.js';
export { agentRegistry, type Agent, type AgentConfig } from './agents/index.js';
export { detectSpecDirectories, getSpecProgress } from './spec/index.js';
+33
View File
@@ -0,0 +1,33 @@
import type { StateManager } from '../state/index.js';
import { LOOP_PROMPT_TEMPLATE } from './templates.js';
export interface PromptContext {
specPath: string;
iteration: number;
maxIterations: number;
stateManager: StateManager;
}
/**
* Build the prompt for the AI agent
* Simple: just pass spec path and let the LLM discover the next task
*/
export async function buildLoopPrompt(context: PromptContext): Promise<string> {
const { specPath, iteration, maxIterations, stateManager } = context;
// Read scratchpad content for session continuity (LLM writes to this)
const scratchpadContent = await stateManager.readScratchpad();
// Project root is where plan2code-loop was invoked from
const projectRoot = process.cwd();
// Simple template substitution
const prompt = LOOP_PROMPT_TEMPLATE
.replace(/{{projectRoot}}/g, projectRoot)
.replace(/{{specPath}}/g, specPath)
.replace(/{{iteration}}/g, iteration.toString())
.replace(/{{maxIterations}}/g, maxIterations.toString())
.replace(/{{scratchpadContent}}/g, scratchpadContent || '(First iteration - no previous progress)');
return prompt;
}
+6
View File
@@ -0,0 +1,6 @@
export {
buildLoopPrompt,
type PromptContext,
} from './builder.js';
export { LOOP_PROMPT_TEMPLATE } from './templates.js';
+85
View File
@@ -0,0 +1,85 @@
export const LOOP_PROMPT_TEMPLATE = `# PLAN-LOOP: Autonomous Task Implementation
## CRITICAL CONSTRAINT
**IMPLEMENT EXACTLY ONE TASK PER ITERATION.**
Do NOT implement multiple tasks. Do NOT complete an entire phase.
Find the FIRST unchecked task, implement ONLY that task, then STOP and report.
## Project Information
- **Project Root:** \`{{projectRoot}}\`
- **Spec Location:** \`{{specPath}}\`
- Read \`AGENTS.md\` for project-specific guidance if available
## IMPORTANT: File Locations
- Write ALL code files relative to the **project root** (\`{{projectRoot}}\`)
- The spec directory (\`{{specPath}}\`) is for documentation ONLY - never write code there
- Example: Create \`{{projectRoot}}/src/index.ts\`, NOT \`{{specPath}}/src/index.ts\`
## Iteration
{{iteration}} of {{maxIterations}}
## Task Discovery Process
1. Read \`{{specPath}}/overview.md\` to see all phases
2. Find the FIRST phase with an unchecked checkbox (\`- [ ]\` or \`- [/]\`)
3. Read that phase's file (e.g., \`phase-1.md\`)
4. Check the \`## Prerequisites\` section FIRST
5. Find the FIRST unchecked prerequisite (\`- [ ]\`)
- If found, that is your task for this iteration
- Verify/complete it, then mark \`[x]\` or \`[?]\`
6. Only if ALL prerequisites are complete (\`[x]\` or \`[?]\`), find the FIRST unchecked task
7. That is your ONE task - implement ONLY that task
## Checkbox States
- \`[ ]\` = incomplete/pending (do the FIRST one you find)
- \`[x]\` = complete (skip)
- \`[?]\` = assumed complete, couldn't verify (skip)
- \`[!]\` = blocked (skip)
## Implementation Steps
1. Read and understand the single task
2. Implement it completely
3. Validate it works (run tests if applicable and double-check code)
4. Mark ONLY that task's checkbox as \`[x]\` in the phase file
5. If that was the LAST task in the phase, also mark the phase \`[x]\` in overview.md
6. Output your completion marker and STOP
## Git Policy
**DO NOT create git commits.** The orchestration system handles commits automatically after each task completion. Just implement the code and leave changes uncommitted.
## Completion Markers (REQUIRED FORMAT)
Output exactly ONE of these at the end, including the task ID and description:
**PREREQ_COMPLETE: [prereq_id] - [description]**
Example: \`PREREQ_COMPLETE: P1.1 - Verified Phase 1 complete\`
**PREREQ_ASSUMED: [prereq_id] - [description]**
Example: \`PREREQ_ASSUMED: P2.1 - Design approval (cannot verify)\`
**TASK_COMPLETE: [task_id] - [task_description]**
Example: \`TASK_COMPLETE: 1.1 - Initialize project structure\`
**TASK_BLOCKED: [task_id] - [reason]**
Example: \`TASK_BLOCKED: 2.3 - Missing API credentials\`
**LOOP_COMPLETE**
Use only when ALL phases in overview.md are marked complete.
## Scratchpad Management
After completing each task, append to \`{{specPath}}/.plan2code-loop/scratchpad.md\`:
- Task completed and Phase item reference
- Key decisions made and reasoning
- Files changed
- Any blockers or notes for next iteration
Keep entries concise. Sacrifice grammar for concision. This file helps future iterations skip exploration.
If key patterns or learnings were discovered, update \`./AGENTS.md\` if it exists.
## Previous Session Context
{{scratchpadContent}}
---
Remember: ONE TASK ONLY. Find it, implement it, mark it done, output TASK_COMPLETE with the task ID and description, then stop.
`;
+4
View File
@@ -0,0 +1,4 @@
export {
detectSpecDirectories,
getSpecProgress,
} from './utils.js';
+70
View File
@@ -0,0 +1,70 @@
import path from 'path';
import fs from 'fs-extra';
/**
* Auto-detect spec directories in the project
* Looks for directories containing overview.md
*/
export async function detectSpecDirectories(cwd: string = process.cwd()): Promise<string[]> {
const specsDir = path.join(cwd, 'specs');
const specsDirs: string[] = [];
if (await fs.pathExists(specsDir)) {
// Look for overview.md files in subdirectories
const entries = await fs.readdir(specsDir, { withFileTypes: true });
for (const entry of entries) {
if (entry.isDirectory()) {
const overviewPath = path.join(specsDir, entry.name, 'overview.md');
if (await fs.pathExists(overviewPath)) {
specsDirs.push(path.join(specsDir, entry.name));
}
}
}
// Also check if specs/ itself contains overview.md
const rootOverview = path.join(specsDir, 'overview.md');
if (await fs.pathExists(rootOverview)) {
specsDirs.push(specsDir);
}
}
return specsDirs;
}
/**
* Simple progress stats by counting phase-*.md files
* Used for CLI display only - LLM handles actual task discovery
*/
export async function getSpecProgress(specPath: string): Promise<{
featureName: string;
totalPhases: number;
}> {
const overviewPath = path.join(specPath, 'overview.md');
// Extract feature name from overview.md
let featureName = path.basename(specPath);
try {
const overviewContent = await fs.readFile(overviewPath, 'utf8');
const h1Match = overviewContent.match(/^#\s+(.+)$/m);
if (h1Match) {
featureName = h1Match[1].trim();
}
} catch {
// Use directory name as fallback
}
// Count phase-*.md files
let totalPhases = 0;
try {
const entries = await fs.readdir(specPath);
totalPhases = entries.filter(name => /^phase-\d+\.md$/i.test(name)).length;
} catch {
// Directory read failed
}
return {
featureName,
totalPhases,
};
}
+29
View File
@@ -0,0 +1,29 @@
export interface SessionConfig {
agent: string; // "claude-code" | "copilot-cli"
model: string; // Selected model
maxIterations: number; // 5-50
timeout: number; // Minutes per iteration
verbose: boolean;
specPath: string; // Path to the spec directory
startedAt: string; // ISO timestamp
currentIteration: number;
jiraTicketId?: string; // JIRA ticket ID for commit messages
}
export interface IterationLogEntry {
iteration: number;
timestamp: string; // ISO timestamp
duration: number; // milliseconds
exitCode: number;
status: 'running' | 'completed' | 'error' | 'timeout' | 'interrupted' | 'blocked';
completionMarker?: string;
}
export type SessionState = 'new' | 'continue' | 'changed';
export const DEFAULT_CONFIG: Partial<SessionConfig> = {
maxIterations: 100,
timeout: 30,
verbose: false,
currentIteration: 0,
};
+9
View File
@@ -0,0 +1,9 @@
import { createHash } from 'crypto';
export function computeHash(content: string): string {
return createHash('sha256').update(content).digest('hex').slice(0, 16);
}
export function hashesMatch(a: string, b: string): boolean {
return a === b;
}
+9
View File
@@ -0,0 +1,9 @@
export {
type SessionConfig,
type IterationLogEntry,
type SessionState,
DEFAULT_CONFIG,
} from './config.js';
export { StateManager } from './manager.js';
export { computeHash, hashesMatch } from './hash.js';
+231
View File
@@ -0,0 +1,231 @@
import path from 'path';
import fs from 'fs-extra';
import type { SessionConfig, IterationLogEntry, SessionState } from './config.js';
import { DEFAULT_CONFIG } from './config.js';
import { computeHash, hashesMatch } from './hash.js';
const SCRATCHPAD_TEMPLATE = `# Scratchpad
Session notes appended by LLM during implementation.
---
`;
export class StateManager {
private readonly stateDir: string;
private readonly configPath: string;
private readonly scratchpadPath: string;
private readonly logPath: string;
private readonly hashPath: string;
private specPath: string | null = null;
constructor(cwd: string = process.cwd()) {
// Default to project root - will be updated when spec is selected
this.stateDir = path.join(cwd, '.plan2code-loop');
this.configPath = path.join(this.stateDir, 'config.json');
this.scratchpadPath = path.join(this.stateDir, 'scratchpad.md');
this.logPath = path.join(this.stateDir, 'iteration.log');
this.hashPath = path.join(this.stateDir, 'spec.hash');
}
/**
* Set the spec path and update all state paths to be inside the spec directory
*/
setSpecPath(specPath: string): void {
this.specPath = specPath;
const stateDir = path.join(specPath, '.plan2code-loop');
// Update all paths to be relative to spec directory
(this as any).stateDir = stateDir;
(this as any).configPath = path.join(stateDir, 'config.json');
(this as any).scratchpadPath = path.join(stateDir, 'scratchpad.md');
(this as any).logPath = path.join(stateDir, 'iteration.log');
(this as any).hashPath = path.join(stateDir, 'spec.hash');
}
// Directory operations
async ensureStateDir(): Promise<boolean> {
const existed = await fs.pathExists(this.stateDir);
await fs.ensureDir(this.stateDir);
return existed;
}
getStateDir(): string {
return this.stateDir;
}
// Config operations
async readConfig(): Promise<SessionConfig | null> {
try {
const content = await fs.readFile(this.configPath, 'utf8');
return JSON.parse(content) as SessionConfig;
} catch {
return null;
}
}
async writeConfig(config: SessionConfig): Promise<void> {
await fs.writeFile(
this.configPath,
JSON.stringify(config, null, 2),
'utf8'
);
}
async updateConfig(updates: Partial<SessionConfig>): Promise<SessionConfig> {
const existing = await this.readConfig();
const updated = { ...DEFAULT_CONFIG, ...existing, ...updates } as SessionConfig;
await this.writeConfig(updated);
return updated;
}
async incrementIteration(): Promise<number> {
const config = await this.readConfig();
if (!config) throw new Error('No session config found');
config.currentIteration++;
await this.writeConfig(config);
return config.currentIteration;
}
// Session state detection
async hasExistingSession(): Promise<boolean> {
const [configExists, scratchpadExists] = await Promise.all([
fs.pathExists(this.configPath),
fs.pathExists(this.scratchpadPath),
]);
return configExists || scratchpadExists;
}
async detectSessionState(specPath: string): Promise<SessionState> {
// Ensure we're using the correct spec path
this.setSpecPath(specPath);
const hasSession = await this.hasExistingSession();
if (!hasSession) {
return 'new';
}
// Check if the spec path has changed
const existingConfig = await this.readConfig();
if (existingConfig && existingConfig.specPath !== specPath) {
return 'changed';
}
// Check if spec content has changed (using hash of overview.md)
const specChanged = await this.hasSpecChanged(specPath);
return specChanged ? 'changed' : 'continue';
}
// Hash management for spec change detection
async computeSpecHash(specPath: string): Promise<string> {
const overviewPath = path.join(specPath, 'overview.md');
try {
const content = await fs.readFile(overviewPath, 'utf8');
return computeHash(content);
} catch {
return '';
}
}
async readStoredHash(): Promise<string | null> {
try {
return await fs.readFile(this.hashPath, 'utf8');
} catch {
return null;
}
}
async storeHash(hash: string): Promise<void> {
await fs.writeFile(this.hashPath, hash, 'utf8');
}
async hasSpecChanged(specPath: string): Promise<boolean> {
const stored = await this.readStoredHash();
if (!stored) return true;
const current = await this.computeSpecHash(specPath);
return !hashesMatch(stored, current);
}
async updateSpecHash(specPath: string): Promise<void> {
const hash = await this.computeSpecHash(specPath);
await this.storeHash(hash);
}
// Scratchpad operations
async initializeScratchpad(): Promise<void> {
await fs.writeFile(this.scratchpadPath, SCRATCHPAD_TEMPLATE, 'utf8');
}
async readScratchpad(): Promise<string> {
try {
return await fs.readFile(this.scratchpadPath, 'utf8');
} catch {
return '';
}
}
async writeScratchpad(content: string): Promise<void> {
await fs.writeFile(this.scratchpadPath, content, 'utf8');
}
// Iteration log operations
async appendIterationLog(entry: IterationLogEntry): Promise<void> {
const line = JSON.stringify(entry) + '\n';
await fs.appendFile(this.logPath, line, 'utf8');
}
async readIterationLog(): Promise<IterationLogEntry[]> {
try {
const content = await fs.readFile(this.logPath, 'utf8');
return content
.trim()
.split('\n')
.filter(Boolean)
.map((line) => JSON.parse(line) as IterationLogEntry);
} catch {
return [];
}
}
async getLastIteration(): Promise<IterationLogEntry | null> {
const log = await this.readIterationLog();
return log.length > 0 ? log[log.length - 1] : null;
}
// State clearing and initialization
async clearState(): Promise<void> {
const filesToDelete = [
this.scratchpadPath,
this.configPath,
this.logPath,
this.hashPath,
];
await Promise.all(
filesToDelete.map((file) => fs.remove(file).catch(() => {}))
);
}
async initializeNewSession(config: SessionConfig): Promise<void> {
// Ensure spec path is set before initializing
this.setSpecPath(config.specPath);
await this.clearState();
await this.ensureStateDir();
await this.initializeScratchpad();
await this.writeConfig({
...config,
startedAt: new Date().toISOString(),
currentIteration: 0,
});
const hash = await this.computeSpecHash(config.specPath);
await this.storeHash(hash);
}
}
+74
View File
@@ -0,0 +1,74 @@
// Completion markers for plan2code-loop
const COMPLETION_MARKERS = ['TASK_COMPLETE', 'TASK_BLOCKED', 'LOOP_COMPLETE'] as const;
export type CompletionMarker = (typeof COMPLETION_MARKERS)[number];
export interface CompletionCheckResult {
completed: boolean;
marker?: CompletionMarker;
taskId?: string; // e.g., "1.1", "2.3"
taskName?: string; // e.g., "Initialize project structure"
reason?: string; // For TASK_BLOCKED: reason
}
export function checkForCompletion(output: string): CompletionCheckResult {
// Check for LOOP_COMPLETE first (highest priority)
if (output.includes('LOOP_COMPLETE')) {
return { completed: true, marker: 'LOOP_COMPLETE' };
}
// Check for TASK_COMPLETE with task info
// Format: TASK_COMPLETE: 1.1 - Task description
// Or: TASK_COMPLETE: 1.1: Task description
// Or: TASK_COMPLETE[1.1]: Task description
const completeMatch = output.match(/TASK_COMPLETE[:\[\s]+(\d+\.\d+)[\]:\-\s]+(.+?)(?:\n|$)/i);
if (completeMatch) {
return {
completed: true,
marker: 'TASK_COMPLETE',
taskId: completeMatch[1],
taskName: completeMatch[2].trim(),
};
}
// Simple TASK_COMPLETE without structured info - try to extract task from context
if (output.includes('TASK_COMPLETE')) {
// Try to find task info nearby
const taskMatch = output.match(/(?:task|completed?)\s+(\d+\.\d+)[:\s]+([^\n]{5,80})/i);
return {
completed: true,
marker: 'TASK_COMPLETE',
taskId: taskMatch?.[1],
taskName: taskMatch?.[2]?.trim(),
};
}
// Check for TASK_BLOCKED with task info and reason
// Format: TASK_BLOCKED: 1.1 - Reason why blocked
const blockedWithTaskMatch = output.match(/TASK_BLOCKED[:\[\s]+(\d+\.\d+)[\]:\-\s]+(.+?)(?:\n|$)/i);
if (blockedWithTaskMatch) {
return {
completed: true,
marker: 'TASK_BLOCKED',
taskId: blockedWithTaskMatch[1],
reason: blockedWithTaskMatch[2].trim(),
};
}
// TASK_BLOCKED with just reason (no task ID)
const blockedMatch = output.match(/TASK_BLOCKED:\s*(.+?)(?:\n|$)/);
if (blockedMatch) {
return {
completed: true,
marker: 'TASK_BLOCKED',
reason: blockedMatch[1].trim()
};
}
// Simple TASK_BLOCKED without any info
if (output.includes('TASK_BLOCKED')) {
return { completed: true, marker: 'TASK_BLOCKED', reason: 'No reason provided' };
}
return { completed: false };
}
+134
View File
@@ -0,0 +1,134 @@
import { execa } from 'execa';
import { existsSync, readFileSync, writeFileSync } from 'fs';
import { join } from 'path';
import { logger } from './logger.js';
export interface GitCommitOptions {
taskName: string;
jiraTicketId?: string;
cwd?: string;
}
/**
* Check if directory is a git repository
*/
export async function isGitRepo(cwd: string): Promise<boolean> {
const result = await execa('git', ['rev-parse', '--git-dir'], { cwd, reject: false });
return result.exitCode === 0;
}
/**
* Initialize a git repository if one doesn't exist
*/
export async function ensureGitRepo(cwd: string): Promise<boolean> {
if (await isGitRepo(cwd)) {
return true;
}
logger.info('Initializing git repository...');
const result = await execa('git', ['init'], { cwd, reject: false });
if (result.exitCode !== 0) {
logger.error(`Failed to initialize git repo: ${result.stderr}`);
return false;
}
logger.success('Git repository initialized');
return true;
}
/**
* Required entries for the .gitignore file
*/
const REQUIRED_GITIGNORE_ENTRIES = ['specs/', 'specs--completed/', 'nul'];
/**
* Ensure .gitignore exists with required entries
*/
export function ensureGitignore(cwd: string): void {
const gitignorePath = join(cwd, '.gitignore');
let content = '';
if (existsSync(gitignorePath)) {
content = readFileSync(gitignorePath, 'utf-8');
}
const lines = content.split('\n').map(line => line.trim());
const missingEntries = REQUIRED_GITIGNORE_ENTRIES.filter(entry => !lines.includes(entry));
if (missingEntries.length > 0) {
const needsNewline = content.length > 0 && !content.endsWith('\n');
const newContent = content + (needsNewline ? '\n' : '') + missingEntries.join('\n') + '\n';
writeFileSync(gitignorePath, newContent);
logger.dim(`Added to .gitignore: ${missingEntries.join(', ')}`);
}
}
/**
* Create a local git commit for a completed task
*/
export async function createTaskCommit(options: GitCommitOptions): Promise<boolean> {
const { taskName, jiraTicketId, cwd = process.cwd() } = options;
logger.dim(`Git commit check in: ${cwd}`);
try {
// Ensure we have a git repo
if (!await ensureGitRepo(cwd)) {
return false;
}
// Ensure .gitignore exists with required entries
ensureGitignore(cwd);
// Check if there are any changes to commit
const statusResult = await execa('git', ['status', '--porcelain'], { cwd, reject: false });
// Debug: show what git status returned
if (statusResult.stdout?.trim()) {
logger.dim(`Git status found changes:\n${statusResult.stdout.slice(0, 500)}`);
}
if (statusResult.exitCode !== 0) {
logger.error(`Git status failed: ${statusResult.stderr}`);
return false;
}
if (!statusResult.stdout?.trim()) {
logger.dim('No changes to commit');
return false;
}
// Stage all changes
const addResult = await execa('git', ['add', '-A'], { cwd, reject: false });
if (addResult.exitCode !== 0) {
logger.error(`Git add failed: ${addResult.stderr}`);
return false;
}
// Build commit message
let commitMessage = taskName;
if (jiraTicketId) {
commitMessage = `${taskName}\n\n${jiraTicketId}`;
}
// Create the commit
const commitResult = await execa('git', ['commit', '-m', commitMessage], { cwd, reject: false });
if (commitResult.exitCode !== 0) {
// Check if it's just "nothing to commit" vs actual error
if (commitResult.stdout?.includes('nothing to commit') || commitResult.stderr?.includes('nothing to commit')) {
logger.dim('No changes to commit');
return false;
}
logger.error(`Git commit failed: ${commitResult.stderr || commitResult.stdout}`);
return false;
}
logger.success(`Created commit: ${taskName}${jiraTicketId ? ` (${jiraTicketId})` : ''}`);
return true;
} catch (error) {
logger.error(`Failed to create commit: ${error instanceof Error ? error.message : String(error)}`);
return false;
}
}
+17
View File
@@ -0,0 +1,17 @@
export { logger, MASCOT, type Logger } from './logger.js';
export {
checkForCompletion,
type CompletionMarker,
type CompletionCheckResult
} from './completion.js';
export {
executeCommand,
type ExecuteOptions,
type ExecuteResult
} from './process.js';
export {
createTaskCommit,
isGitRepo,
ensureGitRepo,
type GitCommitOptions
} from './git.js';
+126
View File
@@ -0,0 +1,126 @@
import chalk from 'chalk';
import ora, { type Ora } from 'ora';
// mascot - our friendly robot assistant
export const MASCOT = {
// Full mascot for headers
full: [
' ╭───╮ ',
' │ ● │ ',
' │ ◡ │ ',
' ╰───╯ ',
],
// Mini mascot for inline use
mini: '(◉‿◉)',
// Waving mascot for greetings
wave: [
' ╭───╮ ',
' │ ● │ ',
' │ ◡ │ ',
' ╰───╯ ',
],
// Celebration mascot for completion
celebrate: [
' ╭───╮ ',
' │ ★ │ ',
' │ ◡ │ ',
' ╰───╯ ',
],
};
export const logger = {
info: (message: string) => console.log(chalk.blue('i'), message),
success: (message: string) => console.log(chalk.green('+'), message),
warning: (message: string) => console.log(chalk.yellow('!'), message),
error: (message: string) => console.log(chalk.red('x'), message),
dim: (message: string) => console.log(chalk.dim(message)),
bold: (message: string) => console.log(chalk.bold(message)),
header: (message: string) => {
console.log();
console.log(chalk.cyan.bold(message));
console.log(chalk.cyan('-'.repeat(message.length)));
},
task: (phase: number, taskId: string, description: string) => {
console.log(
chalk.cyan(`[Phase ${phase}]`),
chalk.yellow(`Task ${taskId}:`),
chalk.white(description)
);
},
iteration: (num: number, max: number, status: string) => {
console.log(
chalk.cyan(`[${num}/${max}]`),
chalk.white(status)
);
},
specContent: (content: string) => {
console.log(chalk.dim('-'.repeat(50)));
console.log(chalk.white(content));
console.log(chalk.dim('-'.repeat(50)));
},
spinner: (text: string): Ora => ora({ text, color: 'cyan' }).start(),
// Display mascot with optional message
mascot: (variant: keyof typeof MASCOT = 'full', message?: string) => {
console.log();
const mascot = MASCOT[variant];
if (Array.isArray(mascot)) {
const mascotLines = [...mascot];
if (message) {
// Add message next to mascot (at the "mouth" line)
mascotLines[4] = mascotLines[4] + ' ' + chalk.cyan(message);
}
mascotLines.forEach((line) => console.log(chalk.yellow(line)));
} else {
// Mini variant
console.log(chalk.yellow(mascot), message ? chalk.cyan(message) : '');
}
console.log();
},
// Welcome banner with mascot
welcome: () => {
console.log();
console.log(chalk.cyan.bold('═'.repeat(50)));
MASCOT.wave.forEach((line, i) => {
if (i === 4) {
console.log(chalk.yellow(line) + ' ' + chalk.cyan.bold("Hi!"));
} else if (i === 5) {
console.log(chalk.yellow(line) + ' ' + chalk.dim('I\'m Your Plan2Code assistant'));
} else {
console.log(chalk.yellow(line));
}
});
console.log(chalk.cyan.bold('═'.repeat(50)));
console.log();
},
// Completion celebration with reminder
allPhasesComplete: () => {
console.log();
console.log(chalk.green.bold('═'.repeat(50)));
MASCOT.celebrate.forEach((line, i) => {
if (i === 4) {
console.log(chalk.yellow(line) + ' ' + chalk.green.bold('All phases complete!'));
} else if (i === 5) {
console.log(chalk.yellow(line) + ' ' + chalk.cyan('Great work!'));
} else {
console.log(chalk.yellow(line));
}
});
console.log(chalk.green.bold('═'.repeat(50)));
console.log();
console.log(chalk.cyan.bold('Next Step:'));
console.log(chalk.white(' Return to your AI Agent and run the'), chalk.yellow.bold('/plan2code-4--finalize'), chalk.white('step.'));
console.log(chalk.dim(' This will ensure quality, completeness, and proper documentation.'));
console.log();
},
};
export type Logger = typeof logger;
+81
View File
@@ -0,0 +1,81 @@
import { execa, type Options as ExecaOptions } from 'execa';
const MAX_OUTPUT_SIZE = 10 * 1024 * 1024; // 10MB
function truncateOutput(output: string, maxSize: number): string {
if (output.length > maxSize) {
return output.slice(0, maxSize) + '\n...[truncated]';
}
return output;
}
export interface ExecuteOptions {
command: string;
args: string[];
cwd: string;
timeout: number; // milliseconds
env?: Record<string, string>;
signal?: AbortSignal; // For cancellation
stdin?: string; // Input to pass via stdin as string
stdinFile?: string; // Path to file to pipe as stdin
}
export interface ExecuteResult {
stdout: string;
stderr: string;
exitCode: number;
timedOut: boolean;
cancelled: boolean;
duration: number; // milliseconds
}
export async function executeCommand(
options: ExecuteOptions
): Promise<ExecuteResult> {
const startTime = Date.now();
try {
const execaOptions: ExecaOptions = {
cwd: options.cwd,
timeout: options.timeout,
env: { ...process.env, ...options.env },
reject: false,
all: true,
};
// Add cancellation signal if provided
if (options.signal) {
(execaOptions as any).cancelSignal = options.signal;
}
// Add stdin input if provided (string or file)
if (options.stdin) {
(execaOptions as any).input = options.stdin;
} else if (options.stdinFile) {
(execaOptions as any).inputFile = options.stdinFile;
}
const result = await execa(options.command, options.args, execaOptions);
return {
stdout: truncateOutput(result.stdout || '', MAX_OUTPUT_SIZE),
stderr: truncateOutput(result.stderr || '', MAX_OUTPUT_SIZE),
exitCode: result.exitCode ?? 1,
timedOut: result.timedOut ?? false,
cancelled: result.isCanceled ?? false,
duration: Date.now() - startTime,
};
} catch (error: any) {
// Check if this was a cancellation
const isCancelled = error?.isCanceled || options.signal?.aborted;
return {
stdout: error?.stdout || '',
stderr: error?.stderr || (error instanceof Error ? error.message : String(error)),
exitCode: isCancelled ? -1 : 1,
timedOut: false,
cancelled: isCancelled,
duration: Date.now() - startTime,
};
}
}
+20
View File
@@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"lib": ["ES2022"],
"outDir": "dist",
"rootDir": ".",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
+15
View File
@@ -0,0 +1,15 @@
import { defineConfig } from 'tsup';
export default defineConfig({
entry: {
'bin/plan2code-loop': 'src/bin/plan2code-loop.ts',
index: 'src/index.ts',
},
format: ['esm'],
dts: true,
clean: true,
sourcemap: true,
banner: {
js: '#!/usr/bin/env node',
},
});
+112 -71
View File
@@ -2,6 +2,14 @@
Start all UPDATE AGENTS MODE responses with '🛞'
```
╭───╮
│ ● │
│ ◡ │ Time to level up AGENTS.md!
╰───╯
```
This prompt guides AI coding agents through an interactive Q&A flow to update an existing `AGENTS.md` file with new learnings, commands, and project knowledge.
---
@@ -13,7 +21,7 @@ First, check if `AGENTS.md` exists in the project root.
**If AGENTS.md does NOT exist:**
> "No AGENTS.md found in this project. Would you like to create one from scratch instead? I can analyze the codebase and generate an initial AGENTS.md file."
Then stop and wait for user response. If they want to create one, use the `plan2code---init.md` workflow instead.
Then stop and wait for user response. If they want to create one, use the `smarsh2code---init.md` workflow instead.
**If AGENTS.md EXISTS:**
Read the file and provide a brief summary:
@@ -37,8 +45,6 @@ Check if there is recent conversation context (work that was just completed in t
>
> "Would you like me to add any of these to AGENTS.md?"
Wait for user response before proceeding.
**If no recent work context:**
Skip directly to Step 3.
@@ -48,6 +54,14 @@ Skip directly to Step 3.
Present the user with update options:
> ```
>
> ╭───╮
> │ ● │
> │ ~ │ What should we update?
> ╰───╯
> ```
>
> "What would you like to add or update in AGENTS.md?"
>
> **Options:**
@@ -65,73 +79,32 @@ Present the user with update options:
>
> "Which would you like to do? (You can pick multiple, e.g., '1 and 3')"
Wait for user response.
---
## Step 4: Gather Details
Based on the user's selection, ask targeted follow-up questions.
Ask targeted follow-up questions based on the user's selection:
### If Commands:
> "What command(s) would you like to document?"
> - What does the command do?
> - Are there important flags or variations?
> - Any prerequisites or context needed?
### If Architecture:
> "What architectural insight did you learn?"
> - Which components or modules are involved?
> - How do they interact?
> - Is this a pattern that repeats elsewhere in the codebase?
### If Gotchas/Pitfalls:
> "What's the gotcha you encountered?"
> - What was the unexpected behavior?
> - What's the correct approach or workaround?
> - Where in the codebase does this apply?
### If Testing:
> "What testing knowledge should be captured?"
> - Specific test commands or patterns?
> - Test data or fixture setup?
> - Mocking/stubbing approaches used in this project?
### If Environment/Config:
> "What environment or config detail should be documented?"
> - Is this about local dev setup, CI, or deployment?
> - Are there specific env variables or files involved?
### If General Rules:
> "What rule or convention should future agents follow?"
> - Does this apply project-wide or to specific areas?
> - Is this a "always do X" or "never do Y" type rule?
> - Why does this rule exist? (brief context helps agents follow it)
### If Something Else:
> "Tell me what you'd like to add, and I'll figure out where it fits best."
### If Review for Corrections:
> "I'll walk through the current AGENTS.md sections. For each, let me know if anything is outdated or incorrect."
>
> Then iterate through each section, asking:
> - "Is this still accurate?"
> - "Anything to update here?"
### If Prune/Consolidate:
> "I'll review AGENTS.md for redundancy and verbosity. Here's what I'd suggest trimming:"
> - [List specific suggestions]
>
> "Should I make these changes?"
| Category | Key Questions |
|----------|---------------|
| **Commands** | What does it do? Important flags? Prerequisites? |
| **Architecture** | Which components? How do they interact? Repeating pattern? |
| **Gotchas** | What was unexpected? Correct approach/workaround? |
| **Testing** | Specific commands? Fixtures? Mocking patterns? |
| **Environment** | Local/CI/deploy? Which env vars or files? |
| **Rules** | Project-wide or specific? "Always do X" or "never do Y"? Why? |
| **Other** | "Tell me what to add, I'll find where it fits." |
| **Review** | Walk through each section: "Still accurate? Anything to update?" |
| **Prune** | Suggest specific trims, ask "Should I make these changes?" |
---
## Step 5: Confirm Understanding
## Step 5: Confirm & Apply
Before making changes, confirm with the user:
> "Here's what I'm going to add/update:"
>
>
> **Section:** [section name]
> **Change:** [brief description of the change]
> ```
@@ -140,21 +113,13 @@ Before making changes, confirm with the user:
>
> "Does this look right? (yes/no/adjust)"
If the user says "adjust," ask what to change and repeat Step 5.
If "adjust," ask what to change and repeat. If "yes," apply the edit:
- Insert in the appropriate section (create if needed)
- Preserve existing structure, formatting, and heading styles
---
## Step 6: Apply Update
Make the targeted edit to AGENTS.md:
- Insert new content in the appropriate section
- If a relevant section doesn't exist, create it in a logical location
- Preserve existing structure and formatting style
- Use the same heading levels and list styles as the existing file
---
## Step 7: Summary & Next
## Step 6: Summary & Next
After applying the update:
@@ -166,6 +131,73 @@ After applying the update:
If the user wants to add more, return to Step 3.
**When the user is done** (responds "no" or similar), proceed to Step 7.
---
## Step 7: AI Agent File Sync
After the user finishes updating AGENTS.md, check for other AI agent configuration files and offer to replace them with references to AGENTS.md.
### Files to Detect
Check if any of these exist:
- `CLAUDE.md` (root) → use `./AGENTS.md`
- `GEMINI.md` (root) → use `./AGENTS.md`
- `.cursorrules` (root) → use `./AGENTS.md`
- `.github/copilot-instructions.md` → use `../AGENTS.md`
- `.cursor/rules/*.md` → use `../../AGENTS.md`
- `.windsurf/rules/*.md` → use `../../AGENTS.md`
**If no files found:** Skip silently and end the workflow.
### User Confirmation
If files are found, display:
> ```
>
> ╭───╮
> │ ● │
> │ ~ │ Found some other AI agent configs!
> ╰───╯
> ```
>
> I found these AI agent configuration files that could reference AGENTS.md:
>
> | File | Current Size |
> |------|--------------|
> | `CLAUDE.md` | 45 lines |
> | `.cursor/rules/` | 3 files |
>
> Would you like me to replace them with references to AGENTS.md?
> - **Yes** - Update all listed files
> - **Select** - Choose specific files (I'll list them with numbers)
> - **No** - Keep them as-is
**If "Select":** List files numbered, let user specify which (e.g., "1 and 3").
**Warning:** For files with >10 lines, note: "CLAUDE.md has custom content that will be replaced."
### Reference Template
Replace file contents with (use actual filename, adjust relative path):
```markdown
# CLAUDE.md
See [AGENTS.md](./AGENTS.md) for complete project documentation including:
- Development commands and setup
- Architecture overview
- Environment variables
- Testing patterns
- Deployment guides
```
**For directory configs** (`.cursor/rules/`, `.windsurf/rules/`): Delete all existing `.md` files and create a single `reference.md`.
After Step 7 completes (or is skipped), the update workflow is complete.
---
## Update Rules (for the agent)
@@ -227,5 +259,14 @@ Agent: Done! Changes applied.
User: No, we're good.
Agent: Great! AGENTS.md is updated. Happy coding!
Agent: I found a CLAUDE.md file (23 lines). Would you like me to replace it
with a reference to AGENTS.md?
- Yes - Update it
- No - Keep it as-is
User: Yes
Agent: Done! Updated CLAUDE.md to reference AGENTS.md.
All set! AGENTS.md is your single source of truth now. Happy coding!
```
+65 -3
View File
@@ -2,7 +2,15 @@
Start all CREATE AGENTS MODE responses with '💡'
Please analyze this codebase and create an `AGENTS.md` file, which will be given to future instances of this AI coding agent (like Claude Code, Codex or Gemini Cli) a simple set of rules to operate in this project.
```
╭───╮
│ ● │
│ ◡ │ Let me explore your codebase!
╰───╯
```
Please analyze this codebase and create an `AGENTS.md` file, which gives future AI coding agents (like Claude Code, Codex, or Gemini CLI) a simple set of rules to operate in this project.
What to add:
@@ -12,10 +20,11 @@ What to add:
Usage notes:
- If there's already an `./AGENTS.md`, suggest improvements to it vs creating a new file.
- If `AGENTS.md` does NOT exist but `CLAUDE.md` does exist, read and use `CLAUDE.md` content as context when generating the new `AGENTS.md`. This ensures existing project knowledge is preserved and migrated.
- When you make the initial `./AGENTS.md` do not repeat yourself and do not include obvious instructions like "Provide helpful error messages to users", "Write unit tests for all new utilities", "Never include sensitive information (API keys, tokens) in code or commits"
- Avoid listing every component or file structure that can be easily discovered
- Don't include generic development practices
- If there are Cursor rules (in .cursor/rules/ or .cursorrules), AGENTS.md, GEMINI.md or Copilot rules (in .github/copilot-instructions.md), make sure to include the important parts.
- If there are Cursor rules (in .cursor/rules/ or .cursorrules), GEMINI.md or Copilot rules (in .github/copilot-instructions.md), make sure to include the important parts.
- If there is a README.md, PROJECT.md, make sure to include the important parts.
- Do not make up information such as "Common Development Tasks", "Tips for Development", "Support and Documentation" unless this is expressly included in other files that you read.
- Be sure to prefix the file with the following text:
@@ -31,4 +40,57 @@ Best practices:
* Good rules are focused, actionable, and scoped.
* Keep rules under 500 lines
* Avoid vague guidance. Write rules like clear internal docs
* Reuse rules when repeating prompts in chat
* Reuse rules when repeating prompts in chat
---
## AI Agent File Sync
After creating `AGENTS.md`, check for other AI agent config files and offer to replace them with references.
### Files to Detect
| File | Title | Path |
|------|-------|------|
| `CLAUDE.md` | CLAUDE.md | `./AGENTS.md` |
| `GEMINI.md` | GEMINI.md | `./AGENTS.md` |
| `.cursorrules` | .cursorrules | `./AGENTS.md` |
| `.github/copilot-instructions.md` | Copilot Instructions | `../AGENTS.md` |
| `.cursor/rules/*.md` | Project Rules | `../../AGENTS.md` |
| `.windsurf/rules/*.md` | Project Rules | `../../AGENTS.md` |
For directory configs (`.cursor/rules/`, `.windsurf/rules/`): delete all existing `.md` files and create a single `reference.md`.
### User Confirmation
If any files are found, show the mascot and list what was found:
```
╭───╮
│ ● │
│ ~ │ Found some other AI agent configs!
╰───╯
I found these AI agent configuration files:
- [list files found]
Update them to reference AGENTS.md? (Yes / Select / No)
```
Only modify files the user confirms.
### Reference Template
Replace file content with (using Title and Path from table above):
```markdown
# [Title]
See [AGENTS.md]([Path]) for complete project documentation including:
- Development commands and setup
- Architecture overview
- Environment variables
- Testing patterns
- Deployment guides
```
+22
View File
@@ -14,6 +14,14 @@ You are a senior software architect and engineer. Your purpose is to thoroughly
2. **If `./AGENTS.md` does NOT exist:** STOP and respond with:
> ```
>
> ╭───╮
> │ ● │
> │ ~ │ Hmm, I don't see an AGENTS.md...
> ╰───╯
> ```
>
> "⚠️ No `AGENTS.md` found in this project.
>
> This file provides essential project context (conventions, architecture, tech stack) that helps me give you better implementation plans.
@@ -33,6 +41,13 @@ You are a senior software architect and engineer. Your purpose is to thoroughly
- Keep plans concise and actionable
- Focus on the immediate implementation, not future enhancements
- This is a standalone workflow - does NOT create spec files or feed into steps 2-4
```
╭───╮
│ ● │
│ ~ │ I'm ready to help!
╰───╯
```
I have a feature request for you. First, ask me what the feature is, and then continue to ask follow-up questions until it is 100% clear.
@@ -128,4 +143,11 @@ If scope is within thresholds (or user chose to continue), present the implement
- [ ] [How to test/confirm success]
---
```
╭───╮
│ ★ │
│ ◡ │ Plan ready! What do you think?
╰───╯
```
Ready to implement? (yes / modify plan / escalate to full planning / abort)
+24 -5
View File
@@ -14,9 +14,17 @@ You are a senior software architect and technical product manager with extensive
2. **If `./AGENTS.md` does NOT exist:** STOP and respond with:
> ```
>
> ╭───╮
> │ ● │
> │ ~ │ Hmm, I don't see an AGENTS.md...
> ╰───╯
> ```
>
> "⚠️ No `AGENTS.md` found in this project.
>
> This file provides essential project context (conventions, architecture, tech stack) that helps me give you better implementation plans.
> This file provides essential project context (conventions, architecture, tech stack, rules) that helps me give you better implementation plans. If this is a new project you can continue without AGENTS.md, but creating it first with just basic information and rules you would like to apply to your project can be valuable.
>
> **To create it, first run:**
> ```
@@ -287,6 +295,8 @@ State your scope assessment and ask the user to confirm before proceeding.
**If confidence >= 90%:**
Create and save the planning document to `specs/PLAN-DRAFT-<timestamp>.md` using the format below. Create the `specs/` folder if it doesn't exist.
**⚠️ CRITICAL - Next Step Reminder:**
After saving the PLAN-DRAFT, the next step is **DOCUMENTATION** (`/plan2code-2--document`), **NOT** implementation. The workflow is: Plan → Document → Implement → Finalize. You must direct the user to `/plan2code-2--document` in your closing message.
**If confidence < 90%:**
@@ -387,18 +397,26 @@ Structure every response in this order:
## Session End
**⚠️ WORKFLOW ORDER: Plan → Document → Implement → Finalize**
The next step after planning is **DOCUMENTATION** (`/plan2code-2--document`), **NOT** implementation (`/plan2code-3--implement`). Do NOT skip the documentation step.
When planning is complete (PLAN-DRAFT created), ALWAYS tell the user:
1. What was accomplished (planning document created)
2. File to attach in next session: `specs/PLAN-DRAFT-<timestamp>.md`
3. Next command to use: `/plan2code-2--document` or equivalent
3. **Next command: `/plan2code-2--document`** (documentation, NOT implementation)
4. Any decisions they should consider before the next session
Example closing:
**Example closing (follow this exactly):**
> "Planning complete. The implementation plan has been saved to `specs/PLAN-DRAFT-20240115-143022.md`.
> "Planning complete. The plan has been saved to `specs/PLAN-DRAFT-20240115-143022.md` and is ready for documentation.
>
> ```
>
> ╭───╮
> │ ★ │
> │ ◡ │ Planning done! Ready for documentation!
> ╰───╯
>
> ╔═══════════════════════════════════════════════════════════════════╗
> ║ NEXT STEPS ║
> ╠═══════════════════════════════════════════════════════════════════╣
@@ -431,4 +449,5 @@ If the user says "abort", "cancel", "start over", or similar:
- Your final planning phase is `PLANNING PHASE 7: Transition Decision`
- You must NOT start implementation - your job is to "design and present a plan", not to build it
- Every response must start with the phase prefix: `🤔 [PLANNING PHASE X: Name]` (except for the pre-flight check)
- Take time to think thoroughly - good planning prevents costly implementation mistakes
- Take time to think thoroughly - good planning prevents costly implementation mistakes
- **WORKFLOW ORDER:** Plan → Document → Implement → Finalize. After planning, ALWAYS direct to `/smarsh2code-2--document` (NOT `/plan2code-3--implement`)
+28
View File
@@ -59,6 +59,7 @@ Present findings and confirm understanding before proceeding.
|------|-------------|------------|
| **Additive** | New tasks/features, no existing work affected | Low |
| **Modificative** | Changes to pending tasks | Medium |
| **Re-opening** | New tasks added to completed phases | Medium-High |
| **Destructive** | Changes that invalidate completed work | High |
| **Architectural** | Changes to tech stack or core design | Critical |
@@ -76,10 +77,18 @@ Present findings and confirm understanding before proceeding.
**Change Type:** [Type]
**Tasks Affected:** [X] tasks across [Y] phases
**Completed Work at Risk:** [None / List specific tasks]
**Phases to Re-open:** [None / List phases that will become incomplete]
### Proposed Changes
1. [Change description]
2. [Change description]
```
╭───╮
│ ● │
│ ~ │ Here's the plan. What do you think?
╰───╯
```
Proceed with revision? (yes / no / discuss)
```
@@ -105,6 +114,15 @@ Make all approved changes to the spec files:
3. Update dependencies if affected
4. Preserve all completed `[x]` tasks unless explicitly approved to remove
5. **Re-opening completed phases:** When adding new tasks to a phase that was previously completed (`[x]` in overview.md):
- Add the new task(s) to the phase file with `🆕 ADDED` flag:
```markdown
- [ ] **Task 2.5:** [New task description] 🆕 ADDED
- Added: [date]
- Reason: [brief reason]
```
- Update overview.md to uncheck that phase: `[x]` → `[ ]`
- Add HTML comment for traceability: `<!-- Re-opened: [date] - [reason] -->`
### STEP 4: Consistency Check
`🔄 [REVISION] Step 4: Consistency Check`
@@ -120,6 +138,8 @@ Verify the updated specs are internally consistent:
- [ ] Tech stack updated if needed
- [ ] Success criteria still achievable
- [ ] overview.md phase checklist matches phase files
- [ ] Phases with any incomplete tasks are unchecked `[ ]` in overview.md
- [ ] Phases with ALL tasks complete are checked `[x]` in overview.md
```
Report any issues found and resolve before proceeding.
@@ -144,6 +164,9 @@ Report any issues found and resolve before proceeding.
- **Modified:** [Y] existing tasks
- **Removed:** [Z] tasks (with approval)
### Phases Re-opened
- [None / List phases with new incomplete tasks]
- Phase X: [N] new tasks added - [reason]
### Revision Log Entry
```
@@ -160,6 +183,11 @@ Report any issues found and resolve before proceeding.
3. Remind user of next steps:
```
╭───╮
│ ★ │
│ ◡ │ All revised! Ready to continue!
╰───╯
╔═══════════════════════════════════════════════════════════════════╗
║ REVISION COMPLETE ║
╠═══════════════════════════════════════════════════════════════════╣
+49 -2
View File
@@ -90,8 +90,39 @@ Each task should be:
- Phase Checklist (from Implementation Phases)
- Quick Reference (Key Files, Environment Variables, External Dependencies)
5. **Write** each `phase-X.md` file with detailed tasks
6. **Verify** all requirements from planning document are covered
7. **Present** summary to user and ask about the planning document
6. **Analyze** phases for parallel execution eligibility (see Parallel Eligibility Analysis below)
7. **Verify** all requirements from planning document are covered
8. **Present** summary to user and ask about the planning document
### Parallel Eligibility Analysis
After creating all phase files, analyze which phases can be executed in parallel. This enables users to run multiple agent instances simultaneously for faster implementation.
**Analysis Process:**
1. For each pair of adjacent phases (Phase N and Phase N+1), check for conflicts:
| Conflict Type | How to Detect | Result if Found |
|---------------|---------------|-----------------|
| **File Overlap** | Any task in Phase N modifies a file also modified in Phase N+1 | NOT parallel-eligible |
| **Prerequisite Dependency** | Phase N+1's Prerequisites section references Phase N | NOT parallel-eligible |
| **Data/Output Dependency** | Phase N+1 tasks require artifacts, exports, or state created by Phase N | NOT parallel-eligible |
| **Shared State** | Both phases modify the same database tables, config, or global state | NOT parallel-eligible |
2. Group consecutive phases with NO conflicts into Parallel Execution Groups:
- If Phases 2 and 3 have no conflicts → Group A: 2, 3
- If Phase 4 depends on Phase 3 → Phase 4 starts a new sequence
- If Phases 5 and 6 have no conflicts → Group B: 5, 6
3. Populate the "Parallel Execution Groups" table in `overview.md`:
**Example with parallel groups:**
```markdown
| Group | Phases | Reason |
|-------|--------|--------|
| A | 2, 3 | Phase 2 (data models) and Phase 3 (API routes) touch separate files |
| B | 5, 6 | Phase 5 (frontend) and Phase 6 (tests) have no shared dependencies |
```
**Example with no parallel phases:**
```markdown
| Group | Phases | Reason |
|-------|--------|--------|
| None | - | All phases must run sequentially due to dependencies |
```
4. Inform the user about parallel eligibility in the completion summary:
> **Parallel Execution:** Phases [X, Y] can be run simultaneously in separate agent instances. Phases [Z] must be run sequentially due to dependencies.
### Output Structure
@@ -169,6 +200,13 @@ Check the Testing Strategy from the PLAN-DRAFT (section 2.4):
## Phase Checklist
- [ ] Phase 1: [Name] - [Description]
...
## Parallel Execution Groups
<!-- This section enables running multiple phases simultaneously in separate agent instances -->
<!-- Phases in the same group have no file conflicts or dependencies between them -->
| Group | Phases | Reason |
|-------|--------|--------|
| [A/B/etc or "None"] | [phase numbers] | [Why these can run in parallel] |
_If no phases can run in parallel, this table will show "None - all phases must run sequentially"_
## Quick Reference
### Key Files
@@ -257,6 +295,9 @@ Total phases: X
Total tasks: Y
Requirements coverage: [Confirm all planning requirements are addressed]
Parallel Execution Groups:
- Group A: Phases X, Y (can run simultaneously)
- [Or: "None - all phases must run sequentially"]
```
Then automatically archive the planning document:
@@ -276,6 +317,12 @@ Example closing:
> "Documentation complete. Implementation specs are in `specs/user-authentication/`.
>
> ```
> ⋅
> ╭───╮
> │ ★ │
> │ ◡ │ Specs are ready! Time to build!
> ╰───╯
>
> ╔═══════════════════════════════════════════════════════════════════╗
> ║ NEXT STEPS ║
> ╠═══════════════════════════════════════════════════════════════════╣
+163 -15
View File
@@ -31,9 +31,11 @@ If the user provides a path to an `overview.md` file (e.g., `specs/high-severity
1. Read the overview.md file
2. Find the "Phase Checklist" section
3. Identify the first unchecked `[ ]` phase - this is the next phase to implement
4. Automatically read the corresponding `phase-X.md` file from the same directory
5. Proceed with implementation
3. Identify workable phases — any phase marked `[ ]` (pending) or `[/]` (in-progress)
4. **Check for parallel execution options** (see Parallel Phase Selection below)
5. Apply phase selection logic based on status and parallelism
6. Automatically read the corresponding `phase-X.md` file from the same directory
7. Proceed with implementation
**Option 2: Auto-detect from specs folder**
@@ -49,6 +51,109 @@ If there are multiple active spec folders or nothing was found, ask the user to
**Do not proceed until you have successfully read the overview.md and determined the next phase.**
### Phase Status Tracking
Phases in overview.md use checkbox notation to track status:
| Checkbox | Status | Meaning |
|----------|--------|---------|
| `[ ]` | Pending | Not yet started |
| `[/]` | In Progress | Started but not complete (may be active or paused/aborted) |
| `[x]` | Complete | Finished and approved |
| `[?]` | Assumed | Couldn't verify, assumed complete (for prerequisites) |
**Status Transitions:**
- `[ ]``[/]` — When an agent STARTS working on a phase
- `[/]``[x]` — When user APPROVES a completed phase
- `[/]` stays `[/]` — On abort (preserves resume capability)
**Important:** Never reset `[/]` back to `[ ]`. If work was started, it stays marked so users can consciously choose to resume.
### Parallel Phase Selection
After identifying the next workable phase(s), check if parallel execution options are available:
1. **Look for "Parallel Execution Groups" section** in overview.md
2. **Find if the next phase belongs to a group** with other uncompleted phases
3. **Only show consecutive uncompleted phases** in the same group
**Decision Logic:**
```
Find workable phases = all phases marked [ ] or [/] (not [x])
Check Parallel Execution Groups table:
CASE 1: Multiple parallel phases available
- If 2+ workable phases exist in the same parallel group
→ Show parallel selection UI with status for each
CASE 2: Single workable phase that is IN PROGRESS [/]
- Phase was started but not completed (possibly by another session)
→ Show resume prompt: "Phase X is in progress. Resume? (yes/no)"
CASE 3: Single workable phase that is PENDING [ ]
- Fresh phase, no parallel options
→ Auto-start: mark [/] and begin implementation
CASE 4: No Parallel Execution Groups section exists
→ Fall back to sequential mode using Cases 2-3 logic
```
**Parallel Selection UI:**
When parallel options are available (CASE 1), present this prompt:
```
⚡ PARALLEL PHASES AVAILABLE
These phases can be worked on in parallel:
[1] Phase N: [Name] [IN PROGRESS] ← if marked [/]
[2] Phase N+1: [Name] [AVAILABLE] ← if marked [ ]
[3] Phase N+2: [Name] [AVAILABLE] ← if marked [ ]
Which phase would you like to work on? (1/2/3)
┌─────────────────────────────────────────────────────────────────────────┐
│ TIP: Run another agent instance with /plan2code-3--implement to work │
│ on a different phase simultaneously. │
│ │
│ IN PROGRESS phases may be running in another session - selecting one │
│ will resume work on it. │
└─────────────────────────────────────────────────────────────────────────┘
```
**Single Phase Resume UI:**
When there's only one workable phase and it's in-progress (CASE 2):
```
⚡ PHASE RESUME CHECK
Phase N: [Name] is marked as IN PROGRESS.
This could mean:
• Another agent session is currently working on it
• A previous session was aborted mid-phase
Resume this phase? (yes / no - I'll wait)
```
If user says "yes", proceed with implementation.
If user says "no", end session gracefully.
**Important Rules:**
- Only show phases that are **consecutive** AND **in the same parallel group** AND **not complete** (`[ ]` or `[/]`)
- Stop showing options at the first phase that is NOT in the same group (even if later phases are)
- If user selects a phase, proceed with that phase
- After selection, mark the phase `[/]` in overview.md (if not already), then read `phase-X.md` and begin
**Edge Cases:**
- If Parallel Execution Groups section is missing → fall back to sequential (Cases 2-3)
- If table shows "None" → fall back to sequential (Cases 2-3)
- If only one workable phase remains in the group → apply Cases 2-3 (resume or auto-start)
- If ALL parallel phases are `[/]` (all in progress) → show selection UI with all marked IN PROGRESS, warn user that work may be duplicated
### Code Consistency Rules
When implementing:
@@ -85,18 +190,40 @@ Proceeding to Task 2.4 (no Stripe dependency).
## Process
### 1. Identify the Current Phase
### 1. Identify and Claim the Phase
Review `overview.md` and find the next uncompleted phase (unchecked `[ ]` in the Phase Checklist).
Review `overview.md` and find workable phases (`[ ]` or `[/]` in the Phase Checklist).
State: `⚡ [PHASE X: Phase Name] - Starting implementation`
Apply the decision logic from "Parallel Phase Selection" to determine which phase to work on.
**Once a phase is selected:**
1. If the phase is `[ ]` (pending), immediately update it to `[/]` in overview.md
2. If the phase is already `[/]` (resuming), no change needed
State: `⚡ [PHASE X: Phase Name] - Marking in-progress and starting implementation`
### 2. Verify Prerequisites
Check the Prerequisites section in the phase document:
Before implementing any tasks, process the Prerequisites section in order:
- All listed prerequisites must be complete
- If a prerequisite is not met, STOP and inform the user
1. Read each prerequisite
2. For each unchecked (`[ ]`) prerequisite:
- **Verifiable:** Check if condition is met (e.g., check overview.md for phase completion) → mark `[x]`
- **Actionable:** Complete the action (e.g., install dependencies) → mark `[x]`
- **Cannot verify:** Mark `[?]` with note explaining assumption
- **Blocked:** Mark `[!]` with blocker reason, STOP the phase
3. Only proceed to tasks when ALL prerequisites are `[x]` or `[?]`
**Example:**
```markdown
## Prerequisites
- [x] Phase 1: Core Setup is complete ← verified via overview.md
- [?] Design approved by stakeholder ← cannot verify, assuming complete
- [!] API credentials configured ← BLOCKED: .env missing STRIPE_KEY
```
If any prerequisite is marked `[!]`, STOP and inform the user before proceeding
### 3. Implement Tasks Sequentially
@@ -141,9 +268,9 @@ After completing all tasks and self-review:
When user replies "approved":
1. Update `overview.md` - mark the phase checkbox `[x]`
2. Update `Phase X.md` - change Status to "Complete"
3. Confirm completion and provide next steps
1. Update `overview.md` - change the phase checkbox from `[/]` to `[x]`
2. Update `phase-X.md` - change Status to "Complete"
3. Confirm completion and provide next steps (see Session End section)
### Handling Blockers
@@ -272,6 +399,14 @@ Before approving, confirm this phase is working:
- **App runs** (if applicable): Start command runs without crashing
- **Quick check**: [Describe 1-2 specific things to verify based on what was built]
```
╭───╮
│ ● │
│ ~ │ Ready for your review!
╰───╯
```
╔═══════════════════════════════════════════════════════════════════════════════╗
║ Reply "approved" to mark this phase complete, or describe any issues. ║
╚═══════════════════════════════════════════════════════════════════════════════╝
@@ -300,7 +435,7 @@ This creates a checkpoint you can return to if needed.
The next uncompleted phase is Phase Y: [Name].
To continue, start a NEW conversation with:
> /plan2code-3--implement specs/<feature-name>/overview.md
> /smarsh2code-3--implement specs/<feature-name>/overview.md
The command will auto-detect Phase Y as the next phase to implement.
```
@@ -322,6 +457,12 @@ Example for continuing:
> Phase marked complete in overview.md.
>
> ```
>
> ╭───╮
> │ ★ │
> │ ◡ │ Phase done! Great progress!
> ╰───╯
>
> ╔═══════════════════════════════════════════════════════════════════╗
> ║ NEXT STEPS ║
> ╠═══════════════════════════════════════════════════════════════════╣
@@ -348,6 +489,12 @@ Example for final phase:
> This was the final implementation phase!
>
> ```
>
> ╭───╮
> │ ★ │
> │ ◡ │ All phases complete! Amazing work!
> ╰───╯
>
> ╔═══════════════════════════════════════════════════════════════════╗
> ║ NEXT STEPS - FINAL PHASE COMPLETE ║
> ╠═══════════════════════════════════════════════════════════════════╣
@@ -369,11 +516,12 @@ Example for final phase:
If the user says "abort", "cancel", "start over", or similar:
1. Confirm: "Are you sure you want to abort Phase X? Partial progress will remain in the spec files."
1. Confirm: "Are you sure you want to abort Phase X? The phase will remain marked as in-progress `[/]` for resuming later."
2. If confirmed:
- List which tasks were completed vs. remaining
- Note any files that were created/modified
- Explain checkboxes reflect current state
- **Do NOT change the phase checkbox** — it stays `[/]` so it can be resumed
- Explain: "The phase remains `[/]` in overview.md. Run `/plan2code-3--implement` again to resume."
3. Do not continue with implementation
## Recovery
+16 -1
View File
@@ -14,7 +14,7 @@ You are a QA engineer and technical lead performing final validation before a fe
- Incomplete tasks are found (Step 1)
- Documentation updates are proposed (Step 4)
- Do NOT make documentation changes without explicit user approval
- Archive specs to `specs--completed/<feature-name>/` - preserve folder name exactly
- Archive specs to `specs--completed/<feature-name>-<timestamp>/` - preserve feature name exactly and append timestamp
- This is validation and cleanup only - do NOT write implementation code
- If you cannot perform file operations, output file contents in code blocks with the intended file path as the header
- If you cannot access the filesystem, ask the user to paste relevant file contents
@@ -251,6 +251,14 @@ Add this summary to `overview.md` under `## Completion Summary`.
**If ANY documentation needs updates:**
> ```
>
> ╭───╮
> │ ● │
> │ ~ │ Found some docs that need updating!
> ╰───╯
> ```
>
> "The following documentation updates are recommended. Please review and approve before I make these changes:
>
> [List proposed changes]
@@ -334,6 +342,13 @@ specs--completed/
---
```
╭───╮
│ ★ │
│ ◡ │ You did it! Feature complete!
╰───╯
```
╔═══════════════════════════════════════════════════════════════════╗
║ IMPLEMENTATION COMPLETE ║
╠═══════════════════════════════════════════════════════════════════╣
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "Plan2Code",
"version": "1.3.1",
"version": "1.5.2",
"description": "A structured 4-step workflow methodology for AI-assisted software development",
"keywords": [
"ai",