mirror of
https://github.com/jparkerweb/plan2code.git
synced 2026-07-21 18:33:22 -07:00
v1.10.0
This commit is contained in:
@@ -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)
|
||||||
@@ -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
|
||||||
@@ -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/).
|
||||||
@@ -10,280 +10,55 @@ Plan2Code is a structured 4-step workflow methodology for AI-assisted software d
|
|||||||
**Author:** Justin Parker
|
**Author:** Justin Parker
|
||||||
**License:** MIT
|
**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
|
## Architecture
|
||||||
|
|
||||||
```
|
High-level directory structure, key files, workflow prompt inventory, and file naming conventions.
|
||||||
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
|
Details: [Architecture](./.agents-docs/AGENTS-architecture.md)
|
||||||
|
|
||||||
| File | Purpose |
|
## Plan2Code Loop
|
||||||
|------|---------|
|
|
||||||
| `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/`)
|
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 |
|
Details: [Plan2Code Loop](./.agents-docs/AGENTS-plan2code-loop.md)
|
||||||
|------|------|---------|
|
|
||||||
| `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) |
|
|
||||||
|
|
||||||
## 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
|
Details: [Plan2Code Metrics](./.agents-docs/AGENTS-plan2code-metrics.md)
|
||||||
|
|
||||||
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/).
|
|
||||||
|
|
||||||
## Development Commands
|
## Development Commands
|
||||||
|
|
||||||
```bash
|
Build commands, installer menu options, how the installer generates platform-specific files, platform format table, and how to edit workflow prompts.
|
||||||
# Install dev dependencies (sets up husky pre-commit hooks)
|
|
||||||
npm install
|
|
||||||
|
|
||||||
# Run the interactive installer (always interactive — any CLI args are silently ignored)
|
Details: [Development Commands](./.agents-docs/AGENTS-development-commands.md)
|
||||||
node install.js
|
|
||||||
|
|
||||||
# Plan2Code Loop
|
## Code Style & Gotchas
|
||||||
cd plan2code-loop && npm install # First time setup
|
|
||||||
cd plan2code-loop && npm run build # Build the CLI
|
|
||||||
|
|
||||||
# Plan2Code Metrics
|
Language/toolchain conventions for `install.js` vs TypeScript packages, and pitfalls to avoid (version sync, `.gitignore` pre-flight, character limits, User Feedback table format).
|
||||||
cd plan2code-metrics && npm install # First time setup
|
|
||||||
cd plan2code-metrics && npm run build # Build the CLI
|
|
||||||
```
|
|
||||||
|
|
||||||
### Installer Menu Options
|
Details: [Code Style & Gotchas](./.agents-docs/AGENTS-code-style.md)
|
||||||
|
|
||||||
**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 `\|`.
|
|
||||||
|
|
||||||
## Git Commit Messages
|
## 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:
|
- **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 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
|
- 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` include the footer
|
- 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
|
- **Init workflow:** `src/plan2code---init.md` generates AGENTS.md files with a Git Commit Messages section that includes this convention by default
|
||||||
|
|
||||||
## Mascot
|
## Mascot
|
||||||
|
|||||||
@@ -2,6 +2,73 @@
|
|||||||
|
|
||||||
All notable changes to Plan2Code will be documented in this file.
|
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
|
## v1.8.1
|
||||||
|
|
||||||
### ✨ Added
|
### ✨ Added
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ The install script requires **Node.js** (v14 or later). If you don't have Node.j
|
|||||||
- VS Code GitHub Copilot
|
- VS Code GitHub Copilot
|
||||||
- Gemini CLI
|
- Gemini CLI
|
||||||
- Crush
|
- Crush
|
||||||
|
- Pi (pi.dev)
|
||||||
- Amp
|
- Amp
|
||||||
- OpenCode
|
- OpenCode
|
||||||
|
|
||||||
|
|||||||
+28
@@ -293,6 +293,16 @@ const LOCAL_DESTINATIONS = [
|
|||||||
'---'
|
'---'
|
||||||
].join('\n')
|
].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',
|
name: 'Claude Code Skills',
|
||||||
dir: '.claude/skills',
|
dir: '.claude/skills',
|
||||||
@@ -379,6 +389,16 @@ const GLOBAL_DESTINATIONS = [
|
|||||||
'---'
|
'---'
|
||||||
].join('\n')
|
].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',
|
name: 'Claude Code Skills',
|
||||||
dir: '.claude/skills',
|
dir: '.claude/skills',
|
||||||
@@ -565,6 +585,13 @@ const INSTALL_TARGETS = [
|
|||||||
type: 'toml',
|
type: 'toml',
|
||||||
filePattern: /^plan2code-.*\.toml$/,
|
filePattern: /^plan2code-.*\.toml$/,
|
||||||
icon: '◉ '
|
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: 'Continue', dir: '.continue/prompts/', desc: 'Prompts for Continue extension' },
|
||||||
{ name: 'Windsurf', dir: '.windsurf/workflows/', desc: 'Workflows for Windsurf' },
|
{ name: 'Windsurf', dir: '.windsurf/workflows/', desc: 'Workflows for Windsurf' },
|
||||||
{ name: 'Codeium (IntelliJ)', dir: '.codeium/global_workflows/', desc: 'Codeium global workflows' },
|
{ 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) => {
|
localDirs.forEach((item, i) => {
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "plan2code",
|
"name": "plan2code",
|
||||||
"version": "1.8.1",
|
"version": "1.10.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"plan2code": "./install.js"
|
"plan2code": "./install.js"
|
||||||
|
|||||||
@@ -224,7 +224,8 @@ export async function setupSession(
|
|||||||
model: 'default',
|
model: 'default',
|
||||||
maxIterations,
|
maxIterations,
|
||||||
specPath,
|
specPath,
|
||||||
timeout: 30,
|
timeout: 3,
|
||||||
|
maxRetries: 5,
|
||||||
verbose: false,
|
verbose: false,
|
||||||
startedAt: new Date().toISOString(),
|
startedAt: new Date().toISOString(),
|
||||||
currentIteration: 0,
|
currentIteration: 0,
|
||||||
|
|||||||
@@ -64,9 +64,82 @@ export class Controller {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async executeIteration(prompt: string): Promise<AgentExecutionResult> {
|
private computeTimeoutMs(attempt: number): number {
|
||||||
const timeoutMs = this.config.timeout * 60 * 1000;
|
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
|
// Create new AbortController for this iteration
|
||||||
this.abortController = new AbortController();
|
this.abortController = new AbortController();
|
||||||
|
|
||||||
@@ -179,19 +252,21 @@ export class Controller {
|
|||||||
// Build prompt - simple, just spec path and iteration info
|
// Build prompt - simple, just spec path and iteration info
|
||||||
const prompt = await this.buildPrompt();
|
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 {
|
try {
|
||||||
const result = await this.executeIteration(prompt);
|
const retryResult = await this.executeWithRetry(prompt, iterNum);
|
||||||
clearInterval(elapsedInterval);
|
|
||||||
spinner.stop();
|
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
|
// Check if cancelled
|
||||||
if (result.cancelled || this.interrupted) {
|
if (result.cancelled || this.interrupted) {
|
||||||
@@ -390,13 +465,8 @@ export class Controller {
|
|||||||
await this.stateManager.updateSpecHash(this.config.specPath);
|
await this.stateManager.updateSpecHash(this.config.specPath);
|
||||||
this.config.currentIteration++;
|
this.config.currentIteration++;
|
||||||
|
|
||||||
// Handle timeout
|
|
||||||
if (result.timedOut) {
|
|
||||||
logger.warning(`Iteration ${iterNum} timed out, continuing...`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle error (but continue - LLM might recover)
|
// 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
|
// In phase mode, check if any tasks completed despite error exit code
|
||||||
const hasCompletions = isPhaseMode
|
const hasCompletions = isPhaseMode
|
||||||
? checkForAllCompletions(result.stdout + result.stderr).tasks.length > 0
|
? checkForAllCompletions(result.stdout + result.stderr).tasks.length > 0
|
||||||
|
|||||||
@@ -80,7 +80,6 @@ export async function run(): Promise<LoopResult | null> {
|
|||||||
logger.dim(' - config.json (session configuration)');
|
logger.dim(' - config.json (session configuration)');
|
||||||
logger.dim(' - scratchpad.md (LLM-managed notes)');
|
logger.dim(' - scratchpad.md (LLM-managed notes)');
|
||||||
logger.dim(' - iteration.log (history)');
|
logger.dim(' - iteration.log (history)');
|
||||||
logger.dim('Tip: run `plan2code-metrics` to capture run data for prompt improvement.');
|
|
||||||
|
|
||||||
return loopResult;
|
return loopResult;
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -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 \`- [/]\`)
|
2. Find the FIRST phase with an unchecked checkbox (\`- [ ]\` or \`- [/]\`)
|
||||||
3. Read that phase's file (e.g., \`phase-1.md\`)
|
3. Read that phase's file (e.g., \`phase-1.md\`)
|
||||||
4. Check the \`## Prerequisites\` section FIRST
|
4. Check the \`## Prerequisites\` section FIRST
|
||||||
5. Find the FIRST unchecked prerequisite (\`- [ ]\`)
|
5. Find the FIRST unverified prerequisite (no "VERIFIED" or "ASSUMED" annotation)
|
||||||
- If found, that is your task for this iteration
|
- If found, verify/complete it, then annotate "VERIFIED" or "ASSUMED: [reason]" inline
|
||||||
- Verify/complete it, then mark \`[x]\` or \`[?]\`
|
6. Only if ALL prerequisites are verified or assumed, find the FIRST unchecked task (\`- [ ]\`)
|
||||||
6. Only if ALL prerequisites are complete (\`[x]\` or \`[?]\`), find the FIRST unchecked task
|
|
||||||
7. That is your ONE task - implement ONLY that 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)
|
- \`[ ]\` = incomplete/pending (do the FIRST one you find)
|
||||||
- \`[x]\` = complete (skip)
|
- \`[x]\` = complete (skip)
|
||||||
- \`[?]\` = assumed complete, couldn't verify (skip)
|
- \`[?]\` = assumed complete, couldn't verify (skip)
|
||||||
- \`[!]\` = blocked (skip)
|
- \`[!]\` = blocked (skip)
|
||||||
|
|
||||||
|
Prerequisites use plain bullets with inline annotations, not checkboxes.
|
||||||
|
|
||||||
## Implementation Steps
|
## Implementation Steps
|
||||||
1. Read and understand the single task
|
1. Read and understand the single task
|
||||||
2. Implement it completely
|
2. Implement it completely
|
||||||
@@ -66,7 +67,10 @@ Use only when ALL phases in overview.md are marked complete.
|
|||||||
|
|
||||||
## Scratchpad Management
|
## 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
|
- Task completed and Phase item reference
|
||||||
- Key decisions made and reasoning
|
- Key decisions made and reasoning
|
||||||
- Files changed
|
- 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 \`- [/]\`)
|
2. Find the FIRST phase with an unchecked checkbox (\`- [ ]\` or \`- [/]\`)
|
||||||
3. Read that phase's file (e.g., \`phase-1.md\`)
|
3. Read that phase's file (e.g., \`phase-1.md\`)
|
||||||
4. Check the \`## Prerequisites\` section FIRST
|
4. Check the \`## Prerequisites\` section FIRST
|
||||||
5. Complete ALL unchecked prerequisites (\`- [ ]\`) first, in order
|
5. Verify ALL unverified prerequisites first, in order
|
||||||
- Verify/complete each, then mark \`[x]\` or \`[?]\`
|
- Annotate each "VERIFIED" or "ASSUMED: [reason]" inline
|
||||||
6. Once ALL prerequisites are complete, implement ALL unchecked tasks in order
|
6. Once ALL prerequisites are verified, implement ALL unchecked tasks in order
|
||||||
7. Continue until every task in the phase is marked \`[x]\`
|
7. Continue until every task in the phase is marked \`[x]\`
|
||||||
|
|
||||||
## Checkbox States
|
## Checkbox States (Task items only)
|
||||||
- \`[ ]\` = incomplete/pending
|
- \`[ ]\` = incomplete/pending
|
||||||
- \`[x]\` = complete (skip)
|
- \`[x]\` = complete (skip)
|
||||||
- \`[?]\` = assumed complete, couldn't verify (skip)
|
- \`[?]\` = assumed complete, couldn't verify (skip)
|
||||||
- \`[!]\` = blocked (skip, note in scratchpad)
|
- \`[!]\` = blocked (skip, note in scratchpad)
|
||||||
|
|
||||||
|
Prerequisites use plain bullets with inline annotations, not checkboxes.
|
||||||
|
|
||||||
## Implementation Steps (repeat for EACH task in the phase)
|
## Implementation Steps (repeat for EACH task in the phase)
|
||||||
1. Read and understand the task
|
1. Read and understand the task
|
||||||
2. Implement it completely
|
2. Implement it completely
|
||||||
@@ -139,9 +145,13 @@ git commit -m "<commit message>"
|
|||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
||||||
**Commit message format:**
|
**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)
|
git add -A
|
||||||
- ALWAYS include the "AI Assisted" footer as the final \`-m\` flag
|
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.
|
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
|
## 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
|
- Task completed and Phase item reference
|
||||||
- Key decisions made and reasoning
|
- Key decisions made and reasoning
|
||||||
- Files changed
|
- Files changed
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ export interface SessionConfig {
|
|||||||
agent: string; // "claude-code" | "copilot-cli"
|
agent: string; // "claude-code" | "copilot-cli"
|
||||||
model: string; // Selected model
|
model: string; // Selected model
|
||||||
maxIterations: number; // 5-50
|
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;
|
verbose: boolean;
|
||||||
specPath: string; // Path to the spec directory
|
specPath: string; // Path to the spec directory
|
||||||
startedAt: string; // ISO timestamp
|
startedAt: string; // ISO timestamp
|
||||||
@@ -26,7 +27,8 @@ export type SessionState = 'new' | 'continue' | 'changed';
|
|||||||
|
|
||||||
export const DEFAULT_CONFIG: Partial<SessionConfig> = {
|
export const DEFAULT_CONFIG: Partial<SessionConfig> = {
|
||||||
maxIterations: 100,
|
maxIterations: 100,
|
||||||
timeout: 30,
|
timeout: 3,
|
||||||
|
maxRetries: 5,
|
||||||
verbose: false,
|
verbose: false,
|
||||||
currentIteration: 0,
|
currentIteration: 0,
|
||||||
loopMode: 'task',
|
loopMode: 'task',
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ export async function createTaskCommit(options: GitCommitOptions): Promise<boole
|
|||||||
// Build commit message
|
// Build commit message
|
||||||
let commitMessage = taskName;
|
let commitMessage = taskName;
|
||||||
if (jiraTicketId) {
|
if (jiraTicketId) {
|
||||||
commitMessage = `${taskName}\n\n${jiraTicketId}\n\nAI Assisted`;
|
commitMessage = `${taskName}\n\n${jiraTicketId}\nAI Assisted`;
|
||||||
} else {
|
} else {
|
||||||
commitMessage = `${taskName}\n\nAI Assisted`;
|
commitMessage = `${taskName}\n\nAI Assisted`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export default defineConfig({
|
|||||||
index: 'src/index.ts',
|
index: 'src/index.ts',
|
||||||
},
|
},
|
||||||
format: ['esm'],
|
format: ['esm'],
|
||||||
dts: true,
|
dts: false,
|
||||||
clean: true,
|
clean: true,
|
||||||
sourcemap: true,
|
sourcemap: true,
|
||||||
banner: {
|
banner: {
|
||||||
|
|||||||
@@ -99,13 +99,9 @@ function buildCohort(runs: RunMetrics[], cohortKey: string): CohortMetrics {
|
|||||||
const avgReqCoverage = avg(runs.map(r => r.step2_document.requirement_coverage_percent));
|
const avgReqCoverage = avg(runs.map(r => r.step2_document.requirement_coverage_percent));
|
||||||
const avgVerifItems = avg(runs.map(r => r.step2_document.verification_items_added));
|
const avgVerifItems = avg(runs.map(r => r.step2_document.verification_items_added));
|
||||||
|
|
||||||
// Step 3 (loop only)
|
// Step 3 averages
|
||||||
const loopRuns = runs.filter(r => r.step3_implement.used_loop_mode);
|
const avgCompletionRate = avg(runs.map(r => r.step3_implement.task_completion_rate));
|
||||||
const avgCompletionRate = avg(loopRuns.map(r => r.step3_implement.task_completion_rate));
|
const avgBlockerCount = avg(runs.map(r => r.step3_implement.blocker_count));
|
||||||
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 4 averages
|
// Step 4 averages
|
||||||
const avgCompletionAtAudit = avg(runs.map(r => r.step4_finalize.completion_rate_at_audit));
|
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_requirement_coverage_percent: avgReqCoverage,
|
||||||
avg_verification_items_added: avgVerifItems,
|
avg_verification_items_added: avgVerifItems,
|
||||||
|
|
||||||
loop_run_count: loopRuns.length,
|
|
||||||
avg_task_completion_rate: avgCompletionRate,
|
avg_task_completion_rate: avgCompletionRate,
|
||||||
avg_blocker_count: avgBlockerCount,
|
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_completion_rate_at_audit: avgCompletionAtAudit,
|
||||||
avg_verification_failures_found: avgVerifFailures,
|
avg_verification_failures_found: avgVerifFailures,
|
||||||
|
|||||||
@@ -57,12 +57,12 @@ export interface AnalyzerOptions {
|
|||||||
aggregatedPath: string; // Path to aggregated.json
|
aggregatedPath: string; // Path to aggregated.json
|
||||||
plan2codeRoot: string; // Path to plan2code repo root
|
plan2codeRoot: string; // Path to plan2code repo root
|
||||||
proposalsDir: string; // Where to save diagnosis output
|
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)
|
agent?: AgentType; // Agent to use (default: claude-code)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function runAnalysis(opts: AnalyzerOptions): Promise<string> {
|
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
|
// Load aggregated metrics
|
||||||
let aggregated: AggregatedMetrics | null = null;
|
let aggregated: AggregatedMetrics | null = null;
|
||||||
@@ -102,7 +102,7 @@ export async function runAnalysis(opts: AnalyzerOptions): Promise<string> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Invoke Claude
|
// 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');
|
console.log('This may take a minute...\n');
|
||||||
|
|
||||||
let diagnosisContent: string;
|
let diagnosisContent: string;
|
||||||
|
|||||||
+227
-31
@@ -5,6 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
import os from 'os';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { select, input, confirm } from '@inquirer/prompts';
|
import { select, input, confirm } from '@inquirer/prompts';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
@@ -18,6 +19,33 @@ import type { AggregatedMetrics, CohortMetrics } from './types.js';
|
|||||||
import { METRIC_TARGETS } from './types.js';
|
import { METRIC_TARGETS } from './types.js';
|
||||||
import { AGENTS, type AgentType } from './invoke-llm.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 ──────────────────────────────────────────────────────────────────
|
// ── Defaults ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
const DEFAULT_METRICS_DIR = '.plan2code-metrics';
|
const DEFAULT_METRICS_DIR = '.plan2code-metrics';
|
||||||
@@ -25,7 +53,7 @@ const DEFAULT_RUNS_SUBDIR = 'runs';
|
|||||||
const DEFAULT_AGGREGATED_FILE = 'aggregated.json';
|
const DEFAULT_AGGREGATED_FILE = 'aggregated.json';
|
||||||
const DEFAULT_PROPOSALS_SUBDIR = 'proposals';
|
const DEFAULT_PROPOSALS_SUBDIR = 'proposals';
|
||||||
|
|
||||||
function getMetricsDirs(baseDir = process.cwd()) {
|
function getMetricsDirs(baseDir = resolvedProjectRoot) {
|
||||||
const metricsDir = path.join(baseDir, DEFAULT_METRICS_DIR);
|
const metricsDir = path.join(baseDir, DEFAULT_METRICS_DIR);
|
||||||
return {
|
return {
|
||||||
metricsDir,
|
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 })),
|
choices: Object.values(AGENTS).map(a => ({ name: a.displayName, value: a.name })),
|
||||||
});
|
});
|
||||||
|
|
||||||
const agentDef = AGENTS[agentChoice];
|
return { agent: agentChoice, model: AGENTS[agentChoice].defaultModel };
|
||||||
const model = await select({
|
|
||||||
message: 'AI model:',
|
|
||||||
choices: agentDef.models.map(m => ({ name: m.label, value: m.value })),
|
|
||||||
});
|
|
||||||
|
|
||||||
return { agent: agentChoice, model };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Flow: Collect metrics ─────────────────────────────────────────────────────
|
// ── Flow: Collect metrics ─────────────────────────────────────────────────────
|
||||||
@@ -107,8 +129,8 @@ async function flowCollect(): Promise<void> {
|
|||||||
const sourceChoice = await select({
|
const sourceChoice = await select({
|
||||||
message: 'Where is the completed project spec?',
|
message: 'Where is the completed project spec?',
|
||||||
choices: [
|
choices: [
|
||||||
{ name: 'Active spec directory (specs/<feature-name>/)', value: 'active' },
|
|
||||||
{ name: 'Archived spec (specs--completed/<feature-name>/)', value: 'archived' },
|
{ name: 'Archived spec (specs--completed/<feature-name>/)', value: 'archived' },
|
||||||
|
{ name: 'Active spec directory (specs/<feature-name>/)', value: 'active' },
|
||||||
{ name: 'Custom path', value: 'custom' },
|
{ name: 'Custom path', value: 'custom' },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@@ -118,10 +140,10 @@ async function flowCollect(): Promise<void> {
|
|||||||
|
|
||||||
if (sourceChoice === 'active' || sourceChoice === 'archived') {
|
if (sourceChoice === 'active' || sourceChoice === 'archived') {
|
||||||
const baseSubdir = sourceChoice === 'active' ? 'specs' : 'specs--completed';
|
const baseSubdir = sourceChoice === 'active' ? 'specs' : 'specs--completed';
|
||||||
const baseDir = path.join(process.cwd(), baseSubdir);
|
const baseDir = path.join(resolvedProjectRoot, baseSubdir);
|
||||||
|
|
||||||
if (!fs.existsSync(baseDir)) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,7 +174,7 @@ async function flowCollect(): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Detect plan2code root
|
// Detect plan2code root
|
||||||
const plan2codeRoot = detectPlan2CodeRoot() ?? process.cwd();
|
const plan2codeRoot = resolvedPlan2CodeRoot;
|
||||||
const plan2codeVersion = detectPlan2CodeVersion(plan2codeRoot);
|
const plan2codeVersion = detectPlan2CodeVersion(plan2codeRoot);
|
||||||
|
|
||||||
const { runsDir, aggregatedPath } = getMetricsDirs();
|
const { runsDir, aggregatedPath } = getMetricsDirs();
|
||||||
@@ -179,7 +201,7 @@ async function flowCollect(): Promise<void> {
|
|||||||
console.log(chalk.bold('Collected:'));
|
console.log(chalk.bold('Collected:'));
|
||||||
console.log(` Step 1 (Plan): ${metrics.step1_plan.present ? chalk.green('✓') : chalk.gray('—')}`);
|
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 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(` 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('—')}`);
|
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)}`);
|
console.log(` avg_verification_items: ${metricStatus('avg_verification_items_added', cohort.avg_verification_items_added)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 3 metrics (loop only)
|
// Step 3 metrics
|
||||||
if (cohort.loop_run_count > 0) {
|
if (cohort.avg_task_completion_rate != null || cohort.avg_blocker_count != null) {
|
||||||
console.log(chalk.bold(` Step 3 (Implement) — ${cohort.loop_run_count} loop run(s):`));
|
console.log(chalk.bold(' Step 3 (Implement):'));
|
||||||
if (cohort.avg_task_completion_rate != null)
|
if (cohort.avg_task_completion_rate != null)
|
||||||
console.log(` avg_task_completion_rate: ${metricStatus('avg_task_completion_rate', cohort.avg_task_completion_rate)}`);
|
console.log(` avg_task_completion_rate: ${metricStatus('avg_task_completion_rate', cohort.avg_task_completion_rate)}`);
|
||||||
if (cohort.avg_blocker_count != null)
|
if (cohort.avg_blocker_count != null)
|
||||||
console.log(` avg_blocker_count: ${metricStatus('avg_blocker_count', cohort.avg_blocker_count)}`);
|
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
|
// Step 4 metrics
|
||||||
@@ -402,7 +422,7 @@ async function flowRunAnalysis(): Promise<string | null> {
|
|||||||
console.log(chalk.bold.cyan('── Run Analysis (Diagnose Weak Steps) ──'));
|
console.log(chalk.bold.cyan('── Run Analysis (Diagnose Weak Steps) ──'));
|
||||||
|
|
||||||
const { aggregatedPath, proposalsDir } = getMetricsDirs();
|
const { aggregatedPath, proposalsDir } = getMetricsDirs();
|
||||||
const plan2codeRoot = detectPlan2CodeRoot() ?? process.cwd();
|
const plan2codeRoot = resolvedPlan2CodeRoot;
|
||||||
|
|
||||||
const aggregated = loadAggregated(aggregatedPath);
|
const aggregated = loadAggregated(aggregatedPath);
|
||||||
if (!aggregated || aggregated.total_runs === 0) {
|
if (!aggregated || aggregated.total_runs === 0) {
|
||||||
@@ -460,7 +480,7 @@ async function flowGenerateProposal(): Promise<void> {
|
|||||||
console.log(chalk.bold.cyan('── Generate Improvement Proposal ──'));
|
console.log(chalk.bold.cyan('── Generate Improvement Proposal ──'));
|
||||||
|
|
||||||
const { aggregatedPath, proposalsDir, runsDir } = getMetricsDirs();
|
const { aggregatedPath, proposalsDir, runsDir } = getMetricsDirs();
|
||||||
const plan2codeRoot = detectPlan2CodeRoot() ?? process.cwd();
|
const plan2codeRoot = resolvedPlan2CodeRoot;
|
||||||
|
|
||||||
// Find diagnosis files
|
// Find diagnosis files
|
||||||
let diagFiles: string[] = [];
|
let diagFiles: string[] = [];
|
||||||
@@ -540,7 +560,7 @@ async function flowReviewAndApply(): Promise<void> {
|
|||||||
console.log(chalk.bold.cyan('── Review and Apply a Proposal ──'));
|
console.log(chalk.bold.cyan('── Review and Apply a Proposal ──'));
|
||||||
|
|
||||||
const { proposalsDir } = getMetricsDirs();
|
const { proposalsDir } = getMetricsDirs();
|
||||||
const plan2codeRoot = detectPlan2CodeRoot() ?? process.cwd();
|
const plan2codeRoot = resolvedPlan2CodeRoot;
|
||||||
|
|
||||||
if (!fs.existsSync(proposalsDir)) {
|
if (!fs.existsSync(proposalsDir)) {
|
||||||
console.log(chalk.yellow('No proposals directory found. Generate a proposal first.'));
|
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 ─────────────────────────────────────────────────────────────────
|
// ── Main menu ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
export async function runCLI(): Promise<void> {
|
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(chalk.gray('Recursive self-improvement toolchain for plan2code contributors'));
|
||||||
console.log();
|
console.log();
|
||||||
|
|
||||||
// Check if we're in (or near) a plan2code repo
|
// ── Prompt for paths ────────────────────────────────────────────────────────
|
||||||
const plan2codeRoot = detectPlan2CodeRoot();
|
|
||||||
if (!plan2codeRoot) {
|
const userConfig = loadUserConfig();
|
||||||
console.log(chalk.yellow('⚠ Could not detect plan2code repository (src/plan2code-*.md not found nearby).'));
|
const savedRoot = userConfig.plan2codeRepoPath && fs.existsSync(userConfig.plan2codeRepoPath)
|
||||||
console.log(chalk.gray(' Prompt version hashing and analysis will be limited.'));
|
? userConfig.plan2codeRepoPath
|
||||||
console.log();
|
: null;
|
||||||
} else {
|
const detectedRoot = savedRoot ?? detectPlan2CodeRoot();
|
||||||
const version = detectPlan2CodeVersion(plan2codeRoot);
|
|
||||||
console.log(chalk.gray(`plan2code root: ${plan2codeRoot} (v${version})`));
|
const s2cPath = await input({
|
||||||
console.log();
|
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;
|
let continueLoop = true;
|
||||||
while (continueLoop) {
|
while (continueLoop) {
|
||||||
const action = await select({
|
const action = await select({
|
||||||
@@ -608,6 +800,7 @@ export async function runCLI(): Promise<void> {
|
|||||||
{ name: 'Run analysis (diagnose weak steps)', value: 'analyze' },
|
{ name: 'Run analysis (diagnose weak steps)', value: 'analyze' },
|
||||||
{ name: 'Generate improvement proposal', value: 'propose' },
|
{ name: 'Generate improvement proposal', value: 'propose' },
|
||||||
{ name: 'Review and apply a proposal', value: 'apply' },
|
{ name: 'Review and apply a proposal', value: 'apply' },
|
||||||
|
{ name: chalk.red('Delete metrics data'), value: 'delete' },
|
||||||
{ name: 'Exit', value: 'exit' },
|
{ name: 'Exit', value: 'exit' },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@@ -631,6 +824,9 @@ export async function runCLI(): Promise<void> {
|
|||||||
case 'apply':
|
case 'apply':
|
||||||
await flowReviewAndApply();
|
await flowReviewAndApply();
|
||||||
break;
|
break;
|
||||||
|
case 'delete':
|
||||||
|
await flowDelete();
|
||||||
|
break;
|
||||||
case 'exit':
|
case 'exit':
|
||||||
continueLoop = false;
|
continueLoop = false;
|
||||||
break;
|
break;
|
||||||
|
|||||||
+112
-144
@@ -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 {
|
function generateRunId(): string {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const ts = now.toISOString().replace(/[-:T]/g, '').slice(0, 14);
|
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 };
|
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
|
// Detect Parallel Execution Groups table
|
||||||
const parallelGroups = (overview.match(/Parallel\s+Execution\s+Group/gi) ?? []).length;
|
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 phaseCount = 0;
|
||||||
let tasksPerPhase: number[] = [];
|
let tasksPerPhase: number[] = [];
|
||||||
try {
|
try {
|
||||||
@@ -190,13 +215,31 @@ export function collectStep2(specDir: string): Step2DocumentMetrics {
|
|||||||
phaseCount = phaseFiles.length;
|
phaseCount = phaseFiles.length;
|
||||||
for (const pf of phaseFiles) {
|
for (const pf of phaseFiles) {
|
||||||
const content = readFileSafe(path.join(specDir, pf)) ?? '';
|
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);
|
tasksPerPhase.push(count);
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// leave empty
|
// 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
|
// Requirement coverage: look for coverage percentage in overview
|
||||||
let reqCoverage: number | null = null;
|
let reqCoverage: number | null = null;
|
||||||
const covMatch = overview.match(/[Cc]overage[:\s]+(\d{1,3})%/);
|
const covMatch = overview.match(/[Cc]overage[:\s]+(\d{1,3})%/);
|
||||||
@@ -207,7 +250,7 @@ export function collectStep2(specDir: string): Step2DocumentMetrics {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
present: true,
|
present: true,
|
||||||
total_tasks: allTasks || null,
|
total_tasks: totalTasks || null,
|
||||||
tasks_per_phase: tasksPerPhase.length > 0 ? tasksPerPhase : null,
|
tasks_per_phase: tasksPerPhase.length > 0 ? tasksPerPhase : null,
|
||||||
phase_count: phaseCount || null,
|
phase_count: phaseCount || null,
|
||||||
parallel_groups_identified: parallelGroups || 0,
|
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 {
|
export function collectStep3(specDir: string): Step3ImplementMetrics {
|
||||||
const loopDir = path.join(specDir, '.plan2code-loop');
|
// Discover phase-*.md files
|
||||||
const iterLogPath = path.join(loopDir, 'iteration.log');
|
let phaseFiles: string[] = [];
|
||||||
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 };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse config.json for loop mode
|
|
||||||
let loopMode: 'task' | 'phase' | null = null;
|
|
||||||
const configContent = readFileSafe(configPath);
|
|
||||||
if (configContent) {
|
|
||||||
try {
|
try {
|
||||||
const config = JSON.parse(configContent);
|
const files = fs.readdirSync(specDir);
|
||||||
loopMode = config.loopMode ?? null;
|
phaseFiles = files
|
||||||
|
.filter(f => /^phase-\d+\.md$/i.test(f))
|
||||||
|
.sort();
|
||||||
} catch {
|
} catch {
|
||||||
// ignore
|
// leave empty
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse NDJSON iteration.log
|
// Read overview.md for Completion Summary
|
||||||
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)
|
|
||||||
const overviewPath = path.join(specDir, 'overview.md');
|
const overviewPath = path.join(specDir, 'overview.md');
|
||||||
const overview = readFileSafe(overviewPath);
|
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 tasksTotal: number | null = null;
|
||||||
let tasksCompleted: number | null = null;
|
let tasksCompleted: number | null = null;
|
||||||
if (overview) {
|
if (summaryCount) {
|
||||||
tasksTotal = (overview.match(/^\s*-\s+\[[ x!/?]\]/gm) ?? []).length || null;
|
tasksTotal = summaryCount.total;
|
||||||
tasksCompleted = (overview.match(/^\s*-\s+\[x\]/gm) ?? []).length || null;
|
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
|
const taskCompletionRate = tasksTotal && tasksTotal > 0 && tasksCompleted != null
|
||||||
@@ -342,17 +328,10 @@ export function collectStep3(specDir: string): Step3ImplementMetrics {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
present: true,
|
present: true,
|
||||||
used_loop_mode: true,
|
|
||||||
loop_mode: loopMode,
|
|
||||||
task_completion_rate: taskCompletionRate,
|
task_completion_rate: taskCompletionRate,
|
||||||
tasks_completed: tasksCompleted || null,
|
tasks_completed: tasksCompleted,
|
||||||
tasks_total: tasksTotal,
|
tasks_total: tasksTotal,
|
||||||
blocker_count: blockerCount,
|
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 };
|
archival_succeeded: archivalSucceeded };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Completion rate: completed tasks / total tasks
|
// Completion rate: prefer Completion Summary, fall back to stripped overview checkboxes
|
||||||
const totalTasks = (overview.match(/^\s*-\s+\[[ x!/?]\]/gm) ?? []).length;
|
let completionRate: number | null = null;
|
||||||
const completedTasks = (overview.match(/^\s*-\s+\[x\]/gm) ?? []).length;
|
const summaryCount = parseCompletionSummaryTaskCount(overview);
|
||||||
const completionRate = totalTasks > 0 ? completedTasks / totalTasks : null;
|
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
|
// Verification failures: look for [!] tasks or "verification failed" text
|
||||||
const blockedTasks = (overview.match(/^\s*-\s+\[!\]/gm) ?? []).length;
|
const blockedTasks = (overview.match(/^\s*-\s+\[!\]/gm) ?? []).length;
|
||||||
@@ -476,24 +462,6 @@ export async function collectRun(opts: CollectorOptions): Promise<RunMetrics> {
|
|||||||
// leave null
|
// 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 = {
|
const metrics: RunMetrics = {
|
||||||
schema_version: '1.0',
|
schema_version: '1.0',
|
||||||
run_id: runId,
|
run_id: runId,
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ export interface ImproverResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function generateImprovement(opts: ImproverOptions): Promise<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
|
// Load diagnosis
|
||||||
let diagnosisContent: string;
|
let diagnosisContent: string;
|
||||||
@@ -193,7 +193,7 @@ export async function generateImprovement(opts: ImproverOptions): Promise<Improv
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Invoke Claude
|
// 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');
|
console.log('This may take a minute...\n');
|
||||||
|
|
||||||
let aiResponse: string;
|
let aiResponse: string;
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export interface AgentDef {
|
|||||||
name: AgentType;
|
name: AgentType;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
command: string;
|
command: string;
|
||||||
models: Array<{ value: string; label: string }>;
|
defaultModel: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AGENTS: Record<AgentType, AgentDef> = {
|
export const AGENTS: Record<AgentType, AgentDef> = {
|
||||||
@@ -26,23 +26,13 @@ export const AGENTS: Record<AgentType, AgentDef> = {
|
|||||||
name: 'claude-code',
|
name: 'claude-code',
|
||||||
displayName: 'Claude Code',
|
displayName: 'Claude Code',
|
||||||
command: 'claude',
|
command: 'claude',
|
||||||
models: [
|
defaultModel: 'default',
|
||||||
{ value: 'claude-opus-4-6', label: 'Claude Opus 4.6 (Recommended)' },
|
|
||||||
{ value: 'claude-sonnet-4-6', label: 'Claude Sonnet 4.6 (faster)' },
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
'copilot-cli': {
|
'copilot-cli': {
|
||||||
name: 'copilot-cli',
|
name: 'copilot-cli',
|
||||||
displayName: 'GitHub Copilot CLI',
|
displayName: 'GitHub Copilot CLI',
|
||||||
command: 'copilot',
|
command: 'copilot',
|
||||||
models: [
|
defaultModel: 'claude-sonnet-4',
|
||||||
{ 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' },
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -69,7 +59,8 @@ export async function invokeLLM(opts: InvokeLLMOptions): Promise<string> {
|
|||||||
'--print',
|
'--print',
|
||||||
'--dangerously-skip-permissions',
|
'--dangerously-skip-permissions',
|
||||||
];
|
];
|
||||||
if (model) {
|
// Only add --model if not using default
|
||||||
|
if (model && model !== 'default') {
|
||||||
args.push('--model', model);
|
args.push('--model', model);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,7 +75,8 @@ export async function invokeLLM(opts: InvokeLLMOptions): Promise<string> {
|
|||||||
} else {
|
} else {
|
||||||
// Copilot CLI: pipe prompt via stdin string
|
// Copilot CLI: pipe prompt via stdin string
|
||||||
const args: string[] = [];
|
const args: string[] = [];
|
||||||
if (model) {
|
// Only add --model if not using default
|
||||||
|
if (model && model !== 'default') {
|
||||||
args.push('--model', model);
|
args.push('--model', model);
|
||||||
}
|
}
|
||||||
args.push('--allow-all-tools', '-s');
|
args.push('--allow-all-tools', '-s');
|
||||||
|
|||||||
@@ -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_verification_items_added (Step 2) | ≤ 1.5 | lower is better |
|
||||||
| avg_task_completion_rate (Step 3) | ≥ 0.95 | higher is better |
|
| avg_task_completion_rate (Step 3) | ≥ 0.95 | higher is better |
|
||||||
| avg_blocker_count (Step 3) | ≤ 1.5 | lower 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 |
|
| avg_verification_failures_found (Step 4) | ≤ 1.0 | lower is better |
|
||||||
| archival_success_rate (Step 4) | ≥ 0.99 | higher 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) |
|
| avg_user_rating (Feedback) | ≥ 7.0 | higher is better (1-10 scale, null if no feedback) |
|
||||||
|
|||||||
@@ -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.
|
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.
|
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.
|
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 `#`).
|
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.
|
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.
|
- 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 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 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_confidence`: Strengthen the confidence calculation instructions with clearer rubrics.
|
||||||
- For low `avg_parallel_groups`: Add explicit guidance for identifying parallel tasks in Step 2.
|
- For low `avg_parallel_groups`: Add explicit guidance for identifying parallel tasks in Step 2.
|
||||||
|
|||||||
@@ -41,17 +41,10 @@ export interface Step2DocumentMetrics {
|
|||||||
|
|
||||||
export interface Step3ImplementMetrics {
|
export interface Step3ImplementMetrics {
|
||||||
present: boolean;
|
present: boolean;
|
||||||
used_loop_mode: boolean;
|
|
||||||
loop_mode: 'task' | 'phase' | null;
|
|
||||||
task_completion_rate: number | null;
|
task_completion_rate: number | null;
|
||||||
tasks_completed: number | null;
|
tasks_completed: number | null;
|
||||||
tasks_total: number | null;
|
tasks_total: number | null;
|
||||||
blocker_count: 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 {
|
export interface Step4FinalizeMetrics {
|
||||||
@@ -132,13 +125,9 @@ export interface CohortMetrics {
|
|||||||
avg_requirement_coverage_percent: number | null;
|
avg_requirement_coverage_percent: number | null;
|
||||||
avg_verification_items_added: number | null;
|
avg_verification_items_added: number | null;
|
||||||
|
|
||||||
// Step 3 averages (loop only)
|
// Step 3 averages
|
||||||
loop_run_count: number;
|
|
||||||
avg_task_completion_rate: number | null;
|
avg_task_completion_rate: number | null;
|
||||||
avg_blocker_count: 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
|
// Step 4 averages
|
||||||
avg_completion_rate_at_audit: number | null;
|
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_verification_items_added: { target: 1.5, direction: 'lte' as const },
|
||||||
avg_task_completion_rate: { target: 0.95, direction: 'gte' as const },
|
avg_task_completion_rate: { target: 0.95, direction: 'gte' as const },
|
||||||
avg_blocker_count: { target: 1.5, direction: 'lte' 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 },
|
avg_verification_failures_found: { target: 1.0, direction: 'lte' as const },
|
||||||
archival_success_rate: { target: 0.99, direction: 'gte' as const },
|
archival_success_rate: { target: 0.99, direction: 'gte' as const },
|
||||||
avg_user_rating: { target: 7.0, direction: 'gte' as const },
|
avg_user_rating: { target: 7.0, direction: 'gte' as const },
|
||||||
|
|||||||
@@ -24,6 +24,42 @@ Check if `AGENTS.md` exists in project root.
|
|||||||
- Main sections (bullets)
|
- Main sections (bullets)
|
||||||
- Current line count
|
- 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.
|
Proceed to Step 2.
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -94,7 +130,12 @@ Present the user with update options:
|
|||||||
|
|
||||||
## Step 5: Confirm & Apply
|
## 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]
|
> **Section:** [name]
|
||||||
> **Change:** [description]
|
> **Change:** [description]
|
||||||
> ```
|
> ```
|
||||||
@@ -105,6 +146,13 @@ Before changes:
|
|||||||
- "adjust" -> ask what to change, repeat
|
- "adjust" -> ask what to change, repeat
|
||||||
- "yes" -> apply edit, insert in appropriate section (create if needed), preserve structure
|
- "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
|
## Step 6: Summary & Next
|
||||||
@@ -112,8 +160,14 @@ Before changes:
|
|||||||
After applying:
|
After applying:
|
||||||
> "Done! Changed:"
|
> "Done! Changed:"
|
||||||
> - [Summary]
|
> - [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?"
|
> "Add anything else?"
|
||||||
|
|
||||||
If yes, return to Step 3. If done, proceed to Step 7.
|
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
|
- Environment variables
|
||||||
- Testing patterns
|
- Testing patterns
|
||||||
- Deployment guides
|
- Deployment guides
|
||||||
|
- Section details in .agents-docs/
|
||||||
```
|
```
|
||||||
|
|
||||||
**For directory configs** (`.cursor/rules/`, `.windsurf/rules/`): Delete existing `.md` files, create single `reference.md`.
|
**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
|
6. **No duplication** - Check for similar content first
|
||||||
7. **Group logically** - Place near related content
|
7. **Group logically** - Place near related content
|
||||||
8. **Be specific** - Include exact commands, paths, names
|
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
|
- Project structure
|
||||||
- API conventions
|
- API conventions
|
||||||
Line count: 127
|
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:
|
Recent work on auth flow. Worth documenting:
|
||||||
- JWT refresh token must validate before access token
|
- JWT refresh token must validate before access token
|
||||||
|
|||||||
+40
-1
@@ -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
|
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
|
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
|
## 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
|
## AI Agent File Sync
|
||||||
|
|
||||||
After creating `AGENTS.md`, check for these files and offer to replace with references:
|
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
|
- Environment variables
|
||||||
- Testing patterns
|
- Testing patterns
|
||||||
- Deployment guides
|
- Deployment guides
|
||||||
|
- Section details in .agents-docs/
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -221,6 +221,7 @@ State assessment and ask user to confirm.
|
|||||||
1. Save PLAN-CONVERSATION (see template below) — transcript + decision summary tables
|
1. Save PLAN-CONVERSATION (see template below) — transcript + decision summary tables
|
||||||
2. Create PLAN-DRAFT (see template below) using same date
|
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.
|
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
|
## Templates
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ Technical writer transforming planning documents into implementation specs any d
|
|||||||
- Follow `./AGENTS.md` if it exists
|
- Follow `./AGENTS.md` if it exists
|
||||||
- Require planning document before proceeding
|
- Require planning document before proceeding
|
||||||
- Tasks must be specific enough for a developer with NO context
|
- 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
|
- Verify all planning requirements are covered
|
||||||
- Do NOT implement - documentation only
|
- Do NOT implement - documentation only
|
||||||
- If no filesystem access, output in code blocks with file path headers
|
- 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)
|
- Tech Stack table (exact copy)
|
||||||
- Architecture Pattern and Component Overview (section 4)
|
- Architecture Pattern and Component Overview (section 4)
|
||||||
- Risks and Mitigations table (section 6)
|
- Risks and Mitigations table (section 6)
|
||||||
- Success Criteria checklist (section 7)
|
- Success Criteria (plain bullet list, no checkboxes) (section 7)
|
||||||
- Phase Checklist (Implementation Phases)
|
- Phase Checklist (Implementation Phases)
|
||||||
- Quick Reference (Key Files, Environment Variables, External Dependencies)
|
- Quick Reference (Key Files, Environment Variables, External Dependencies)
|
||||||
7. **Write** each `phase-X.md` with detailed tasks
|
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.
|
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
|
## Session End
|
||||||
|
|
||||||
|
|||||||
@@ -51,6 +51,8 @@ Do not proceed without successfully reading overview.md and determining the next
|
|||||||
| `[x]` | Complete | Finished and approved |
|
| `[x]` | Complete | Finished and approved |
|
||||||
| `[?]` | Assumed | Couldn't verify, assumed complete |
|
| `[?]` | 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:**
|
**Transitions:**
|
||||||
- `[ ]` -> `[/]`: Agent STARTS phase
|
- `[ ]` -> `[/]`: Agent STARTS phase
|
||||||
- `[/]` -> `[x]`: User APPROVES completed 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.
|
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
|
## Parallel Phase Selection
|
||||||
|
|
||||||
Check overview.md for "Parallel Execution Groups" section. Find workable phases (`[ ]` or `[/]`).
|
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
|
### 2. Verify Prerequisites
|
||||||
|
|
||||||
Process Prerequisites section in order:
|
Process Prerequisites section in order. Prerequisites use plain bullets (no checkboxes).
|
||||||
|
|
||||||
For each unchecked (`[ ]`) prerequisite:
|
For each prerequisite:
|
||||||
- **Verifiable:** Check condition -> mark `[x]`
|
- **Verifiable:** Check condition, note "VERIFIED" inline
|
||||||
- **Actionable:** Complete action -> mark `[x]`
|
- **Actionable:** Complete action, note "VERIFIED" inline
|
||||||
- **Cannot verify:** Mark `[?]` with assumption note
|
- **Cannot verify:** Note "ASSUMED: [reason]" inline
|
||||||
- **Blocked:** Mark `[!]` with reason, STOP phase
|
- **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
|
### 3. Implement Tasks Sequentially
|
||||||
|
|
||||||
@@ -195,7 +199,7 @@ Sections: Summary (2-3 sentences), Tasks Completed (Y/Z + blocked list), Test Re
|
|||||||
On user "approved":
|
On user "approved":
|
||||||
1. Mark `[/]` → `[x]` in overview.md, update phase-X.md status to "Complete"
|
1. Mark `[/]` → `[x]` in overview.md, update phase-X.md status to "Complete"
|
||||||
2. Show Planny art with completion message
|
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`"
|
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`"
|
5. **If final phase:** "NEXT STEP: Start NEW conversation and run: `/plan2code-4--finalize`"
|
||||||
6. Mention `/plan2code-1b--revise` option
|
6. Mention `/plan2code-1b--revise` option
|
||||||
|
|||||||
@@ -48,7 +48,8 @@ Complete steps in order. Report progress after each.
|
|||||||
**Objective:** Verify all tasks across all phases completed.
|
**Objective:** Verify all tasks across all phases completed.
|
||||||
|
|
||||||
1. Open each `phase-X.md` file
|
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 |
|
| Status | Meaning | Action |
|
||||||
|--------|---------|--------|
|
|--------|---------|--------|
|
||||||
@@ -299,9 +300,6 @@ specs--completed/
|
|||||||
> Specs archived to `specs--completed/<feature-name>/`.
|
> Specs archived to `specs--completed/<feature-name>/`.
|
||||||
> Thank you for using the Plan2Code workflow!
|
> 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
|
### Handling Incomplete Implementations
|
||||||
|
|
||||||
| Completion | Action |
|
| Completion | Action |
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "Plan2Code",
|
"name": "Plan2Code",
|
||||||
"version": "1.8.1",
|
"version": "1.10.0",
|
||||||
"description": "A structured 4-step workflow methodology for AI-assisted software development",
|
"description": "A structured 4-step workflow methodology for AI-assisted software development",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"ai",
|
"ai",
|
||||||
|
|||||||
Reference in New Issue
Block a user