This commit is contained in:
2026-03-01 12:24:11 -08:00
parent 628e688ab9
commit 63656d339d
33 changed files with 1018 additions and 539 deletions
+63
View File
@@ -0,0 +1,63 @@
# Architecture
> Part of [AGENTS.md](../AGENTS.md) — project guidance for AI coding agents.
## Directory Structure
```
plan2code/
├── src/ # Source workflow prompts (8 markdown files)
├── plan2code-loop/ # Autonomous loop CLI tool (Node.js/TypeScript)
│ ├── src/ # TypeScript source
│ └── dist/ # Built output (tsup)
├── plan2code-metrics/ # Recursive self-improvement toolchain
│ ├── src/ # TypeScript source
│ │ └── prompts/ # Internal AI prompt templates (no char limit)
│ └── dist/ # Built output (tsup)
├── scripts/ # Development scripts
│ └── validate-char-count.js # Pre-commit character count validator
├── dist/ # Generated distribution files (auto-generated)
│ ├── global-commands/ # For global installation (~/.claude/, etc.)
│ └── local-commands/ # For per-project installation (.claude/, etc.)
├── .husky/ # Git hooks (husky)
│ └── pre-commit # Runs character count validation
├── docs/ # Documentation and assets
├── specs/ # Feature specs (if any in-progress)
├── install.js # Interactive installer (Node.js)
├── package.json # Root package (husky only, private: true)
├── version.json # Version metadata
└── README.md # User documentation
```
## Key Files
| File | Purpose |
|------|---------|
| `install.js` | Main installer - generates and installs workflow files to AI tool directories |
| `src/plan2code-*.md` | Source workflow prompts (the "source of truth") |
| `scripts/validate-char-count.js` | Pre-commit validator ensuring all source prompts ≤ 11,000 chars |
| `version.json` | Version metadata (name, version, description) |
| `QUICK-REFERENCE.md` | User quick-reference card |
## Workflow Prompts (in `src/`)
| File | Step | Purpose |
|------|------|---------|
| `plan2code---init.md` | Init | Generate AGENTS.md 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) |
## Naming Convention
Workflow files follow a strict naming pattern:
- **Utilities:** `plan2code---<name>.md` (triple dash)
- **Numbered steps:** `plan2code-<N>--<name>.md` (single dash, number, double dash)
Examples:
- `plan2code---init.md` (utility)
- `plan2code-1--plan.md` (step 1)
- `plan2code-1b--revise-plan.md` (step 1b)
+18
View File
@@ -0,0 +1,18 @@
# Code Style & Gotchas
> Part of [AGENTS.md](../AGENTS.md) — project guidance for AI coding agents.
## Code Style
- **install.js:** CommonJS, Node.js built-ins only (no external deps), ANSI colors via `COLORS` constant, readline-based prompts
- **plan2code-loop & plan2code-metrics:** TypeScript + ESM, built with tsup (target ES2022, moduleResolution: bundler)
- External deps: `@inquirer/prompts`, `chalk`, `execa`, `ora`
- Interactive CLI via `@inquirer/prompts` (select, input, confirm)
- **File operations:** Synchronous fs in all packages
## Gotchas / Pitfalls
- **Version sync:** When adding a new version to `CHANGELOG.md`, also update `version.json` and `package.json` to match. The installer displays the version from `version.json` in its header.
- **Loop `.gitignore` setup:** `ensureGitignore()` runs at startup in `Controller.run()` as a pre-flight step, not just inside `createTaskCommit()`. This is critical for phase mode where the Node controller doesn't handle commits — without it, `git add -A` would stage spec files.
- **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 `\|`.
@@ -0,0 +1,73 @@
# Development Commands
> Part of [AGENTS.md](../AGENTS.md) — project guidance for AI coding agents.
## Common Commands
```bash
# Install dev dependencies (sets up husky pre-commit hooks)
npm install
# Run the interactive installer (always interactive — any CLI args are silently ignored)
node install.js
# Plan2Code Loop
cd plan2code-loop && npm install # First time setup
cd plan2code-loop && npm run build # Build the CLI
# Plan2Code Metrics
cd plan2code-metrics && npm install # First time setup
cd plan2code-metrics && npm run build # Build the CLI
```
## Installer Menu Options
**Main menu:**
| Option | Action |
|--------|--------|
| `I` | Install Plan2Code for all platforms + loop CLI |
| `U` | Uninstall Plan2Code files + loop CLI (confirmation required) |
| `C` | Open CUSTOM sub-menu |
| `Q` | Quit |
**CUSTOM sub-menu (`C`):**
| Option | Action |
|--------|--------|
| `L` | Show local (per-project) install instructions |
| `O` | Install plan2code-loop CLI only |
| `M` | Install plan2code-metrics CLI only |
| `Q` | Return to main menu |
## How the Installer Works
1. **Reads source prompts** from `src/plan2code-*.md`
2. **Generates platform-specific files** with appropriate headers (YAML frontmatter for some platforms)
3. **Writes to `dist/`** subdirectories organized by destination type
4. **Copies to target directories** (global: `~/.claude/commands/`, etc.)
## Platform-Specific File Formats
| Platform | Extension / File | Local Dir | Global Dir | Header |
|----------|-----------------|-----------|------------|--------|
| Claude Code | `.md` | — | — | None |
| Cursor | `.md` | — | — | None |
| Copilot CLI | `.md` | — | — | YAML frontmatter |
| Continue | `.prompt.md` | — | — | YAML frontmatter |
| Windsurf | `.md` | — | — | YAML frontmatter |
| VS Code Copilot | `.prompt.md` | — | — | YAML frontmatter |
| Codeium | `.md` | — | — | YAML frontmatter |
| 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) |
| 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`) |
## Editing Workflow Prompts
When modifying workflow prompts in `src/`:
1. Edit the source file in `src/`
2. Run `node install.js` to regenerate distribution files
3. Test the workflow in your AI tool of choice
4. The `dist/` folder is regenerated automatically — don't edit files there directly
+47
View File
@@ -0,0 +1,47 @@
# Plan2Code Loop
> Part of [AGENTS.md](../AGENTS.md) — project guidance for AI coding agents.
A separate Node.js CLI tool that autonomously implements specs by looping through tasks.
## Loop Architecture
The loop uses an **LLM-driven discovery** approach:
- Node app just orchestrates iterations and parses completion markers
- The LLM reads spec files (`overview.md`, `phase-X.md`) to discover tasks
- The LLM finds unchecked checkboxes, implements ONE task per iteration, marks it complete
- No regex parsing of markdown in Node - the AI handles all task discovery
## Loop Commands
```bash
# Build the loop CLI
cd plan2code-loop && npm run build
# Run the loop (after linking) - fully interactive
plan2code-loop
```
The CLI auto-detects specs in `./specs/`, prompts for selection if multiple found, and handles session continuation interactively. Session state is stored per-spec in `specs/<feature>/.plan2code-loop/`.
## Loop Modes
The CLI asks users to choose a loop mode:
- **One task per loop** (default) - Each agent invocation implements exactly one task. The Node controller handles git commits.
- **One phase per loop** - Each agent invocation implements all remaining tasks in the current phase. The LLM handles git commits (with JIRA ticket ID if provided). The controller parses multiple completion markers from a single iteration.
## Completion Markers
The LLM must output one of these formats:
- `TASK_COMPLETE: 1.1 - Task description` - Task done successfully
- `TASK_BLOCKED: 1.1 - Reason` - Cannot complete task
- `PHASE_COMPLETE` - Current phase finished (phase mode only)
- `LOOP_COMPLETE` - All phases finished
## Key Source Files
| File | Purpose |
|------|---------|
| `plan2code-loop/src/controller.ts` | Main loop orchestrator |
| `plan2code-loop/src/prompt/templates.ts` | Prompt templates for both loop modes |
| `plan2code-loop/src/utils/git.ts` | `createTaskCommit()` — handles task-mode commits with footer |
| `plan2code-loop/src/cli.ts` | Interactive CLI entry point |
@@ -0,0 +1,82 @@
# Plan2Code Metrics
> Part of [AGENTS.md](../AGENTS.md) — project guidance for AI coding agents.
A recursive self-improvement toolchain for plan2code contributors. Collects run metrics, aggregates by prompt generation, diagnoses weak steps via AI, and proposes surgical prompt edits.
## Metrics Data Flow
```
Collect → Aggregate → Analyze → Improve → Apply
```
1. **Collector** reads project artifacts (`specs/<feature>/`) → writes `RunMetrics` JSON per run
2. **Aggregator** groups runs by prompt SHA fingerprint (cohorts) → `aggregated.json`
3. **Analyzer** invokes AI with aggregated metrics + prompt contents → diagnosis markdown
4. **Improver** invokes AI with diagnosis → validated `PromptEdit[]` proposals (char limit + verbatim checks)
5. **Applier** shows interactive diffs → patches `src/plan2code-*.md` files
## Metrics Commands
```bash
cd plan2code-metrics && npm run build # Build the CLI
plan2code-metrics # Run (fully interactive, no flags)
```
## Metrics CLI Menu
| Option | Action |
|--------|--------|
| Collect | Read spec artifacts → run JSON |
| Import | Copy run JSON from another project |
| View | Display cohort metrics with health indicators |
| Analyze | AI diagnosis of weak metrics |
| Propose | AI improvement proposals with validation |
| Apply | Interactive diff review + file patching |
## Key Source Files
| File | Purpose |
|------|---------|
| `types.ts` | All interfaces (`RunMetrics`, `UserFeedback`, `CohortMetrics`, etc.) + `METRIC_TARGETS` |
| `collector.ts` | Reads project artifacts → run JSON (parses plan drafts, overview.md, loop logs) |
| `aggregator.ts` | Merges runs by prompt generation (SHA cohort) → `aggregated.json` |
| `analyzer.ts` | AI diagnosis via `prompts/analyze.md` template |
| `improver.ts` | AI proposals via `prompts/improve.md` + validation (char count, old_text match) |
| `applier.ts` | Interactive diff review + file patching |
| `cli.ts` | Menu-driven interactive CLI (100% prompts, no flags) |
| `invoke-llm.ts` | Unified LLM interface (Claude Code or Copilot CLI) |
## User Feedback
The collector parses an optional `## User Feedback` table from `overview.md`:
```markdown
## User Feedback
| Field | Value |
|-------|-------|
| Rating | 8 |
| Reason | Smooth workflow |
| Went Well | Planning was thorough |
| Went Poorly | Some tasks unclear |
```
Feedback is collected during finalize (Step 5) or retroactively via the CLI. Pipe characters in values are escaped as `\|`. The aggregator computes `avg_user_rating` and `feedback_count` per cohort.
## Supported AI Agents
- **Claude Code** (recommended): `claude` CLI with `--inputFile` for prompt delivery
- **GitHub Copilot CLI**: `copilot` CLI with stdin prompt delivery
## Metric Targets
| Metric | Target | Direction |
|--------|--------|-----------|
| `avg_confidence` | ≥ 90 | higher is better |
| `avg_task_completion_rate` | ≥ 0.95 | higher is better |
| `avg_blocker_count` | ≤ 1.5 | lower is better |
| `avg_completion_marker_success_rate` | ≥ 0.95 | higher is better |
| `avg_verification_failures_found` | ≤ 1.0 | lower is better |
| `archival_success_rate` | ≥ 0.99 | higher is better |
| `avg_user_rating` | ≥ 7.0 | higher is better |
Data stored in `.plan2code-metrics/` (runs/, aggregated.json, proposals/).
+28 -253
View File
@@ -10,280 +10,55 @@ Plan2Code is a structured 4-step workflow methodology for AI-assisted software d
**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
```
plan2code/
├── src/ # Source workflow prompts (8 markdown files)
├── plan2code-loop/ # Autonomous loop CLI tool (Node.js/TypeScript)
│ ├── src/ # TypeScript source
│ └── dist/ # Built output (tsup)
├── plan2code-metrics/ # Recursive self-improvement toolchain
│ ├── src/ # TypeScript source
│ │ └── prompts/ # Internal AI prompt templates (no char limit)
│ └── dist/ # Built output (tsup)
├── scripts/ # Development scripts
│ └── validate-char-count.js # Pre-commit character count validator
├── dist/ # Generated distribution files (auto-generated)
│ ├── global-commands/ # For global installation (~/.claude/, etc.)
│ └── local-commands/ # For per-project installation (.claude/, etc.)
├── .husky/ # Git hooks (husky)
│ └── pre-commit # Runs character count validation
├── docs/ # Documentation and assets
├── specs/ # Feature specs (if any in-progress)
├── install.js # Interactive installer (Node.js)
├── package.json # Root package (husky only, private: true)
├── version.json # Version metadata
└── README.md # User documentation
```
High-level directory structure, key files, workflow prompt inventory, and file naming conventions.
## Key Files
Details: [Architecture](./.agents-docs/AGENTS-architecture.md)
| File | Purpose |
|------|---------|
| `install.js` | Main installer - generates and installs workflow files to AI tool directories |
| `src/plan2code-*.md` | Source workflow prompts (the "source of truth") |
| `scripts/validate-char-count.js` | Pre-commit validator ensuring all source prompts ≤ 11,000 chars |
| `version.json` | Version metadata (name, version, description) |
| `QUICK-REFERENCE.md` | User quick-reference card |
## Plan2Code Loop
## Workflow Prompts (in `src/`)
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.
| File | Step | Purpose |
|------|------|---------|
| `plan2code---init.md` | Init | Generate AGENTS.md for projects |
| `plan2code---init-update.md` | Update | Update AGENTS.md with learnings |
| `plan2code---quick-task.md` | 0 | Lightweight planning for small tasks |
| `plan2code-1--plan.md` | 1 | Requirements analysis & architecture |
| `plan2code-1b--revise-plan.md` | 1b | Mid-implementation revisions |
| `plan2code-2--document.md` | 2 | Create implementation specs |
| `plan2code-3--implement.md` | 3 | Execute implementation (phase by phase) |
| `plan2code-4--finalize.md` | 4 | Validate, summarize, feedback, archive (7 steps) |
Details: [Plan2Code Loop](./.agents-docs/AGENTS-plan2code-loop.md)
## Plan2Code Loop (`plan2code-loop/`)
## Plan2Code Metrics
A separate Node.js CLI tool that autonomously implements specs by looping through tasks.
Recursive self-improvement toolchain (`plan2code-metrics/`) for collecting run metrics, aggregating by prompt generation, diagnosing weak steps, and proposing prompt edits.
### Loop Architecture
The loop uses an **LLM-driven discovery** approach:
- Node app just orchestrates iterations and parses completion markers
- The LLM reads spec files (`overview.md`, `phase-X.md`) to discover tasks
- The LLM finds unchecked checkboxes, implements ONE task per iteration, marks it complete
- No regex parsing of markdown in Node - the AI handles all task discovery
### Loop Commands
```bash
# Build the loop CLI
cd plan2code-loop && npm run build
# Run the loop (after linking) - fully interactive
plan2code-loop
```
The CLI auto-detects specs in `./specs/`, prompts for selection if multiple found, and handles session continuation interactively. Session state is stored per-spec in `specs/<feature>/.plan2code-loop/`.
### Loop Modes
The CLI asks users to choose a loop mode:
- **One task per loop** (default) - Each agent invocation implements exactly one task. The Node controller handles git commits.
- **One phase per loop** - Each agent invocation implements all remaining tasks in the current phase. The LLM handles git commits (with JIRA ticket ID if provided). The controller parses multiple completion markers from a single iteration.
### Completion Markers
The LLM must output one of these formats:
- `TASK_COMPLETE: 1.1 - Task description` - Task done successfully
- `TASK_BLOCKED: 1.1 - Reason` - Cannot complete task
- `PHASE_COMPLETE` - Current phase finished (phase mode only)
- `LOOP_COMPLETE` - All phases finished
## Plan2Code Metrics (`plan2code-metrics/`)
A recursive self-improvement toolchain for plan2code contributors. Collects run metrics, aggregates by prompt generation, diagnoses weak steps via AI, and proposes surgical prompt edits.
### Metrics Data Flow
```
Collect → Aggregate → Analyze → Improve → Apply
```
1. **Collector** reads project artifacts (`specs/<feature>/`) → writes `RunMetrics` JSON per run
2. **Aggregator** groups runs by prompt SHA fingerprint (cohorts) → `aggregated.json`
3. **Analyzer** invokes AI with aggregated metrics + prompt contents → diagnosis markdown
4. **Improver** invokes AI with diagnosis → validated `PromptEdit[]` proposals (char limit + verbatim checks)
5. **Applier** shows interactive diffs → patches `src/plan2code-*.md` files
### Metrics Commands
```bash
cd plan2code-metrics && npm run build # Build the CLI
plan2code-metrics # Run (fully interactive, no flags)
```
### Metrics CLI Menu
| Option | Action |
|--------|--------|
| Collect | Read spec artifacts → run JSON |
| Import | Copy run JSON from another project |
| View | Display cohort metrics with health indicators |
| Analyze | AI diagnosis of weak metrics |
| Propose | AI improvement proposals with validation |
| Apply | Interactive diff review + file patching |
### Key Files
| File | Purpose |
|------|---------|
| `types.ts` | All interfaces (`RunMetrics`, `UserFeedback`, `CohortMetrics`, etc.) + `METRIC_TARGETS` |
| `collector.ts` | Reads project artifacts → run JSON (parses plan drafts, overview.md, loop logs) |
| `aggregator.ts` | Merges runs by prompt generation (SHA cohort) → `aggregated.json` |
| `analyzer.ts` | AI diagnosis via `prompts/analyze.md` template |
| `improver.ts` | AI proposals via `prompts/improve.md` + validation (char count, old_text match) |
| `applier.ts` | Interactive diff review + file patching |
| `cli.ts` | Menu-driven interactive CLI (100% prompts, no flags) |
| `invoke-llm.ts` | Unified LLM interface (Claude Code or Copilot CLI) |
### User Feedback
The collector parses an optional `## User Feedback` table from `overview.md`:
```markdown
## User Feedback
| Field | Value |
|-------|-------|
| Rating | 8 |
| Reason | Smooth workflow |
| Went Well | Planning was thorough |
| Went Poorly | Some tasks unclear |
```
Feedback is collected during finalize (Step 5) or retroactively via the CLI. Pipe characters in values are escaped as `\|`. The aggregator computes `avg_user_rating` and `feedback_count` per cohort.
### Supported AI Agents
- **Claude Code** (recommended): `claude` CLI with `--inputFile` for prompt delivery
- **GitHub Copilot CLI**: `copilot` CLI with stdin prompt delivery
### Metric Targets
| Metric | Target | Direction |
|--------|--------|-----------|
| `avg_confidence` | ≥ 90 | higher is better |
| `avg_task_completion_rate` | ≥ 0.95 | higher is better |
| `avg_blocker_count` | ≤ 1.5 | lower is better |
| `avg_completion_marker_success_rate` | ≥ 0.95 | higher is better |
| `avg_verification_failures_found` | ≤ 1.0 | lower is better |
| `archival_success_rate` | ≥ 0.99 | higher is better |
| `avg_user_rating` | ≥ 7.0 | higher is better |
Data stored in `.plan2code-metrics/` (runs/, aggregated.json, proposals/).
Details: [Plan2Code Metrics](./.agents-docs/AGENTS-plan2code-metrics.md)
## Development Commands
```bash
# Install dev dependencies (sets up husky pre-commit hooks)
npm install
Build commands, installer menu options, how the installer generates platform-specific files, platform format table, and how to edit workflow prompts.
# Run the interactive installer (always interactive — any CLI args are silently ignored)
node install.js
Details: [Development Commands](./.agents-docs/AGENTS-development-commands.md)
# Plan2Code Loop
cd plan2code-loop && npm install # First time setup
cd plan2code-loop && npm run build # Build the CLI
## Code Style & Gotchas
# Plan2Code Metrics
cd plan2code-metrics && npm install # First time setup
cd plan2code-metrics && npm run build # Build the CLI
```
Language/toolchain conventions for `install.js` vs TypeScript packages, and pitfalls to avoid (version sync, `.gitignore` pre-flight, character limits, User Feedback table format).
### Installer Menu Options
**Main menu:**
| Option | Action |
|--------|--------|
| `I` | Install Plan2Code for all platforms + loop CLI |
| `U` | Uninstall Plan2Code files + loop CLI (confirmation required) |
| `C` | Open CUSTOM sub-menu |
| `Q` | Quit |
**CUSTOM sub-menu (`C`):**
| Option | Action |
|--------|--------|
| `L` | Show local (per-project) install instructions |
| `O` | Install plan2code-loop CLI only |
| `M` | Install plan2code-metrics CLI only |
| `Q` | Return to main menu |
## How the Installer Works
1. **Reads source prompts** from `src/plan2code-*.md`
2. **Generates platform-specific files** with appropriate headers (YAML frontmatter for some platforms)
3. **Writes to `dist/`** subdirectories organized by destination type
4. **Copies to target directories** (global: `~/.claude/commands/`, etc.)
### Platform-Specific File Formats
| Platform | Extension / File | Local Dir | Global Dir | Header |
|----------|-----------------|-----------|------------|--------|
| Claude Code | `.md` | — | — | None |
| Cursor | `.md` | — | — | None |
| Copilot CLI | `.md` | — | — | YAML frontmatter |
| Continue | `.prompt.md` | — | — | YAML frontmatter |
| Windsurf | `.md` | — | — | YAML frontmatter |
| VS Code Copilot | `.prompt.md` | — | — | YAML frontmatter |
| Codeium | `.md` | — | — | YAML frontmatter |
| 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) |
| 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`) |
## Naming Convention
Workflow files follow a strict naming pattern:
- **Utilities:** `plan2code---<name>.md` (triple dash)
- **Numbered steps:** `plan2code-<N>--<name>.md` (single dash, number, double dash)
Examples:
- `plan2code---init.md` (utility)
- `plan2code-1--plan.md` (step 1)
- `plan2code-1b--revise-plan.md` (step 1b)
## Editing Workflow Prompts
When modifying workflow prompts in `src/`:
1. Edit the source file in `src/`
2. Run `node install.js` to regenerate distribution files
3. Test the workflow in your AI tool of choice
4. The `dist/` folder is regenerated automatically - don't edit files there directly
## Code Style
- **install.js:** CommonJS, Node.js built-ins only (no external deps), ANSI colors via `COLORS` constant, readline-based prompts
- **plan2code-loop & plan2code-metrics:** TypeScript + ESM, built with tsup (target ES2022, moduleResolution: bundler)
- External deps: `@inquirer/prompts`, `chalk`, `execa`, `ora`
- Interactive CLI via `@inquirer/prompts` (select, input, confirm)
- **File operations:** Synchronous fs in all packages
## Gotchas/Pitfalls
- **Version sync:** When adding a new version to `CHANGELOG.md`, also update `version.json` and `package.json` to match. The installer displays the version from `version.json` in its header.
- **Loop `.gitignore` setup:** `ensureGitignore()` runs at startup in `Controller.run()` as a pre-flight step, not just inside `createTaskCommit()`. This is critical for phase mode where the Node controller doesn't handle commits — without it, `git add -A` would stage spec files.
- **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 `\|`.
Details: [Code Style & Gotchas](./.agents-docs/AGENTS-code-style.md)
## Git Commit Messages
- **AI Assisted footer:** All git commit messages must include `AI Assisted` as the final line, separated from the message body by a blank line
- **Commit message format:**
```
<description of what this commit is about>
<JIRA-Ticket-ID>
AI Assisted
```
- **JIRA ticket ID:** Derive the ticket ID from the current branch name. The branch format is `<prefix>/<TICKET-ID>-description`, where the ticket ID is an uppercase project key followed by a hyphen and integer (e.g., `PCWEB-10968`, `ABC-123`).
- **AI Assisted footer:** Always the last line of every commit message.
- **Commit paths:** This is enforced across all commit surfaces:
- Loop task mode: `createTaskCommit()` in `plan2code-loop/src/utils/git.ts` appends the footer automatically
- Loop phase mode: Prompt template instructs the LLM to add `-m "AI Assisted"` as the final flag
- Implement mode: User-facing commit suggestions in `src/plan2code-3--implement.md` include the footer
- Loop phase mode: Prompt template instructs the LLM to follow the format above
- Implement mode: User-facing commit suggestions in `src/plan2code-3--implement.md` follow the format above
- **Init workflow:** `src/plan2code---init.md` generates AGENTS.md files with a Git Commit Messages section that includes this convention by default
## Mascot
+67
View File
@@ -2,6 +2,73 @@
All notable changes to Plan2Code will be documented in this file.
## v1.10.0
### ✨ Added
- **Retry logic with escalating timeouts in loop controller** — Timed-out iterations now retry automatically instead of silently continuing
- Default base timeout reduced from 30 minutes to 3 minutes per attempt
- Up to 5 retry attempts per iteration (configurable via `maxRetries`)
- Each retry escalates timeout by +30 seconds (attempt 0 = base, attempt 1 = base + 30s, etc.)
- New `executeWithRetry()` method wraps `executeIteration()` with retry loop
- Fatal timeout (all attempts exhausted) stops the loop cleanly with a logged error
- Spinner displays elapsed seconds during each attempt; retries show attempt count
- `maxRetries` field added to `SessionConfig` and `DEFAULT_CONFIG`
### 🔧 Changed
- **Append-only scratchpad enforcement in loop prompt templates** — Both task-mode and phase-mode templates now explicitly prohibit editing or reorganizing existing scratchpad content
- Instruction changed from "append to scratchpad.md" to "add a new entry at the **bottom**"
- Added rule: "Never edit, reorganize, or insert into existing content — only append new entries to the end of the file"
- **AGENTS.md restructured into progressive discovery format** — AGENTS.md converted to a lightweight index with summaries and markdown links; full detail moved to `.agents-docs/` section files
- `.agents-docs/AGENTS-architecture.md` — Architecture overview and key design decisions
- `.agents-docs/AGENTS-code-style.md` — Code style conventions
- `.agents-docs/AGENTS-development-commands.md` — Development commands and setup
- `.agents-docs/AGENTS-plan2code-loop.md` — Loop CLI architecture and commands
- `.agents-docs/AGENTS-plan2code-metrics.md` — Metrics toolchain details
- `init-update` prompt updated with `.agents-docs/` detection in pre-flight check
- **Agents mode emoji updated** — Loop controller agents-mode indicator changed from wheel to hammer
## v1.9.1
### ✨ Added
- **Pi (pi.dev) platform support** — New target for the Pi terminal-based coding agent
- Local prompt templates installed to `.pi/prompts/` (flat `.md` with YAML `description` frontmatter)
- Global prompt templates installed to `~/.pi/agent/prompts/`
- Pi users already get skill support via existing `.agents/skills/` target; this adds native slash command access
- No new helper functions needed — Pi uses the same flat-file-with-YAML pattern as Windsurf, Copilot CLI, and Codeium
### 📚 Documentation
- `AGENTS.md` Platform-Specific File Formats table updated with Pi row
- `README.md` Supported Platforms list updated with Pi (pi.dev)
## v1.9.0
### ✨ Added
- **Progressive discovery for AGENTS.md** — Init and init-update prompts now generate AGENTS.md as a lightweight index with summaries and markdown links, plus `.agents-docs/` section files containing full detail
- **Init prompt** — New `## Progressive Discovery` section defines index format, always-inline sections (Project Overview, Git Commit Messages, How to Use This File), `.agents-docs/` directory setup, grouping heuristics, and opt-in restructure offer for existing single-file AGENTS.md
- **Init-update prompt** — `.agents-docs/` detection in pre-flight check, legacy migration offer, edit routing (inline sections → AGENTS.md, detailed sections → `.agents-docs/` files), section file lifecycle (create, delete, orphan cleanup), enhanced summary with file count, new update rule 9 (route edits to correct file)
- **Reference templates** updated in both prompts to include `.agents-docs/` in the bullet list
- **"How to Use This File"** added as a required content section in generated AGENTS.md files
## v1.8.2
### 🐛 Fixed
- **Inflated metrics task counts** — Metrics collector regex now matches only `**Task X.N:**` checkbox items instead of all checkboxes, fixing ~100-200% count inflation from prerequisite and acceptance criteria checkboxes
- `collectStep2` (`tasks_per_phase`) uses Task-pattern-only regex
- `collectStep3` (`phaseTotal`, `phaseCompleted`, `blockerCount`) all use Task-pattern-only regex
- Step 4 overview fallback left unchanged (correctly counts Phase Checklist checkboxes)
### 🔧 Changed
- **Document workflow** — Checkbox format `- [ ]` now restricted to Task items only; Prerequisites, Acceptance Criteria, and Success Criteria use plain bullet lists (no checkboxes)
- **Implement workflow** — Prerequisite verification changed from checkbox-based (`[x]`/`[?]`/`[!]`) to inline annotation approach (`VERIFIED`/`ASSUMED: [reason]`/`BLOCKED: [reason]`); added clarifying note that checkbox states apply to Task items and Phase Checklist only
- **Finalize workflow** — Task completion audit now specifies counting only `**Task X.N:**` checkbox items
- **Loop prompt templates** — Both task-mode and phase-mode templates updated: prerequisites use plain bullets with inline annotations instead of checkboxes; Checkbox States section scoped to "Task items only"
## v1.8.1
### ✨ Added
+1
View File
@@ -63,6 +63,7 @@ The install script requires **Node.js** (v14 or later). If you don't have Node.j
- VS Code GitHub Copilot
- Gemini CLI
- Crush
- Pi (pi.dev)
- Amp
- OpenCode
+28
View File
@@ -293,6 +293,16 @@ const LOCAL_DESTINATIONS = [
'---'
].join('\n')
},
{
name: 'Pi Prompts',
dir: '.pi/prompts',
filePattern: (prompt) => generateFilename(prompt, '.md'),
header: (prompt) => [
'---',
`description: "Plan2Code ${prompt.stepNumber === 'init' ? 'Init' : `Step ${prompt.stepNumber}`}: ${prompt.displayName} - ${prompt.description}"`,
'---'
].join('\n')
},
{
name: 'Claude Code Skills',
dir: '.claude/skills',
@@ -379,6 +389,16 @@ const GLOBAL_DESTINATIONS = [
'---'
].join('\n')
},
{
name: 'Pi (Global)',
dir: '.pi/agent/prompts',
filePattern: (prompt) => generateFilename(prompt, '.md'),
header: (prompt) => [
'---',
`description: "Plan2Code ${prompt.stepNumber === 'init' ? 'Init' : `Step ${prompt.stepNumber}`}: ${prompt.displayName} - ${prompt.description}"`,
'---'
].join('\n')
},
{
name: 'Claude Code Skills',
dir: '.claude/skills',
@@ -565,6 +585,13 @@ const INSTALL_TARGETS = [
type: 'toml',
filePattern: /^plan2code-.*\.toml$/,
icon: '◉ '
},
{
name: 'Pi',
id: 'pi',
dir: '.pi/agent/prompts',
filePattern: /^plan2code-.*\.md$/,
icon: '◉ '
}
];
@@ -1299,6 +1326,7 @@ function displayLocalInstructions() {
{ name: 'Continue', dir: '.continue/prompts/', desc: 'Prompts for Continue extension' },
{ name: 'Windsurf', dir: '.windsurf/workflows/', desc: 'Workflows for Windsurf' },
{ name: 'Codeium (IntelliJ)', dir: '.codeium/global_workflows/', desc: 'Codeium global workflows' },
{ name: 'Pi', dir: '.pi/prompts/', desc: 'Prompt templates for pi.dev coding agent' },
];
localDirs.forEach((item, i) => {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "plan2code",
"version": "1.8.1",
"version": "1.10.0",
"private": true,
"bin": {
"plan2code": "./install.js"
+2 -1
View File
@@ -224,7 +224,8 @@ export async function setupSession(
model: 'default',
maxIterations,
specPath,
timeout: 30,
timeout: 3,
maxRetries: 5,
verbose: false,
startedAt: new Date().toISOString(),
currentIteration: 0,
+90 -20
View File
@@ -64,9 +64,82 @@ export class Controller {
});
}
private async executeIteration(prompt: string): Promise<AgentExecutionResult> {
const timeoutMs = this.config.timeout * 60 * 1000;
private computeTimeoutMs(attempt: number): number {
const baseMs = this.config.timeout * 60 * 1000;
return baseMs + (attempt * 30 * 1000);
}
private formatDuration(ms: number): string {
const totalSec = Math.round(ms / 1000);
const min = Math.floor(totalSec / 60);
const sec = totalSec % 60;
if (min === 0) return `${sec}s`;
if (sec === 0) return `${min}m`;
return `${min}m ${sec}s`;
}
private async executeWithRetry(prompt: string, iterNum: number): Promise<AgentExecutionResult | 'fatal_timeout'> {
const maxAttempts = (this.config.maxRetries ?? 5) + 1;
for (let attempt = 0; attempt < maxAttempts; attempt++) {
const timeoutMs = this.computeTimeoutMs(attempt);
if (attempt > 0) {
const prevTimeoutMs = this.computeTimeoutMs(attempt - 1);
logger.warning(
`Timed out after ${this.formatDuration(prevTimeoutMs)}, retrying (${attempt}/${maxAttempts - 1})...`
);
}
const spinnerBase = attempt > 0
? `Waiting for AI Agent response (retry ${attempt}/${maxAttempts - 1})`
: 'Waiting for AI Agent response (please be patient)';
const spinner = logger.spinner(spinnerBase);
const startTime = Date.now();
const elapsedInterval = setInterval(() => {
const elapsed = Math.round((Date.now() - startTime) / 1000);
spinner.text = `${spinnerBase} ... (${elapsed}s)`;
}, 1000);
const result = await this.executeIteration(prompt, timeoutMs);
clearInterval(elapsedInterval);
spinner.stop();
// Cancelled or interrupted — return immediately, don't retry
if (result.cancelled || this.interrupted) {
return result;
}
// Completed (success or error exit code) — return to caller
if (!result.timedOut) {
return result;
}
// Timed out — retry if attempts remain, otherwise fatal
if (attempt < maxAttempts - 1) {
continue;
}
// All attempts exhausted
logger.error(
`Iteration ${iterNum} timed out on all ${maxAttempts} attempt${maxAttempts === 1 ? '' : 's'}. Stopping loop.`
);
const entry: IterationLogEntry = {
iteration: iterNum,
timestamp: new Date().toISOString(),
duration: result.duration,
exitCode: -1,
status: 'timeout',
};
await this.stateManager.appendIterationLog(entry);
return 'fatal_timeout';
}
return 'fatal_timeout'; // unreachable, satisfies TS
}
private async executeIteration(prompt: string, timeoutMs: number): Promise<AgentExecutionResult> {
// Create new AbortController for this iteration
this.abortController = new AbortController();
@@ -179,19 +252,21 @@ export class Controller {
// Build prompt - simple, just spec path and iteration info
const prompt = await this.buildPrompt();
const spinner = logger.spinner('Waiting for AI Agent response (please be patient)');
const startTime = Date.now();
// Update spinner with elapsed time every second
const elapsedInterval = setInterval(() => {
const elapsed = Math.round((Date.now() - startTime) / 1000);
spinner.text = `Waiting for AI Agent response (please be patient) ... (${elapsed}s)`;
}, 1000);
try {
const result = await this.executeIteration(prompt);
clearInterval(elapsedInterval);
spinner.stop();
const retryResult = await this.executeWithRetry(prompt, iterNum);
if (retryResult === 'fatal_timeout') {
return {
completed: false,
iterations: this.config.currentIteration,
exitReason: 'error',
tasksCompleted: this.tasksCompleted,
prereqsCompleted: this.prereqsCompleted,
error: new Error(`Iteration ${iterNum} failed after all retry attempts`),
};
}
const result = retryResult;
// Check if cancelled
if (result.cancelled || this.interrupted) {
@@ -390,13 +465,8 @@ export class Controller {
await this.stateManager.updateSpecHash(this.config.specPath);
this.config.currentIteration++;
// Handle timeout
if (result.timedOut) {
logger.warning(`Iteration ${iterNum} timed out, continuing...`);
}
// Handle error (but continue - LLM might recover)
if (result.exitCode !== 0 && !result.timedOut) {
if (result.exitCode !== 0) {
// In phase mode, check if any tasks completed despite error exit code
const hasCompletions = isPhaseMode
? checkForAllCompletions(result.stdout + result.stderr).tasks.length > 0
-1
View File
@@ -80,7 +80,6 @@ export async function run(): Promise<LoopResult | null> {
logger.dim(' - config.json (session configuration)');
logger.dim(' - scratchpad.md (LLM-managed notes)');
logger.dim(' - iteration.log (history)');
logger.dim('Tip: run `plan2code-metrics` to capture run data for prompt improvement.');
return loopResult;
} finally {
+27 -14
View File
@@ -23,18 +23,19 @@ Find the FIRST unchecked task, implement ONLY that task, then STOP and report.
2. Find the FIRST phase with an unchecked checkbox (\`- [ ]\` or \`- [/]\`)
3. Read that phase's file (e.g., \`phase-1.md\`)
4. Check the \`## Prerequisites\` section FIRST
5. Find the FIRST unchecked prerequisite (\`- [ ]\`)
- If found, that is your task for this iteration
- Verify/complete it, then mark \`[x]\` or \`[?]\`
6. Only if ALL prerequisites are complete (\`[x]\` or \`[?]\`), find the FIRST unchecked task
5. Find the FIRST unverified prerequisite (no "VERIFIED" or "ASSUMED" annotation)
- If found, verify/complete it, then annotate "VERIFIED" or "ASSUMED: [reason]" inline
6. Only if ALL prerequisites are verified or assumed, find the FIRST unchecked task (\`- [ ]\`)
7. That is your ONE task - implement ONLY that task
## Checkbox States
## Checkbox States (Task items only)
- \`[ ]\` = incomplete/pending (do the FIRST one you find)
- \`[x]\` = complete (skip)
- \`[?]\` = assumed complete, couldn't verify (skip)
- \`[!]\` = blocked (skip)
Prerequisites use plain bullets with inline annotations, not checkboxes.
## Implementation Steps
1. Read and understand the single task
2. Implement it completely
@@ -66,7 +67,10 @@ Use only when ALL phases in overview.md are marked complete.
## Scratchpad Management
After completing each task, append to \`{{specPath}}/.plan2code-loop/scratchpad.md\`:
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.
Each entry should include:
- Task completed and Phase item reference
- Key decisions made and reasoning
- Files changed
@@ -108,17 +112,19 @@ Complete each task fully before moving to the next task within the phase.
2. Find the FIRST phase with an unchecked checkbox (\`- [ ]\` or \`- [/]\`)
3. Read that phase's file (e.g., \`phase-1.md\`)
4. Check the \`## Prerequisites\` section FIRST
5. Complete ALL unchecked prerequisites (\`- [ ]\`) first, in order
- Verify/complete each, then mark \`[x]\` or \`[?]\`
6. Once ALL prerequisites are complete, implement ALL unchecked tasks in order
5. Verify ALL unverified prerequisites first, in order
- Annotate each "VERIFIED" or "ASSUMED: [reason]" inline
6. Once ALL prerequisites are verified, implement ALL unchecked tasks in order
7. Continue until every task in the phase is marked \`[x]\`
## Checkbox States
## Checkbox States (Task items only)
- \`[ ]\` = incomplete/pending
- \`[x]\` = complete (skip)
- \`[?]\` = assumed complete, couldn't verify (skip)
- \`[!]\` = blocked (skip, note in scratchpad)
Prerequisites use plain bullets with inline annotations, not checkboxes.
## Implementation Steps (repeat for EACH task in the phase)
1. Read and understand the task
2. Implement it completely
@@ -139,9 +145,13 @@ git commit -m "<commit message>"
\`\`\`
**Commit message format:**
- With JIRA ticket: Use \`-m "Task X.Y: description" -m "{{jiraTicketId}}" -m "AI Assisted"\` (three \`-m\` flags)
- Without JIRA ticket: \`-m "Task X.Y: description" -m "AI Assisted"\` (two \`-m\` flags)
- ALWAYS include the "AI Assisted" footer as the final \`-m\` flag
\`\`\`
git add -A
git commit -m "Task X.Y: description" -m "{{jiraTicketId}}" -m "AI Assisted"
\`\`\`
- With JIRA ticket: three \`-m\` flags (description, ticket ID, AI Assisted)
- Without JIRA ticket: two \`-m\` flags (description, AI Assisted)
- ALWAYS include "AI Assisted" as the final \`-m\` flag
Replace X.Y with the actual task ID and description with a concise summary of what was implemented.
@@ -167,7 +177,10 @@ After ALL tasks in the phase are complete (or blocked), output:
## Scratchpad Management
After completing each task, append to \`{{specPath}}/.plan2code-loop/scratchpad.md\`:
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.
Each entry should include:
- Task completed and Phase item reference
- Key decisions made and reasoning
- Files changed
+4 -2
View File
@@ -4,7 +4,8 @@ export interface SessionConfig {
agent: string; // "claude-code" | "copilot-cli"
model: string; // Selected model
maxIterations: number; // 5-50
timeout: number; // Minutes per iteration
timeout: number; // Base timeout in minutes per iteration attempt
maxRetries: number; // Max retry attempts per iteration (timeout increments by 30s each retry)
verbose: boolean;
specPath: string; // Path to the spec directory
startedAt: string; // ISO timestamp
@@ -26,7 +27,8 @@ export type SessionState = 'new' | 'continue' | 'changed';
export const DEFAULT_CONFIG: Partial<SessionConfig> = {
maxIterations: 100,
timeout: 30,
timeout: 3,
maxRetries: 5,
verbose: false,
currentIteration: 0,
loopMode: 'task',
+1 -1
View File
@@ -109,7 +109,7 @@ export async function createTaskCommit(options: GitCommitOptions): Promise<boole
// Build commit message
let commitMessage = taskName;
if (jiraTicketId) {
commitMessage = `${taskName}\n\n${jiraTicketId}\n\nAI Assisted`;
commitMessage = `${taskName}\n\n${jiraTicketId}\nAI Assisted`;
} else {
commitMessage = `${taskName}\n\nAI Assisted`;
}
+1 -1
View File
@@ -6,7 +6,7 @@ export default defineConfig({
index: 'src/index.ts',
},
format: ['esm'],
dts: true,
dts: false,
clean: true,
sourcemap: true,
banner: {
+3 -11
View File
@@ -99,13 +99,9 @@ function buildCohort(runs: RunMetrics[], cohortKey: string): CohortMetrics {
const avgReqCoverage = avg(runs.map(r => r.step2_document.requirement_coverage_percent));
const avgVerifItems = avg(runs.map(r => r.step2_document.verification_items_added));
// Step 3 (loop only)
const loopRuns = runs.filter(r => r.step3_implement.used_loop_mode);
const avgCompletionRate = avg(loopRuns.map(r => r.step3_implement.task_completion_rate));
const avgBlockerCount = avg(loopRuns.map(r => r.step3_implement.blocker_count));
const avgTotalIter = avg(loopRuns.map(r => r.step3_implement.total_iterations));
const avgIterDuration = avg(loopRuns.map(r => r.step3_implement.avg_iteration_duration_ms));
const avgMarkerSuccess = avg(loopRuns.map(r => r.step3_implement.completion_marker_success_rate));
// Step 3 averages
const avgCompletionRate = avg(runs.map(r => r.step3_implement.task_completion_rate));
const avgBlockerCount = avg(runs.map(r => r.step3_implement.blocker_count));
// Step 4 averages
const avgCompletionAtAudit = avg(runs.map(r => r.step4_finalize.completion_rate_at_audit));
@@ -140,12 +136,8 @@ function buildCohort(runs: RunMetrics[], cohortKey: string): CohortMetrics {
avg_requirement_coverage_percent: avgReqCoverage,
avg_verification_items_added: avgVerifItems,
loop_run_count: loopRuns.length,
avg_task_completion_rate: avgCompletionRate,
avg_blocker_count: avgBlockerCount,
avg_total_iterations: avgTotalIter,
avg_iteration_duration_ms: avgIterDuration,
avg_completion_marker_success_rate: avgMarkerSuccess,
avg_completion_rate_at_audit: avgCompletionAtAudit,
avg_verification_failures_found: avgVerifFailures,
+3 -3
View File
@@ -57,12 +57,12 @@ export interface AnalyzerOptions {
aggregatedPath: string; // Path to aggregated.json
plan2codeRoot: string; // Path to plan2code repo root
proposalsDir: string; // Where to save diagnosis output
model?: string; // AI model to use (default: claude-opus-4-6)
model?: string; // AI model to use (default: agent's default)
agent?: AgentType; // Agent to use (default: claude-code)
}
export async function runAnalysis(opts: AnalyzerOptions): Promise<string> {
const { aggregatedPath, plan2codeRoot, proposalsDir, model = 'claude-opus-4-6', agent = 'claude-code' } = opts;
const { aggregatedPath, plan2codeRoot, proposalsDir, model = 'default', agent = 'claude-code' } = opts;
// Load aggregated metrics
let aggregated: AggregatedMetrics | null = null;
@@ -102,7 +102,7 @@ export async function runAnalysis(opts: AnalyzerOptions): Promise<string> {
});
// Invoke Claude
console.log(`\nInvoking AI analysis (model: ${model})...`);
console.log(`\nInvoking AI analysis (agent: ${agent}, model: ${model === 'default' ? 'user default' : model})...`);
console.log('This may take a minute...\n');
let diagnosisContent: string;
+227 -31
View File
@@ -5,6 +5,7 @@
*/
import fs from 'fs';
import os from 'os';
import path from 'path';
import { select, input, confirm } from '@inquirer/prompts';
import chalk from 'chalk';
@@ -18,6 +19,33 @@ import type { AggregatedMetrics, CohortMetrics } from './types.js';
import { METRIC_TARGETS } from './types.js';
import { AGENTS, type AgentType } from './invoke-llm.js';
// ── Session state (set at startup via interactive prompts) ───────────────────
let resolvedPlan2CodeRoot = '';
let resolvedProjectRoot = '';
// ── Persisted user config (~/.plan2code-metrics.json) ──────────────────────
const USER_CONFIG_PATH = path.join(os.homedir(), '.plan2code-metrics.json');
interface UserConfig {
plan2codeRepoPath?: string;
}
function loadUserConfig(): UserConfig {
try {
return JSON.parse(fs.readFileSync(USER_CONFIG_PATH, 'utf8'));
} catch {
return {};
}
}
function saveUserConfig(config: UserConfig): void {
try {
fs.writeFileSync(USER_CONFIG_PATH, JSON.stringify(config, null, 2), 'utf8');
} catch { /* best-effort */ }
}
// ── Defaults ──────────────────────────────────────────────────────────────────
const DEFAULT_METRICS_DIR = '.plan2code-metrics';
@@ -25,7 +53,7 @@ const DEFAULT_RUNS_SUBDIR = 'runs';
const DEFAULT_AGGREGATED_FILE = 'aggregated.json';
const DEFAULT_PROPOSALS_SUBDIR = 'proposals';
function getMetricsDirs(baseDir = process.cwd()) {
function getMetricsDirs(baseDir = resolvedProjectRoot) {
const metricsDir = path.join(baseDir, DEFAULT_METRICS_DIR);
return {
metricsDir,
@@ -89,13 +117,7 @@ async function selectAgentAndModel(): Promise<{ agent: AgentType; model: string
choices: Object.values(AGENTS).map(a => ({ name: a.displayName, value: a.name })),
});
const agentDef = AGENTS[agentChoice];
const model = await select({
message: 'AI model:',
choices: agentDef.models.map(m => ({ name: m.label, value: m.value })),
});
return { agent: agentChoice, model };
return { agent: agentChoice, model: AGENTS[agentChoice].defaultModel };
}
// ── Flow: Collect metrics ─────────────────────────────────────────────────────
@@ -107,8 +129,8 @@ async function flowCollect(): Promise<void> {
const sourceChoice = await select({
message: 'Where is the completed project spec?',
choices: [
{ name: 'Active spec directory (specs/<feature-name>/)', value: 'active' },
{ name: 'Archived spec (specs--completed/<feature-name>/)', value: 'archived' },
{ name: 'Active spec directory (specs/<feature-name>/)', value: 'active' },
{ name: 'Custom path', value: 'custom' },
],
});
@@ -118,10 +140,10 @@ async function flowCollect(): Promise<void> {
if (sourceChoice === 'active' || sourceChoice === 'archived') {
const baseSubdir = sourceChoice === 'active' ? 'specs' : 'specs--completed';
const baseDir = path.join(process.cwd(), baseSubdir);
const baseDir = path.join(resolvedProjectRoot, baseSubdir);
if (!fs.existsSync(baseDir)) {
console.log(chalk.red(`No ${baseSubdir}/ directory found in ${process.cwd()}`));
console.log(chalk.red(`No ${baseSubdir}/ directory found in ${resolvedProjectRoot}`));
return;
}
@@ -152,7 +174,7 @@ async function flowCollect(): Promise<void> {
}
// Detect plan2code root
const plan2codeRoot = detectPlan2CodeRoot() ?? process.cwd();
const plan2codeRoot = resolvedPlan2CodeRoot;
const plan2codeVersion = detectPlan2CodeVersion(plan2codeRoot);
const { runsDir, aggregatedPath } = getMetricsDirs();
@@ -179,7 +201,7 @@ async function flowCollect(): Promise<void> {
console.log(chalk.bold('Collected:'));
console.log(` Step 1 (Plan): ${metrics.step1_plan.present ? chalk.green('✓') : chalk.gray('—')}`);
console.log(` Step 2 (Document): ${metrics.step2_document.present ? chalk.green('✓') : chalk.gray('—')}`);
console.log(` Step 3 (Implement): ${metrics.step3_implement.present ? (metrics.step3_implement.used_loop_mode ? chalk.green('✓ (loop)') : chalk.yellow('✓ (manual)')) : chalk.gray('—')}`);
console.log(` Step 3 (Implement): ${metrics.step3_implement.present ? chalk.green('✓') : chalk.gray('—')}`);
console.log(` Step 4 (Finalize): ${metrics.step4_finalize.present ? chalk.green('✓') : chalk.gray('—')}`);
console.log(` User Feedback: ${metrics.user_feedback ? chalk.green(`✓ (${metrics.user_feedback.overall_rating}/10)`) : chalk.gray('—')}`);
@@ -334,15 +356,13 @@ async function flowViewStatus(): Promise<void> {
console.log(` avg_verification_items: ${metricStatus('avg_verification_items_added', cohort.avg_verification_items_added)}`);
}
// Step 3 metrics (loop only)
if (cohort.loop_run_count > 0) {
console.log(chalk.bold(` Step 3 (Implement)${cohort.loop_run_count} loop run(s):`));
// Step 3 metrics
if (cohort.avg_task_completion_rate != null || cohort.avg_blocker_count != null) {
console.log(chalk.bold(' Step 3 (Implement):'));
if (cohort.avg_task_completion_rate != null)
console.log(` avg_task_completion_rate: ${metricStatus('avg_task_completion_rate', cohort.avg_task_completion_rate)}`);
if (cohort.avg_blocker_count != null)
console.log(` avg_blocker_count: ${metricStatus('avg_blocker_count', cohort.avg_blocker_count)}`);
if (cohort.avg_completion_marker_success_rate != null)
console.log(` avg_marker_success_rate: ${metricStatus('avg_completion_marker_success_rate', cohort.avg_completion_marker_success_rate)}`);
}
// Step 4 metrics
@@ -402,7 +422,7 @@ async function flowRunAnalysis(): Promise<string | null> {
console.log(chalk.bold.cyan('── Run Analysis (Diagnose Weak Steps) ──'));
const { aggregatedPath, proposalsDir } = getMetricsDirs();
const plan2codeRoot = detectPlan2CodeRoot() ?? process.cwd();
const plan2codeRoot = resolvedPlan2CodeRoot;
const aggregated = loadAggregated(aggregatedPath);
if (!aggregated || aggregated.total_runs === 0) {
@@ -460,7 +480,7 @@ async function flowGenerateProposal(): Promise<void> {
console.log(chalk.bold.cyan('── Generate Improvement Proposal ──'));
const { aggregatedPath, proposalsDir, runsDir } = getMetricsDirs();
const plan2codeRoot = detectPlan2CodeRoot() ?? process.cwd();
const plan2codeRoot = resolvedPlan2CodeRoot;
// Find diagnosis files
let diagFiles: string[] = [];
@@ -540,7 +560,7 @@ async function flowReviewAndApply(): Promise<void> {
console.log(chalk.bold.cyan('── Review and Apply a Proposal ──'));
const { proposalsDir } = getMetricsDirs();
const plan2codeRoot = detectPlan2CodeRoot() ?? process.cwd();
const plan2codeRoot = resolvedPlan2CodeRoot;
if (!fs.existsSync(proposalsDir)) {
console.log(chalk.yellow('No proposals directory found. Generate a proposal first.'));
@@ -577,6 +597,138 @@ async function flowReviewAndApply(): Promise<void> {
});
}
// ── Flow: Delete metrics data ─────────────────────────────────────────────────
async function flowDelete(): Promise<void> {
console.log();
console.log(chalk.bold.cyan('── Delete Metrics Data ──'));
const { metricsDir, runsDir, aggregatedPath, proposalsDir } = getMetricsDirs();
if (!fs.existsSync(metricsDir)) {
console.log(chalk.yellow('No metrics data found (.plan2code-metrics/ does not exist).'));
return;
}
const scope = await select({
message: 'What do you want to delete?',
choices: [
{ name: 'Delete specific run(s)', value: 'select' },
{ name: 'Delete all runs and aggregated data', value: 'runs' },
{ name: 'Delete everything (runs, aggregated data, proposals)', value: 'all' },
{ name: 'Cancel', value: 'cancel' },
],
});
if (scope === 'cancel') return;
if (scope === 'select') {
const runs = loadRunFiles(runsDir);
if (runs.length === 0) {
console.log(chalk.yellow('No run files found.'));
return;
}
const choices = runs.map(r => ({
name: `${r.run_id} ${r.project.name} v${r.plan2code_version}`,
value: r.run_id,
}));
// Select runs one at a time since @inquirer/prompts select is single-choice
const toDelete: string[] = [];
let selecting = true;
while (selecting) {
const remaining = choices.filter(c => !toDelete.includes(c.value));
if (remaining.length === 0) break;
const chosen = await select({
message: `Select a run to delete (${toDelete.length} selected so far):`,
choices: [
...remaining,
{ name: toDelete.length > 0 ? `Done selecting (delete ${toDelete.length})` : 'Cancel', value: '__done__' },
],
});
if (chosen === '__done__') {
selecting = false;
} else {
toDelete.push(chosen);
console.log(chalk.gray(` + ${chosen}`));
}
}
if (toDelete.length === 0) return;
const confirmed = await confirm({
message: `Delete ${toDelete.length} run(s)? This cannot be undone.`,
default: false,
});
if (!confirmed) return;
let deleted = 0;
for (const runId of toDelete) {
const filePath = path.join(runsDir, `${runId}.json`);
try {
fs.unlinkSync(filePath);
deleted++;
} catch {
console.log(chalk.yellow(` Could not delete ${runId}.json`));
}
}
console.log(chalk.green(`✓ Deleted ${deleted} run(s).`));
// Re-aggregate with remaining runs
const remainingRuns = loadRunFiles(runsDir);
if (remainingRuns.length > 0) {
aggregate(runsDir, aggregatedPath);
console.log(chalk.gray(` Aggregated metrics updated (${remainingRuns.length} runs remaining).`));
} else {
try { fs.unlinkSync(aggregatedPath); } catch { /* ignore */ }
console.log(chalk.gray(' No runs remaining — aggregated data removed.'));
}
return;
}
// scope === 'runs' or 'all'
const label = scope === 'all'
? 'ALL metrics data (runs, aggregated data, and proposals)'
: 'all runs and aggregated data';
const confirmed = await confirm({
message: `Delete ${label}? This cannot be undone.`,
default: false,
});
if (!confirmed) return;
// Delete run files
let runCount = 0;
if (fs.existsSync(runsDir)) {
const files = fs.readdirSync(runsDir).filter(f => f.endsWith('.json'));
for (const f of files) {
try { fs.unlinkSync(path.join(runsDir, f)); runCount++; } catch { /* ignore */ }
}
}
// Delete aggregated file
try { fs.unlinkSync(aggregatedPath); } catch { /* ignore */ }
console.log(chalk.green(`✓ Deleted ${runCount} run(s) and aggregated data.`));
if (scope === 'all') {
// Delete proposals
let proposalCount = 0;
if (fs.existsSync(proposalsDir)) {
const files = fs.readdirSync(proposalsDir);
for (const f of files) {
try { fs.unlinkSync(path.join(proposalsDir, f)); proposalCount++; } catch { /* ignore */ }
}
try { fs.rmdirSync(proposalsDir); } catch { /* ignore */ }
}
console.log(chalk.green(`✓ Deleted ${proposalCount} proposal file(s).`));
}
}
// ── Main menu ─────────────────────────────────────────────────────────────────
export async function runCLI(): Promise<void> {
@@ -585,18 +737,58 @@ export async function runCLI(): Promise<void> {
console.log(chalk.gray('Recursive self-improvement toolchain for plan2code contributors'));
console.log();
// Check if we're in (or near) a plan2code repo
const plan2codeRoot = detectPlan2CodeRoot();
if (!plan2codeRoot) {
console.log(chalk.yellow('⚠ Could not detect plan2code repository (src/plan2code-*.md not found nearby).'));
console.log(chalk.gray(' Prompt version hashing and analysis will be limited.'));
console.log();
} else {
const version = detectPlan2CodeVersion(plan2codeRoot);
console.log(chalk.gray(`plan2code root: ${plan2codeRoot} (v${version})`));
console.log();
// ── Prompt for paths ────────────────────────────────────────────────────────
const userConfig = loadUserConfig();
const savedRoot = userConfig.plan2codeRepoPath && fs.existsSync(userConfig.plan2codeRepoPath)
? userConfig.plan2codeRepoPath
: null;
const detectedRoot = savedRoot ?? detectPlan2CodeRoot();
const s2cPath = await input({
message: 'Path to plan2code repo:',
default: detectedRoot ?? undefined,
validate: (v) => {
if (!v.trim()) return 'Path is required';
const resolved = path.resolve(v.trim());
if (!fs.existsSync(resolved)) return 'Directory not found';
return true;
},
});
resolvedPlan2CodeRoot = path.resolve(s2cPath.trim());
// Persist the path for next run
if (resolvedPlan2CodeRoot !== userConfig.plan2codeRepoPath) {
saveUserConfig({ ...userConfig, plan2codeRepoPath: resolvedPlan2CodeRoot });
}
// 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'));
if (!hasSrcPrompts) {
console.log(chalk.yellow('⚠ No src/plan2code-*.md prompts found at that path. Hashing and analysis may be limited.'));
}
const projPath = await input({
message: 'Path to project repo (metrics source):',
default: process.cwd(),
validate: (v) => {
if (!v.trim()) return 'Path is required';
const resolved = path.resolve(v.trim());
if (!fs.existsSync(resolved)) return 'Directory not found';
return true;
},
});
resolvedProjectRoot = path.resolve(projPath.trim());
// Display resolved paths
const version = detectPlan2CodeVersion(resolvedPlan2CodeRoot);
console.log();
console.log(chalk.gray(`plan2code repo: ${resolvedPlan2CodeRoot} (v${version})`));
console.log(chalk.gray(`project repo: ${resolvedProjectRoot}`));
console.log();
let continueLoop = true;
while (continueLoop) {
const action = await select({
@@ -608,6 +800,7 @@ export async function runCLI(): Promise<void> {
{ name: 'Run analysis (diagnose weak steps)', value: 'analyze' },
{ name: 'Generate improvement proposal', value: 'propose' },
{ name: 'Review and apply a proposal', value: 'apply' },
{ name: chalk.red('Delete metrics data'), value: 'delete' },
{ name: 'Exit', value: 'exit' },
],
});
@@ -631,6 +824,9 @@ export async function runCLI(): Promise<void> {
case 'apply':
await flowReviewAndApply();
break;
case 'delete':
await flowDelete();
break;
case 'exit':
continueLoop = false;
break;
+114 -146
View File
@@ -36,6 +36,34 @@ function readFileSafe(filePath: string): string | null {
}
}
/** Strip the "## Success Criteria" section so its checkboxes aren't counted as tasks. */
function stripSuccessCriteriaSection(overview: string): string {
return overview.replace(/^## Success Criteria\s*\n[\s\S]*?(?=\n## |\n*$)/m, '');
}
/**
* Extract canonical task counts from the Completion Summary section.
* Supports two formats found in real specs:
* - Bold text: "**Completion Rate:** 100% (5/5 tasks)"
* - Table row: "| Total Tasks | 28 (28/28 complete — 100%) |"
* Returns null when no parseable counts are found.
*/
function parseCompletionSummaryTaskCount(overview: string): { completed: number; total: number } | null {
const summaryMatch = overview.match(/## Completion Summary\s*\n([\s\S]*?)(?=\n## |\n*$)/);
if (!summaryMatch) return null;
const summary = summaryMatch[1];
// "Completion Rate: X% (Y/Z tasks)"
const rateMatch = summary.match(/Completion Rate[:\s*]*\d{1,3}%\s*\((\d+)\/(\d+)\s*tasks?\)/i);
if (rateMatch) return { completed: parseInt(rateMatch[1], 10), total: parseInt(rateMatch[2], 10) };
// "Total Tasks | 28 (28/28 complete"
const tableMatch = summary.match(/Total Tasks\s*\|\s*(\d+)\s*\((\d+)\/(\d+)\s*complete/i);
if (tableMatch) return { completed: parseInt(tableMatch[2], 10), total: parseInt(tableMatch[3], 10) };
return null;
}
function generateRunId(): string {
const now = new Date();
const ts = now.toISOString().replace(/[-:T]/g, '').slice(0, 14);
@@ -173,13 +201,10 @@ export function collectStep2(specDir: string): Step2DocumentMetrics {
requirement_coverage_percent: null, verification_items_added: null };
}
// Count all checkbox tasks: - [ ], - [x], - [!], - [/]
const allTasks = (overview.match(/^\s*-\s+\[[ x!/?]\]/gm) ?? []).length;
// Detect Parallel Execution Groups table
const parallelGroups = (overview.match(/Parallel\s+Execution\s+Group/gi) ?? []).length;
// Count phase files
// Count phase files (scanned first so we can use sum as a total_tasks fallback)
let phaseCount = 0;
let tasksPerPhase: number[] = [];
try {
@@ -190,13 +215,31 @@ export function collectStep2(specDir: string): Step2DocumentMetrics {
phaseCount = phaseFiles.length;
for (const pf of phaseFiles) {
const content = readFileSafe(path.join(specDir, pf)) ?? '';
const count = (content.match(/^\s*-\s+\[[ x!/?]\]/gm) ?? []).length;
const count = (content.match(/^\s*-\s+\[[ x!/?]\]\s+\*\*Task\s+\d/gm) ?? []).length;
tasksPerPhase.push(count);
}
} catch {
// leave empty
}
// total_tasks priority:
// 1. Completion Summary canonical count
// 2. Sum of phase file task counts
// 3. Stripped overview checkboxes (excluding Success Criteria)
const summaryCount = parseCompletionSummaryTaskCount(overview);
const phaseSum = tasksPerPhase.length > 0 ? tasksPerPhase.reduce((a, b) => a + b, 0) : 0;
const strippedOverview = stripSuccessCriteriaSection(overview);
const strippedCheckboxCount = (strippedOverview.match(/^\s*-\s+\[[ x!/?]\]/gm) ?? []).length;
let totalTasks: number;
if (summaryCount) {
totalTasks = summaryCount.total;
} else if (phaseSum > 0) {
totalTasks = phaseSum;
} else {
totalTasks = strippedCheckboxCount;
}
// Requirement coverage: look for coverage percentage in overview
let reqCoverage: number | null = null;
const covMatch = overview.match(/[Cc]overage[:\s]+(\d{1,3})%/);
@@ -207,7 +250,7 @@ export function collectStep2(specDir: string): Step2DocumentMetrics {
return {
present: true,
total_tasks: allTasks || null,
total_tasks: totalTasks || null,
tasks_per_phase: tasksPerPhase.length > 0 ? tasksPerPhase : null,
phase_count: phaseCount || null,
parallel_groups_identified: parallelGroups || 0,
@@ -216,124 +259,67 @@ export function collectStep2(specDir: string): Step2DocumentMetrics {
};
}
// ── Step 3: Implement (loop data) ─────────────────────────────────────────────
// ── Step 3: Implement ──────────────────────────────────────────────────────────
export function collectStep3(specDir: string): Step3ImplementMetrics {
const loopDir = path.join(specDir, '.plan2code-loop');
const iterLogPath = path.join(loopDir, 'iteration.log');
const configPath = path.join(loopDir, 'config.json');
if (!fs.existsSync(loopDir)) {
return { present: true, used_loop_mode: false, loop_mode: null,
task_completion_rate: null, tasks_completed: null, tasks_total: null,
blocker_count: null, blocker_categories: null, total_iterations: null,
avg_iteration_duration_ms: null, exit_code_distribution: null,
completion_marker_success_rate: null };
// Discover phase-*.md files
let phaseFiles: string[] = [];
try {
const files = fs.readdirSync(specDir);
phaseFiles = files
.filter(f => /^phase-\d+\.md$/i.test(f))
.sort();
} catch {
// leave empty
}
// Parse config.json for loop mode
let loopMode: 'task' | 'phase' | null = null;
const configContent = readFileSafe(configPath);
if (configContent) {
try {
const config = JSON.parse(configContent);
loopMode = config.loopMode ?? null;
} catch {
// ignore
}
}
// Parse NDJSON iteration.log
const iterLogContent = readFileSafe(iterLogPath);
if (!iterLogContent) {
return { present: true, used_loop_mode: true, loop_mode: loopMode,
task_completion_rate: null, tasks_completed: null, tasks_total: null,
blocker_count: null, blocker_categories: null, total_iterations: null,
avg_iteration_duration_ms: null, exit_code_distribution: null,
completion_marker_success_rate: null };
}
interface IterEntry {
iteration: number;
timestamp: string;
duration: number;
exitCode: number;
status: string;
completionMarker?: string;
}
const entries: IterEntry[] = [];
for (const line of iterLogContent.split('\n')) {
const trimmed = line.trim();
if (!trimmed) continue;
try {
entries.push(JSON.parse(trimmed));
} catch {
// skip malformed lines
}
}
const totalIterations = entries.length;
if (totalIterations === 0) {
return { present: true, used_loop_mode: true, loop_mode: loopMode,
task_completion_rate: null, tasks_completed: null, tasks_total: null,
blocker_count: null, blocker_categories: null, total_iterations: 0,
avg_iteration_duration_ms: null, exit_code_distribution: null,
completion_marker_success_rate: null };
}
// Exit code distribution
const exitCodes: Record<string, number> = {};
for (const e of entries) {
const key = String(e.exitCode ?? 'other');
exitCodes[key] = (exitCodes[key] ?? 0) + 1;
}
// Average duration
const durations = entries.map(e => e.duration).filter(d => d != null && d > 0);
const avgDuration = durations.length > 0
? Math.round(durations.reduce((a, b) => a + b, 0) / durations.length)
: null;
// Completion markers
const markerEntries = entries.filter(e => e.completionMarker && e.completionMarker.trim() !== '');
const taskCompleteEntries = markerEntries.filter(e =>
e.completionMarker?.startsWith('TASK_COMPLETE')
);
const blockedEntries = markerEntries.filter(e =>
e.completionMarker?.startsWith('TASK_BLOCKED')
);
// Completion marker success rate: entries where a valid marker was detected vs total
const validMarkerCount = markerEntries.length;
const markerSuccessRate = totalIterations > 0
? validMarkerCount / totalIterations
: null;
// Task counts from markers (used for completion_marker_success_rate)
const blockerCount = blockedEntries.length;
// Blocker categories (parse from "TASK_BLOCKED: X.Y - reason")
const blockerCategories: string[] = [];
for (const e of blockedEntries) {
const match = e.completionMarker?.match(/TASK_BLOCKED:\s*[\d.]+\s*-\s*(.+)/);
if (match) {
// Normalize to snake_case category
const reason = match[1].toLowerCase().replace(/\s+/g, '_').replace(/[^a-z0-9_]/g, '');
if (!blockerCategories.includes(reason)) {
blockerCategories.push(reason);
}
}
}
// Count tasks from overview.md checkboxes (consistent numerator and denominator)
// Read overview.md for Completion Summary
const overviewPath = path.join(specDir, 'overview.md');
const overview = readFileSafe(overviewPath);
const summaryCount = overview ? parseCompletionSummaryTaskCount(overview) : null;
// present = true if phase files exist OR overview has completion data
const present = phaseFiles.length > 0 || summaryCount != null;
if (!present) {
return { present: false, task_completion_rate: null, tasks_completed: null,
tasks_total: null, blocker_count: null };
}
// Count checkboxes in phase files
let phaseTotal = 0;
let phaseCompleted = 0;
let blockerCount = 0;
for (const pf of phaseFiles) {
const content = readFileSafe(path.join(specDir, pf)) ?? '';
phaseTotal += (content.match(/^\s*-\s+\[[ x!/?]\]\s+\*\*Task\s+\d/gm) ?? []).length;
phaseCompleted += (content.match(/^\s*-\s+\[x\]\s+\*\*Task\s+\d/gm) ?? []).length;
blockerCount += (content.match(/^\s*-\s+\[!\]\s+\*\*Task\s+\d/gm) ?? []).length;
}
// Count checkboxes in overview (stripped of Success Criteria)
let overviewTotal = 0;
let overviewCompleted = 0;
if (overview) {
const stripped = stripSuccessCriteriaSection(overview);
overviewTotal = (stripped.match(/^\s*-\s+\[[ x!/?]\]/gm) ?? []).length;
overviewCompleted = (stripped.match(/^\s*-\s+\[x\]/gm) ?? []).length;
}
// Task counts priority:
// 1. Completion Summary in overview.md
// 2. Checkbox counting in phase files
// 3. Checkbox counting in overview.md (stripped of Success Criteria)
let tasksTotal: number | null = null;
let tasksCompleted: number | null = null;
if (overview) {
tasksTotal = (overview.match(/^\s*-\s+\[[ x!/?]\]/gm) ?? []).length || null;
tasksCompleted = (overview.match(/^\s*-\s+\[x\]/gm) ?? []).length || null;
if (summaryCount) {
tasksTotal = summaryCount.total;
tasksCompleted = summaryCount.completed;
} else if (phaseTotal > 0) {
tasksTotal = phaseTotal;
tasksCompleted = phaseCompleted;
} else if (overviewTotal > 0) {
tasksTotal = overviewTotal;
tasksCompleted = overviewCompleted;
}
const taskCompletionRate = tasksTotal && tasksTotal > 0 && tasksCompleted != null
@@ -342,17 +328,10 @@ export function collectStep3(specDir: string): Step3ImplementMetrics {
return {
present: true,
used_loop_mode: true,
loop_mode: loopMode,
task_completion_rate: taskCompletionRate,
tasks_completed: tasksCompleted || null,
tasks_completed: tasksCompleted,
tasks_total: tasksTotal,
blocker_count: blockerCount,
blocker_categories: blockerCategories.length > 0 ? blockerCategories : null,
total_iterations: totalIterations,
avg_iteration_duration_ms: avgDuration,
exit_code_distribution: exitCodes,
completion_marker_success_rate: markerSuccessRate,
};
}
@@ -380,10 +359,17 @@ export function collectStep4(specDir: string): Step4FinalizeMetrics {
archival_succeeded: archivalSucceeded };
}
// Completion rate: completed tasks / total tasks
const totalTasks = (overview.match(/^\s*-\s+\[[ x!/?]\]/gm) ?? []).length;
const completedTasks = (overview.match(/^\s*-\s+\[x\]/gm) ?? []).length;
const completionRate = totalTasks > 0 ? completedTasks / totalTasks : null;
// Completion rate: prefer Completion Summary, fall back to stripped overview checkboxes
let completionRate: number | null = null;
const summaryCount = parseCompletionSummaryTaskCount(overview);
if (summaryCount) {
completionRate = summaryCount.total > 0 ? summaryCount.completed / summaryCount.total : null;
} else {
const stripped = stripSuccessCriteriaSection(overview);
const totalTasks = (stripped.match(/^\s*-\s+\[[ x!/?]\]/gm) ?? []).length;
const completedTasks = (stripped.match(/^\s*-\s+\[x\]/gm) ?? []).length;
completionRate = totalTasks > 0 ? completedTasks / totalTasks : null;
}
// Verification failures: look for [!] tasks or "verification failed" text
const blockedTasks = (overview.match(/^\s*-\s+\[!\]/gm) ?? []).length;
@@ -476,24 +462,6 @@ export async function collectRun(opts: CollectorOptions): Promise<RunMetrics> {
// leave null
}
// Try to get started_at from earliest iteration log entry
const loopDir = path.join(specDir, '.plan2code-loop');
const iterLogPath = path.join(loopDir, 'iteration.log');
const iterLogContent = readFileSafe(iterLogPath);
if (iterLogContent) {
const lines = iterLogContent.split('\n').filter(l => l.trim());
if (lines.length > 0) {
try {
const first = JSON.parse(lines[0]);
if (first.timestamp) startedAt = first.timestamp;
} catch { /* ignore */ }
try {
const last = JSON.parse(lines[lines.length - 1]);
if (last.timestamp) completedAt = last.timestamp;
} catch { /* ignore */ }
}
}
const metrics: RunMetrics = {
schema_version: '1.0',
run_id: runId,
+2 -2
View File
@@ -154,7 +154,7 @@ export interface ImproverResult {
}
export async function generateImprovement(opts: ImproverOptions): Promise<ImproverResult> {
const { diagnosisPath, plan2codeRoot, proposalsDir, runsDir, model = 'claude-opus-4-6', agent = 'claude-code' } = opts;
const { diagnosisPath, plan2codeRoot, proposalsDir, runsDir, model = 'default', agent = 'claude-code' } = opts;
// Load diagnosis
let diagnosisContent: string;
@@ -193,7 +193,7 @@ export async function generateImprovement(opts: ImproverOptions): Promise<Improv
});
// Invoke Claude
console.log(`\nInvoking AI improvement proposal (model: ${model})...`);
console.log(`\nInvoking AI improvement proposal (agent: ${agent}, model: ${model === 'default' ? 'user default' : model})...`);
console.log('This may take a minute...\n');
let aiResponse: string;
+7 -15
View File
@@ -18,7 +18,7 @@ export interface AgentDef {
name: AgentType;
displayName: string;
command: string;
models: Array<{ value: string; label: string }>;
defaultModel: string;
}
export const AGENTS: Record<AgentType, AgentDef> = {
@@ -26,23 +26,13 @@ export const AGENTS: Record<AgentType, AgentDef> = {
name: 'claude-code',
displayName: 'Claude Code',
command: 'claude',
models: [
{ value: 'claude-opus-4-6', label: 'Claude Opus 4.6 (Recommended)' },
{ value: 'claude-sonnet-4-6', label: 'Claude Sonnet 4.6 (faster)' },
],
defaultModel: 'default',
},
'copilot-cli': {
name: 'copilot-cli',
displayName: 'GitHub Copilot CLI',
command: 'copilot',
models: [
{ value: 'claude-sonnet-4', label: 'Claude Sonnet 4 (Default)' },
{ value: 'claude-sonnet-4.5', label: 'Claude Sonnet 4.5' },
{ value: 'claude-opus-4.5', label: 'Claude Opus 4.5' },
{ value: 'gpt-5', label: 'GPT-5' },
{ value: 'gpt-5-mini', label: 'GPT-5 Mini' },
{ value: 'gemini-3-pro-preview', label: 'Gemini 3 Pro' },
],
defaultModel: 'claude-sonnet-4',
},
};
@@ -69,7 +59,8 @@ export async function invokeLLM(opts: InvokeLLMOptions): Promise<string> {
'--print',
'--dangerously-skip-permissions',
];
if (model) {
// Only add --model if not using default
if (model && model !== 'default') {
args.push('--model', model);
}
@@ -84,7 +75,8 @@ export async function invokeLLM(opts: InvokeLLMOptions): Promise<string> {
} else {
// Copilot CLI: pipe prompt via stdin string
const args: string[] = [];
if (model) {
// Only add --model if not using default
if (model && model !== 'default') {
args.push('--model', model);
}
args.push('--allow-all-tools', '-s');
-1
View File
@@ -35,7 +35,6 @@ The following are the current contents of the plan2code workflow prompt files be
| avg_verification_items_added (Step 2) | ≤ 1.5 | lower is better |
| avg_task_completion_rate (Step 3) | ≥ 0.95 | higher is better |
| avg_blocker_count (Step 3) | ≤ 1.5 | lower is better |
| avg_completion_marker_success_rate (Step 3) | ≥ 0.95 | higher is better |
| avg_verification_failures_found (Step 4) | ≤ 1.0 | lower is better |
| archival_success_rate (Step 4) | ≥ 0.99 | higher is better |
| avg_user_rating (Feedback) | ≥ 7.0 | higher is better (1-10 scale, null if no feedback) |
+1 -2
View File
@@ -28,7 +28,7 @@ The following diagnosis was produced by the analysis step:
1. **Char limit:** Each target file MUST stay under 11,000 characters after your edit is applied. This is enforced by code — edits that violate it will be automatically rejected.
2. **Maximum 5 edits per cycle.** Focus on the highest-impact changes only.
3. **Each edit must cite a specific metric** in its `expected_metric_impact` field (e.g., "avg_completion_marker_success_rate", "avg_blocker_count").
3. **Each edit must cite a specific metric** in its `expected_metric_impact` field (e.g., "avg_task_completion_rate", "avg_blocker_count").
4. **`old_text` must be verbatim** from the file. Copy-paste exactly — include surrounding whitespace/newlines as they appear. Edits with mismatched old_text will be automatically rejected.
5. **Do NOT modify Role sections** (lines starting with "You are" at the top of each file) or step headings (lines starting with `#`).
6. **Each edit must be independently applicable** — no edit should depend on another edit being applied first.
@@ -41,7 +41,6 @@ The following diagnosis was produced by the analysis step:
- Target the specific sections identified in "Recommended Improvement Targets" from the diagnosis.
- For high `avg_clarification_rounds`: Add more upfront specification examples or decision criteria to Step 1.
- For low `avg_completion_marker_success_rate`: Clarify or simplify the completion marker format in Step 3.
- For high `avg_blocker_count`: Add blocker-recovery guidance or prerequisite check instructions.
- For low `avg_confidence`: Strengthen the confidence calculation instructions with clearer rubrics.
- For low `avg_parallel_groups`: Add explicit guidance for identifying parallel tasks in Step 2.
+1 -13
View File
@@ -41,17 +41,10 @@ export interface Step2DocumentMetrics {
export interface Step3ImplementMetrics {
present: boolean;
used_loop_mode: boolean;
loop_mode: 'task' | 'phase' | null;
task_completion_rate: number | null;
tasks_completed: number | null;
tasks_total: number | null;
blocker_count: number | null;
blocker_categories: string[] | null;
total_iterations: number | null;
avg_iteration_duration_ms: number | null;
exit_code_distribution: Record<string, number> | null;
completion_marker_success_rate: number | null;
}
export interface Step4FinalizeMetrics {
@@ -132,13 +125,9 @@ export interface CohortMetrics {
avg_requirement_coverage_percent: number | null;
avg_verification_items_added: number | null;
// Step 3 averages (loop only)
loop_run_count: number;
// Step 3 averages
avg_task_completion_rate: number | null;
avg_blocker_count: number | null;
avg_total_iterations: number | null;
avg_iteration_duration_ms: number | null;
avg_completion_marker_success_rate: number | null;
// Step 4 averages
avg_completion_rate_at_audit: number | null;
@@ -168,7 +157,6 @@ export const METRIC_TARGETS = {
avg_verification_items_added: { target: 1.5, direction: 'lte' as const },
avg_task_completion_rate: { target: 0.95, direction: 'gte' as const },
avg_blocker_count: { target: 1.5, direction: 'lte' as const },
avg_completion_marker_success_rate: { target: 0.95, direction: 'gte' as const },
avg_verification_failures_found: { target: 1.0, direction: 'lte' as const },
archival_success_rate: { target: 0.99, direction: 'gte' as const },
avg_user_rating: { target: 7.0, direction: 'gte' as const },
+67 -3
View File
@@ -24,6 +24,42 @@ Check if `AGENTS.md` exists in project root.
- Main sections (bullets)
- Current line count
Check for `.agents-docs/` directory:
**If `.agents-docs/` exists:**
```
Structure: Progressive discovery (index + N section files)
Section files:
- .agents-docs/AGENTS-architecture.md
- .agents-docs/AGENTS-development.md
[etc.]
```
**If `.agents-docs/` does not exist:** Note: `Structure: Single-file (no .agents-docs/ directory)`
**IMPORTANT — you MUST offer restructuring.** Stop and ask:
> "Your AGENTS.md uses a single-file format. Want to restructure for progressive discovery? This splits detailed sections into `.agents-docs/` files and converts AGENTS.md to a lightweight index with summaries and links."
Wait for user response before continuing. If user accepts, restructure existing content: create `.agents-docs/` directory with section files, convert AGENTS.md to index with summaries and links. Always-inline sections (Project Overview, Git Commit Messages, How to Use This File) stay in AGENTS.md. If user declines, continue with single-file editing.
### Git Commit Messages Audit
Check AGENTS.md for a `## Git Commit Messages` section describing this format:
```
<description>
<JIRA-Ticket-ID>
AI Assisted
```
Required rules:
1. **JIRA ticket ID from branch** — derive from branch name format `<prefix>/<TICKET-ID>-description` (uppercase project key + hyphen + integer, e.g., `PCWEB-10968`)
2. **AI Assisted footer** — always the last line, on its own line directly after the ticket ID
If the section is missing or either rule is absent, flag it:
> "Your Git Commit Messages section is missing [missing items]. Want me to add them?"
If user confirms, add/update the section before proceeding. If user declines, continue.
Proceed to Step 2.
---
@@ -94,7 +130,12 @@ Present the user with update options:
## Step 5: Confirm & Apply
Before changes:
Before changes, route edits to the correct file when `.agents-docs/` exists:
- Always-inline sections (Project Overview, Git Commit Messages, How to Use This File) → edit AGENTS.md directly
- All other sections → edit the corresponding `.agents-docs/AGENTS-<section-name>.md` file
Preview format:
> **File:** `.agents-docs/AGENTS-architecture.md` (or `AGENTS.md` for inline sections)
> **Section:** [name]
> **Change:** [description]
> ```
@@ -105,6 +146,13 @@ Before changes:
- "adjust" -> ask what to change, repeat
- "yes" -> apply edit, insert in appropriate section (create if needed), preserve structure
### Section File Lifecycle (when `.agents-docs/` exists)
- **New section (>~10 lines):** Create `.agents-docs/AGENTS-<section-name>.md` with breadcrumb header, add summary + link in AGENTS.md
- **New section (<~10 lines):** Keep inline in AGENTS.md
- **Delete section:** Remove the `.agents-docs/` file and its summary + link from AGENTS.md
- **Orphan cleanup:** After all edits, check for `.agents-docs/` files with no corresponding AGENTS.md section — offer to remove them
---
## Step 6: Summary & Next
@@ -112,8 +160,14 @@ Before changes:
After applying:
> "Done! Changed:"
> - [Summary]
> - Line count: X/500
>
If `.agents-docs/` exists:
> Structure: AGENTS.md (index) + N section files in .agents-docs/
> Index line count: X/500
Otherwise:
> Line count: X/500
> "Add anything else?"
If yes, return to Step 3. If done, proceed to Step 7.
@@ -172,6 +226,7 @@ See [AGENTS.md](./AGENTS.md) for complete project documentation including:
- Environment variables
- Testing patterns
- Deployment guides
- Section details in .agents-docs/
```
**For directory configs** (`.cursor/rules/`, `.windsurf/rules/`): Delete existing `.md` files, create single `reference.md`.
@@ -188,6 +243,7 @@ See [AGENTS.md](./AGENTS.md) for complete project documentation including:
6. **No duplication** - Check for similar content first
7. **Group logically** - Place near related content
8. **Be specific** - Include exact commands, paths, names
9. **Route edits to correct file** - Inline sections edit AGENTS.md directly, detailed sections edit the corresponding .agents-docs/ file
---
@@ -199,6 +255,14 @@ Agent: Found AGENTS.md covering:
- Project structure
- API conventions
Line count: 127
Structure: Single-file (no .agents-docs/ directory)
Your AGENTS.md uses a single-file format. Want to
restructure for progressive discovery?
User: No thanks, just update it.
Agent: OK, keeping single-file format.
Recent work on auth flow. Worth documenting:
- JWT refresh token must validate before access token
+40 -1
View File
@@ -16,7 +16,8 @@ Analyze this codebase and create `AGENTS.md` to guide future AI coding agents (C
1. **Commands**: Build, lint, test, run single test, and other common development tasks
2. **Architecture**: High-level "big picture" structure requiring multi-file context to understand
3. **Git Commit Messages**: Always append `AI Assisted` as the last line of every git commit message, separated from the rest of the message body with a blank line
3. **Git Commit Messages**: All commits must follow this format: `<description>\n\n<JIRA-Ticket-ID>\nAI Assisted`. Derive the JIRA ticket ID from the current branch name — the format is `<prefix>/<TICKET-ID>-description` where the ticket ID is an uppercase project key, hyphen, and integer (e.g., `PCWEB-10968`). The ticket ID and `AI Assisted` go on consecutive lines after a blank line.
4. **How to Use This File**: A short paragraph explaining that sections below contain brief summaries and agents should follow the markdown links to `.agents-docs/` for full details — only read what's relevant to the current task.
## Rules
@@ -36,6 +37,43 @@ This file provides guidance to AI coding agents like Claude Code (claude.ai/code
---
## Progressive Discovery
Generate AGENTS.md as an **index file** — each section gets a 2-3 line summary with a markdown link to a detail file. Full content goes in `.agents-docs/` directory files.
- **Index format** — each section in AGENTS.md: heading, brief summary, then `Details: [Section Name](./.agents-docs/AGENTS-<section-name>.md)`
- **Detail files** — named `AGENTS-<section-name>.md` using kebab-case from the section header (e.g., "Development Commands" → `AGENTS-development-commands.md`)
- **Detail file header** — each file starts with:
```
# <Section Name>
> Part of [AGENTS.md](../AGENTS.md) — project guidance for AI coding agents.
```
### Always Inline
These sections must remain fully inline in AGENTS.md (never split to separate files):
- Project Overview
- Git Commit Messages
- How to Use This File
### Directory Setup
- Create `.agents-docs/` directory in the project root
- Each file: `AGENTS-<section-name>.md` where `<section-name>` is kebab-case of the section header
- Each file starts with `# <Section Name>` followed by breadcrumb: `> Part of [AGENTS.md](../AGENTS.md) — project guidance for AI coding agents.`
### Grouping Heuristics
- Combine related subsections into a single detail file (e.g., "Architecture" with its subsections → one file)
- Sections under ~10 lines of content should stay inline in AGENTS.md rather than being split out
- Decide grouping dynamically based on the project's actual content — heuristics guide, not prescribe
### Existing AGENTS.md Without `.agents-docs/`
If AGENTS.md exists but `.agents-docs/` does not, offer to restructure: "Your AGENTS.md uses a single-file format. Want to restructure it for progressive discovery? This splits detailed sections into `.agents-docs/` files and converts AGENTS.md to a lightweight index." Only restructure if the user confirms — never auto-restructure.
---
## AI Agent File Sync
After creating `AGENTS.md`, check for these files and offer to replace with references:
@@ -81,4 +119,5 @@ See [AGENTS.md]([Path]) for complete project documentation including:
- Environment variables
- Testing patterns
- Deployment guides
- Section details in .agents-docs/
```
+1
View File
@@ -221,6 +221,7 @@ State assessment and ask user to confirm.
1. Save PLAN-CONVERSATION (see template below) — transcript + decision summary tables
2. Create PLAN-DRAFT (see template below) using same date
3. Verify: re-read conversation as source of truth, cross-reference against PLAN-DRAFT sections (Reqs→2.1/2.2, Tech→3, Architecture→4, Risks→6, Assumptions→9, Criteria→7). Add gaps with `<!-- VERIFICATION -->` comments. Output verification summary table.
4. Append `## Planning Metrics` to PLAN-DRAFT with these exact fields: `confidence: [0-100]`, `clarification_rounds: [N]`, `functional_requirements_count: [N]`, `non_functional_requirements_count: [N]`, `risk_count: [N]`, `phase_count: [N]`, `verification_gaps_found: [N]`. Use exact field names — the metrics pipeline parses this section.
## Templates
+3 -3
View File
@@ -11,7 +11,7 @@ Technical writer transforming planning documents into implementation specs any d
- Follow `./AGENTS.md` if it exists
- Require planning document before proceeding
- Tasks must be specific enough for a developer with NO context
- Use checkbox format `- [ ]` for tracking
- Use checkbox format `- [ ]` for Task items ONLY (not prerequisites, acceptance criteria, or success criteria)
- Verify all planning requirements are covered
- Do NOT implement - documentation only
- If no filesystem access, output in code blocks with file path headers
@@ -80,7 +80,7 @@ If no plan exists and user wants to skip:
- Tech Stack table (exact copy)
- Architecture Pattern and Component Overview (section 4)
- Risks and Mitigations table (section 6)
- Success Criteria checklist (section 7)
- Success Criteria (plain bullet list, no checkboxes) (section 7)
- Phase Checklist (Implementation Phases)
- Quick Reference (Key Files, Environment Variables, External Dependencies)
7. **Write** each `phase-X.md` with detailed tasks
@@ -162,7 +162,7 @@ Sections: Summary (from Executive Summary), Tech Stack table (exact copy from PL
Header: Phase name, Status, Estimated Tasks count.
Sections: Overview (2-3 sentences), Prerequisites checklist, Tasks (grouped by category, `- [ ] **Task X.N:** [Description]` with File path and details), Phase Testing (if enabled), Acceptance Criteria checklist, Notes, Phase Completion Summary (filled after implementation: date, implementer, what was done, files changed, issues).
Sections: Overview (2-3 sentences), Prerequisites (plain bullet list, no checkboxes), Tasks (grouped by category, `- [ ] **Task X.N:** [Description]` with File path and details), Phase Testing (if enabled), Acceptance Criteria (plain bullet list, no checkboxes), Notes, Phase Completion Summary (filled after implementation: date, implementer, what was done, files changed, issues).
## Session End
+13 -9
View File
@@ -51,6 +51,8 @@ Do not proceed without successfully reading overview.md and determining the next
| `[x]` | Complete | Finished and approved |
| `[?]` | Assumed | Couldn't verify, assumed complete |
These checkbox states apply to Task items and the Phase Checklist in overview.md only. Prerequisites and Acceptance Criteria use plain bullets.
**Transitions:**
- `[ ]` -> `[/]`: Agent STARTS phase
- `[/]` -> `[x]`: User APPROVES completed phase
@@ -58,6 +60,8 @@ Do not proceed without successfully reading overview.md and determining the next
Never reset `[/]` to `[ ]`. Started work stays marked for conscious resume decisions.
**Disk Write Rule:** Write task completion status (`[x]`) to `phase-X.md` on disk immediately after each task — never batch status updates. If a session ends unexpectedly, on-disk state must reflect all completed work.
## Parallel Phase Selection
Check overview.md for "Parallel Execution Groups" section. Find workable phases (`[ ]` or `[/]`).
@@ -102,17 +106,17 @@ State: `⚡ [PHASE X: Phase Name] - Marking in-progress and starting`
### 2. Verify Prerequisites
Process Prerequisites section in order:
Process Prerequisites section in order. Prerequisites use plain bullets (no checkboxes).
For each unchecked (`[ ]`) prerequisite:
- **Verifiable:** Check condition -> mark `[x]`
- **Actionable:** Complete action -> mark `[x]`
- **Cannot verify:** Mark `[?]` with assumption note
- **Blocked:** Mark `[!]` with reason, STOP phase
For each prerequisite:
- **Verifiable:** Check condition, note "VERIFIED" inline
- **Actionable:** Complete action, note "VERIFIED" inline
- **Cannot verify:** Note "ASSUMED: [reason]" inline
- **Blocked:** Note "BLOCKED: [reason]" inline, STOP phase
Proceed only when ALL prerequisites are `[x]` or `[?]`.
Proceed only when all prerequisites are verified or assumed.
If any `[!]`, STOP and inform user.
If any blocked, STOP and inform user.
### 3. Implement Tasks Sequentially
@@ -195,7 +199,7 @@ Sections: Summary (2-3 sentences), Tasks Completed (Y/Z + blocked list), Test Re
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 "AI Assisted"`
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
+2 -4
View File
@@ -48,7 +48,8 @@ Complete steps in order. Report progress after each.
**Objective:** Verify all tasks across all phases completed.
1. Open each `phase-X.md` file
2. Verify each task status:
2. Count only `**Task X.N:**` checkbox items (prerequisites and acceptance criteria use plain bullets)
3. Verify each task status:
| Status | Meaning | Action |
|--------|---------|--------|
@@ -299,9 +300,6 @@ specs--completed/
> Specs archived to `specs--completed/<feature-name>/`.
> Thank you for using the Plan2Code workflow!
### Metrics Capture (Contributors)
To help improve plan2code's prompts, run `plan2code-metrics` in the project root.
### Handling Incomplete Implementations
| Completion | Action |
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "Plan2Code",
"version": "1.8.1",
"version": "1.10.0",
"description": "A structured 4-step workflow methodology for AI-assisted software development",
"keywords": [
"ai",