mirror of
https://github.com/jparkerweb/plan2code.git
synced 2026-07-21 18:33:22 -07:00
v1.5.2
This commit is contained in:
+5
-1
@@ -1,3 +1,7 @@
|
||||
specs/
|
||||
specs--completed/
|
||||
CLAUDE.md
|
||||
dist/
|
||||
CLAUDE.md
|
||||
plan2code-loop/dist
|
||||
plan2code-loop/node_modules
|
||||
plan2code-loop/package-lock.json
|
||||
|
||||
@@ -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.
|
||||
```
|
||||
╭───╮
|
||||
│ ● │
|
||||
│ ◡ │
|
||||
╰───╯
|
||||
```
|
||||
@@ -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
@@ -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/`
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
```
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
@@ -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();
|
||||
@@ -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);
|
||||
@@ -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();
|
||||
@@ -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>;
|
||||
}
|
||||
@@ -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();
|
||||
@@ -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 };
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export {
|
||||
buildLoopPrompt,
|
||||
type PromptContext,
|
||||
} from './builder.js';
|
||||
|
||||
export { LOOP_PROMPT_TEMPLATE } from './templates.js';
|
||||
@@ -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.
|
||||
`;
|
||||
@@ -0,0 +1,4 @@
|
||||
export {
|
||||
detectSpecDirectories,
|
||||
getSpecProgress,
|
||||
} from './utils.js';
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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';
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
@@ -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;
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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"]
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
|
||||
```
|
||||
@@ -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)
|
||||
|
||||
@@ -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`)
|
||||
@@ -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 ║
|
||||
╠═══════════════════════════════════════════════════════════════════╣
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user