v1.14.0 — add Step 3b review workflow, unify file naming, resilient code references

- Add /plan2code-3b-review: 5-step post-implementation review with adaptive
  scope, 11 dimensions, reference-file architecture, and Plan/Apply/Verify fixes
- Unify workflow/skill file naming to single-dash (drop -- and --- conventions)
- Add Devin platform support and CLAUDE.md MANDATORY FIRST STEP template
- Guide agents to use semantic anchors over line numbers in workflow prompts
- Bump version to 1.14.0
This commit is contained in:
2026-05-23 06:58:20 -07:00
parent 8e0b1a2ab2
commit 5e48e98293
42 changed files with 3431 additions and 2542 deletions
+32 -14
View File
@@ -5,7 +5,11 @@
```
plan2code/
├── src/ # Source workflow prompts (8 markdown files)
├── src/ # Source workflow prompts (9 markdown files)
│ └── plan2code-3b-review-references/ # Reference files for review skill
│ ├── verification-protocol.md # Deep verification + confidence calibration
│ ├── dimensions.md # 11 dimensions with detailed checklists
│ └── false-positives.md # Known false-positive patterns
├── plan2code-loop/ # Autonomous loop CLI tool (Node.js/TypeScript)
│ ├── src/ # TypeScript source
│ └── dist/ # Built output (tsup)
@@ -42,22 +46,36 @@ plan2code/
| File | Step | Purpose |
|------|------|---------|
| `plan2code---init.md` | Init | Generate AGENTS.md as index + `.agents-docs/` section files (progressive discovery) |
| `plan2code---init-update.md` | Update | Update AGENTS.md with learnings; detects and routes edits to `.agents-docs/` files |
| `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, feedback, archive (7 steps) |
| `plan2code-init.md` | Init | Generate AGENTS.md as index + `.agents-docs/` section files (progressive discovery) |
| `plan2code-init-update.md` | Update | Update AGENTS.md with learnings; detects and routes edits to `.agents-docs/` files |
| `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-3b-review.md` | 3b | Post-implementation comprehensive review |
| `plan2code-4-finalize.md` | 4 | Validate, summarize, feedback, archive (7 steps) |
## 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)
- **Utilities:** `plan2code-<name>.md` (single dash)
- **Numbered steps:** `plan2code-<N>-<name>.md` (single dash, number, single dash)
Examples:
- `plan2code---init.md` (utility)
- `plan2code-1--plan.md` (step 1)
- `plan2code-1b--revise-plan.md` (step 1b)
- `plan2code-init.md` (utility)
- `plan2code-1-plan.md` (step 1)
- `plan2code-1b-revise-plan.md` (step 1b)
## Reference Files
Some workflows use companion reference files for depth that exceeds the 11k char limit. The orchestrator (main workflow file) loads them via `Read` directives during execution.
**Pattern:** `src/<source-filename-without-extension>-references/` (e.g., `plan2code-3b-review-references/`)
**How the installer handles them:**
- **Skill-directory platforms** (Claude Code, Agents, Crush, Devin): reference files are nested as `<skill-name>/references/`. Read paths use canonical `references/<file>.md`.
- **Flat-file platforms** (Windsurf, Cursor, Copilot, Continue): reference files are placed as a sibling directory. The installer rewrites Read paths to the sibling directory name (e.g., `plan2code-3b-review-references/<file>.md`).
- **TOML platforms** (Gemini CLI): reference files are skipped — TOML embeds content inline, so Read directives won't resolve. The orchestrator's inline fallback text covers this.
Reference files are NOT subject to the 11,000 character limit. Currently only the review workflow (Step 3b) uses this pattern — it serves as the POC for potential adoption by other workflows.
+1
View File
@@ -16,3 +16,4 @@
- **Workflow file character limit:** All `src/plan2code-*.md` files must be ≤ 11,000 characters. A husky pre-commit hook enforces this. The 11,000 limit leaves buffer for platform-specific YAML headers (106-142 chars) to stay under Windsurf's 12,000 char limit.
- **Metrics internal prompts have no char limit:** Files in `plan2code-metrics/src/prompts/` are NOT subject to the 11,000 char limit — only `src/plan2code-*.md` consumer-facing prompts are.
- **User Feedback table format:** The `## User Feedback` markdown table in `overview.md` has a strict format the collector regex depends on. Field names must be exactly `Rating`, `Reason`, `Went Well`, `Went Poorly`. Pipe characters in values must be escaped as `\|`.
- **Reference file sizing guideline:** Files in `src/plan2code-*-references/` directories target ~100-200 lines each (soft guideline; evaluate splitting above 300). They are NOT subject to the 11,000 character limit. The pre-commit hook (`validate-char-count.js`) only checks `src/plan2code-*.md` flat files — subdirectory contents are automatically excluded.
+1 -1
View File
@@ -58,7 +58,7 @@ cd plan2code-metrics && npm run build # Build the CLI
| VS Code Copilot | `.prompt.md` | — | — | YAML frontmatter |
| Codeium | `.md` | — | — | YAML frontmatter |
| Claude Code (Skills) | `SKILL.md` in subdir | `.claude/skills/<skill-name>/` | `~/.claude/skills/<skill-name>/` | YAML frontmatter + `disable-model-invocation: true` |
| Agent Skills (Amp · Gemini CLI · OpenCode) | `SKILL.md` in subdir | `.agents/skills/<skill-name>/` | `~/.agents/skills/<skill-name>/` | YAML frontmatter (no disable flag) |
| Agent Skills (Amp · Devin · Gemini CLI · OpenCode) | `SKILL.md` in subdir | `.agents/skills/<skill-name>/` | `~/.agents/skills/<skill-name>/` | YAML frontmatter (no disable flag) |
| Crush | `SKILL.md` in subdir | — (global only) | `~/.config/crush/skills/<skill-name>/` (Unix) / `%LOCALAPPDATA%\crush\skills\<skill-name>\` (Windows) | YAML frontmatter |
| Gemini CLI (TOML) | `.toml` | `.gemini/commands/` | `~/.gemini/commands/` | None (TOML fields: `description`, `prompt`) |
| Pi (pi.dev) | `.md` | `.pi/prompts/` | `~/.pi/agent/prompts/` | YAML frontmatter (`description`) |
+2
View File
@@ -11,5 +11,7 @@ plan2code-metrics/package-lock.json
.plan2code-loop
.plan2code-metrics
nul
.cognition/
node_modules/
package-lock.json
SYNC.md
+56 -56
View File
@@ -1,56 +1,56 @@
# 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
## How to Use This File
This file is an index — each section below contains a brief summary and a link to a detail file in `.agents-docs/`. Read only the sections relevant to your current task. Full details (commands, tables, file lists) are in the linked files. The sections "Git Commit Messages" and "Project Overview" are fully inline here.
## Architecture
High-level directory structure, key files, workflow prompt inventory, and file naming conventions.
Details: [Architecture](./.agents-docs/AGENTS-architecture.md)
## Plan2Code Loop
Autonomous CLI tool (`plan2code-loop/`) that implements specs by looping through tasks. Covers loop architecture, modes (one-task vs one-phase), completion markers, and key source files.
Details: [Plan2Code Loop](./.agents-docs/AGENTS-plan2code-loop.md)
## Plan2Code Metrics
Recursive self-improvement toolchain (`plan2code-metrics/`) for collecting run metrics, aggregating by prompt generation, diagnosing weak steps, and proposing prompt edits.
Details: [Plan2Code Metrics](./.agents-docs/AGENTS-plan2code-metrics.md)
## Development Commands
Build commands, installer menu options, how the installer generates platform-specific files, platform format table, and how to edit workflow prompts.
Details: [Development Commands](./.agents-docs/AGENTS-development-commands.md)
## Code Style & Gotchas
Language/toolchain conventions for `install.js` vs TypeScript packages, and pitfalls to avoid (version sync, `.gitignore` pre-flight, character limits, User Feedback table format).
Details: [Code Style & Gotchas](./.agents-docs/AGENTS-code-style.md)
## Mascot
The project has a mascot called "Planny" - 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.
```
╭───╮
│ ● │
│ ◡ │
╰───╯
```
# AGENTS.md
This file provides guidance to AI coding agents like Claude Code (claude.ai/code), Cursor AI, Codex, Gemini CLI, GitHub Copilot, Devin, 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, Devin).
**Version:** Check `version.json` for current version
**Author:** Justin Parker
**License:** MIT
## How to Use This File
This file is an index — each section below contains a brief summary and a link to a detail file in `.agents-docs/`. Read only the sections relevant to your current task. Full details (commands, tables, file lists) are in the linked files. The sections "Git Commit Messages" and "Project Overview" are fully inline here.
## Architecture
High-level directory structure, key files, workflow prompt inventory, and file naming conventions.
Details: [Architecture](./.agents-docs/AGENTS-architecture.md)
## Plan2Code Loop
Autonomous CLI tool (`plan2code-loop/`) that implements specs by looping through tasks. Covers loop architecture, modes (one-task vs one-phase), completion markers, and key source files.
Details: [Plan2Code Loop](./.agents-docs/AGENTS-plan2code-loop.md)
## Plan2Code Metrics
Recursive self-improvement toolchain (`plan2code-metrics/`) for collecting run metrics, aggregating by prompt generation, diagnosing weak steps, and proposing prompt edits.
Details: [Plan2Code Metrics](./.agents-docs/AGENTS-plan2code-metrics.md)
## Development Commands
Build commands, installer menu options, how the installer generates platform-specific files, platform format table, and how to edit workflow prompts.
Details: [Development Commands](./.agents-docs/AGENTS-development-commands.md)
## Code Style & Gotchas
Language/toolchain conventions for `install.js` vs TypeScript packages, and pitfalls to avoid (version sync, `.gitignore` pre-flight, character limits, User Feedback table format).
Details: [Code Style & Gotchas](./.agents-docs/AGENTS-code-style.md)
## Mascot
The project has a mascot called "Planny" - 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.
```
╭───╮
│ ● │
│ ◡ │
╰───╯
```
+52
View File
@@ -2,6 +2,58 @@
All notable changes to Plan2Code will be documented in this file.
## v1.14.0
### ✨ Added
- **Review workflow (Step 3b)** — New `/plan2code-3b-review` command for comprehensive post-implementation code review
- 5-step process: scope detection, context analysis, 11-dimension review, spec/test assessment, summary with fix options
- Adaptive scope: focused (named files), branch (git diff), or full (subsystem) — auto-detected with user override
- 3 severity levels (Critical, Warning, Suggestion) with High-confidence-only findings
- Reference file architecture: orchestrator (≤11k chars) + 3 companion reference files loaded via Read directives
- Deep verification protocol with use-case tracing for architectural findings and adversarial self-check
- Detailed dimension checklists (8 non-obvious items per dimension with anti-patterns and "don't flag" guidance)
- False-positive catalog with detection shortcuts to prevent common false findings
- Finding verification gate — re-reads source at each cited line before presenting
- Mnemonic fix options: `H` (high-priority), `A` (all), `S` (specify by number)
- Post-fix Plan/Apply/Verify pipeline — enterprise-grade fix quality with full validation
- Doc review mode — >70% doc changes triggers editorial critique
- Graceful degradation for agents that can't read external files
- Spec-aware when `specs/` exists; works standalone for any codebase
- Context-aware session end with pipeline state detection
- Registered in installer for all 14+ platform destinations
- Installer copies reference directories for all 13+ platform targets
- Reference content inlined into TOML output (Gemini CLI) so platforms that cannot resolve runtime file reads still get full workflow depth
- Uninstall now removes orphaned `*-references/` directories even when prompt files were already removed manually
- Implement workflow session-end now suggests review after each phase
## v1.13.0
### 🔧 Changed
- **Simplified workflow naming convention** — All workflow and skill files unified to single-dash naming, eliminating double-dash (`--`) and triple-dash (`---`) conventions
- Renamed 8 source prompt files in `src/` (e.g., `plan2code---init.md``plan2code-init.md`, `plan2code-1--plan.md``plan2code-1-plan.md`)
- Updated `install.js` `SOURCE_PROMPTS` metadata and `generateFilename()` to emit single-dash names
- Updated all cross-references inside workflow prompt markdown content
- Updated documentation: `README.md`, `QUICK-REFERENCE.md`, `AGENTS.md`, `.agents-docs/AGENTS-architecture.md`
- Updated tooling references in `plan2code-loop/`, `plan2code-bot/`, and `plan2code-metrics/`
- Fixed pre-existing broken test assertion in `plan2code-bot` step-instructions test
- Repaired Windows-1252 / U+FFFD encoding artifacts (em-dashes) in `plan2code-loop` and `plan2code-bot` source files
### 🎁 Added
- **Devin platform support** — Added Devin to supported platforms list across `README.md`, `AGENTS.md`, `install.js` Agent Skills targets, `.agents-docs/AGENTS-development-commands.md`, and the `/plan2code-init` prompt
- **CLAUDE.md MANDATORY FIRST STEP template** — `/plan2code-init` and `/plan2code-init-update` now generate a CLAUDE.md template containing a `CRITICAL — MANDATORY FIRST STEP` directive that forces Claude Code to read AGENTS.md before responding to any user message
## v1.12.0
### 🔧 Changed
- **Resilient code references in workflow prompts** — Workflow prompts now explicitly guide AI agents to use semantic anchors (function names, class names, code patterns) instead of line numbers, which become stale as tasks modify files during implementation
- **Document mode** (`plan2code-2-document.md`) — New "Code references" block in Task Writing section lists four preferred anchor types with examples; line numbers allowed only as supplemental context
- **Revise-plan mode** (`plan2code-1b-revise-plan.md`) — Matching code reference rule added to Step 3 (Execute Revisions) so revised and new tasks follow the same convention
- **Implement mode** (`plan2code-3-implement.md`) — New "Verify locations" row in Code Consistency Rules table instructs agents to treat line numbers as approximate and locate by function/symbol name
## v1.11.1
### 🔧 Changed
+23 -22
View File
@@ -1,17 +1,18 @@
# Plam2Code Quick Reference
# Plan2Code Quick Reference
## Commands
| Step | Command | Input | Output |
| ------ | ------------------------------- | --------------- | ----------------------------------- |
| Init | /plan2code---init | None | AGENTS.md file |
| Update | /plan2code---init-update | AGENTS.md | Updated AGENTS.md |
| 0 | /plan2code---quick-task | Requirements | Conversational plan |
| 1 | /plan2code-1--plan | Requirements | PLAN-CONVERSATION-<date>.md + PLAN-DRAFT-<date>.md |
| 1b | /plan2code-1b--revise-plan | Specs + changes | Updated specs |
| 2 | /plan2code-2--document | PLAN-DRAFT.md | overview.md + Phase files |
| 3 | /plan2code-3--implement | overview.md | Implemented code |
| 4 | /plan2code-4--finalize | overview.md | Archived specs |
| Init | /plan2code-init | None | AGENTS.md file |
| Update | /plan2code-init-update | AGENTS.md | Updated AGENTS.md |
| 0 | /plan2code-quick-task | Requirements | Conversational plan |
| 1 | /plan2code-1-plan | Requirements | PLAN-CONVERSATION-<date>.md + PLAN-DRAFT-<date>.md |
| 1b | /plan2code-1b-revise-plan | Specs + changes | Updated specs |
| 2 | /plan2code-2-document | PLAN-DRAFT.md | overview.md + Phase files |
| 3 | /plan2code-3-implement | overview.md | Implemented code |
| 3b | /plan2code-3b-review | Scope guidance | Review findings + fixes |
| 4 | /plan2code-4-finalize | overview.md | Archived specs |
## File Structure
@@ -51,7 +52,7 @@ 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
3. Run multiple `/plan2code-3-implement` instances on different phases
4. `[/]` status shows which phases are actively being worked on
## Quick Troubleshooting
@@ -60,31 +61,31 @@ When phases have no file conflicts or dependencies, they can run simultaneously:
| ---------------------- | ------------------------------------------------- |
| 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` |
| 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 |
| 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
```
New to a project?
└── /plan2code---init → Generate AGENTS.md for project-specific guidance
└── /plan2code-init → Generate AGENTS.md for project-specific guidance
Learned something during a session?
└── /plan2code---init-update → Add learnings to AGENTS.md
└── /plan2code-init-update → Add learnings to AGENTS.md
Is it a quick, small task?
├── Yes → /plan2code---quick-task (standalone)
└── No → /plan2code-1--plan (full workflow)
├── /plan2code-2--document
├── /plan2code-3--implement (repeat per phase)
├── Yes → /plan2code-quick-task (standalone)
└── No → /plan2code-1-plan (full workflow)
├── /plan2code-2-document
├── /plan2code-3-implement (repeat per phase)
│ └── OR: plan2code-loop (autonomous alternative)
└── /plan2code-4--finalize
└── /plan2code-4-finalize
Need to revise mid-implementation?
└── /plan2code-1b--revise-plan
└── /plan2code-1b-revise-plan
```
## Autonomous Loop (Alternative)
@@ -93,7 +94,7 @@ 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-3-implement` | You want interactive control per phase |
| `plan2code-loop` | You want hands-off autonomous execution |
```bash
+546 -517
View File
File diff suppressed because it is too large Load Diff
+1227 -1207
View File
File diff suppressed because it is too large Load Diff
+203 -48
View File
@@ -116,7 +116,7 @@ const SRC_DIR = path.join(__dirname, 'src');
// Configuration for source prompts
const SOURCE_PROMPTS = [
{
source: 'plan2code---init.md',
source: 'plan2code-init.md',
stepNumber: 'init',
name: 'init',
displayName: 'Init Mode',
@@ -124,7 +124,7 @@ const SOURCE_PROMPTS = [
isUtility: true
},
{
source: 'plan2code---init-update.md',
source: 'plan2code-init-update.md',
stepNumber: 'update',
name: 'init-update',
displayName: 'Init Update Mode',
@@ -132,42 +132,49 @@ const SOURCE_PROMPTS = [
isUtility: true
},
{
source: 'plan2code---quick-task.md',
source: 'plan2code-quick-task.md',
stepNumber: 0,
name: 'quick-task',
displayName: 'Quick Task Mode',
description: 'Lightweight planning for small tasks'
},
{
source: 'plan2code-1--plan.md',
source: 'plan2code-1-plan.md',
stepNumber: 1,
name: 'plan',
displayName: 'Planning Mode',
description: 'Requirements analysis and architecture design'
},
{
source: 'plan2code-1b--revise-plan.md',
source: 'plan2code-1b-revise-plan.md',
stepNumber: '1b',
name: 'revise-plan',
displayName: 'Revision Mode',
description: 'Modify specs mid-implementation when requirements change'
},
{
source: 'plan2code-2--document.md',
source: 'plan2code-2-document.md',
stepNumber: 2,
name: 'document',
displayName: 'Documentation Mode',
description: 'Transform planning output into structured implementation docs'
},
{
source: 'plan2code-3--implement.md',
source: 'plan2code-3-implement.md',
stepNumber: 3,
name: 'implement',
displayName: 'Implementation Mode',
description: 'Execute implementation phase by phase'
},
{
source: 'plan2code-4--finalize.md',
source: 'plan2code-3b-review.md',
stepNumber: '3b',
name: 'review',
displayName: 'Review Mode',
description: 'Comprehensive post-implementation review with spec compliance checking'
},
{
source: 'plan2code-4-finalize.md',
stepNumber: 4,
name: 'finalize',
displayName: 'Finalization Mode',
@@ -178,9 +185,9 @@ const SOURCE_PROMPTS = [
// Helper function to generate destination filename based on stepNumber
function generateFilename(prompt, extension = '.md') {
if (prompt.isUtility || prompt.stepNumber === 0 || prompt.stepNumber === 'init') {
return `plan2code---${prompt.name}${extension}`;
return `plan2code-${prompt.name}${extension}`;
}
return `plan2code-${prompt.stepNumber}--${prompt.name}${extension}`;
return `plan2code-${prompt.stepNumber}-${prompt.name}${extension}`;
}
// Helper function to generate skill name (normalizes double hyphens to single)
@@ -310,7 +317,7 @@ const LOCAL_DESTINATIONS = [
header: (prompt) => generateSkillHeader(prompt, true),
},
{
name: 'Agent Skills (Amp · Gemini CLI · OpenCode)',
name: 'Agent Skills (Amp · Devin · Gemini CLI · OpenCode)',
dir: '.agents/skills',
type: 'skill',
header: (prompt) => generateSkillHeader(prompt, false),
@@ -406,7 +413,7 @@ const GLOBAL_DESTINATIONS = [
header: (prompt) => generateSkillHeader(prompt, true),
},
{
name: 'Agent Skills (Amp · Gemini CLI · OpenCode)',
name: 'Agent Skills (Amp · Devin · Gemini CLI · OpenCode)',
dir: '.agents/skills',
type: 'skill',
header: (prompt) => generateSkillHeader(prompt, false),
@@ -555,7 +562,7 @@ const INSTALL_TARGETS = [
icon: '◉ '
},
{
name: 'Agent Skills (Amp · Gemini CLI · OpenCode)',
name: 'Agent Skills (Amp · Devin · Gemini CLI · OpenCode)',
id: 'agent-skills',
dir: getAgentSkillsDir,
sourceDir: '.agents/skills',
@@ -821,6 +828,66 @@ function writeSkillToDestination(rootDir, baseDir, dest, prompt, sourceContent,
}
}
/**
* Recursively copy a directory and all its contents (files and subdirectories).
*/
function copyDirRecursive(src, dest, stats) {
fs.mkdirSync(dest, { recursive: true });
const entries = fs.readdirSync(src);
for (const entry of entries) {
const srcPath = path.join(src, entry);
const destPath = path.join(dest, entry);
if (fs.statSync(srcPath).isDirectory()) {
copyDirRecursive(srcPath, destPath, stats);
} else {
fs.copyFileSync(srcPath, destPath);
stats.copied++;
}
}
}
/**
* Inline reference file content into orchestrator content.
* Replaces each `Read references/X.md` directive with the file's actual content,
* wrapped in markers. Used for TOML targets (Gemini CLI), which cannot resolve
* relative file reads at runtime.
*/
function inlineReferenceContent(sourceContent, srcRefDir) {
if (!fs.existsSync(srcRefDir)) return sourceContent;
return sourceContent.replace(
/^Read\s+references\/([^\s]+\.md)(.*)$/gm,
(match, filename, suffix) => {
const refPath = path.join(srcRefDir, filename);
if (!fs.existsSync(refPath)) return match;
const refContent = fs.readFileSync(refPath, 'utf8');
return `<!-- BEGIN inlined references/${filename}${suffix} -->\n${refContent}\n<!-- END inlined references/${filename} -->`;
}
);
}
/**
* Copy a reference directory (e.g., plan2code-3b-review-references/) to a destination.
* Used by syncPrompts() to distribute reference files alongside orchestrator files.
*/
function copyReferenceDirectory(srcRefDir, destRefDir, stats, quiet = false) {
const copyStats = { copied: 0 };
try {
copyDirRecursive(srcRefDir, destRefDir, copyStats);
} catch (err) {
console.error(` ${COLORS.RED}${SYMBOLS.ERROR}${COLORS.RESET} Failed to copy reference directory: ${err.message}`);
stats.errors++;
return;
}
stats.updated += copyStats.copied;
if (!quiet) {
const refDirName = path.basename(destRefDir);
const files = fs.readdirSync(srcRefDir);
for (const file of files) {
console.log(` ${COLORS.GREEN}▰▰▰${COLORS.RESET} ${refDirName}/${file}`);
}
}
}
/**
* Write a TOML file to a destination for Gemini CLI
*/
@@ -903,25 +970,44 @@ function syncPrompts(quiet = false) {
continue;
}
// Write to dist/local-commands/
for (const dest of LOCAL_DESTINATIONS) {
if (dest.type === 'skill') {
writeSkillToDestination(projectRoot, 'dist/local-commands', dest, prompt, sourceContent, stats, quiet);
} else if (dest.type === 'toml') {
writeTomlToDestination(projectRoot, 'dist/local-commands', dest, prompt, sourceContent, stats, quiet);
} else {
writeToDestination(projectRoot, 'dist/local-commands', dest, prompt, sourceContent, stats, quiet);
}
}
// Detect reference directory (e.g., plan2code-3b-review-references/)
const refDirName = prompt.source.replace(/\.md$/, '-references');
const srcRefDir = path.join(SRC_DIR, refDirName);
const hasRefDir = fs.existsSync(srcRefDir);
// Write to dist/global-commands/
for (const dest of GLOBAL_DESTINATIONS) {
if (dest.type === 'skill') {
writeSkillToDestination(projectRoot, 'dist/global-commands', dest, prompt, sourceContent, stats, quiet);
} else if (dest.type === 'toml') {
writeTomlToDestination(projectRoot, 'dist/global-commands', dest, prompt, sourceContent, stats, quiet);
} else {
writeToDestination(projectRoot, 'dist/global-commands', dest, prompt, sourceContent, stats, quiet);
// Write to dist/local-commands/ and dist/global-commands/
const distBases = [
{ base: 'dist/local-commands', destinations: LOCAL_DESTINATIONS },
{ base: 'dist/global-commands', destinations: GLOBAL_DESTINATIONS },
];
for (const { base, destinations } of distBases) {
for (const dest of destinations) {
if (dest.type === 'skill') {
writeSkillToDestination(projectRoot, base, dest, prompt, sourceContent, stats, quiet);
if (hasRefDir) {
const skillName = generateSkillName(prompt);
const destRefDir = path.join(projectRoot, base, dest.dir, skillName, 'references');
copyReferenceDirectory(srcRefDir, destRefDir, stats, quiet);
}
} else if (dest.type === 'toml') {
// TOML targets (Gemini CLI) cannot resolve relative file reads at runtime,
// so inline reference content directly into the prompt body.
const contentForToml = hasRefDir
? inlineReferenceContent(sourceContent, srcRefDir)
: sourceContent;
writeTomlToDestination(projectRoot, base, dest, prompt, contentForToml, stats, quiet);
} else {
// For flat-file destinations, rewrite Read directive paths to sibling directory name
const contentForDest = hasRefDir
? sourceContent.replace(/^(Read\s+)references\//gm, '$1' + refDirName + '/')
: sourceContent;
writeToDestination(projectRoot, base, dest, prompt, contentForDest, stats, quiet);
if (hasRefDir) {
const destRefDir = path.join(projectRoot, base, dest.dir, refDirName);
copyReferenceDirectory(srcRefDir, destRefDir, stats, quiet);
}
}
}
}
}
@@ -970,8 +1056,15 @@ function copySkillDirectory(sourcePath, destPath, stats) {
fs.mkdirSync(destSubdir, { recursive: true });
const files = fs.readdirSync(srcSubdir);
for (const file of files) {
fs.copyFileSync(path.join(srcSubdir, file), path.join(destSubdir, file));
stats.copied++;
const srcEntry = path.join(srcSubdir, file);
const destEntry = path.join(destSubdir, file);
if (fs.statSync(srcEntry).isDirectory()) {
// Recursively copy nested subdirectories (e.g., references/)
copyDirRecursive(srcEntry, destEntry, stats);
} else {
fs.copyFileSync(srcEntry, destEntry);
stats.copied++;
}
}
}
}
@@ -994,6 +1087,15 @@ function cleanLegacyTargets() {
fs.unlinkSync(entryPath);
}
}
// Clean up any orphaned reference directories
for (const entry of entries) {
if (/^plan2code-.*-references$/.test(entry)) {
const entryPath = path.join(targetDir, entry);
if (fs.existsSync(entryPath) && fs.statSync(entryPath).isDirectory()) {
fs.rmSync(entryPath, { recursive: true, force: true });
}
}
}
}
}
@@ -1100,6 +1202,18 @@ function install(targets = null) {
}
}
}
// Also clean old reference directories for flat-file targets
if (target.type !== 'skill') {
const existingRefDirs = fs.readdirSync(destPath).filter(f =>
/^plan2code-.*-references$/.test(f) &&
fs.existsSync(path.join(destPath, f)) &&
fs.statSync(path.join(destPath, f)).isDirectory()
);
for (const refDir of existingRefDirs) {
fs.rmSync(path.join(destPath, refDir), { recursive: true, force: true });
console.log(` ${COLORS.YELLOW}░░░${COLORS.RESET} Removed: ${refDir}/`);
}
}
} catch (err) {
// Directory might be newly created, ignore read errors
}
@@ -1131,6 +1245,28 @@ function install(targets = null) {
totalErrors++;
}
}
// Copy sibling reference directories for flat-file targets.
// Source-of-truth: scan sourcePath for *-references directories rather than deriving
// names from prompt filenames (extension assumptions break when new file types are added).
const refDirs = fs.readdirSync(sourcePath).filter(f =>
/^plan2code-.*-references$/.test(f) &&
fs.statSync(path.join(sourcePath, f)).isDirectory()
);
for (const refDirName of refDirs) {
const srcRefDir = path.join(sourcePath, refDirName);
const destRefDir = path.join(destPath, refDirName);
const refStats = { copied: 0 };
try {
copyDirRecursive(srcRefDir, destRefDir, refStats);
} catch (err) {
console.log(` ${COLORS.RED}✖✖✖${COLORS.RESET} Failed to copy ${refDirName}/: ${err.message}`);
totalErrors++;
continue;
}
console.log(` ${COLORS.GREEN}▰▰▰${COLORS.RESET} ${refDirName}/ (${refStats.copied} reference files)`);
totalInstalled += refStats.copied;
}
}
console.log('');
}
@@ -1229,26 +1365,45 @@ function uninstallFiles(targets = null) {
}
if (files.length === 0) {
console.log(` ${COLORS.DIM}${SYMBOLS.INFO} No Plan2Code files found - nothing to remove\n${COLORS.RESET}`);
continue;
console.log(` ${COLORS.DIM}${SYMBOLS.INFO} No Plan2Code files found${COLORS.RESET}`);
} else {
// Remove files/skill directories
console.log(` ${COLORS.CYAN}${SYMBOLS.ACTIVE} REMOVING${COLORS.RESET} ${files.length} file(s)...`);
for (const file of files) {
const destFile = path.join(destPath, file);
try {
if (target.type === 'skill') {
// recursive: true handles nested subdirectories (e.g., references/)
fs.rmSync(destFile, { recursive: true, force: true });
} else {
fs.unlinkSync(destFile);
}
console.log(` ${COLORS.GREEN}▰▰▰${COLORS.RESET} Removed: ${file}`);
totalRemoved++;
} catch (err) {
console.log(` ${COLORS.RED}✖✖✖${COLORS.RESET} ${file}: ${err.message}`);
totalErrors++;
}
}
}
// Remove files/skill directories
console.log(` ${COLORS.CYAN}${SYMBOLS.ACTIVE} REMOVING${COLORS.RESET} ${files.length} file(s)...`);
for (const file of files) {
const destFile = path.join(destPath, file);
// Remove sibling reference directories for flat-file targets.
// Runs unconditionally so orphaned reference dirs are cleaned even if prompt files were already removed.
if (target.type !== 'skill') {
try {
if (target.type === 'skill') {
fs.rmSync(destFile, { recursive: true, force: true });
} else {
fs.unlinkSync(destFile);
const refDirs = fs.readdirSync(destPath).filter(f =>
/^plan2code-.*-references$/.test(f) &&
fs.existsSync(path.join(destPath, f)) &&
fs.statSync(path.join(destPath, f)).isDirectory()
);
for (const refDir of refDirs) {
fs.rmSync(path.join(destPath, refDir), { recursive: true, force: true });
console.log(` ${COLORS.GREEN}▰▰▰${COLORS.RESET} Removed: ${refDir}/`);
totalRemoved++;
}
console.log(` ${COLORS.GREEN}▰▰▰${COLORS.RESET} Removed: ${file}`);
totalRemoved++;
} catch (err) {
console.log(` ${COLORS.RED}✖✖✖${COLORS.RESET} ${file}: ${err.message}`);
totalErrors++;
// Directory may already be removed, ignore
}
}
console.log('');
+3 -2
View File
@@ -1,12 +1,13 @@
{
"name": "plan2code",
"version": "1.11.0",
"version": "1.14.0",
"private": true,
"bin": {
"plan2code": "./install.js"
},
"scripts": {
"prepare": "husky"
"prepare": "husky",
"test": "node scripts/validate-char-count.js"
},
"devDependencies": {
"husky": "^9.0.0"
+6 -6
View File
@@ -3,10 +3,10 @@ import { runCLI } from '../cli.js';
function showHelp(): void {
console.log(`
+----------------------------------------------------------------+
PLAN2CODEDE-BOT
----------------------------------------------------------------
Autonomous workflow test runner foplan2codede
Features LLM-as-judge for honest quality evaluation
PLAN2CODEDE-BOT
----------------------------------------------------------------
Autonomous workflow test runner foplan2codede
Features LLM-as-judge for honest quality evaluation
+----------------------------------------------------------------+
Usage:
@@ -20,11 +20,11 @@ Options:
--resume Resume a previous incomplete run
Modes:
New Project Mode - Run from empty directory
New Project Mode - Run from empty directory
The bot generates an app idea, creates a subdirectory, writes
IDEA.md, runs init, then all 4 workflow steps.
Enhancement Mode - Run from directory with AGENTS.md
Enhancement Mode - Run from directory with AGENTS.md
The bot scans the existing codebase, proposes an enhancement,
writes IDEA.md, then runs plan through finalize.
+3 -3
View File
@@ -39,12 +39,12 @@ export async function generateNewAppIdea(seed?: string): Promise<IdeaResult> {
const category = categories[Math.floor(Math.random() * categories.length)];
const seedClause = seed
? `\n\nThe user provided this seed for inspiration. Stay closely aligned with the theme and intent of the seed — build on it, don't ignore it:\n"${seed}"`
? `\n\nThe user provided this seed for inspiration. Stay closely aligned with the theme and intent of the seed — build on it, don't ignore it:\n"${seed}"`
: '';
const prompt = `Generate a random, creative idea for a ${category}. The project should be achievable in a single coding session (1-2 hours) and should be interesting but not overly complex.
IMPORTANT: Be creative and diverse with your ideas. Avoid defaulting to developer-centric tools (git analyzers, code formatters, repo scanners, etc.) unless the category specifically calls for it. Think about ideas that would appeal to a broad audience — productivity, entertainment, education, health, finance, art, music, social, cooking, travel, fitness, etc.${seedClause}
IMPORTANT: Be creative and diverse with your ideas. Avoid defaulting to developer-centric tools (git analyzers, code formatters, repo scanners, etc.) unless the category specifically calls for it. Think about ideas that would appeal to a broad audience — productivity, entertainment, education, health, finance, art, music, social, cooking, travel, fitness, etc.${seedClause}
Respond in EXACTLY this format (no other text):
NAME: <kebab-case-project-name>
@@ -54,7 +54,7 @@ DESCRIPTION: <2-3 sentence description of what the app does, its key features, a
prompt,
options: {
maxTurns: 1,
systemPrompt: 'You are a wildly creative project idea generator. You come up with surprising, fun, and diverse software project ideas spanning many domains — not just developer tools. Respond only in the exact format requested.',
systemPrompt: 'You are a wildly creative project idea generator. You come up with surprising, fun, and diverse software project ideas spanning many domains — not just developer tools. Respond only in the exact format requested.',
},
});
@@ -34,9 +34,9 @@ describe('buildStepPrompt', () => {
expect(prompt).toContain('/plan2code-2-document');
});
it('implement prompt includes /plan2code-3-implement skill invocation', () => {
it('implement prompt instructs direct tool usage without skill invocation', () => {
const prompt = buildStepPrompt('implement', config);
expect(prompt).toContain('/plan2code-3-implement');
expect(prompt).toContain('Do NOT use the Skill tool');
});
it('finalize prompt includes /plan2code-4-finalize skill invocation', () => {
@@ -20,7 +20,7 @@ export function buildStepPrompt(step: StepName, config: BotConfig): string {
function buildInitPrompt(config: BotConfig): string {
return `${AUTONOMOUS_PREAMBLE}
This is a brand new project with no existing code. Create a minimal stub AGENTS.md file and an IDEA.md file. Do NOT run the /plan2code-init skill there is no codebase to analyze yet.
This is a brand new project with no existing code. Create a minimal stub AGENTS.md file and an IDEA.md file. Do NOT run the /plan2code-init skill there is no codebase to analyze yet.
The project idea is: ${config.ideaDescription}
The project name is: ${config.ideaName}
@@ -28,7 +28,7 @@ The project name is: ${config.ideaName}
## What to create
### AGENTS.md
Create a minimal stub with ONLY the following do NOT invent architecture, tech stack details, commands, or file structures:
Create a minimal stub with ONLY the following do NOT invent architecture, tech stack details, commands, or file structures:
\`\`\`markdown
# AGENTS.md
@@ -47,7 +47,7 @@ This project is in the planning phase. Architecture, commands, and detailed docu
If IDEA.md does not already exist, create it with the project name and description.
## Rules
- Do NOT create .agents-docs/ or any detail files there is nothing to document yet
- Do NOT create .agents-docs/ or any detail files there is nothing to document yet
- Do NOT hallucinate architecture, dependencies, file structures, or tech stack choices
- Do NOT install dependencies or scaffold project files
- ONLY create the two files above`;
@@ -85,7 +85,7 @@ When making decisions:
function buildImplementPrompt(config: BotConfig): string {
return `${AUTONOMOUS_PREAMBLE}
You are a senior software engineer implementing a project phase. Do NOT use the Skill tool implement directly using Read, Write, Edit, Glob, and Grep tools.
You are a senior software engineer implementing a project phase. Do NOT use the Skill tool implement directly using Read, Write, Edit, Glob, and Grep tools.
## Process
@@ -95,7 +95,7 @@ You are a senior software engineer implementing a project phase. Do NOT use the
4. **Mark phase in-progress**: Update \`[ ]\` to \`[/]\` in overview.md
5. **Implement each task sequentially**:
- Read the task specification completely
- Write the code using Write or Edit tools create real files, not code blocks
- Write the code using Write or Edit tools create real files, not code blocks
- Mark the task \`[x]\` in the phase file immediately after completing it
6. **Complete the phase**: After all tasks, fill in the "Phase Completion Summary" in the phase file
7. **Mark phase complete**: Update \`[/]\` to \`[x]\` in overview.md
@@ -103,11 +103,11 @@ You are a senior software engineer implementing a project phase. Do NOT use the
## Rules
- Follow AGENTS.md if it exists
- Implement specs EXACTLY no creative additions or unsolicited improvements
- Write task completion status (\`[x]\`) to disk immediately after each task never batch
- Implement specs EXACTLY no creative additions or unsolicited improvements
- Write task completion status (\`[x]\`) to disk immediately after each task never batch
- Only create files mentioned in the spec tasks
- Use the specified file paths, function names, and structures from the spec
- No placeholder code fully implement every function
- No placeholder code fully implement every function
- Match existing codebase conventions
- Do NOT run git commands
- Skip running tests unless explicitly listed as a phase task
+210 -210
View File
@@ -1,210 +1,210 @@
# 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, loop mode, 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 tasks (one per iteration in task mode, or all in a phase in phase mode)
4. **Checkbox Update** - The AI marks tasks complete in the markdown file
5. **Scratchpad Update** - The AI appends notes to the per-spec scratchpad
6. **Completion Marker** - The AI outputs structured markers (e.g., `TASK_COMPLETE: 1.1 - description`)
7. **Loop** - Repeat until all tasks done or max iterations reached
### Loop Modes
The CLI asks you to choose a loop mode:
| Mode | Behavior | Git Commits | Best For |
|------|----------|-------------|----------|
| **One task per loop** (default) | Each agent invocation implements exactly one task | Node controller commits after each task | Smaller models, careful step-by-step execution |
| **One phase per loop** | Each agent invocation implements all remaining tasks in the current phase | LLM commits after each task (with JIRA ID if provided) | Smart models with larger context windows, keeping related tasks together |
### 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
PHASE_COMPLETE
LOOP_COMPLETE
```
| Marker | Meaning |
|--------|---------|
| `TASK_COMPLETE: X.X - desc` | Task implemented and marked complete |
| `TASK_BLOCKED: X.X - reason` | Cannot complete task (explains why) |
| `PHASE_COMPLETE` | Current phase finished (phase mode only) |
| `LOOP_COMPLETE` | All phases finished |
In **phase mode**, the AI outputs multiple `TASK_COMPLETE` markers (one per task) within a single iteration, followed by `PHASE_COMPLETE` or `LOOP_COMPLETE`.
## 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
? Tasks per loop iteration: One task per loop (default)
? Maximum iterations: 100
Starting Plan2Code Loop
══════════════════════════════════════════════
Agent: Claude Code
Model: default
Spec: C:\projects\my-app\specs\todo-app
Loop mode: One task per loop
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
```
# 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, loop mode, 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 tasks (one per iteration in task mode, or all in a phase in phase mode)
4. **Checkbox Update** - The AI marks tasks complete in the markdown file
5. **Scratchpad Update** - The AI appends notes to the per-spec scratchpad
6. **Completion Marker** - The AI outputs structured markers (e.g., `TASK_COMPLETE: 1.1 - description`)
7. **Loop** - Repeat until all tasks done or max iterations reached
### Loop Modes
The CLI asks you to choose a loop mode:
| Mode | Behavior | Git Commits | Best For |
|------|----------|-------------|----------|
| **One task per loop** (default) | Each agent invocation implements exactly one task | Node controller commits after each task | Smaller models, careful step-by-step execution |
| **One phase per loop** | Each agent invocation implements all remaining tasks in the current phase | LLM commits after each task (with JIRA ID if provided) | Smart models with larger context windows, keeping related tasks together |
### 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
PHASE_COMPLETE
LOOP_COMPLETE
```
| Marker | Meaning |
|--------|---------|
| `TASK_COMPLETE: X.X - desc` | Task implemented and marked complete |
| `TASK_BLOCKED: X.X - reason` | Cannot complete task (explains why) |
| `PHASE_COMPLETE` | Current phase finished (phase mode only) |
| `LOOP_COMPLETE` | All phases finished |
In **phase mode**, the AI outputs multiple `TASK_COMPLETE` markers (one per task) within a single iteration, followed by `PHASE_COMPLETE` or `LOOP_COMPLETE`.
## 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
? Tasks per loop iteration: One task per loop (default)
? Maximum iterations: 100
Starting Plan2Code Loop
══════════════════════════════════════════════
Agent: Claude Code
Model: default
Spec: C:\projects\my-app\specs\todo-app
Loop mode: One task per loop
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
```
+240 -240
View File
@@ -1,240 +1,240 @@
import path from 'path';
import { confirm, input, select } from '@inquirer/prompts';
import { agentRegistry } from './agents/index.js';
import { StateManager, type SessionConfig, type LoopMode } 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('');
logger.info('1. Create a spec using `plan2code-1--plan`');
logger.info(' command in our AI Agent');
logger.info('');
logger.info('2. Come back here and run `plan2code-loop`');
logger.info(' as an alternative to `plan2code-3--implement`');
logger.info('');
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 allAgents = agentRegistry.getAll();
const agentName = await select({
message: 'Select AI agent:',
choices: allAgents.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;
}
/**
* Select loop mode: one task per loop or one phase per loop
*/
async function selectLoopMode(): Promise<LoopMode> {
const mode = await select<LoopMode>({
message: 'Tasks per loop iteration:',
choices: [
{
name: 'One task per loop (default)',
value: 'task' as LoopMode,
},
{
name: 'One phase per loop (related tasks together)',
value: 'phase' as LoopMode,
},
],
default: 'task',
});
return mode;
}
/**
* 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 Planny 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) {
const agent = await selectAgent();
existingConfig.agent = agent;
await stateManager.writeConfig(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 loopMode = await selectLoopMode();
const maxIterations = await selectMaxIterations();
const config: SessionConfig = {
agent,
model: 'default',
maxIterations,
specPath,
timeout: 3,
maxRetries: 5,
verbose: false,
startedAt: new Date().toISOString(),
currentIteration: 0,
jiraTicketId,
loopMode,
};
// Initialize session
await stateManager.initializeNewSession(config);
return { config, isResume: false };
}
import path from 'path';
import { confirm, input, select } from '@inquirer/prompts';
import { agentRegistry } from './agents/index.js';
import { StateManager, type SessionConfig, type LoopMode } 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('');
logger.info('1. Create a spec using `plan2code-1-plan`');
logger.info(' command in our AI Agent');
logger.info('');
logger.info('2. Come back here and run `plan2code-loop`');
logger.info(' as an alternative to `plan2code-3-implement`');
logger.info('');
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 allAgents = agentRegistry.getAll();
const agentName = await select({
message: 'Select AI agent:',
choices: allAgents.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;
}
/**
* Select loop mode: one task per loop or one phase per loop
*/
async function selectLoopMode(): Promise<LoopMode> {
const mode = await select<LoopMode>({
message: 'Tasks per loop iteration:',
choices: [
{
name: 'One task per loop (default)',
value: 'task' as LoopMode,
},
{
name: 'One phase per loop (related tasks together)',
value: 'phase' as LoopMode,
},
],
default: 'task',
});
return mode;
}
/**
* 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 Planny 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) {
const agent = await selectAgent();
existingConfig.agent = agent;
await stateManager.writeConfig(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 loopMode = await selectLoopMode();
const maxIterations = await selectMaxIterations();
const config: SessionConfig = {
agent,
model: 'default',
maxIterations,
specPath,
timeout: 3,
maxRetries: 5,
verbose: false,
startedAt: new Date().toISOString(),
currentIteration: 0,
jiraTicketId,
loopMode,
};
// Initialize session
await stateManager.initializeNewSession(config);
return { config, isResume: false };
}
+3 -3
View File
@@ -106,17 +106,17 @@ export class Controller {
clearInterval(elapsedInterval);
spinner.stop();
// Cancelled or interrupted — return immediately, don't retry
// Cancelled or interrupted — return immediately, don't retry
if (result.cancelled || this.interrupted) {
return result;
}
// Completed (success or error exit code) — return to caller
// Completed (success or error exit code) — return to caller
if (!result.timedOut) {
return result;
}
// Timed out — retry if attempts remain, otherwise fatal
// Timed out — retry if attempts remain, otherwise fatal
if (attempt < maxAttempts - 1) {
continue;
}
+3 -2
View File
@@ -68,7 +68,7 @@ Use only when ALL phases in overview.md are marked complete.
## Scratchpad Management
After completing each task, add a new entry at the **bottom** of \`{{specPath}}/.plan2code-loop/scratchpad.md\`.
Never edit, reorganize, or insert into existing content only append new entries to the end of the file.
Never edit, reorganize, or insert into existing content only append new entries to the end of the file.
Each entry should include:
- Task completed and Phase item reference
@@ -94,6 +94,7 @@ export const LOOP_PROMPT_TEMPLATE_PHASE = `# PLAN2CODE-LOOP: Autonomous Phase Im
**IMPLEMENT ALL REMAINING TASKS IN THE CURRENT PHASE.**
Find the first incomplete phase, then implement every remaining task in that phase before stopping.
Complete each task fully before moving to the next task within the phase.
## Project Information
- **Project Root:** \`{{projectRoot}}\`
- **Spec Location:** \`{{specPath}}\`
@@ -178,7 +179,7 @@ After ALL tasks in the phase are complete (or blocked), output:
## Scratchpad Management
After completing each task, add a new entry at the **bottom** of \`{{specPath}}/.plan2code-loop/scratchpad.md\`.
Never edit, reorganize, or insert into existing content only append new entries to the end of the file.
Never edit, reorganize, or insert into existing content only append new entries to the end of the file.
Each entry should include:
- Task completed and Phase item reference
+126 -126
View File
@@ -1,126 +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;
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;
+8 -8
View File
@@ -16,14 +16,14 @@ const ANALYZE_PROMPT_PATH = new URL('../src/prompts/analyze.md', import.meta.url
function readPromptFiles(plan2codeRoot: string): Record<string, string> {
const srcDir = path.join(plan2codeRoot, 'src');
const promptFiles = [
'plan2code-1--plan.md',
'plan2code-1b--revise-plan.md',
'plan2code-2--document.md',
'plan2code-3--implement.md',
'plan2code-4--finalize.md',
'plan2code---init.md',
'plan2code---init-update.md',
'plan2code---quick-task.md',
'plan2code-1-plan.md',
'plan2code-1b-revise-plan.md',
'plan2code-2-document.md',
'plan2code-3-implement.md',
'plan2code-4-finalize.md',
'plan2code-init.md',
'plan2code-init-update.md',
'plan2code-quick-task.md',
];
const contents: Record<string, string> = {};
+4 -4
View File
@@ -69,8 +69,8 @@ function detectPlan2CodeRoot(): string | null {
for (let i = 0; i < 5; i++) {
const srcDir = path.join(dir, 'src');
if (
fs.existsSync(path.join(srcDir, 'plan2code-1--plan.md')) &&
fs.existsSync(path.join(srcDir, 'plan2code-2--document.md'))
fs.existsSync(path.join(srcDir, 'plan2code-1-plan.md')) &&
fs.existsSync(path.join(srcDir, 'plan2code-2-document.md'))
) {
return dir;
}
@@ -764,8 +764,8 @@ export async function runCLI(): Promise<void> {
// Warn if the path doesn't look like a plan2code repo
const hasSrcPrompts =
fs.existsSync(path.join(resolvedPlan2CodeRoot, 'src', 'plan2code-1--plan.md')) &&
fs.existsSync(path.join(resolvedPlan2CodeRoot, 'src', 'plan2code-2--document.md'));
fs.existsSync(path.join(resolvedPlan2CodeRoot, 'src', 'plan2code-1-plan.md')) &&
fs.existsSync(path.join(resolvedPlan2CodeRoot, 'src', 'plan2code-2-document.md'));
if (!hasSrcPrompts) {
console.log(chalk.yellow('⚠ No src/plan2code-*.md prompts found at that path. Hashing and analysis may be limited.'));
}
+8 -8
View File
@@ -99,14 +99,14 @@ function generateRunId(): string {
export function collectPromptVersions(plan2codeRoot: string): PromptVersions {
const srcDir = path.join(plan2codeRoot, 'src');
return {
plan: sha256File(path.join(srcDir, 'plan2code-1--plan.md')),
revise_plan: sha256File(path.join(srcDir, 'plan2code-1b--revise-plan.md')),
document: sha256File(path.join(srcDir, 'plan2code-2--document.md')),
implement: sha256File(path.join(srcDir, 'plan2code-3--implement.md')),
finalize: sha256File(path.join(srcDir, 'plan2code-4--finalize.md')),
init: sha256File(path.join(srcDir, 'plan2code---init.md')),
init_update: sha256File(path.join(srcDir, 'plan2code---init-update.md')),
quick_task: sha256File(path.join(srcDir, 'plan2code---quick-task.md')),
plan: sha256File(path.join(srcDir, 'plan2code-1-plan.md')),
revise_plan: sha256File(path.join(srcDir, 'plan2code-1b-revise-plan.md')),
document: sha256File(path.join(srcDir, 'plan2code-2-document.md')),
implement: sha256File(path.join(srcDir, 'plan2code-3-implement.md')),
finalize: sha256File(path.join(srcDir, 'plan2code-4-finalize.md')),
init: sha256File(path.join(srcDir, 'plan2code-init.md')),
init_update: sha256File(path.join(srcDir, 'plan2code-init-update.md')),
quick_task: sha256File(path.join(srcDir, 'plan2code-quick-task.md')),
};
}
+9 -9
View File
@@ -5,18 +5,18 @@ import type { PromptEdit } from './types.js';
// ── Shared fixtures ───────────────────────────────────────────────────────────
const PROMPT_CONTENTS: Record<string, string> = {
'plan2code-1--plan.md': 'This is the plan prompt content. It has some text here.',
'plan2code-2--document.md': 'Document prompt with repeated text. repeated text. Done.',
'plan2code-3--implement.md': 'Implement prompt content.',
'plan2code-1-plan.md': 'This is the plan prompt content. It has some text here.',
'plan2code-2-document.md': 'Document prompt with repeated text. repeated text. Done.',
'plan2code-3-implement.md': 'Implement prompt content.',
};
function makeEdit(overrides: Partial<PromptEdit> = {}): PromptEdit {
return {
file: 'plan2code-1--plan.md',
file: 'plan2code-1-plan.md',
rationale: 'test rationale',
expected_metric_impact: 'test impact',
char_count_before: PROMPT_CONTENTS['plan2code-1--plan.md'].length,
char_count_after: PROMPT_CONTENTS['plan2code-1--plan.md'].length,
char_count_before: PROMPT_CONTENTS['plan2code-1-plan.md'].length,
char_count_after: PROMPT_CONTENTS['plan2code-1-plan.md'].length,
char_count_delta: 0,
old_text: 'some text',
new_text: 'better text',
@@ -59,10 +59,10 @@ describe('validateEdit', () => {
it('warns when old_text appears multiple times', () => {
const edit = makeEdit({
file: 'plan2code-2--document.md',
file: 'plan2code-2-document.md',
old_text: 'repeated text',
char_count_before: PROMPT_CONTENTS['plan2code-2--document.md'].length,
char_count_after: PROMPT_CONTENTS['plan2code-2--document.md'].length,
char_count_before: PROMPT_CONTENTS['plan2code-2-document.md'].length,
char_count_after: PROMPT_CONTENTS['plan2code-2-document.md'].length,
});
const result = validateEdit(edit, PROMPT_CONTENTS);
expect(result.valid).toBe(true);
+8 -8
View File
@@ -32,14 +32,14 @@ function generateProposalId(): string {
function readPromptFiles(plan2codeRoot: string): Record<string, string> {
const srcDir = path.join(plan2codeRoot, 'src');
const promptFiles = [
'plan2code-1--plan.md',
'plan2code-1b--revise-plan.md',
'plan2code-2--document.md',
'plan2code-3--implement.md',
'plan2code-4--finalize.md',
'plan2code---init.md',
'plan2code---init-update.md',
'plan2code---quick-task.md',
'plan2code-1-plan.md',
'plan2code-1b-revise-plan.md',
'plan2code-2-document.md',
'plan2code-3-implement.md',
'plan2code-4-finalize.md',
'plan2code-init.md',
'plan2code-init-update.md',
'plan2code-quick-task.md',
];
const contents: Record<string, string> = {};
+1 -1
View File
@@ -76,7 +76,7 @@ For each step (14), provide:
Numbered list. For each underperforming metric:
1. **Metric:** [metric name] | **Value:** [actual] | **Target:** [target]
- **File:** [plan2code-X--name.md]
- **File:** [plan2code-X-name.md]
- **Section:** [specific heading or section name]
- **Hypothesis:** [specific gap in the prompt that would explain the metric miss]
- **Confidence:** [High/Medium/Low] — [reason for confidence level]
+1 -1
View File
@@ -67,7 +67,7 @@ Produce EXACTLY the following sections. The JSON block is parsed by machine —
```json
[
{
"file": "plan2code-X--name.md",
"file": "plan2code-X-name.md",
"rationale": "One sentence explaining what this edit fixes",
"expected_metric_impact": "avg_metric_name: expected direction and magnitude",
"char_count_before": 0,
+8 -8
View File
@@ -1,14 +1,14 @@
// All TypeScript interfaces for plan2code-metrics
export interface PromptVersions {
plan: string; // sha256:... plan2code-1--plan.md
revise_plan: string; // plan2code-1b--revise-plan.md
document: string; // plan2code-2--document.md
implement: string; // plan2code-3--implement.md
finalize: string; // plan2code-4--finalize.md
init: string; // plan2code---init.md
init_update: string; // plan2code---init-update.md
quick_task: string; // plan2code---quick-task.md
plan: string; // sha256:... plan2code-1-plan.md
revise_plan: string; // plan2code-1b-revise-plan.md
document: string; // plan2code-2-document.md
implement: string; // plan2code-3-implement.md
finalize: string; // plan2code-4-finalize.md
init: string; // plan2code-init.md
init_update: string; // plan2code-init-update.md
quick_task: string; // plan2code-quick-task.md
}
export interface Step1PlanMetrics {
+3
View File
@@ -6,6 +6,9 @@ const path = require('path');
const CHAR_LIMIT = 11000;
const srcDir = path.join(__dirname, '..', 'src');
// Note: readdirSync is non-recursive, so files in subdirectories like
// plan2code-3b-review-references/ are automatically excluded from
// the character limit check. Reference files have no char limit.
const files = fs.readdirSync(srcDir)
.filter(f => f.startsWith('plan2code-') && f.endsWith('.md'))
.sort();
@@ -23,7 +23,7 @@ Check if `./AGENTS.md` exists:
>
> "No `AGENTS.md` found. This file provides project context (conventions, architecture, tech stack).
>
> **To create it:** `plan2code---init`
> **To create it:** `plan2code-init`
>
> Let me know when ready to continue."
@@ -157,7 +157,7 @@ State assessment and ask user to confirm.
3. Create `specs/<feature-name>/PLAN-DRAFT-<YYYYMMDD>.md` with Phases 1-3
4. Set status: `Phase 3 Complete - Resume at Phase 4`
5. Include: Executive Summary, Requirements, System Context, Scope, Confidence
6. Instruct: "Large project. Progress saved. Start NEW conversation with `/plan2code-1--plan` to resume at Phase 4."
6. Instruct: "Large project. Progress saved. Start NEW conversation with `/plan2code-1-plan` to resume at Phase 4."
7. STOP
### PHASE 4: Tech Stack
@@ -210,7 +210,7 @@ State assessment and ask user to confirm.
2. Create `specs/<feature-name>/` if needed
3. Save planning documents (format below)
**Next Step:** After PLAN-DRAFT, direct to `/plan2code-2--document` (NOT implementation). Workflow: Plan -> Document -> Implement -> Finalize.
**Next Step:** After PLAN-DRAFT, direct to `/plan2code-2-document` (NOT implementation). Workflow: Plan -> Document -> Implement -> Finalize.
**If < 90%:**
- List areas needing clarification (reference dimension)
@@ -263,7 +263,7 @@ Sections:
When complete (PLAN-DRAFT created), tell user:
1. What was accomplished
2. **Next: `/plan2code-2--document`** (NOT implementation)
2. **Next: `/plan2code-2-document`** (NOT implementation)
3. Documentation auto-discovers planning files
**Closing example:**
@@ -279,7 +279,7 @@ When complete (PLAN-DRAFT created), tell user:
> ╰───╯
> =============================================
> NEXT STEP: Start a NEW conversation then run:
> `/plan2code-2--document`
> `/plan2code-2-document`
> ```"
## Abort / Recovery
@@ -293,6 +293,6 @@ When complete (PLAN-DRAFT created), tell user:
- Final phase: PLANNING PHASE 7: Transition Decision
- Do NOT implement - design and present plan only
- Responses start with: `🤔 [PLANNING PHASE X: Name]`
- **Workflow:** Plan -> Document -> Implement -> Finalize. After planning: `/plan2code-2--document`
- **Workflow:** Plan -> Document -> Implement -> Finalize. After planning: `/plan2code-2-document`
- Save conversation log (7A) before PLAN-DRAFT (7B), run verification (7C) after
- Run verification (7C) after PLAN-DRAFT - conversation log is source of truth
@@ -91,6 +91,8 @@ Proceed with revision? (yes / no / discuss)
`🔄 [REVISION] Step 3: Execute Revisions`
**Code references:** When writing or revising task descriptions, never use line numbers as primary references — they become stale as tasks modify files. Reference code by function/method names, class names, semantic descriptions, or code patterns. Line numbers may only appear as supplemental context (e.g., "Update `validateEmail()` (currently ~L45) to...").
1. Update affected tasks with `🔄 REVISED` flag:
```markdown
@@ -178,7 +180,7 @@ Report and resolve issues before proceeding.
║ Specs have been updated. To continue implementation: ║
║ ║
║ 1. Start a NEW conversation ║
║ 2. Use command: /plan2code-3--implement ║
║ 2. Use command: /plan2code-3-implement ║
║ 3. Provide path: specs/<feature-name>/overview.md ║
║ ║
║ The command will auto-detect the next Phase to implement. ║
@@ -37,7 +37,7 @@ If no PLAN-DRAFT found and user hasn't provided one, ask for:
If no plan exists and user wants to skip:
> "Documentation transforms planning into specs. Without a plan, either:
> 1. Run planning first (`/plan2code-1--plan`)
> 1. Run planning first (`/plan2code-1-plan`)
> 2. Describe requirements so I can help create a minimal plan"
## Phase Sizing
@@ -60,6 +60,14 @@ If no plan exists and user wants to skip:
| Action-oriented | Imperative: "Create...", "Implement..." |
| Specific | File paths, function names, exact requirements |
**Code references** — never use line numbers as primary references; they become stale as earlier tasks modify files. Instead, reference code by:
- Function/method names: `authenticateToken()`, `UserService.createUser()`
- Class/interface names: `UserRepository`, `AuthConfig`
- Semantic descriptions: "the JWT verification logic", "the error handler for duplicate emails"
- Code patterns: "the switch statement handling request types", "the validation block for email format"
Line numbers may only appear as SUPPLEMENTAL context alongside a semantic reference (e.g., "Update `validateEmail()` (currently ~L45) to...").
**Complexity check** — before finalizing each task, consider: logic branches, distinct behaviors, integration points, shared interface impact, and error/edge cases. Tasks that are complex on 3+ of these signals should be split.
**Examples:**
@@ -206,7 +214,7 @@ Replace values with actuals. `verification_items_added` = total Added column fro
**Tell user:**
1. What was created (spec files list)
2. Path for next session: `specs/<feature-name>/overview.md`
3. Next command: `/plan2code-3--implement`
3. Next command: `/plan2code-3-implement`
4. Start NEW conversation for implementation
**Closing example:**
@@ -220,7 +228,7 @@ Replace values with actuals. `verification_items_added` = total Added column fro
> ╰───╯
> ============================================
> NEXT STEP: Start a NEW conversation and run:
> `/plan2code-3--implement`
> `/plan2code-3-implement`
> ```"
## Abort Handling
@@ -240,4 +248,4 @@ If user says "abort", "cancel", "start over":
## Session Hint
If you discovered project-specific insights during documentation, suggest `/plan2code---init-update` to capture them in `AGENTS.md`.
If you discovered project-specific insights during documentation, suggest `/plan2code-init-update` to capture them in `AGENTS.md`.
@@ -85,6 +85,7 @@ Only show consecutive same-group incomplete phases. After selection, mark `[/]`,
| No extra files | Only create files mentioned in tasks |
| Minimal dependencies | No packages outside approved tech stack |
| No placeholder code | Fully implement every function |
| Verify locations | Treat any line numbers in specs as approximate — read the file and locate by function/symbol name |
## Examples
@@ -200,9 +201,10 @@ On user "approved":
1. Mark `[/]``[x]` in overview.md, update phase-X.md status to "Complete"
2. Show Planny art with completion message
3. Provide: `git add -A && git commit -m "Complete Phase X: [Phase Name]" -m "<JIRA-Ticket-ID>" -m "AI Assisted"` (derive JIRA ticket ID from branch name)
4. **If more phases:** "NEXT STEP: Start NEW conversation and run: `/plan2code-3--implement`"
5. **If final phase:** "NEXT STEP: Start NEW conversation and run: `/plan2code-4--finalize`"
6. Mention `/plan2code-1b--revise` option
4. **If more phases:** "NEXT STEP: Start NEW conversation and run: `/plan2code-3-implement`"
5. **If final phase:** "NEXT STEP: Start NEW conversation and run: `/plan2code-4-finalize`"
6. Mention `/plan2code-1b-revise-plan` option
7. Suggest: "Optional: run `/plan2code-3b-review` for a post-phase code review -- recommended after key features or milestones."
Planny (continuing):
```
@@ -230,7 +232,7 @@ If user says "abort", "cancel", or similar:
- List completed vs remaining tasks
- Note created/modified files
- Do NOT change phase checkbox (stays `[/]`)
- Explain: "Run `/plan2code-3--implement` again to resume."
- Explain: "Run `/plan2code-3-implement` again to resume."
3. Stop implementation
## Recovery
@@ -239,7 +241,7 @@ If user says "abort", "cancel", or similar:
|-------|----------|
| Lost context mid-phase | Attach specs, say "resume from Task X.Y" |
| Spec unclear/conflicting | Mark task blocked, ask user |
| Need to change plan | Pause, use `/plan2code-1b--revise-plan` |
| Need to change plan | Pause, use `/plan2code-1b-revise-plan` |
## Learning Capture
@@ -0,0 +1,191 @@
# Dimension Checklists
> Part of plan2code-3b-review — loaded during Step 3 (Analyze).
Non-obvious check items, anti-patterns, and "don't flag" guidance for each review dimension. Focus on what LLMs commonly miss.
---
## 1. Correctness
1. Trace variable mutations through async paths — can a value change between check and use?
2. Check type coercion at boundaries — string-to-number, truthy/falsy assumptions on `0`/`""`/`false`
3. Check that map/filter/reduce callbacks handle all element shapes (null members, missing keys)
4. Verify copy operations produce deep copies when mutation independence is required
5. Check array bounds — does code handle empty arrays and out-of-range indices?
6. Verify regex patterns reject edge cases (empty string, special chars, Unicode)
7. Check for race conditions — shared mutable state across async reads/writes
8. Verify error handling paths return/throw correctly — no silent swallowing
**Anti-patterns:** `catch(e) {}` silent suppression · `if (value)` when `0`/`""`/`false` are valid · `indexOf > 0` instead of `!== -1` · Mutating arguments instead of returning new values
**Don't flag:** Intentional `==` for null coalescing (`if (x == null)`) · Missing switch default with TypeScript `never` exhaustive check · Optional chaining `?.` returning undefined
---
## 2. Completeness
1. Search for TODO, FIXME, HACK, XXX, TEMP — each must be intentional or tracked
2. Check cleanup/teardown exists for every setup/initialization
3. Verify rollback/undo logic for multi-step operations that can partially fail
4. Check event listeners and subscriptions have corresponding unsubscribe/cleanup
5. Verify all file/resource handles closed in both success and error paths
6. Check all enum/union type values have handling — no missing cases
7. Verify pagination — does code fetch all pages or just the first?
8. Check retry logic has backoff and maximum retry count
**Anti-patterns:** Happy-path-only functions that return undefined on error · Event listeners without cleanup · Missing else in critical chains where the "impossible" case can occur
**Don't flag:** TODOs with linked ticket numbers · Features explicitly "not in scope" · Bare `throw` in catch blocks (intentional re-throw)
---
## 3. Security
1. Check for injection — string concatenation in queries instead of parameterized
2. Search for hardcoded secrets, API keys, tokens in source files and configs
3. Verify sensitive data (passwords, tokens, PII) is not logged, even at debug level
4. Check file path operations prevent traversal — no unsanitized `../` from user input
5. Check for mass assignment — `Object.assign`/spread from user input without allow-list
6. Verify error responses don't leak stack traces, internal paths, or system info
7. Verify session tokens use cryptographic randomness, not `Math.random()`
8. Check dependency versions against known CVEs
**Anti-patterns:** Dynamic code evaluation with user-influenced input · Plaintext password storage · JWT `none` algorithm accepted
**Don't flag:** Hardcoded non-sensitive values (page sizes, route paths) · Internal tools documented as trusted-environment-only · Test fixture credentials in `.test.`/`.fixture.` files
---
## 4. Performance
1. Check for N+1 queries — loops issuing a query per iteration instead of batching
2. Check for synchronous blocking in async contexts (fs.readFileSync in server request handlers)
3. Check for memory leaks — growing arrays/maps without bounds, unclosed streams
4. Check for quadratic complexity — nested iterations over the same collection
5. Verify bulk operations used where available (bulk insert vs individual)
6. Check regex for catastrophic backtracking potential (nested quantifiers)
7. Verify connection pools for database and HTTP clients, not per-request connections
8. Check event handlers debounced/throttled for high-frequency events
**Anti-patterns:** `await` in `for` loops instead of `Promise.all` · Loading entire tables to filter in app code · New regex instances inside loops
**Don't flag:** `readFileSync` at startup/module load · Loading small files (<100KB) once · Missing caching in one-shot scripts/CLI tools
---
## 5. Standards
1. Check function length — functions over 50 lines warrant scrutiny
2. Verify DRY applied judiciously — shared logic extracted, not over-abstracted
3. Check magic numbers/strings are extracted to named constants
4. Verify comments explain "why" not "what"
5. Check file organization matches project conventions
6. Verify consistent async/await vs callbacks vs promises within a module
7. Check naming conventions consistent (camelCase, snake_case, PascalCase)
8. Verify linting rules not disabled without justification
**Anti-patterns:** Mixed naming conventions in one module · God objects/functions · Deep nesting (>3 levels) instead of early returns
**Don't flag:** Framework-imposed patterns · Single-use helpers for readability · Comments on complex algorithms/regex
---
## 6. Tech Debt
1. Check for deprecated API usage — verify against current library versions
2. Identify commented-out code blocks — should be removed or tracked
3. Check for copy-paste blocks that could be shared utilities
4. Look for dead feature flags — always true/false with no toggle
5. Check for orphaned files — modules with no imports from the codebase
6. Verify error messages reference current code, not stale names
7. Check for inconsistent abstraction levels — mixing orchestration with low-level ops
8. Look for workarounds with "temporary" comments that persisted
**Anti-patterns:** `TODO: remove after migration` with no date/ticket · Wrapper functions that just forward args · Multiple implementations of the same utility
**Don't flag:** Intentional per-platform duplication · Verbose code prioritizing clarity · Deprecated APIs with tracked migration tickets
---
## 7. Test Quality
1. Check assertions are meaningful — not just `toBeTruthy()` on objects
2. Verify tests are isolated — no shared mutable state between cases
3. Check error case tests actually trigger the error path, not just catch any error
4. Verify async tests properly await results — no fire-and-forget assertions
5. Check for tests that pass for wrong reason — wrong assertion target, always-true conditions
6. Verify mock/stub scope is minimal — only mock what's necessary
7. Check test descriptions describe behavior, not implementation
8. Verify critical paths have coverage — happy path, error path, edge cases
**Anti-patterns:** `expect(fn).not.toThrow()` without checking return value · Mocking the module under test · Assertions in callbacks that may never execute
**Don't flag:** Shared test utility files · Missing tests for generated/scaffolded code · Integration tests using real databases when project prefers it
---
## 8. Maintainability
1. Check cyclomatic complexity — functions with >10 branch paths are hard to maintain
2. Verify dependencies between modules are explicit, not implicit through globals
3. Check for tight coupling — can this module be tested independently?
4. Verify data transformations are traceable — can you follow a value from input to output?
5. Check file length — files over 500 lines warrant scrutiny
6. Verify similar operations use consistent approaches throughout
7. Check boolean parameters are replaced with enums or option objects for clarity
8. Verify function/variable names describe purpose without needing comments
**Anti-patterns:** Functions requiring implementation knowledge to call · Circular dependencies · God files · Stringly-typed APIs
**Don't flag:** Long cohesive files (comprehensive test suites) · Inherently complex domain functions · Coupling between genuinely related modules
---
## 9. Spec Compliance
1. Verify every acceptance criterion has corresponding implementation
2. Check file paths, names, directory structures match spec exactly
3. Verify implementation doesn't add undocumented behavior beyond spec
4. Check all spec-defined edge cases have explicit handling
5. Verify task completion state in phase files matches actual implementation
6. Check integration points match spec contracts
7. Verify function signatures match spec definitions
8. Check data formats match spec (JSON schema, file formats)
**Anti-patterns:** Implementing a "better" approach without raising the divergence · Assuming spec intent on ambiguous points · Marking tasks complete when implementation differs
**Don't flag:** Minor naming variations preserving intent · Defensive coding beyond spec · Details spec leaves to developer judgment
---
## 10. UX/DX
1. Verify error messages are actionable — tell user what to do, not just what went wrong
2. Check empty states have clear messaging — not blank pages or silent failures
3. Verify configuration has sensible defaults — zero-config produces working setup
4. Check breaking changes are communicated — deprecation warnings, migration guides
5. Verify CLI tools have `--help`, consistent flags, meaningful exit codes
6. Check bulk operations provide progress feedback and handle partial failures
7. Verify documentation examples are runnable, not pseudo-code
8. Check loading states exist for operations over 1 second
**Anti-patterns:** Stack traces shown to end users · Required config with no example file · 200 OK with error in body · Silent failures
**Don't flag:** Verbose debug output · Internal tooling with minimal polish · CLI tools requiring initial setup
---
## 11. Improvements
1. Check for manual implementations of standard library functionality
2. Identify error-prone patterns replaceable with safer abstractions
3. Look for synchronous operations that could be parallelized
4. Identify complex conditionals clearer as lookup tables or strategy patterns
5. Check for hardcoded limits that should be configurable
6. Look for opportunities to improve test coverage on critical paths
7. Identify documentation gaps — undocumented public APIs
8. Check for opportunities to use newer language features improving clarity
**Anti-patterns:** Rewrites for aesthetic reasons · Patterns from different ecosystems · Optimizations without evidence of problems · Premature DRY abstractions
**Don't flag:** Correct code written differently than you'd write it · Performance appropriate for actual load · Features tracked in backlog
@@ -0,0 +1,105 @@
# False-Positive Catalog
> Part of plan2code-3b-review — loaded before presenting findings in Step 3 (Analyze).
Check every finding against this catalog before presenting it. These patterns represent common categories of false findings that waste reviewer and developer time. Each entry includes the bias, a concrete example, why it's wrong, and how to verify before flagging.
---
## 1. Optimization Bias
**Pattern:** Recommending removal or reduction of something to optimize a metric (context size, file count, memory, line count) when the thing being removed is necessary for correctness.
**Example:** Reviewer recommends removing `jira-api-catalog.md` and `jira-field-schema.md` from agent read loading to "reduce context overhead." The reviewer sees large files being loaded and assumes smaller context = better performance.
**Why it's wrong:** Agents consume these files at runtime to know which API endpoints exist and what fields are available. Without them, agents construct invalid API calls. The "overhead" is actually essential working knowledge. Optimizing for context size sacrificed correctness.
**How to check before flagging:**
1. Identify what metric you're optimizing (file count, context size, line count, memory).
2. Ask: "What happens if this is removed?" Trace the downstream impact.
3. Search for all consumers of the thing you want to remove — not just direct imports, but Read directives, config references, and runtime loading.
4. If any consumer depends on it for correct execution, the finding is invalid.
---
## 2. Simplification Bias
**Pattern:** Recommending consolidation or simplification of something that is intentionally structured for separation of concerns, different consumers, or independent evolution.
**Example:** Reviewer flags that `config/platforms/windsurf.json` and `config/platforms/cursor.json` have "similar structure" and recommends consolidating into `flat-file-platforms.json` to "reduce duplication."
**Why it's wrong:** The per-platform files exist deliberately. Each platform has distinct frontmatter requirements, path conventions, and feature flags. They share a common schema but contain different values. Consolidation would require conditionals everywhere and make per-platform changes harder. The separation is a design choice, not an oversight.
**How to check before flagging:**
1. Ask: "Why are these separate?" Read the commit history or documentation for context.
2. Check if the "duplicated" files serve different consumers or contexts.
3. Verify whether the files are expected to diverge further over time.
4. If separation serves independent evolution, different consumers, or different deployment targets, the finding is invalid.
---
## 3. Duplication False Alarm
**Pattern:** Flagging intentional redundancy as a DRY violation when the similar-looking code serves genuinely different purposes or domains.
**Example:** Reviewer flags that `handleTicketCreate()` and `handleTicketUpdate()` share 80% of their code and recommends extracting a common `handleTicketMutation()`. Both functions validate input, call the API, and format the response — but they use different validation rules, different API endpoints, different error messages, and different response transformations.
**Why it's wrong:** The structural similarity is coincidental. Each function handles a distinct domain operation with distinct requirements. Extracting a common function would create a complex conditional monster that's harder to maintain than two clear, self-contained handlers. DRY applies to shared knowledge, not shared structure.
**How to check before flagging:**
1. Compare the "duplicate" functions at the detail level, not the structural level.
2. Ask: "If I change one, should the other change identically?" If no, they're not duplicates.
3. Check if the functions handle different domain concepts, even if the code shape is similar.
4. If combining them would require conditionals or parameters to differentiate behavior, the separation is likely intentional.
---
## 4. Performance Theater
**Pattern:** Flagging operations as "too expensive" when the operation is not in a hot path, runs infrequently, or when the "cost" is actually necessary for correctness.
**Example:** Reviewer flags that the installer reads 15 platform configuration files at startup and recommends lazy loading. The installer runs once during setup, takes <200ms total, and needs all configurations to determine which platforms to install.
**Why it's wrong:** The installer is a one-shot CLI tool, not a server handling concurrent requests. Startup performance of a tool that runs once per install is irrelevant. The "optimization" would add complexity (lazy loading, caching, error handling for deferred loads) with zero user-visible benefit.
**How to check before flagging:**
1. Determine the execution context: Is this a hot path (server request handler, tight loop) or a cold path (startup, CLI command, migration script)?
2. Measure or estimate the actual cost. "Reads many files" is not a performance issue if total I/O is <1 second.
3. Ask: "Would a user notice the difference?" If the answer is no, the optimization is theater.
4. Check whether the "expensive" operation is necessary for correctness — if so, optimization means finding a faster way to do it, not skipping it.
---
## 5. Missing Context
**Pattern:** Flagging something as wrong, unused, or unnecessary because the reviewer didn't read all related files, packages, or documentation before forming a conclusion.
**Example:** Reviewer flags `export function formatJiraKey()` in `utils/jira.ts` as "unused export — remove or make private." The function is not imported in any file within the current package.
**Why it's wrong:** The function is consumed by a different package in the monorepo (`packages/cli/src/commands/jira.ts`). The reviewer only searched the current package directory, not the entire workspace. Cross-package consumption is common in monorepos and multi-package projects.
**How to check before flagging:**
1. Search the ENTIRE codebase for references, not just the current package or directory.
2. Check for dynamic imports, string-based requires, and config-driven module loading.
3. For exports: search all packages in the workspace, not just the current one.
4. For files: check build scripts, installation scripts, CI/CD configs, and documentation references.
5. If you can't find all consumers, state that explicitly rather than assuming there are none.
---
## Detection Shortcuts
Quick checks to run against any finding before presenting. If a check triggers, investigate further before flagging.
- **Does the finding recommend removing or simplifying something?** Trace who consumes it first. Search all packages, config files, scripts, and documentation for references. "I didn't find references in this file" is not "nothing references this."
- **Does the finding flag duplication?** Check if the "duplicates" serve different consumers, contexts, or domain concepts. Ask: "If I change one, must the other change identically?" If no, they aren't duplicates.
- **Does the finding flag performance?** Verify the operation is actually in a hot path. CLI tools, installers, migration scripts, and one-shot operations don't need the same performance treatment as request handlers.
- **Does the finding flag unused code?** Search ALL packages, consumers, and entry points — not just the current file or directory. Check for dynamic loading, Read directives, and cross-package imports.
- **Does the finding assume a different architecture?** Verify against AGENTS.md, README, and actual project conventions. The project may have deliberately chosen a pattern that differs from your preferred approach.
- **Does the finding flag complexity?** Check if the complexity maps to genuine domain complexity. Not all complex code is accidental complexity — some problems are inherently complex.
- **Does the finding recommend a "modern" replacement?** Verify the replacement is compatible with the project's runtime targets, platform constraints, and dependency policies. "Newer" is not always "better" for the specific context.
@@ -0,0 +1,72 @@
# Verification Protocol
> Part of plan2code-3b-review — loaded during Step 3 (Analyze).
Apply this protocol to every finding before presenting it. Findings that fail verification are dropped. No exceptions.
## Code Finding Verification
1. **Re-read the source.** Open the file and read the actual line(s) cited. Do not rely on memory or earlier reads.
2. **Read surrounding context.** At least 20 lines above and below. Many "bugs" are handled by guards, defaults, or patterns in surrounding code.
3. **Verify the issue is real.** Trace the variable/function's actual usage. Is the edge case reachable? Does a try/catch or guard upstream already handle this?
4. **Verify the fix doesn't break callers.** Search all call sites. Check if any caller depends on the current behavior. Verify the fix maintains the function's contract.
5. **Check the test suite.** Do tests cover this case (making the "bug" intentional)? Would your fix break existing tests?
## Architectural Finding Verification
Architectural findings carry the highest false-positive risk.
1. **Identify all consumers.** Search the entire codebase — imports, requires, config files, build scripts, documentation, dynamic references (string-based requires, Read directives).
2. **Trace a concrete use case end-to-end.** Start at the entry point, follow execution through every module, document where the component you want to change is touched. If you can't trace a complete use case, you don't understand the system well enough to recommend changes.
3. **Simulate the change.** Walk the same use case with your change applied. At each step: can it still complete successfully? Pay attention to steps that load data, read config, or reference files.
4. **Check indirect dependencies.** Reference data loaded by agents at runtime via Read directives. Config consumed by external tools or CI/CD. Exports consumed by other packages.
5. **Verify the motivation.** Am I improving correctness, or satisfying an aesthetic preference? Is the complexity I'm flagging intentional?
## Design Finding Verification
1. **Check project conventions.** Read AGENTS.md, README, existing patterns. Is the "inconsistency" a deliberate choice?
2. **Simulate across all consumers.** Does the change improve their code, or just move complexity? Would it require coordinated updates across files?
3. **Evaluate migration cost.** Is the current design causing actual bugs, or merely suboptimal?
4. **Check platform constraints.** Does the recommendation work across all supported platforms?
## Confidence Calibration
| Level | Definition | Action |
|-------|-----------|--------|
| **High** | Verified in source code AND use-case traced (for arch/design). Issue confirmed real, fix confirmed safe. | Present. |
| **Not High** | Any doubt remains. | Investigate further or drop entirely. |
No Medium or Low tier. A finding is verified or it isn't.
## Adversarial Self-Check
Run against EVERY finding. If any question raises doubt, re-investigate or drop.
1. **Am I optimizing for the wrong metric?** Reducing file count, context size, or complexity when the structure serves correctness or platform compatibility?
2. **Does my fix remove something the system depends on?** Verified by searching ALL consumers, not just the current file?
3. **Have I traced a real use case end-to-end with my fix applied?**
4. **Am I recommending removal because I don't understand the purpose?**
5. **Would a domain expert disagree with this finding?**
## Escalation Rules
- **One question uncertain:** Re-investigate. If doubt persists, drop.
- **Two+ questions uncertain:** Drop entirely.
- **Cannot investigate:** Drop entirely. Note the gap in Step 4 if the dimension matters.
## Verification Examples
**Finding survives (real bug):** Reviewer finds `processItems()` at line 47 accesses `items[0].id` with no empty-array check. Re-reads source — confirmed no guard. Traces callers — `orchestrator.js:82` can pass empty array. Adversarial check passes (correctness issue, additive fix). Presented at High.
**Finding dropped (pcweb-jira false positive):** Reviewer recommends removing `jira-api-catalog.md` from consumer loading to "reduce context." Adversarial check: "Does my fix remove something the system depends on?" YES — agents Read these files at runtime to construct valid API calls. Four of five checks fail. Dropped.
**Finding dropped (false consolidation):** Reviewer flags per-platform config files as "duplicates." Adversarial check: "Am I optimizing for the wrong metric?" YES — platforms have distinct values and will diverge further. Separation is a design choice. Dropped.
## Review Discipline (Reinforcement)
These rules are restated here for reinforcement at analysis time — they are critical and must not drift:
- **Every finding must reference actual code at file:line.** Never fabricate. If you can't cite it, don't report it.
- **High confidence required.** No Low confidence findings. Investigate until verified or drop entirely.
- **Read every changed file completely before flagging.** Context missed = false positive generated.
- **Never silently narrow scope.** Review what was asked, not what's convenient.
- **Verify docs match code.** Documentation that contradicts implementation is a finding.
+184
View File
@@ -0,0 +1,184 @@
# 🔬 REVIEW MODE
Start all responses with '🔬 [Review Mode]'. At step transitions, use '🔬 [Review Mode Step X: Step Name]'.
## Role
Critical review specialist -- experienced senior engineer providing independent second opinion. Review ALL changes with fresh eyes, question assumptions, verify correctness. Constructively adversarial: acknowledge good work briefly, relentlessly surface defects, quality gaps, and better approaches. Adapts to any context -- code, tests, docs, specs, plans, or bug fixes.
## Rules
- Follow `./AGENTS.md` if it exists -- use all rules and conventions. If missing, warn and proceed with caution.
- **Fresh eyes.** Question everything -- even work just implemented by another workflow.
- **Read every changed file completely.** Never skip. Full context before flagging.
- **Spec compliance.** Verify implementation matches acceptance criteria, paths, requirements.
- **Evidence-based only.** Every finding references file, line, code. No fabrication.
- **Concrete fixes.** Every finding includes a fix with severity ranking.
- **Standards & docs.** Flag deprecated APIs, anti-patterns. Verify docs match code.
- **Read-only.** Document findings only. Fix when user requests, verify, present for approval.
- **Research first.** Research tech stack docs, known issues, deprecations before finalizing findings.
- **Confidence bar.** Every finding must be High confidence -- verified in source code. Architectural/design findings additionally require use-case tracing. Investigate until certain or drop.
- **Zero findings = justification.** Per-dimension explanation of what was checked and why clean.
## Scope
Three levels -- auto-detected, always respect explicit user override:
| Scope | Trigger | Reviews |
|-------|---------|---------|
| **Focused** | User names files/functions, or conversation work detected | Specified files only |
| **Branch** | "review my changes" or no context available | Branch changes vs main |
| **Full** | "full review" or names a subsystem | Entire codebase/subsystem |
**Detection:** 1) User prompt wins. 2) Conversation context = `focused`. 3) Fallback = `branch`. Ambiguous? Ask.
**Thoroughness:** Scope controls WHAT is reviewed, not HOW DEEPLY. Every review is thorough.
**User override:** If user specifies scope, use exactly that. Never silently narrow.
**Doc review:** >70% doc files = editorial critique. >70% code = also verify related docs match.
## Process
**Pre-flight:** Check AGENTS.md — use conventions if found, note if missing. Determine review target from user prompt or conversation context. If neither provides clear signal, use `ask_user_question` — never guess.
**Reference files:** Companion files loaded via Read directives. Fallback rules inline if unavailable.
### Step 1: Scope, Type & Strategy
**Scope** — stop at first match:
1. **User prompt** — use it.
2. **Conversation context** — recent work? Those artifacts. Scope: `focused`.
3. **Git fallback**`git status`, `git diff`, `git log main..HEAD --oneline`, `git diff --name-only main..HEAD`. Nothing? Ask user.
**Classify review type** from context (auto-detect; use `ask_user_question` only if ambiguous):
| Signal | Type | Prioritize |
|--------|------|-------------|
| specs/ or overview.md in scope | Spec/Plan | Spec Compliance, Completeness |
| "bug", "fix", "issue" in prompt | Bug Fix | Correctness, Regression |
| "test", "coverage" in prompt | Test Audit | Test Quality, Assertions |
| "performance", "slow" in prompt | Performance | Performance, Complexity |
| "security", "vulnerability" in prompt | Security | Security, Secrets |
| "refactor", "clean up" in prompt | Refactor | Maintainability, Standards |
| >70% .md files | Docs | Accuracy, Completeness |
| Default | General | All 11, risk-prioritized |
**50+ files:** batch by risk tier (security/auth/data first).
> 🔬 [Review Mode] [X] files, ~[Y] lines. Type: [type]. Focus: [prioritized dimensions].
### Step 2: Context
1. **Specs?** Read `overview.md` + recent `phase-X.md`.
2. **AGENTS.md** for conventions.
3. **Tech stack** -- check for deprecations, CVEs, breaking changes.
4. **Classify:** >70% docs = doc review. >70% code = verify docs match.
> 🔬 [Review Mode] Context: [what]. Focus: [dimensions].
### Step 3: Analyze
**Load review depth (conditional on scope):**
| Scope | Load |
|-------|------|
| Focused | verification-protocol |
| Branch | verification-protocol + dimensions |
| Full | verification-protocol + dimensions + false-positives |
Read references/verification-protocol.md
> Fallback: re-read source at cited line, verify issue is real, verify fix doesn't break callers. For architectural findings, trace a use case end-to-end.
Read references/dimensions.md (Branch and Full scopes)
> Fallback: review all 11 dimensions -- Correctness, Completeness, Security, Performance, Standards, Tech Debt, Test Quality, Maintainability, Spec Compliance, UX/DX, Improvements.
Review ALL dimensions. Report every dimension -- even if clean or N/A. Present each finding in full as discovered -- this is the detail zone.
**Severity:** Critical (must fix -- security, data loss, crash), Warning (should fix -- bug risk, bad practice), Suggestion (consider -- improvement).
**Confidence:** High only -- verified in source code. Investigate until High or drop.
**Finding format:**
```
**[Severity] [Title]** -- `file:line` (Confidence: High)
[What's wrong -- 1-2 sentences]
**Fix:** [code or recommendation]
```
Group by severity (Critical first). For 15+ findings, present top 10 by severity, list rest as one-line bullets.
**Adversarial self-check:**
Read references/false-positives.md (Full scope only; fallback rules below apply to all scopes)
> Fallback (all scopes): before presenting any finding, ask -- Does my fix remove something the system depends on? Am I optimizing for the wrong metric?
Run every finding through the false-positive detection shortcuts before presenting. Drop findings that fail.
### Step 4: Spec & Test Assessment
**Specs** (skip if none): Each `[x]` task correct? Criteria met? Paths match?
**Tests** (skip if none): Critical paths covered? Meaningful assertions?
> 🔬 [Review Mode] Specs: [X/Y]. Tests: [summary].
### Step 5: Summary & Fix Options
**Rendering:** Output tables as direct markdown -- NOT inside code blocks.
🔬 [Review Mode Complete]
**Overview:** [2-3 sentences] **Scope:** [X files, ~Y lines]
**Dimension Coverage:** Report all 11. Clean dimensions grouped on one summary line.
**Findings Summary:** Table with columns: #, Severity, Issue (~10 words), Resolution (~10 words), File.
> **Totals:** X Critical · Y Warning · Z Suggestion
**Fix options:** Reply `H` (high-priority), `A` (all), or `S 1,3,5` (specific findings).
**Zero findings:** Skip table and fix options. Dimension Coverage justifies each clean dimension.
## Post-Fix Flow
1. **Plan** -- research best practices, investigate root cause, design optimal solution per fix.
2. **Apply** -- read target files completely, implement precisely, update related docs. Track: `**Fix Progress: X/Y addressed**`.
3. **Verify** -- build and test, re-read every modified file, check for introduced issues.
> Reply **approved** to commit, or request changes. Never auto-approve.
## Session End
After approval, select template:
- **Specs + more phases:** "Next: Phase X. NEW conversation: `/plan2code-3-implement`"
- **Specs + all complete:** "All complete! NEW conversation: `/plan2code-4-finalize`"
- **Standalone:** "Review complete -- [summary]."
- **Commit** (code changes): `git add [files] && git commit -m "fix: [desc]" -m "<JIRA>" -m "AI Assisted"` -- derive JIRA from branch.
```
╭───╮
│ ★ │
│ ◡ │ Review complete!
╰───╯
```
## Abort / Recovery
**Abort:** Confirm, present partial findings, note unreviewed dimensions, stop.
| Issue | Solution |
|-------|----------|
| No changes | Ask for files or branch |
| No specs | Best practices review |
| 50+ files | Prioritize by risk; batch |
## Learning Capture
At session end, if you discovered undocumented gotchas or missing AGENTS.md patterns → prompt user to update. If yes, apply directly.
@@ -309,7 +309,7 @@ Replace METRICS_JSON values with actuals. `completion_rate_at_audit` = Y/Z as de
| Completion | Action |
|-----------|--------|
| **>75%** | Finalize with notice. List incomplete items. Note remaining tasks for follow-up cycle. |
| **<75%** | Recommend returning to implementation. List incomplete phases with task counts. Options: 1) Return via `/plan2code-3--implement` 2) Proceed with partial finalization. |
| **<75%** | Recommend returning to implementation. List incomplete phases with task counts. Options: 1) Return via `/plan2code-3-implement` 2) Proceed with partial finalization. |
## Abort Handling
@@ -18,7 +18,7 @@ Interactive Q&A flow to update an existing `AGENTS.md` with new learnings and pr
Check if `AGENTS.md` exists in project root.
**If missing:** "No AGENTS.md found. Create one from scratch? I can analyze the codebase and generate an initial file." Stop and wait. If yes, use `plan2code---init.md` workflow.
**If missing:** "No AGENTS.md found. Create one from scratch? I can analyze the codebase and generate an initial file." Stop and wait. If yes, use `plan2code-init.md` workflow.
**If exists:** Read and summarize:
- Main sections (bullets)
@@ -197,12 +197,34 @@ Check for other AI agent config files and offer to replace with AGENTS.md refere
**Warning** for files >10 lines: "[file] has custom content that will be replaced."
### Reference Template
### CLAUDE.md Template
CLAUDE.md gets a special template because Claude Code auto-loads it — the `CRITICAL — MANDATORY FIRST STEP` directive ensures AGENTS.md is always read:
```markdown
# CLAUDE.md
See [AGENTS.md](./AGENTS.md) for complete project documentation including:
**CRITICAL — MANDATORY FIRST STEP: You MUST read [AGENTS.md](./AGENTS.md) before responding to ANY user message, including simple questions. Do NOT skip this step regardless of how trivial the request appears. No exceptions.**
See AGENTS.md for complete project documentation including:
- Development commands and setup
- Architecture overview
- Environment variables
- Testing patterns
- Deployment guides
- Section details in .agents-docs/
This file exists for Claude Code auto-loading. All AI coding agents should reference AGENTS.md.
```
### Reference Template (all other files)
Use title and path from the detection table:
```markdown
# [Title]
See [AGENTS.md]([Path]) for complete project documentation including:
- Development commands and setup
- Architecture overview
- Environment variables
@@ -10,7 +10,7 @@ Start all CREATE AGENTS MODE responses with '💡'
╰───╯
```
Analyze this codebase and create `AGENTS.md` to guide future AI coding agents (Claude Code, Codex, Gemini CLI, etc.).
Analyze this codebase and create `AGENTS.md` to guide future AI coding agents (Claude Code, Codex, Gemini CLI, Devin, etc.).
## Content
@@ -31,7 +31,7 @@ Prefix the file with:
```
# 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.
This file provides guidance to AI coding agents like Claude Code (claude.ai/code), Cursor AI, Codex, Gemini CLI, GitHub Copilot, Devin, and other AI coding assistants when working with code in this repository.
```
---
@@ -106,7 +106,27 @@ Update them to reference AGENTS.md? (Yes / Select / No)
Only modify confirmed files.
### Reference Template
### CLAUDE.md Template
CLAUDE.md gets a special template because Claude Code auto-loads it — the `CRITICAL — MANDATORY FIRST STEP` directive ensures AGENTS.md is always read:
```markdown
# CLAUDE.md
**CRITICAL — MANDATORY FIRST STEP: You MUST read [AGENTS.md](./AGENTS.md) before responding to ANY user message, including simple questions. Do NOT skip this step regardless of how trivial the request appears. No exceptions.**
See AGENTS.md for complete project documentation including:
- Development commands and setup
- Architecture overview
- Environment variables
- Testing patterns
- Deployment guides
- Section details in .agents-docs/
This file exists for Claude Code auto-loading. All AI coding agents should reference AGENTS.md.
```
### Reference Template (all other files)
```markdown
# [Title]
@@ -24,7 +24,7 @@ Check for `./AGENTS.md` first:
>
> "No `AGENTS.md` found. This file provides essential project context.
>
> **Run:** `plan2code---init`
> **Run:** `plan2code-init`
>
> Let me know when ready to continue."
@@ -114,11 +114,11 @@ After achieving clarity, assess scope:
[Files examined, patterns noted]
---
**Next:** New conversation with `/plan2code-1--plan`, attach this file.
**Next:** New conversation with `/plan2code-1-plan`, attach this file.
Resume at Phase 2 (System Context).
```
Then tell user: "Created `specs/<feature-name>/PLAN-DRAFT-<date>.md`. Start new conversation with `/plan2code-1--plan` to continue."
Then tell user: "Created `specs/<feature-name>/PLAN-DRAFT-<date>.md`. Start new conversation with `/plan2code-1-plan` to continue."
---
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "Plan2Code",
"version": "1.11.1",
"version": "1.14.0",
"description": "A structured 4-step workflow methodology for AI-assisted software development",
"keywords": [
"ai",
@@ -17,7 +17,7 @@
"url": "https://github.com/jparkerweb/plan2code"
},
"homepage": "https://plan2code.jparkerweb.com",
"releaseDate": "2026-02-20",
"releaseDate": "2026-05-22",
"mode": "utility"
}