v1.15.2 — add Claude CLI status line, Zed support, rename 3b-review to review

- Status line: new src/statusline-claude/ (Planny box-star mascot icon),
  install/uninstall wiring in install.js, Install All (A) + Custom (S)
- Zed agent support across README, AGENTS, docs, init prompt, installer
- Rename /plan2code-3b-review to /plan2code-review (now a standalone
  utility workflow); update file, reference dir, and all cross-references
- install.js: fs.rmSync simplifications, async install, summary cleanup
- Bump to v1.15.2; CHANGELOG entries for v1.15.0/1/2
This commit is contained in:
2026-06-01 16:39:35 -07:00
parent 5e48e98293
commit 25d874d87d
21 changed files with 1029 additions and 136 deletions
+33 -5
View File
@@ -6,7 +6,7 @@
```
plan2code/
├── src/ # Source workflow prompts (9 markdown files)
│ └── plan2code-3b-review-references/ # Reference files for review skill
│ └── plan2code-review-references/ # Reference files for review skill
│ ├── verification-protocol.md # Deep verification + confidence calibration
│ ├── dimensions.md # 11 dimensions with detailed checklists
│ └── false-positives.md # Known false-positive patterns
@@ -17,6 +17,9 @@ plan2code/
│ ├── src/ # TypeScript source
│ │ └── prompts/ # Internal AI prompt templates (no char limit)
│ └── dist/ # Built output (tsup)
├── src/statusline-claude/ # Claude CLI status line (Node.js, zero deps, single file)
│ ├── statusline.js # Self-contained: config, git, formatters, render
│ └── statusline-config.json # Default config template
├── scripts/ # Development scripts
│ └── validate-char-count.js # Pre-commit character count validator
├── dist/ # Generated distribution files (auto-generated)
@@ -41,6 +44,7 @@ plan2code/
| `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 |
| `src/statusline-claude/` | Claude CLI status bar (included in `A` Install All + dev tools; also via Custom → S) |
## Workflow Prompts (in `src/`)
@@ -53,7 +57,7 @@ plan2code/
| `plan2code-1b-revise-plan.md` | 1b | Mid-implementation revisions |
| `plan2code-2-document.md` | 2 | Create implementation specs |
| `plan2code-3-implement.md` | 3 | Execute implementation (phase by phase) |
| `plan2code-3b-review.md` | 3b | Post-implementation comprehensive review |
| `plan2code-review.md` | review | Post-implementation comprehensive review |
| `plan2code-4-finalize.md` | 4 | Validate, summarize, feedback, archive (7 steps) |
## Naming Convention
@@ -71,11 +75,35 @@ Examples:
Some workflows use companion reference files for depth that exceeds the 11k char limit. The orchestrator (main workflow file) loads them via `Read` directives during execution.
**Pattern:** `src/<source-filename-without-extension>-references/` (e.g., `plan2code-3b-review-references/`)
**Pattern:** `src/<source-filename-without-extension>-references/` (e.g., `plan2code-review-references/`)
**How the installer handles them:**
- **Skill-directory platforms** (Claude Code, Agents, Crush, Devin): reference files are nested as `<skill-name>/references/`. Read paths use canonical `references/<file>.md`.
- **Flat-file platforms** (Windsurf, Cursor, Copilot, Continue): reference files are placed as a sibling directory. The installer rewrites Read paths to the sibling directory name (e.g., `plan2code-3b-review-references/<file>.md`).
- **Flat-file platforms** (Windsurf, Cursor, Copilot, Continue): reference files are placed as a sibling directory. The installer rewrites Read paths to the sibling directory name (e.g., `plan2code-review-references/<file>.md`).
- **TOML platforms** (Gemini CLI): reference files are skipped — TOML embeds content inline, so Read directives won't resolve. The orchestrator's inline fallback text covers this.
Reference files are NOT subject to the 11,000 character limit. Currently only the review workflow (Step 3b) uses this pattern — it serves as the POC for potential adoption by other workflows.
Reference files are NOT subject to the 11,000 character limit. Currently only the review workflow uses this pattern — it serves as the POC for potential adoption by other workflows.
## Status Line
Optional Claude Code status bar living in `src/statusline-claude/`. Three-line bar (icon + content per line) showing model, project, branch, uncommitted diff stats, session duration + cost, context window usage, and plan/quota usage.
**Design constraints:**
- **Zero runtime dependencies** — `statusline.js` is self-contained (config loader, git helpers, formatters, render). Copied verbatim to `~/.claude/plan2code-statusline.js` on install; no bundler step.
- **Stdin-driven** — all data comes from Claude Code's stdin JSON (`model`, `workspace`, `context_window`, `rate_limits`, `cost`). No API calls, no auth, no background processes.
- **Silent failure** — outer `try/catch` around `main()` plus `process.exit(0)` on missing stdin guarantees the script never crashes the CLI. All git ops are timeout-bounded (1.5s) and non-git workspaces short-circuit via `fs.existsSync('.git')`.
- **Atomic settings writes** — installer writes `~/.claude/settings.json` via temp file + rename so a crash never leaves the file truncated.
- **Custom-config respect** — installer detects non-plan2code `statusLine` entries, prompts before replacing, and backs up to `statusline-previous.json`. Uninstall only removes `settings.statusLine` if it points to the plan2code bundle.
**Layout:**
```
src/statusline-claude/
├── statusline.js # Self-contained: config, git, formatters, render
├── statusline-config.json # Default config template
└── README.md # User docs: install, config, debugging
```
**Adaptive plan-usage display:** the formatter auto-selects between `5h/7d` rate-limit percentages (Pro/Max/Teams — when `rate_limits` present in stdin) and `Nk in · Nk out` session-token counts (Bedrock/Vertex/PAYG — when `rate_limits` absent). Segment is hidden when neither shape is available.
**Installer integration** lives in `install.js` under the `STATUS LINE INSTALLATION` section (`installStatusLine`, `uninstallStatusLine`). Included in `A` (Install All + dev tools); also available individually via Custom → `S`.
+6 -3
View File
@@ -25,8 +25,9 @@ cd plan2code-metrics && npm run build # Build the CLI
| Option | Action |
|--------|--------|
| `I` | Install Plan2Code for all platforms + loop CLI |
| `U` | Uninstall Plan2Code files + loop CLI (confirmation required) |
| `I` | Install Plan2Code workflow prompts for all platforms, plus `plan2code-loop` CLI |
| `A` | Everything in `I` plus `plan2code-bot`, `plan2code-metrics` (dev tools), and Claude Code status line |
| `U` | Uninstall Plan2Code files: prompts + `plan2code-loop` + `plan2code-metrics` + `plan2code-bot` + Claude Code status line (confirmation required) |
| `C` | Open CUSTOM sub-menu |
| `Q` | Quit |
@@ -37,6 +38,8 @@ cd plan2code-metrics && npm run build # Build the CLI
| `L` | Show local (per-project) install instructions |
| `O` | Install plan2code-loop CLI only |
| `M` | Install plan2code-metrics CLI only |
| `S` | Install Claude Code status line only |
| `B` | Install plan2code-bot CLI only |
| `Q` | Return to main menu |
## How the Installer Works
@@ -58,7 +61,7 @@ cd plan2code-metrics && npm run build # Build the CLI
| VS Code Copilot | `.prompt.md` | — | — | YAML frontmatter |
| Codeium | `.md` | — | — | YAML frontmatter |
| Claude Code (Skills) | `SKILL.md` in subdir | `.claude/skills/<skill-name>/` | `~/.claude/skills/<skill-name>/` | YAML frontmatter + `disable-model-invocation: true` |
| Agent Skills (Amp · Devin · Gemini CLI · OpenCode) | `SKILL.md` in subdir | `.agents/skills/<skill-name>/` | `~/.agents/skills/<skill-name>/` | YAML frontmatter (no disable flag) |
| Agent Skills (Amp · Devin · Gemini CLI · OpenCode · Zed) | `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`) |
+8 -2
View File
@@ -1,10 +1,10 @@
# AGENTS.md
This file provides guidance to AI coding agents like Claude Code (claude.ai/code), Cursor AI, Codex, Gemini CLI, GitHub Copilot, Devin, and other AI coding assistants when working with code in this repository.
This file provides guidance to AI coding agents like Claude Code (claude.ai/code), Cursor AI, Codex, Gemini CLI, GitHub Copilot, Devin, Zed, and other AI coding assistants when working with code in this repository.
## Project Overview
Plan2Code is a structured 4-step workflow methodology for AI-assisted software development. It provides prompt templates that can be installed globally or per-project for various AI coding tools (Claude Code, Cursor, Copilot, Continue, Windsurf, Codeium, Devin).
Plan2Code is a structured 4-step workflow methodology for AI-assisted software development. It provides prompt templates that can be installed globally or per-project for various AI coding tools (Claude Code, Cursor, Copilot, Continue, Windsurf, Codeium, Devin, Zed).
**Version:** Check `version.json` for current version
**Author:** Justin Parker
@@ -32,6 +32,12 @@ Recursive self-improvement toolchain (`plan2code-metrics/`) for collecting run m
Details: [Plan2Code Metrics](./.agents-docs/AGENTS-plan2code-metrics.md)
## Plan2Code Status Line (Claude CLI)
Optional CLI status bar (`src/statusline-claude/`) for Claude Code. Displays model, project, branch, context usage, usage stats (rate limits or token counts), git diff stats, and duration. Reads data directly from Claude Code's stdin JSON — no API calls, no auth, no background processes. Included in `Install All + dev tools` (`A`); also available via `install.js` Custom → S (opt-in).
Details: [Architecture](./.agents-docs/AGENTS-architecture.md) (see Status Line section)
## Development Commands
Build commands, installer menu options, how the installer generates platform-specific files, platform format table, and how to edit workflow prompts.
+48
View File
@@ -2,6 +2,54 @@
All notable changes to Plan2Code will be documented in this file.
## v1.15.2
### 🔧 Changed
- **Review workflow renamed** — `/plan2code-3b-review``/plan2code-review`
- Removed `3b` step-number prefix; review is now a standalone utility workflow (like `init` and `quick-task`)
- Source file: `src/plan2code-review.md` (was `plan2code-3b-review.md`)
- Reference directory: `src/plan2code-review-references/` (was `plan2code-3b-review-references/`)
- Updated all docs, installer config, architecture docs, and cross-references
## v1.15.1
### 🔧 Changed
- **Zed agent support** — Added Zed to all agent support documentation
- `README.md` Supported Platforms list
- `AGENTS.md` intro and Project Overview
- `.agents-docs/AGENTS-development-commands.md` Platform-Specific File Formats table
- `install.js` Agent Skills platform label
- `src/plan2code-init.md` AGENTS.md template text
## v1.15.0
### ✨ Added
- **Claude CLI status line** — Persistent three-line status bar for Claude Code, displaying model, project, git branch, uncommitted diff stats, session duration, context window usage bar, and plan/quota usage. Reads all data from Claude Code's stdin JSON — no API calls, no auth, no background processes.
- `src/statusline-claude/statusline.js` — self-contained: stdin parsing, config loader, ANSI formatting, orchestration
- `src/statusline-claude/statusline-config.json` — distributed default config
- `src/statusline-claude/README.md` — user docs: install, config, usage modes, troubleshooting
- **Planny mascot icons** on each line (`╭─╮`, `│★│`, `╰─╯`) in brand colors, with monochrome fallback
- **Context window bar** (12-cell `▰▱`) with configurable `autocompactBuffer` (default 33000 tokens) so the percentage reflects *usable* context, not the raw window
- **Adaptive plan usage segment** — auto-detects data shape:
- Pro/Max/Teams (rate_limits present) → `5h: NN% · 7d: NN%`
- Bedrock / Vertex / PAYG (no rate_limits) → `NNk in · NNk out` session tokens
- Segment hidden when neither is available
- **Real uncommitted diff stats** via `git diff HEAD --numstat``+NN -NN`
- **Color-coded thresholds** — green / yellow / red for context bar and rate limits
- **Separator line** rendered at fixed 55-character width for consistent alignment
- **Session cost display** — Estimated session cost shown next to duration (e.g. `49m ($4.62)`) via new `items.sessionCost` config (default on); reads `cost.total_cost_usd` from Claude Code stdin; hidden when zero or unavailable
- **Compact mode redesigned** — `"compact": true` now renders two content lines (no mascot icons, no separator) instead of one cramped single line. The single-line variant truncated in narrow terminals — the exact case compact mode was meant to help. Default remains `false`.
- **Silent failure** on all errors — never crashes, never blocks the CLI; 1.5s per-call timeouts on git operations; non-git workspaces short-circuit without spawning subprocesses
- **Installer integration** — Status line included in `Install All + dev tools` (`A`); also available via `Custom → S` (opt-in).
- Copies `statusline.js` verbatim to `~/.claude/plan2code-statusline.js` (single-file design, no bundling)
- Registers in `~/.claude/settings.json` under `statusLine` via atomic temp-file + rename write
- Preserves existing `statusline-config.json` on reinstall
- Detects non-plan2code custom `statusLine` configs — prompts before replacing; auto-backs up to `statusline-previous.json`
- Uninstall (`U`) removes bundled script + `settings.json` entry; preserves `statusline-config.json`. Only removes `settings.statusLine` if it points to the plan2code bundle — non-plan2code entries are left intact.
## v1.14.0
### ✨ Added
+1 -1
View File
@@ -7,11 +7,11 @@
| Init | /plan2code-init | None | AGENTS.md file |
| Update | /plan2code-init-update | AGENTS.md | Updated AGENTS.md |
| 0 | /plan2code-quick-task | Requirements | Conversational plan |
| review | /plan2code-review | Scope guidance | Review findings + fixes |
| 1 | /plan2code-1-plan | Requirements | PLAN-CONVERSATION-<date>.md + PLAN-DRAFT-<date>.md |
| 1b | /plan2code-1b-revise-plan | Specs + changes | Updated specs |
| 2 | /plan2code-2-document | PLAN-DRAFT.md | overview.md + Phase files |
| 3 | /plan2code-3-implement | overview.md | Implemented code |
| 3b | /plan2code-3b-review | Scope guidance | Review findings + fixes |
| 4 | /plan2code-4-finalize | overview.md | Archived specs |
## File Structure
+11 -4
View File
@@ -20,11 +20,11 @@ A structured 4-step workflow for developing features and projects with AI assist
| `/plan2code-init` | Generate AGENTS.md file for new/existing projects |
| `/plan2code-init-update` | Update AGENTS.md with new learnings from coding sessions |
| `/plan2code-quick-task` | Small, quick tasks that don't need full workflow |
| `/plan2code-review` | Post-phase code review (after key features or milestones)|
| `/plan2code-1-plan` | Starting a new feature (full planning) |
| `/plan2code-1b-revise-plan` | Requirements change mid-implementation |
| `/plan2code-2-document` | After planning, create implementation specs |
| `/plan2code-3-implement` | Execute implementation (one phase per conversation) |
| `/plan2code-3b-review` | Post-phase code review (after key features or milestones)|
| `/plan2code-4-finalize` | All phases complete, ready to archive |
**Key Rules:**
@@ -68,6 +68,7 @@ The install script requires **Node.js** (v14 or later). If you don't have Node.j
- Amp
- OpenCode
- Devin
- Zed
### Install via npx (Recommended — No Clone Required)
@@ -134,6 +135,12 @@ SELECT OPTION (I, U, C, Q) [I]:
---
## Status Line — Claude CLI (Optional)
A persistent three-line status bar for Claude Code showing model, project, git branch, uncommitted diff stats, context usage, and plan quota. Included in `node install.js``A` (Install All + dev tools), or available individually via Custom → Status Line. See [src/statusline-claude/README.md](src/statusline-claude/README.md) for details.
---
## Important: Start Fresh Conversations
**Start a new conversation/chat session before each step.** This includes:
@@ -233,7 +240,7 @@ The `overview.md` includes a "Parallel Execution Groups" section that identifies
---
### Step 3b: Review Mode 🔬 (Optional)
### Review Mode 🔬 (Optional)
**Purpose:** Comprehensive post-implementation code review with adaptive scope and spec compliance checking.
@@ -282,7 +289,7 @@ After running `node install.js`, use the slash commands directly in your AI tool
/plan2code-1-plan # Start planning a new feature
/plan2code-2-document # Create implementation docs from plan
/plan2code-3-implement # Begin/continue implementation
/plan2code-3b-review # Post-phase code review (optional)
/plan2code-review # Post-phase code review (optional)
/plan2code-4-finalize # Wrap up after all phases complete
```
@@ -422,7 +429,7 @@ The `[/]` status enables parallel execution - multiple agents can work on differ
| Step 1 (Plan) | None (describe your feature/project) |
| Step 2 (Document) | `specs/<feature>/PLAN-DRAFT-<date>.md` or planning conversation |
| Step 3 (Implement) | `specs/<feature>/overview.md` (auto-detects phase) |
| Step 3b (Review) | Optional: scope guidance (e.g., "review last 2 phases", "just the auth module", "whole PR"). Auto-detects changes and specs if no scope given. |
| Review (Optional) | Scope guidance (e.g., "review last 2 phases", "just the auth module", "whole PR"). Auto-detects changes and specs if no scope given. |
| Step 4 (Finalize) | `specs/<feature>/overview.md` |
---
+2 -2
View File
@@ -988,7 +988,7 @@
<section class="step">
<div class="step-content">
<div class="step-header">
<div class="step-number headline">3b</div>
<div class="step-number headline">review</div>
<div class="step-icon" style="color: var(--accent);">🔬</div>
</div>
<h2 class="step-title subheadline">Review</h2>
@@ -1112,7 +1112,7 @@
<li><span class="code-line">/plan2code-1-plan</span></li>
<li><span class="code-line">/plan2code-2-document</span></li>
<li><span class="code-line">/plan2code-3-implement</span> <span class="gray">(per phase)</span></li>
<li><span class="code-line">/plan2code-3b-review</span> <span class="gray">(optional)</span></li>
<li><span class="code-line">/plan2code-review</span> <span class="gray">(optional)</span></li>
<li><span class="code-line">/plan2code-4-finalize</span></li>
</ul>
<br>
+301 -107
View File
@@ -167,11 +167,12 @@ const SOURCE_PROMPTS = [
description: 'Execute implementation phase by phase'
},
{
source: 'plan2code-3b-review.md',
stepNumber: '3b',
source: 'plan2code-review.md',
stepNumber: 'review',
name: 'review',
displayName: 'Review Mode',
description: 'Comprehensive post-implementation review with spec compliance checking'
description: 'Comprehensive post-implementation review with spec compliance checking',
isUtility: true
},
{
source: 'plan2code-4-finalize.md',
@@ -195,12 +196,19 @@ function generateSkillName(prompt) {
return generateFilename(prompt, '').replace(/--+/g, '-');
}
// Helper function to generate step label for descriptions
function generateStepLabel(prompt) {
if (prompt.stepNumber === 'init') return 'Init';
if (prompt.stepNumber === 'review') return 'Review';
return `Step ${prompt.stepNumber}`;
}
// Helper function to generate YAML frontmatter for SKILL.md files
function generateSkillHeader(prompt, disableModelInvocation = false) {
const lines = [
'---',
`name: ${generateSkillName(prompt)}`,
`description: "Plan2Code ${prompt.stepNumber === 'init' ? 'Init' : `Step ${prompt.stepNumber}`}: ${prompt.displayName} - user-initiated workflow step. Do not invoke autonomously."`,
`description: "Plan2Code ${generateStepLabel(prompt)}: ${prompt.displayName} - user-initiated workflow step. Do not invoke autonomously."`,
];
if (disableModelInvocation) lines.push('disable-model-invocation: true');
lines.push('---');
@@ -209,7 +217,7 @@ function generateSkillHeader(prompt, disableModelInvocation = false) {
// Helper function to generate complete .toml file content for Gemini CLI
function generateTomlContent(prompt, sourceContent) {
const stepLabel = prompt.stepNumber === 'init' ? 'Init' : `Step ${prompt.stepNumber}`;
const stepLabel = generateStepLabel(prompt);
const desc = `Plan2Code ${stepLabel}: ${prompt.displayName} - user-initiated ${prompt.description || 'workflow step'}`;
return `description = "${desc}"\nprompt = '''\n${sourceContent}\n'''\n`;
}
@@ -235,7 +243,7 @@ const LOCAL_DESTINATIONS = [
header: (prompt) => [
'---',
'mode: agent',
`description: "Plan2Code ${prompt.stepNumber === 'init' ? 'Init' : `Step ${prompt.stepNumber}`}: ${prompt.displayName} - ${prompt.description}"`,
`description: "Plan2Code ${generateStepLabel(prompt)}: ${prompt.displayName} - ${prompt.description}"`,
'---'
].join('\n')
},
@@ -245,7 +253,7 @@ const LOCAL_DESTINATIONS = [
filePattern: (prompt) => generateFilename(prompt, '.agent.md'),
header: (prompt) => [
'---',
`description: "Plan2Code ${prompt.stepNumber === 'init' ? 'Init' : `Step ${prompt.stepNumber}`}: ${prompt.displayName} - ${prompt.description}"`,
`description: "Plan2Code ${generateStepLabel(prompt)}: ${prompt.displayName} - ${prompt.description}"`,
'---'
].join('\n')
},
@@ -256,7 +264,7 @@ const LOCAL_DESTINATIONS = [
header: (prompt) => [
'---',
`name: ${prompt.name}`,
`description: "Plan2Code ${prompt.stepNumber === 'init' ? 'Init' : `Step ${prompt.stepNumber}`}: ${prompt.displayName} - ${prompt.description}"`,
`description: "Plan2Code ${generateStepLabel(prompt)}: ${prompt.displayName} - ${prompt.description}"`,
'---'
].join('\n')
},
@@ -266,7 +274,7 @@ const LOCAL_DESTINATIONS = [
filePattern: (prompt) => generateFilename(prompt, '.md'),
header: (prompt) => [
'---',
`description: "Plan2Code ${prompt.stepNumber === 'init' ? 'Init' : `Step ${prompt.stepNumber}`}: ${prompt.displayName} - ${prompt.description}"`,
`description: "Plan2Code ${generateStepLabel(prompt)}: ${prompt.displayName} - ${prompt.description}"`,
'---'
].join('\n')
},
@@ -276,7 +284,7 @@ const LOCAL_DESTINATIONS = [
filePattern: (prompt) => generateFilename(prompt, '.md'),
header: (prompt) => [
'---',
`description: "Plan2Code ${prompt.stepNumber === 'init' ? 'Init' : `Step ${prompt.stepNumber}`}: ${prompt.displayName} - ${prompt.description}"`,
`description: "Plan2Code ${generateStepLabel(prompt)}: ${prompt.displayName} - ${prompt.description}"`,
'---'
].join('\n')
},
@@ -286,7 +294,7 @@ const LOCAL_DESTINATIONS = [
filePattern: (prompt) => generateFilename(prompt, '.md'),
header: (prompt) => [
'---',
`description: "Plan2Code ${prompt.stepNumber === 'init' ? 'Init' : `Step ${prompt.stepNumber}`}: ${prompt.displayName} - ${prompt.description}"`,
`description: "Plan2Code ${generateStepLabel(prompt)}: ${prompt.displayName} - ${prompt.description}"`,
'---'
].join('\n')
},
@@ -296,7 +304,7 @@ const LOCAL_DESTINATIONS = [
filePattern: (prompt) => generateFilename(prompt, '.md'),
header: (prompt) => [
'---',
`description: "Plan2Code ${prompt.stepNumber === 'init' ? 'Init' : `Step ${prompt.stepNumber}`}: ${prompt.displayName} - ${prompt.description}"`,
`description: "Plan2Code ${generateStepLabel(prompt)}: ${prompt.displayName} - ${prompt.description}"`,
'---'
].join('\n')
},
@@ -306,7 +314,7 @@ const LOCAL_DESTINATIONS = [
filePattern: (prompt) => generateFilename(prompt, '.md'),
header: (prompt) => [
'---',
`description: "Plan2Code ${prompt.stepNumber === 'init' ? 'Init' : `Step ${prompt.stepNumber}`}: ${prompt.displayName} - ${prompt.description}"`,
`description: "Plan2Code ${generateStepLabel(prompt)}: ${prompt.displayName} - ${prompt.description}"`,
'---'
].join('\n')
},
@@ -344,7 +352,7 @@ const GLOBAL_DESTINATIONS = [
filePattern: (prompt) => generateFilename(prompt, '.md'),
header: (prompt) => [
'---',
`description: "Plan2Code ${prompt.stepNumber === 'init' ? 'Init' : `Step ${prompt.stepNumber}`}: ${prompt.displayName} - ${prompt.description}"`,
`description: "Plan2Code ${generateStepLabel(prompt)}: ${prompt.displayName} - ${prompt.description}"`,
'---'
].join('\n')
},
@@ -361,7 +369,7 @@ const GLOBAL_DESTINATIONS = [
header: (prompt) => [
'---',
`name: ${prompt.name}`,
`description: "Plan2Code ${prompt.stepNumber === 'init' ? 'Init' : `Step ${prompt.stepNumber}`}: ${prompt.displayName} - ${prompt.description}"`,
`description: "Plan2Code ${generateStepLabel(prompt)}: ${prompt.displayName} - ${prompt.description}"`,
'---'
].join('\n')
},
@@ -371,7 +379,7 @@ const GLOBAL_DESTINATIONS = [
filePattern: (prompt) => generateFilename(prompt, '.md'),
header: (prompt) => [
'---',
`description: "Plan2Code ${prompt.stepNumber === 'init' ? 'Init' : `Step ${prompt.stepNumber}`}: ${prompt.displayName} - ${prompt.description}"`,
`description: "Plan2Code ${generateStepLabel(prompt)}: ${prompt.displayName} - ${prompt.description}"`,
'---'
].join('\n')
},
@@ -382,7 +390,7 @@ const GLOBAL_DESTINATIONS = [
header: (prompt) => [
'---',
'agent: agent',
`description: "Plan2Code ${prompt.stepNumber === 'init' ? 'Init' : `Step ${prompt.stepNumber}`}: ${prompt.displayName} - ${prompt.description}"`,
`description: "Plan2Code ${generateStepLabel(prompt)}: ${prompt.displayName} - ${prompt.description}"`,
'---'
].join('\n')
},
@@ -392,7 +400,7 @@ const GLOBAL_DESTINATIONS = [
filePattern: (prompt) => generateFilename(prompt, '.md'),
header: (prompt) => [
'---',
`description: "Plan2Code ${prompt.stepNumber === 'init' ? 'Init' : `Step ${prompt.stepNumber}`}: ${prompt.displayName} - ${prompt.description}"`,
`description: "Plan2Code ${generateStepLabel(prompt)}: ${prompt.displayName} - ${prompt.description}"`,
'---'
].join('\n')
},
@@ -402,7 +410,7 @@ const GLOBAL_DESTINATIONS = [
filePattern: (prompt) => generateFilename(prompt, '.md'),
header: (prompt) => [
'---',
`description: "Plan2Code ${prompt.stepNumber === 'init' ? 'Init' : `Step ${prompt.stepNumber}`}: ${prompt.displayName} - ${prompt.description}"`,
`description: "Plan2Code ${generateStepLabel(prompt)}: ${prompt.displayName} - ${prompt.description}"`,
'---'
].join('\n')
},
@@ -413,10 +421,10 @@ const GLOBAL_DESTINATIONS = [
header: (prompt) => generateSkillHeader(prompt, true),
},
{
name: 'Agent Skills (Amp · Devin · Gemini CLI · OpenCode)',
name: 'Agent Skills (Amp · Devin · Gemini CLI · OpenCode · Zed)',
dir: '.agents/skills',
type: 'skill',
header: (prompt) => generateSkillHeader(prompt, false),
header: (prompt) => generateSkillHeader(prompt, true),
},
{
name: 'Crush',
@@ -460,10 +468,6 @@ function getCrushSkillsDir() {
// Helper function to get Agent Skills global directory based on platform
function getAgentSkillsDir() {
const homedir = os.homedir();
if (process.platform === 'win32') {
const localAppData = process.env.LOCALAPPDATA || path.join(homedir, 'AppData', 'Local');
return path.join(localAppData, 'agents', 'skills');
}
return path.join(homedir, '.agents', 'skills');
}
@@ -562,15 +566,13 @@ const INSTALL_TARGETS = [
icon: '◉ '
},
{
name: 'Agent Skills (Amp · Devin · Gemini CLI · OpenCode)',
name: 'Agent Skills (Amp · Devin · Gemini CLI · OpenCode · Zed)',
id: 'agent-skills',
dir: getAgentSkillsDir,
sourceDir: '.agents/skills',
type: 'skill',
filePattern: /^plan2code-/,
displayPath: process.platform === 'win32'
? '%LOCALAPPDATA%\\agents\\skills'
: '~/.agents/skills',
displayPath: '~/.agents/skills',
icon: '◉ '
},
{
@@ -713,19 +715,8 @@ function deleteDirectory(dirPath) {
if (!fs.existsSync(dirPath)) {
return;
}
try {
const files = fs.readdirSync(dirPath);
for (const file of files) {
const filePath = path.join(dirPath, file);
const stat = fs.statSync(filePath);
if (stat.isDirectory()) {
deleteDirectory(filePath);
} else {
fs.unlinkSync(filePath);
}
}
fs.rmdirSync(dirPath);
fs.rmSync(dirPath, { recursive: true, force: true });
} catch (err) {
console.error(` ${COLORS.RED}${SYMBOLS.ERROR}${COLORS.RESET} Could not delete directory ${dirPath}: ${err.message}`);
}
@@ -866,7 +857,7 @@ function inlineReferenceContent(sourceContent, srcRefDir) {
}
/**
* Copy a reference directory (e.g., plan2code-3b-review-references/) to a destination.
* Copy a reference directory (e.g., plan2code-review-references/) to a destination.
* Used by syncPrompts() to distribute reference files alongside orchestrator files.
*/
function copyReferenceDirectory(srcRefDir, destRefDir, stats, quiet = false) {
@@ -970,7 +961,7 @@ function syncPrompts(quiet = false) {
continue;
}
// Detect reference directory (e.g., plan2code-3b-review-references/)
// Detect reference directory (e.g., plan2code-review-references/)
const refDirName = prompt.source.replace(/\.md$/, '-references');
const srcRefDir = path.join(SRC_DIR, refDirName);
const hasRefDir = fs.existsSync(srcRefDir);
@@ -1102,13 +1093,12 @@ function cleanLegacyTargets() {
/**
* Execute installation
*/
function install(targets = null) {
async function install(targets = null) {
const projectRoot = __dirname;
const homeDir = os.homedir();
const targetList = targets || getTargets();
let totalInstalled = 0;
let totalSkipped = 0;
let totalErrors = 0;
displaySectionHeader(' INSTALLING FILES ');
@@ -1286,9 +1276,7 @@ function install(targets = null) {
console.log(padEndVisible(`${COLORS.CYAN}${COLORS.RESET} ${statusColor}Status:${COLORS.RESET} ${statusColor}${statusText}${COLORS.RESET}`, 76) + `${COLORS.CYAN}${COLORS.RESET}`);
console.log(padEndVisible(`${COLORS.CYAN}${COLORS.RESET} Files Installed: ${COLORS.GREEN}${totalInstalled}${COLORS.RESET}`, 76) + `${COLORS.CYAN}${COLORS.RESET}`);
if (totalSkipped > 0) {
console.log(padEndVisible(`${COLORS.CYAN}${COLORS.RESET} Files Skipped: ${COLORS.YELLOW}${totalSkipped}${COLORS.RESET}`, 76) + `${COLORS.CYAN}${COLORS.RESET}`);
}
if (totalErrors > 0) {
console.log(padEndVisible(`${COLORS.CYAN}${COLORS.RESET} Errors: ${COLORS.RED}${totalErrors}${COLORS.RESET}`, 76) + `${COLORS.CYAN}${COLORS.RESET}`);
}
@@ -1559,12 +1547,12 @@ function runInteractive() {
rl.close();
const loopResult = installPlan2CodeLoop();
console.log('');
const installResult = install();
const installResult = await install();
const exitCode = loopResult !== 0 ? loopResult : installResult;
process.exit(exitCode);
}
// A path — Install Everything (prompts + loop + bot + metrics)
// A path — Install Everything (prompts + loop + bot + metrics + Claude status line)
if (input === 'A') {
rl.close();
const loopResult = installPlan2CodeLoop();
@@ -1573,8 +1561,10 @@ function runInteractive() {
console.log('');
const metricsResult = installPlan2CodeMetrics();
console.log('');
const installResult = install();
const exitCode = loopResult !== 0 ? loopResult : installResult !== 0 ? installResult : botResult !== 0 ? botResult : metricsResult;
const statusLineResult = await installStatusLine();
console.log('');
const installResult = await install();
const exitCode = loopResult !== 0 ? loopResult : botResult !== 0 ? botResult : metricsResult !== 0 ? metricsResult : statusLineResult !== 0 ? statusLineResult : installResult;
process.exit(exitCode);
}
@@ -1599,7 +1589,8 @@ function runInteractive() {
const loopResult = uninstallPlan2CodeLoop();
const metricsResult = uninstallPlan2CodeMetrics();
const botResult = uninstallPlan2CodeBot();
const exitCode = uninstallResult !== 0 ? uninstallResult : loopResult !== 0 ? loopResult : metricsResult !== 0 ? metricsResult : botResult;
const statusLineResult = uninstallStatusLine();
const exitCode = uninstallResult !== 0 ? uninstallResult : loopResult !== 0 ? loopResult : metricsResult !== 0 ? metricsResult : botResult !== 0 ? botResult : statusLineResult;
process.exit(exitCode);
} else {
console.log(`\n${COLORS.YELLOW}${SYMBOLS.WARNING} CANCELLED${COLORS.RESET}\n`);
@@ -1617,11 +1608,12 @@ function runInteractive() {
console.log(padEndVisible(`${COLORS.CYAN}${COLORS.RESET} ${COLORS.BRIGHT}L.${COLORS.RESET} ${COLORS.BLUE}LOCAL${COLORS.RESET} Show local (project) install instructions`, 65) + `${COLORS.CYAN}${COLORS.RESET}`);
console.log(padEndVisible(`${COLORS.CYAN}${COLORS.RESET} ${COLORS.BRIGHT}O.${COLORS.RESET} ${COLORS.GREEN}LOOP CLI${COLORS.RESET} Install plan2code-loop CLI only`, 65) + `${COLORS.CYAN}${COLORS.RESET}`);
console.log(padEndVisible(`${COLORS.CYAN}${COLORS.RESET} ${COLORS.BRIGHT}M.${COLORS.RESET} ${COLORS.GREEN}METRICS${COLORS.RESET} Install plan2code-metrics CLI only`, 65) + `${COLORS.CYAN}${COLORS.RESET}`);
console.log(padEndVisible(`${COLORS.CYAN}${COLORS.RESET} ${COLORS.BRIGHT}S.${COLORS.RESET} ${COLORS.GREEN}STATUS${COLORS.RESET} Install Claude Code status line`, 65) + `${COLORS.CYAN}${COLORS.RESET}`);
console.log(padEndVisible(`${COLORS.CYAN}${COLORS.RESET} ${COLORS.BRIGHT}B.${COLORS.RESET} ${COLORS.GREEN}BOT${COLORS.RESET} Install plan2code-bot CLI only`, 65) + `${COLORS.CYAN}${COLORS.RESET}`);
console.log(padEndVisible(`${COLORS.CYAN}${COLORS.RESET} ${COLORS.BRIGHT}Q.${COLORS.RESET} ${COLORS.DIM}BACK${COLORS.RESET} Return to main menu`, 65) + `${COLORS.CYAN}${COLORS.RESET}`);
console.log(`${COLORS.CYAN}╚════════════════════════════════════════════════════════════════╝${COLORS.RESET}`);
console.log('');
const customAnswer = await question(`${COLORS.CYAN}${SYMBOLS.SELECT} SELECT OPTION${COLORS.RESET} (L, O, M, B, Q) [Q]: `);
const customAnswer = await question(`${COLORS.CYAN}${SYMBOLS.SELECT} SELECT OPTION${COLORS.RESET} (L, O, M, S, B, Q) [Q]: `);
const customInput = customAnswer.trim().toUpperCase() || 'Q';
// C > L — show local install instructions
@@ -1644,6 +1636,13 @@ function runInteractive() {
process.exit(result);
}
// C > S — install status line
if (customInput === 'S') {
rl.close();
const result = await installStatusLine();
process.exit(result);
}
// C > B — install bot CLI only
if (customInput === 'B') {
rl.close();
@@ -1678,7 +1677,7 @@ function runInteractive() {
// PLAN2CODE-LOOP INSTALLATION
// ============================================================================
const { execSync, spawn } = require('child_process');
const { execSync } = require('child_process');
/**
* Build plan2code-loop package
@@ -1870,27 +1869,13 @@ function removeGlobalSymlink() {
for (const file of filesToRemove) {
try {
fs.lstatSync(file.path);
// File exists, try to remove it
try {
// Try rmdirSync first for junctions/symlinks
fs.rmdirSync(file.path);
console.log(` ${COLORS.GREEN}${SYMBOLS.SUCCESS}${COLORS.RESET} Removed: ${file.name}`);
removedCount++;
} catch (rmdirErr) {
// Fall back to unlinkSync for regular files
try {
fs.unlinkSync(file.path);
console.log(` ${COLORS.GREEN}${SYMBOLS.SUCCESS}${COLORS.RESET} Removed: ${file.name}`);
removedCount++;
} catch (unlinkErr) {
// Last resort: rmSync
fs.rmSync(file.path, { force: true, recursive: true });
console.log(` ${COLORS.GREEN}${SYMBOLS.SUCCESS}${COLORS.RESET} Removed: ${file.name}`);
removedCount++;
}
}
fs.rmSync(file.path, { force: true, recursive: true });
console.log(` ${COLORS.GREEN}${SYMBOLS.SUCCESS}${COLORS.RESET} Removed: ${file.name}`);
removedCount++;
} catch (err) {
// File doesn't exist, skip it
if (err.code !== 'ENOENT') {
console.log(` ${COLORS.RED}${SYMBOLS.ERROR}${COLORS.RESET} Failed to remove ${file.name}: ${err.message}`);
}
}
}
@@ -1904,6 +1889,7 @@ function removeGlobalSymlink() {
return false;
}
}
/**
* Install plan2code-loop (build + symlink)
*/
@@ -2119,23 +2105,13 @@ function removeMetricsGlobalSymlink() {
for (const file of filesToRemove) {
try {
fs.lstatSync(file.path);
try {
fs.rmdirSync(file.path);
console.log(` ${COLORS.GREEN}${SYMBOLS.SUCCESS}${COLORS.RESET} Removed: ${file.name}`);
removedCount++;
} catch (rmdirErr) {
try {
fs.unlinkSync(file.path);
console.log(` ${COLORS.GREEN}${SYMBOLS.SUCCESS}${COLORS.RESET} Removed: ${file.name}`);
removedCount++;
} catch (unlinkErr) {
fs.rmSync(file.path, { force: true, recursive: true });
console.log(` ${COLORS.GREEN}${SYMBOLS.SUCCESS}${COLORS.RESET} Removed: ${file.name}`);
removedCount++;
}
}
fs.rmSync(file.path, { force: true, recursive: true });
console.log(` ${COLORS.GREEN}${SYMBOLS.SUCCESS}${COLORS.RESET} Removed: ${file.name}`);
removedCount++;
} catch (err) {
// File doesn't exist, skip it
if (err.code !== 'ENOENT') {
console.log(` ${COLORS.RED}${SYMBOLS.ERROR}${COLORS.RESET} Failed to remove ${file.name}: ${err.message}`);
}
}
}
@@ -2191,6 +2167,234 @@ function uninstallPlan2CodeMetrics() {
return 0;
}
// ============================================================================
// STATUS LINE INSTALLATION
// ============================================================================
/**
* Status line source layout — single-file design, no bundling step.
* `statusline.js` is self-contained (config loader, formatters, entry point)
* and copied verbatim to ~/.claude/plan2code-statusline.js on install.
*/
function getStatusLineSrcDir() {
return path.join(__dirname, 'src', 'statusline-claude');
}
/**
* Install status line (copy + configure settings).
* Async because it may prompt when a custom (non-plan2code) statusLine exists.
*/
async function installStatusLine({ quiet = false } = {}) {
if (!quiet) {
displaySectionHeader('CLAUDE STATUS LINE', '[ INSTALL ]');
} else {
console.log(` ${COLORS.CYAN}${SYMBOLS.ACTIVE} INSTALLING${COLORS.RESET} Claude Status Line...`);
}
const srcDir = getStatusLineSrcDir();
if (!fs.existsSync(srcDir)) {
console.log(` ${COLORS.RED}${SYMBOLS.ERROR}${COLORS.RESET} src/statusline-claude directory not found`);
return 1;
}
const homeDir = os.homedir();
const claudeDir = path.join(homeDir, '.claude');
try {
fs.mkdirSync(claudeDir, { recursive: true });
} catch (error) {
console.log(` ${COLORS.RED}${SYMBOLS.ERROR}${COLORS.RESET} Cannot create ~/.claude/: ${error.message}`);
return 1;
}
// Copy script directly from src (no bundling — statusline.js is self-contained)
const statuslineDest = path.join(claudeDir, 'plan2code-statusline.js');
const configDest = path.join(claudeDir, 'statusline-config.json');
try {
fs.copyFileSync(path.join(srcDir, 'statusline.js'), statuslineDest);
if (!quiet) console.log(` ${COLORS.GREEN}${SYMBOLS.SUCCESS}${COLORS.RESET} Installed: ~/.claude/plan2code-statusline.js`);
} catch (error) {
console.log(` ${COLORS.RED}${SYMBOLS.ERROR}${COLORS.RESET} Failed to copy script: ${error.message}`);
return 1;
}
// Preserve existing user config
if (!fs.existsSync(configDest)) {
try {
fs.copyFileSync(path.join(srcDir, 'statusline-config.json'), configDest);
if (!quiet) console.log(` ${COLORS.GREEN}${SYMBOLS.SUCCESS}${COLORS.RESET} Created: ~/.claude/statusline-config.json`);
} catch (error) {
console.log(` ${COLORS.RED}${SYMBOLS.ERROR}${COLORS.RESET} Failed to copy config: ${error.message}`);
return 1;
}
} else {
if (!quiet) console.log(` ${COLORS.DIM}${SYMBOLS.INFO} Preserved existing: ~/.claude/statusline-config.json${COLORS.RESET}`);
}
// Make executable on non-Windows
if (process.platform !== 'win32') {
try {
fs.chmodSync(statuslineDest, 0o755);
} catch {}
}
// Configure Claude Code settings.json
let settingsOk = false;
const settingsPath = path.join(claudeDir, 'settings.json');
const expectedCommand = `node "${statuslineDest.replace(/\\/g, '/')}"`;
try {
let settings = {};
if (fs.existsSync(settingsPath)) {
settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
}
// Detect existing statusLine — skip prompt if ours or absent
if (settings.statusLine && settings.statusLine.command) {
const existingCmd = settings.statusLine.command;
const isPlan2Code = existingCmd.includes('plan2code-statusline');
if (!isPlan2Code) {
// Custom statusLine detected — prompt before overwriting
console.log(` ${COLORS.YELLOW}${SYMBOLS.WARNING}${COLORS.RESET} Existing statusLine detected in settings.json:`);
console.log(` ${COLORS.DIM} ${existingCmd}${COLORS.RESET}`);
const rl2 = readline.createInterface({ input: process.stdin, output: process.stdout });
const answer = await new Promise(resolve => {
rl2.question(` ${COLORS.CYAN}Replace with Plan2Code status line?${COLORS.RESET} (Y/n): `, resolve);
});
rl2.close();
if (answer.trim().toLowerCase() === 'n') {
console.log(` ${COLORS.DIM}${SYMBOLS.INFO} Skipped — scripts installed but settings.json unchanged${COLORS.RESET}`);
console.log('');
return 0;
}
// Backup existing config before replacing
const backupPath = path.join(claudeDir, 'statusline-previous.json');
fs.writeFileSync(backupPath, JSON.stringify({ statusLine: settings.statusLine }, null, 2));
console.log(` ${COLORS.GREEN}${SYMBOLS.SUCCESS}${COLORS.RESET} Backed up existing config: ~/.claude/statusline-previous.json`);
}
}
settings.statusLine = {
type: 'command',
command: expectedCommand,
};
// Atomic write: temp file + rename so a crash/power-loss never leaves
// settings.json truncated.
const tmpSettings = settingsPath + '.tmp';
fs.writeFileSync(tmpSettings, JSON.stringify(settings, null, 2));
fs.renameSync(tmpSettings, settingsPath);
if (!quiet) console.log(` ${COLORS.GREEN}${SYMBOLS.SUCCESS}${COLORS.RESET} Configured: ~/.claude/settings.json (statusLine)`);
settingsOk = true;
} catch (error) {
console.log(` ${COLORS.RED}${SYMBOLS.ERROR}${COLORS.RESET} Failed to update settings.json: ${error.message}`);
}
// Clean up legacy filenames from previous installs
for (const legacy of ['statusline.js', 'plan-usage.js', 'plan2code-plan-usage.js', 'statusline-cache.json']) {
const legacyPath = path.join(claudeDir, legacy);
try {
if (fs.existsSync(legacyPath)) {
fs.unlinkSync(legacyPath);
if (!quiet) console.log(` ${COLORS.DIM}${SYMBOLS.INFO} Removed legacy: ${legacy}${COLORS.RESET}`);
}
} catch {}
}
if (settingsOk) {
if (!quiet) {
console.log('');
console.log(`${COLORS.GREEN}${SYMBOLS.SUCCESS} Claude Status Line installed successfully!${COLORS.RESET}`);
console.log('');
console.log(`${COLORS.CYAN}Usage:${COLORS.RESET}`);
console.log(` ${COLORS.DIM}Restart Claude Code to see the status bar${COLORS.RESET}`);
console.log(` ${COLORS.DIM}Config: ~/.claude/statusline-config.json${COLORS.RESET}`);
console.log('');
} else {
console.log(` ${COLORS.GREEN}${SYMBOLS.SUCCESS}${COLORS.RESET} Claude Status Line → ${COLORS.DIM}~/.claude/plan2code-statusline.js${COLORS.RESET}`);
}
} else {
console.log('');
console.log(` ${COLORS.YELLOW}${SYMBOLS.WARNING}${COLORS.RESET} Scripts installed but settings.json not updated.`);
console.log(` ${COLORS.DIM}Add statusLine config to ~/.claude/settings.json manually.${COLORS.RESET}`);
console.log('');
}
return settingsOk ? 0 : 1;
}
/**
* Uninstall status line
*/
function uninstallStatusLine() {
displaySectionHeader('CLAUDE STATUS LINE', '[ UNINSTALL ]');
const homeDir = os.homedir();
const claudeDir = path.join(homeDir, '.claude');
const settingsPath = path.join(claudeDir, 'settings.json');
// Remove statusLine from settings.json (only if it points to our bundle)
try {
if (fs.existsSync(settingsPath)) {
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
if (settings.statusLine) {
const existingCmd = settings.statusLine.command || '';
if (existingCmd.includes('plan2code-statusline')) {
delete settings.statusLine;
const tmpSettings = settingsPath + '.tmp';
fs.writeFileSync(tmpSettings, JSON.stringify(settings, null, 2));
fs.renameSync(tmpSettings, settingsPath);
console.log(` ${COLORS.GREEN}${SYMBOLS.SUCCESS}${COLORS.RESET} Removed statusLine from settings.json`);
} else {
console.log(` ${COLORS.DIM}${SYMBOLS.INFO} Preserved non-plan2code statusLine in settings.json${COLORS.RESET}`);
console.log(` ${COLORS.DIM} ${existingCmd}${COLORS.RESET}`);
}
} else {
console.log(` ${COLORS.DIM}${SYMBOLS.INFO} No statusLine config in settings.json${COLORS.RESET}`);
}
}
} catch (error) {
console.log(` ${COLORS.RED}${SYMBOLS.ERROR}${COLORS.RESET} Failed to update settings.json: ${error.message}`);
}
// Remove script files (preserve config)
const filesToRemove = [
{ path: path.join(claudeDir, 'plan2code-statusline.js'), name: 'plan2code-statusline.js' },
{ path: path.join(claudeDir, 'plan2code-plan-usage.js'), name: 'plan2code-plan-usage.js' },
{ path: path.join(claudeDir, 'statusline.js'), name: 'statusline.js (legacy)' },
{ path: path.join(claudeDir, 'plan-usage.js'), name: 'plan-usage.js (legacy)' },
{ path: path.join(claudeDir, 'statusline-cache.json'), name: 'statusline-cache.json' },
];
let removedCount = 0;
for (const file of filesToRemove) {
try {
if (fs.existsSync(file.path)) {
fs.unlinkSync(file.path);
console.log(` ${COLORS.GREEN}${SYMBOLS.SUCCESS}${COLORS.RESET} Removed: ${file.name}`);
removedCount++;
}
} catch (error) {
console.log(` ${COLORS.RED}${SYMBOLS.ERROR}${COLORS.RESET} Failed to remove ${file.name}: ${error.message}`);
}
}
if (removedCount === 0) {
console.log(` ${COLORS.DIM}${SYMBOLS.INFO} No status line files found${COLORS.RESET}`);
}
console.log(` ${COLORS.DIM}${SYMBOLS.INFO} Preserved: statusline-config.json${COLORS.RESET}`);
console.log('');
console.log(`${COLORS.GREEN}${SYMBOLS.SUCCESS} Status line uninstalled${COLORS.RESET}`);
console.log('');
return 0;
}
// ============================================================================
// PLAN2CODE-BOT INSTALLATION
// ============================================================================
@@ -2362,23 +2566,13 @@ function removeBotGlobalSymlink() {
for (const file of filesToRemove) {
try {
fs.lstatSync(file.path);
try {
fs.rmdirSync(file.path);
console.log(` ${COLORS.GREEN}${SYMBOLS.SUCCESS}${COLORS.RESET} Removed: ${file.name}`);
removedCount++;
} catch (rmdirErr) {
try {
fs.unlinkSync(file.path);
console.log(` ${COLORS.GREEN}${SYMBOLS.SUCCESS}${COLORS.RESET} Removed: ${file.name}`);
removedCount++;
} catch (unlinkErr) {
fs.rmSync(file.path, { force: true, recursive: true });
console.log(` ${COLORS.GREEN}${SYMBOLS.SUCCESS}${COLORS.RESET} Removed: ${file.name}`);
removedCount++;
}
}
fs.rmSync(file.path, { force: true, recursive: true });
console.log(` ${COLORS.GREEN}${SYMBOLS.SUCCESS}${COLORS.RESET} Removed: ${file.name}`);
removedCount++;
} catch (err) {
// File doesn't exist, skip it
if (err.code !== 'ENOENT') {
console.log(` ${COLORS.RED}${SYMBOLS.ERROR}${COLORS.RESET} Failed to remove ${file.name}: ${err.message}`);
}
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "plan2code",
"version": "1.14.0",
"version": "1.15.2",
"private": true,
"bin": {
"plan2code": "./install.js"
+2 -2
View File
@@ -6,7 +6,7 @@ Recursive self-improvement toolchain for plan2code contributors. Collects metric
```bash
# Install (one-time, from the plan2code root)
node install.js # → "I" (Install All) includes metrics
node install.js # → "A" (Install All + dev tools) includes metrics
# → or "M" (Metrics only) under the CUSTOM sub-menu
# After finishing any project spec (steps 1-4):
@@ -78,7 +78,7 @@ From the plan2code repo root:
node install.js
```
Choose **I** (Install All) to install prompts, loop CLI, and metrics together. Or choose **C** (Custom) then **M** (Metrics) to install just the metrics CLI.
Choose **A** (Install All + dev tools) to install prompts, loop CLI, bot, metrics, and status line together. Or choose **C** (Custom) then **M** (Metrics) to install just the metrics CLI.
The installer handles `npm install`, `npm run build`, and global linking automatically. After install, `plan2code-metrics` is available from any directory.
+1 -1
View File
@@ -7,7 +7,7 @@ const CHAR_LIMIT = 11000;
const srcDir = path.join(__dirname, '..', 'src');
// Note: readdirSync is non-recursive, so files in subdirectories like
// plan2code-3b-review-references/ are automatically excluded from
// plan2code-review-references/ are automatically excluded from
// the character limit check. Reference files have no char limit.
const files = fs.readdirSync(srcDir)
.filter(f => f.startsWith('plan2code-') && f.endsWith('.md'))
+1 -1
View File
@@ -204,7 +204,7 @@ On user "approved":
4. **If more phases:** "NEXT STEP: Start NEW conversation and run: `/plan2code-3-implement`"
5. **If final phase:** "NEXT STEP: Start NEW conversation and run: `/plan2code-4-finalize`"
6. Mention `/plan2code-1b-revise-plan` option
7. Suggest: "Optional: run `/plan2code-3b-review` for a post-phase code review -- recommended after key features or milestones."
7. Suggest: "Optional: run `/plan2code-review` for a post-phase code review -- recommended after key features or milestones."
Planny (continuing):
```
+2 -2
View File
@@ -10,7 +10,7 @@ Start all CREATE AGENTS MODE responses with '💡'
╰───╯
```
Analyze this codebase and create `AGENTS.md` to guide future AI coding agents (Claude Code, Codex, Gemini CLI, Devin, etc.).
Analyze this codebase and create `AGENTS.md` to guide future AI coding agents (Claude Code, Codex, Gemini CLI, Devin, Zed, etc.).
## Content
@@ -31,7 +31,7 @@ Prefix the file with:
```
# AGENTS.md
This file provides guidance to AI coding agents like Claude Code (claude.ai/code), Cursor AI, Codex, Gemini CLI, GitHub Copilot, Devin, and other AI coding assistants when working with code in this repository.
This file provides guidance to AI coding agents like Claude Code (claude.ai/code), Cursor AI, Codex, Gemini CLI, GitHub Copilot, Devin, Zed, and other AI coding assistants when working with code in this repository.
```
---
@@ -1,5 +1,5 @@
# Dimension Checklists
> Part of plan2code-3b-review — loaded during Step 3 (Analyze).
> Part of plan2code-review — loaded during Step 3 (Analyze).
Non-obvious check items, anti-patterns, and "don't flag" guidance for each review dimension. Focus on what LLMs commonly miss.
@@ -1,5 +1,5 @@
# False-Positive Catalog
> Part of plan2code-3b-review — loaded before presenting findings in Step 3 (Analyze).
> Part of plan2code-review — loaded before presenting findings in Step 3 (Analyze).
Check every finding against this catalog before presenting it. These patterns represent common categories of false findings that waste reviewer and developer time. Each entry includes the bias, a concrete example, why it's wrong, and how to verify before flagging.
@@ -1,5 +1,5 @@
# Verification Protocol
> Part of plan2code-3b-review — loaded during Step 3 (Analyze).
> Part of plan2code-review — loaded during Step 3 (Analyze).
Apply this protocol to every finding before presenting it. Findings that fail verification are dropped. No exceptions.
+167
View File
@@ -0,0 +1,167 @@
# Plan2Code Status Line (Claude CLI)
A persistent three-line status bar for Claude Code that displays model info, project context, context window usage, usage stats (rate limits or token counts), and git diff stats.
## Example Output
**Pro / Max / Teams** — rate limits segment:
```
╭─╮ Opus 4.6 │ plan2code │ feature/statusline │ +12 -3
│★│ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
╰─╯ 3h 5m ($4.62) │ ▰▰▰▰▰▱▱▱▱▱▱▱ 42% │ 5h: 28% · 7d: 61%
```
**Enterprise / Bedrock / Vertex / PAYG** — session token counts (no `rate_limits` in stdin):
```
╭─╮ Sonnet 4.5 │ plan2code │ main │ +12 -3
│★│ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
╰─╯ 2m ($0.18) │ ▰▰▱▱▱▱▱▱▱▱▱▱ 18% │ 88k in · 3k out
```
**Line 1:** Planny icon | Model name | Project name | Git branch | Uncommitted changes
**Line 2:** Planny icon | Gray separator
**Line 3:** Planny icon | Session duration + cost | Context window bar | Usage (rate limits or token counts)
## Installation
### Via install.js (Recommended)
```bash
node install.js
# Select A (Install All + dev tools) or C (Custom) → S (Status Line)
```
This automatically:
1. Copies `statusline.js``~/.claude/plan2code-statusline.js`
2. Creates default config at `~/.claude/statusline-config.json` (preserves existing)
3. Registers the status line in `~/.claude/settings.json` (atomic write)
### Manual Setup
1. Copy `statusline.js``~/.claude/plan2code-statusline.js`
2. Copy `statusline-config.json``~/.claude/statusline-config.json`
3. Add to `~/.claude/settings.json`:
```json
{
"statusLine": {
"type": "command",
"command": "node /path/to/home/.claude/plan2code-statusline.js"
}
}
```
## Configuration
Edit `~/.claude/statusline-config.json`:
```json
{
"autocompactBuffer": 33000,
"color": true,
"compact": false,
"items": {
"model": true,
"project": true,
"branch": true,
"contextBar": true,
"planUsage": true,
"linesChanged": true,
"duration": true,
"sessionCost": true
}
}
```
| Option | Default | Description |
|--------|---------|-------------|
| `autocompactBuffer` | `33000` | Autocompact buffer size in tokens. Context bar scales usable window = total buffer. Adjust to match your model's autocompact buffer. |
| `color` | `true` | Enable ANSI color output. Set `false` for monochrome. The `NO_COLOR` environment variable (per [no-color.org](https://no-color.org)) also disables color. |
| `compact` | `false` | Two-line mode — drops the mascot icons and the separator line. Goes from 3 lines to 2, no horizontal truncation. |
| `items.model` | `true` | Show model name (Opus, Sonnet, Haiku) |
| `items.project` | `true` | Show project directory name |
| `items.branch` | `true` | Show current git branch (falls back to short SHA when detached HEAD) |
| `items.contextBar` | `true` | Show context window usage bar with percentage |
| `items.planUsage` | `true` | Show usage info: rate limits (Pro/Max) or token counts (Bedrock/Vertex/PAYG) |
| `items.linesChanged` | `true` | Show uncommitted git diff stats (+added -removed) |
| `items.duration` | `true` | Show session duration |
| `items.sessionCost` | `true` | Append estimated session cost in parens next to duration (e.g. `49m ($4.62)`). Reads `cost.total_cost_usd` from Claude Code stdin — client-side estimate, not billing-exact. Session-scoped only; monthly enterprise totals are not exposed by Claude Code. Hidden when cost is 0 or unavailable. |
Malformed config falls back to defaults silently. Type errors on individual keys also fall back to defaults.
## Platform Support
| Platform | Status | Notes |
|----------|--------|-------|
| Claude Code | Supported | Full feature set via `settings.json` registration |
| Copilot CLI | Planned | Deferred — CLI lacks status line script support as of v0.0.421 |
| Others | Not supported | Codex CLI, Gemini CLI have built-in status, not scriptable |
## Usage Display
The usage segment reads data directly from Claude Code's stdin JSON — no API calls, no auth needed, no background processes.
Two display modes are automatically selected based on the data present in stdin:
| User Type | Condition | Display Format | Data Source |
|-----------|-----------|----------------|-------------|
| Pro/Max/Teams | `rate_limits` present in stdin | `5h: 28% · 7d: 61%` | `rate_limits.five_hour.used_percentage`, `rate_limits.seven_day.used_percentage` |
| Bedrock/Vertex/PAYG | No `rate_limits` in stdin | `12k in · 4k out` | `context_window.total_input_tokens`, `context_window.total_output_tokens` |
### Color Thresholds
- Rate limits: green < 60%, yellow 6079%, red 80%+
- Token counts: neutral/white (no thresholds)
- Context bar: green < 50%, yellow 5069%, red 70%+
## Debugging
The bundled script accepts two diagnostic flags for local testing:
```bash
# Print parsed stdin and merged config to stderr
echo '{"model":{"display_name":"Opus 4.6"}}' | node ~/.claude/plan2code-statusline.js --debug
# Load a fixture file instead of reading from stdin
node ~/.claude/plan2code-statusline.js --fixture ./fixture.json
node ~/.claude/plan2code-statusline.js --fixture ./fixture.json --debug
```
Fixture format = whatever Claude Code sends on stdin (JSON with `model`, `workspace`, `context_window`, `rate_limits`, `cost.total_duration_ms`).
## Troubleshooting
| Issue | Solution |
|-------|----------|
| Garbled ANSI codes in output | Set `"color": false` in config, or export `NO_COLOR=1` |
| Status line not appearing | Verify `~/.claude/settings.json` has `statusLine` key with correct path to `plan2code-statusline.js` |
| Slow execution (>100ms) | Git calls are timeout-bounded to 1.5s each; non-repos short-circuit via `.git` directory check. If still slow, disable `branch` or `linesChanged`. |
| Usage segment not showing | Rate limits require Pro/Max/Teams plan. Token counts require at least one assistant message. Use `--debug` to inspect stdin. |
| Wrong context bar percentage | Adjust `autocompactBuffer` to match your model. Default 33000 tokens. |
| Three lines take too much space | Set `"compact": true` to drop the mascot icons + separator line (3 lines → 2). |
## Architecture
```
src/statusline-claude/
├── statusline.js # Self-contained: config, git, formatters, render
└── statusline-config.json # Default config template
```
Single-file design — `statusline.js` is copied verbatim to `~/.claude/plan2code-statusline.js` on install. No bundler, no runtime dependencies.
## Uninstalling
### Via install.js
```bash
node install.js
# Select U (Uninstall) → confirms removal
```
### Manual Removal
1. Remove `statusLine` key from `~/.claude/settings.json`
2. Delete `~/.claude/plan2code-statusline.js`
3. Optionally delete `~/.claude/statusline-config.json`
@@ -0,0 +1,15 @@
{
"autocompactBuffer": 33000,
"color": true,
"compact": false,
"items": {
"model": true,
"project": true,
"branch": true,
"contextBar": true,
"planUsage": true,
"linesChanged": true,
"duration": true,
"sessionCost": true
}
}
+425
View File
@@ -0,0 +1,425 @@
#!/usr/bin/env node
const fs = require('fs');
const os = require('os');
const path = require('path');
const { execFileSync } = require('child_process');
// ============================================================================
// Constants
// ============================================================================
const CONTEXT_BAR_LEN = 12;
const CONTEXT_THRESHOLDS = { green: 50, yellow: 70 };
const RATE_LIMIT_THRESHOLDS = { green: 60, yellow: 80 };
const GIT_TIMEOUT_MS = 1500;
const DEFAULT_WINDOW_SIZE = 200000;
const SEPARATOR_WIDTH = 55;
// ============================================================================
// Config
// ============================================================================
const DEFAULTS = {
autocompactBuffer: 33000,
color: true,
compact: false,
items: {
model: true,
project: true,
branch: true,
contextBar: true,
planUsage: true,
linesChanged: true,
duration: true,
sessionCost: true,
},
};
function loadConfig() {
const configPath = path.join(os.homedir(), '.claude', 'statusline-config.json');
let merged = { ...DEFAULTS, items: { ...DEFAULTS.items } };
try {
const raw = fs.readFileSync(configPath, 'utf8');
const user = JSON.parse(raw);
merged = {
...DEFAULTS,
...user,
items: { ...DEFAULTS.items, ...(user.items || {}) },
};
} catch {
// Fall through to defaults
}
// Type validation with fallback
if (!Number.isInteger(merged.autocompactBuffer) || merged.autocompactBuffer < 0 || merged.autocompactBuffer >= 1_000_000) {
merged.autocompactBuffer = DEFAULTS.autocompactBuffer;
}
if (typeof merged.color !== 'boolean') merged.color = DEFAULTS.color;
if (typeof merged.compact !== 'boolean') merged.compact = DEFAULTS.compact;
for (const key of Object.keys(DEFAULTS.items)) {
if (typeof merged.items[key] !== 'boolean') merged.items[key] = DEFAULTS.items[key];
}
// NO_COLOR convention (https://no-color.org) — env trumps config
if (process.env.NO_COLOR !== undefined && process.env.NO_COLOR !== '') {
merged.color = false;
}
return merged;
}
// ============================================================================
// Colors
// ============================================================================
const rgb = (r, g, b) => `\x1b[38;2;${r};${g};${b}m`;
const C = {
reset: '\x1b[0m',
bold: '\x1b[1m',
dim: '\x1b[2m',
green: '\x1b[32m',
yellow: '\x1b[33m',
red: '\x1b[31m',
cyan: '\x1b[36m',
white: '\x1b[37m',
gray: '\x1b[90m',
// Plan2Code brand
teal: rgb(190, 192, 200), // Silver — repo
sky: rgb(120, 195, 255), // Light sky blue — branch
muted: rgb(40, 120, 200), // Mid blue — model
label: rgb(130, 135, 150), // Mid gray — field labels (5h, 7d)
// Bar & quota thresholds
barGreen: rgb(50, 220, 100),
barYellow: rgb(220, 180, 50),
barRed: rgb(220, 80, 70),
// Planny mascot
sGreen: rgb(60, 200, 120),
sBlue: rgb(50, 100, 200),
sEye: rgb(255, 255, 255),
};
const SEP = (config) => config.color ? `${C.gray}${C.reset}` : ' │ ';
const DOT = (config) => config.color ? `${C.gray} · ${C.reset}` : ' · ';
// ============================================================================
// Git helpers
// ============================================================================
function gitExec(args, cwd) {
try {
return execFileSync('git', args, {
cwd,
timeout: GIT_TIMEOUT_MS,
encoding: 'utf8',
stdio: ['pipe', 'pipe', 'pipe'],
windowsHide: true,
}).trim();
} catch {
return null;
}
}
function isGitRepo(cwd) {
try {
// Walk up parent dirs (bounded) — monorepos open Claude Code in a subdir
// where .git lives at the repo root. .git can be a directory or a file.
let dir = cwd;
for (let i = 0; i < 10; i++) {
if (fs.existsSync(path.join(dir, '.git'))) return true;
const parent = path.dirname(dir);
if (parent === dir) return false;
dir = parent;
}
return false;
} catch {
return false;
}
}
function getGitBranch(stdinData) {
const cwd = stdinData?.workspace?.project_dir || process.cwd();
if (!isGitRepo(cwd)) return '';
const branch = gitExec(['symbolic-ref', '--short', 'HEAD'], cwd);
if (branch) return branch;
// Detached HEAD fallback — show short SHA so branch segment isn't empty
const sha = gitExec(['rev-parse', '--short', 'HEAD'], cwd);
return sha || '';
}
// ============================================================================
// Formatters
// ============================================================================
function getModelName(stdinData) {
const raw = stdinData?.model;
if (!raw) return '';
if (typeof raw === 'object' && raw.display_name) {
return raw.display_name.replace(/ context\)/gi, ')');
}
const model = typeof raw === 'object' ? raw.id : raw;
if (!model || typeof model !== 'string') return '';
const claudeMatch = model.match(/^claude-(\w+)-/);
if (claudeMatch) {
return claudeMatch[1].charAt(0).toUpperCase() + claudeMatch[1].slice(1);
}
const gptMatch = model.match(/^(gpt-[\w.]+)/i);
if (gptMatch) {
return gptMatch[1].toUpperCase();
}
return model;
}
function getProjectName(stdinData) {
const projectDir = stdinData?.workspace?.project_dir;
if (!projectDir || typeof projectDir !== 'string') return '';
return path.basename(projectDir);
}
function calculateContextPercent(stdinData, config) {
const cw = stdinData?.context_window;
if (!cw) return null;
const windowSize = cw.context_window_size || DEFAULT_WINDOW_SIZE;
const buffer = config?.autocompactBuffer ?? DEFAULTS.autocompactBuffer;
const usableTokens = windowSize - buffer;
if (usableTokens <= 0) return 100;
const rawUsedPct = cw.used_percentage ?? 0;
const rawUsedTokens = (rawUsedPct / 100) * windowSize;
return Math.round(Math.min(100, (rawUsedTokens / usableTokens) * 100));
}
function formatContextBar(percent, config) {
if (percent == null) return null;
const clamped = Math.max(0, Math.min(100, percent));
const filled = Math.round((clamped / 100) * CONTEXT_BAR_LEN);
const filledStr = '▰'.repeat(filled);
const emptyStr = '▱'.repeat(CONTEXT_BAR_LEN - filled);
if (!config.color) return `${filledStr}${emptyStr} ${clamped}%`;
const barColor = clamped >= CONTEXT_THRESHOLDS.yellow ? C.barRed
: clamped >= CONTEXT_THRESHOLDS.green ? C.barYellow
: C.barGreen;
return `${barColor}${filledStr}${C.gray}${emptyStr}${C.reset} ${barColor}${clamped}%${C.reset}`;
}
function formatLinesChanged(stdinData, config) {
const cwd = stdinData?.workspace?.project_dir || process.cwd();
if (!isGitRepo(cwd)) return null;
const raw = gitExec(['diff', 'HEAD', '--numstat'], cwd);
if (raw == null) return null;
let added = 0, removed = 0;
for (const line of raw.split('\n')) {
if (!line) continue;
const [a, r] = line.split('\t');
if (a !== '-') added += parseInt(a, 10) || 0;
if (r !== '-') removed += parseInt(r, 10) || 0;
}
if (added === 0 && removed === 0) return null;
if (!config.color) return `+${added} -${removed}`;
return `${C.green}+${added}${C.reset} ${C.red}-${removed}${C.reset}`;
}
function formatSessionCost(stdinData) {
const usd = stdinData?.cost?.total_cost_usd;
if (usd == null || isNaN(usd) || usd <= 0) return null;
if (usd < 0.01) return '<$0.01';
if (usd < 100) return `$${usd.toFixed(2)}`;
return `$${Math.round(usd)}`;
}
function formatDuration(stdinData, config) {
const ms = stdinData?.cost?.total_duration_ms;
if (!ms) return null;
const totalSec = Math.round(ms / 1000);
let text;
if (totalSec < 60) text = `${totalSec}s`;
else {
const hours = Math.floor(totalSec / 3600);
const mins = Math.floor((totalSec % 3600) / 60);
text = hours > 0 ? `${hours}h ${mins}m` : `${mins}m`;
}
// Append session cost in parens when enabled: "49m ($4.62)"
if (config.items.sessionCost) {
const cost = formatSessionCost(stdinData);
if (cost) {
text = config.color
? `${C.label}${text} (${cost})${C.reset}`
: `${text} (${cost})`;
return text;
}
}
return config.color ? `${C.label}${text}${C.reset}` : text;
}
function colorize(text, percent, thresholds, config) {
if (!config.color || percent == null) return text;
let code;
if (percent >= thresholds.yellow) code = C.barRed;
else if (percent >= thresholds.green) code = C.barYellow;
else code = C.barGreen;
return `${code}${text}${C.reset}`;
}
function formatTokenCount(n) {
if (n == null || isNaN(n) || n < 0) return '0';
if (n < 1000) return String(Math.round(n));
if (n < 999500) return Math.round(n / 1000) + 'k';
return (n / 1_000_000).toFixed(1) + 'M';
}
function formatRateLimits(stdinData, config) {
if (!config.items.planUsage) return null;
const fiveHour = stdinData?.rate_limits?.five_hour?.used_percentage;
const sevenDay = stdinData?.rate_limits?.seven_day?.used_percentage;
if (fiveHour == null && sevenDay == null) return null;
const parts = [];
if (fiveHour != null) {
const pct = Math.round(fiveHour);
const val = colorize(`${pct}%`, pct, RATE_LIMIT_THRESHOLDS, config);
parts.push(config.color ? `${C.label}5h:${C.reset} ${val}` : `5h: ${pct}%`);
}
if (sevenDay != null) {
const pct = Math.round(sevenDay);
const val = colorize(`${pct}%`, pct, RATE_LIMIT_THRESHOLDS, config);
parts.push(config.color ? `${C.label}7d:${C.reset} ${val}` : `7d: ${pct}%`);
}
return parts.join(DOT(config));
}
function formatTokenUsage(stdinData, config) {
if (!config.items.planUsage) return null;
const inp = stdinData?.context_window?.total_input_tokens;
const out = stdinData?.context_window?.total_output_tokens;
if ((!inp || inp === 0) && (!out || out === 0)) return null;
const inStr = formatTokenCount(inp || 0);
const outStr = formatTokenCount(out || 0);
if (!config.color) return `${inStr} in${DOT(config)}${outStr} out`;
return `${C.white}${inStr}${C.reset} ${C.label}in${C.reset}${DOT(config)}${C.white}${outStr}${C.reset} ${C.label}out${C.reset}`;
}
function formatLine1(stdinData, config) {
const segments = [];
if (config.items.model) {
const model = getModelName(stdinData);
if (model) segments.push(config.color ? `${C.muted}${model}${C.reset}` : model);
}
if (config.items.project) {
const project = getProjectName(stdinData);
if (project) segments.push(config.color ? `${C.bold}${C.teal}${project}${C.reset}` : project);
}
if (config.items.branch) {
const branch = getGitBranch(stdinData);
if (branch) segments.push(config.color ? `${C.sky}${branch}${C.reset}` : branch);
}
if (config.items.linesChanged) {
const lines = formatLinesChanged(stdinData, config);
if (lines) segments.push(lines);
}
return segments.join(SEP(config));
}
function formatLine2(stdinData, config) {
const segments = [];
if (config.items.duration) {
const dur = formatDuration(stdinData, config);
if (dur) segments.push(dur);
}
if (config.items.contextBar) {
const percent = calculateContextPercent(stdinData, config);
const bar = formatContextBar(percent, config);
if (bar) segments.push(bar);
}
if (config.items.planUsage) {
const usage = formatRateLimits(stdinData, config) || formatTokenUsage(stdinData, config);
if (usage) segments.push(usage);
}
return segments.join(SEP(config));
}
// ============================================================================
// Main
// ============================================================================
function loadStdin() {
// --fixture <path> for local diagnostics (bypasses stdin)
const fixtureIdx = process.argv.indexOf('--fixture');
if (fixtureIdx !== -1 && process.argv[fixtureIdx + 1]) {
try {
return JSON.parse(fs.readFileSync(process.argv[fixtureIdx + 1], 'utf8'));
} catch (err) {
process.stderr.write(`[statusline] fixture load failed: ${err.message}\n`);
process.exit(1);
}
}
try {
return JSON.parse(fs.readFileSync(0, 'utf8'));
} catch {
return null;
}
}
function main() {
const debug = process.argv.includes('--debug');
const stdinData = loadStdin();
if (!stdinData) process.exit(0);
const config = loadConfig();
if (debug) {
process.stderr.write('[statusline debug] parsed stdin:\n');
process.stderr.write(JSON.stringify(stdinData, null, 2) + '\n');
process.stderr.write('[statusline debug] merged config:\n');
process.stderr.write(JSON.stringify(config, null, 2) + '\n');
}
const line1 = formatLine1(stdinData, config);
const line2 = formatLine2(stdinData, config);
if (!line1 && !line2) return;
if (config.compact) {
if (line1) process.stdout.write(line1 + '\n');
if (line2) process.stdout.write(line2 + '\n');
return;
}
if (config.color) {
const ico1 = `${C.sGreen}╭─╮${C.reset} `;
const ico2 = `${C.sGreen}${C.sEye}${C.sGreen}${C.reset} `;
const ico3 = `${C.sGreen}╰─╯${C.reset} `;
const pad = `${C.gray}${'┄'.repeat(SEPARATOR_WIDTH)}${C.reset}`;
process.stdout.write(ico1 + line1 + '\n' + ico2 + pad + '\n' + ico3 + line2 + '\n');
} else {
const ico1 = '╭─╮ ';
const ico2 = '│★│ ';
const ico3 = '╰─╯ ';
const pad = '┄'.repeat(SEPARATOR_WIDTH);
process.stdout.write(ico1 + line1 + '\n' + ico2 + pad + '\n' + ico3 + line2 + '\n');
}
}
try {
main();
} catch {
process.exit(0);
}
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "Plan2Code",
"version": "1.14.0",
"version": "1.15.2",
"description": "A structured 4-step workflow methodology for AI-assisted software development",
"keywords": [
"ai",
@@ -17,7 +17,7 @@
"url": "https://github.com/jparkerweb/plan2code"
},
"homepage": "https://plan2code.jparkerweb.com",
"releaseDate": "2026-05-22",
"releaseDate": "2026-06-01",
"mode": "utility"
}