@sienklogic/plan-build-run 2.0.0 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +56 -56
- package/CLAUDE.md +149 -149
- package/LICENSE +21 -21
- package/README.md +247 -247
- package/dashboard/bin/cli.js +25 -25
- package/dashboard/package.json +34 -34
- package/dashboard/public/css/layout.css +406 -406
- package/dashboard/public/css/status-colors.css +98 -98
- package/dashboard/public/js/htmx-title.js +5 -5
- package/dashboard/public/js/sidebar-toggle.js +20 -20
- package/dashboard/src/app.js +78 -78
- package/dashboard/src/middleware/errorHandler.js +52 -52
- package/dashboard/src/middleware/notFoundHandler.js +9 -9
- package/dashboard/src/repositories/planning.repository.js +128 -128
- package/dashboard/src/routes/events.routes.js +40 -40
- package/dashboard/src/routes/index.routes.js +31 -31
- package/dashboard/src/routes/pages.routes.js +245 -195
- package/dashboard/src/server.js +42 -42
- package/dashboard/src/services/dashboard.service.js +222 -222
- package/dashboard/src/services/phase.service.js +220 -167
- package/dashboard/src/services/project.service.js +57 -57
- package/dashboard/src/services/roadmap.service.js +171 -171
- package/dashboard/src/services/sse.service.js +58 -58
- package/dashboard/src/services/todo.service.js +254 -254
- package/dashboard/src/services/watcher.service.js +48 -48
- package/dashboard/src/views/coming-soon.ejs +11 -11
- package/dashboard/src/views/error.ejs +13 -13
- package/dashboard/src/views/index.ejs +5 -5
- package/dashboard/src/views/layout.ejs +1 -1
- package/dashboard/src/views/partials/dashboard-content.ejs +77 -77
- package/dashboard/src/views/partials/footer.ejs +3 -3
- package/dashboard/src/views/partials/head.ejs +21 -21
- package/dashboard/src/views/partials/header.ejs +12 -12
- package/dashboard/src/views/partials/layout-bottom.ejs +15 -15
- package/dashboard/src/views/partials/layout-top.ejs +8 -8
- package/dashboard/src/views/partials/phase-content.ejs +188 -181
- package/dashboard/src/views/partials/phase-doc-content.ejs +38 -0
- package/dashboard/src/views/partials/phases-content.ejs +117 -117
- package/dashboard/src/views/partials/roadmap-content.ejs +142 -142
- package/dashboard/src/views/partials/sidebar.ejs +38 -38
- package/dashboard/src/views/partials/todo-create-content.ejs +53 -53
- package/dashboard/src/views/partials/todo-detail-content.ejs +38 -38
- package/dashboard/src/views/partials/todos-content.ejs +53 -53
- package/dashboard/src/views/phase-detail.ejs +5 -5
- package/dashboard/src/views/phase-doc.ejs +5 -0
- package/dashboard/src/views/phases.ejs +5 -5
- package/dashboard/src/views/roadmap.ejs +5 -5
- package/dashboard/src/views/todo-create.ejs +5 -5
- package/dashboard/src/views/todo-detail.ejs +5 -5
- package/dashboard/src/views/todos.ejs +5 -5
- package/package.json +57 -57
- package/plugins/cursor-pbr/.cursor-plugin/plugin.json +22 -0
- package/plugins/cursor-pbr/agents/.gitkeep +0 -0
- package/plugins/cursor-pbr/assets/.gitkeep +0 -0
- package/plugins/cursor-pbr/hooks/hooks.json +11 -0
- package/plugins/cursor-pbr/references/.gitkeep +0 -0
- package/plugins/cursor-pbr/rules/.gitkeep +0 -0
- package/plugins/cursor-pbr/skills/.gitkeep +0 -0
- package/plugins/cursor-pbr/templates/.gitkeep +0 -0
- package/plugins/pbr/.claude-plugin/plugin.json +13 -13
- package/plugins/pbr/UI-CONSISTENCY-GAPS.md +61 -61
- package/plugins/pbr/agents/codebase-mapper.md +279 -271
- package/plugins/pbr/agents/debugger.md +281 -281
- package/plugins/pbr/agents/executor.md +428 -407
- package/plugins/pbr/agents/general.md +164 -164
- package/plugins/pbr/agents/integration-checker.md +169 -141
- package/plugins/pbr/agents/plan-checker.md +296 -280
- package/plugins/pbr/agents/planner.md +358 -358
- package/plugins/pbr/agents/researcher.md +363 -363
- package/plugins/pbr/agents/synthesizer.md +230 -230
- package/plugins/pbr/agents/verifier.md +489 -454
- package/plugins/pbr/commands/begin.md +5 -5
- package/plugins/pbr/commands/build.md +5 -5
- package/plugins/pbr/commands/config.md +5 -5
- package/plugins/pbr/commands/continue.md +5 -5
- package/plugins/pbr/commands/debug.md +5 -5
- package/plugins/pbr/commands/discuss.md +5 -5
- package/plugins/pbr/commands/explore.md +5 -5
- package/plugins/pbr/commands/health.md +5 -5
- package/plugins/pbr/commands/help.md +5 -5
- package/plugins/pbr/commands/import.md +5 -5
- package/plugins/pbr/commands/milestone.md +5 -5
- package/plugins/pbr/commands/note.md +5 -5
- package/plugins/pbr/commands/pause.md +5 -5
- package/plugins/pbr/commands/plan.md +5 -5
- package/plugins/pbr/commands/quick.md +5 -5
- package/plugins/pbr/commands/resume.md +5 -5
- package/plugins/pbr/commands/review.md +5 -5
- package/plugins/pbr/commands/scan.md +5 -5
- package/plugins/pbr/commands/setup.md +5 -5
- package/plugins/pbr/commands/status.md +5 -5
- package/plugins/pbr/commands/todo.md +5 -5
- package/plugins/pbr/contexts/dev.md +27 -27
- package/plugins/pbr/contexts/research.md +28 -28
- package/plugins/pbr/contexts/review.md +36 -36
- package/plugins/pbr/hooks/hooks.json +183 -183
- package/plugins/pbr/references/agent-anti-patterns.md +24 -24
- package/plugins/pbr/references/agent-interactions.md +134 -134
- package/plugins/pbr/references/agent-teams.md +54 -54
- package/plugins/pbr/references/checkpoints.md +157 -157
- package/plugins/pbr/references/common-bug-patterns.md +13 -13
- package/plugins/pbr/references/config-reference.md +441 -0
- package/plugins/pbr/references/continuation-format.md +212 -212
- package/plugins/pbr/references/deviation-rules.md +112 -112
- package/plugins/pbr/references/git-integration.md +226 -226
- package/plugins/pbr/references/integration-patterns.md +117 -117
- package/plugins/pbr/references/model-profiles.md +99 -99
- package/plugins/pbr/references/model-selection.md +31 -31
- package/plugins/pbr/references/pbr-rules.md +193 -193
- package/plugins/pbr/references/plan-authoring.md +181 -181
- package/plugins/pbr/references/plan-format.md +287 -283
- package/plugins/pbr/references/planning-config.md +213 -213
- package/plugins/pbr/references/questioning.md +214 -214
- package/plugins/pbr/references/reading-verification.md +127 -127
- package/plugins/pbr/references/stub-patterns.md +160 -160
- package/plugins/pbr/references/subagent-coordination.md +119 -119
- package/plugins/pbr/references/ui-formatting.md +461 -399
- package/plugins/pbr/references/verification-patterns.md +198 -198
- package/plugins/pbr/references/wave-execution.md +95 -95
- package/plugins/pbr/scripts/auto-continue.js +80 -80
- package/plugins/pbr/scripts/check-dangerous-commands.js +136 -136
- package/plugins/pbr/scripts/check-doc-sprawl.js +102 -102
- package/plugins/pbr/scripts/check-phase-boundary.js +196 -196
- package/plugins/pbr/scripts/check-plan-format.js +270 -270
- package/plugins/pbr/scripts/check-roadmap-sync.js +322 -252
- package/plugins/pbr/scripts/check-skill-workflow.js +262 -262
- package/plugins/pbr/scripts/check-state-sync.js +476 -476
- package/plugins/pbr/scripts/check-subagent-output.js +144 -144
- package/plugins/pbr/scripts/config-schema.json +251 -251
- package/plugins/pbr/scripts/context-budget-check.js +287 -287
- package/plugins/pbr/scripts/event-handler.js +151 -151
- package/plugins/pbr/scripts/event-logger.js +92 -92
- package/plugins/pbr/scripts/hook-logger.js +80 -76
- package/plugins/pbr/scripts/hooks-schema.json +79 -79
- package/plugins/pbr/scripts/log-subagent.js +164 -152
- package/plugins/pbr/scripts/log-tool-failure.js +88 -88
- package/plugins/pbr/scripts/pbr-tools.js +1378 -1301
- package/plugins/pbr/scripts/post-write-dispatch.js +66 -66
- package/plugins/pbr/scripts/post-write-quality.js +207 -207
- package/plugins/pbr/scripts/pre-bash-dispatch.js +86 -56
- package/plugins/pbr/scripts/pre-write-dispatch.js +97 -62
- package/plugins/pbr/scripts/progress-tracker.js +281 -228
- package/plugins/pbr/scripts/run-hook.js +92 -0
- package/plugins/pbr/scripts/session-cleanup.js +254 -254
- package/plugins/pbr/scripts/status-line.js +288 -285
- package/plugins/pbr/scripts/suggest-compact.js +119 -119
- package/plugins/pbr/scripts/task-completed.js +45 -45
- package/plugins/pbr/scripts/track-context-budget.js +149 -119
- package/plugins/pbr/scripts/validate-commit.js +200 -200
- package/plugins/pbr/scripts/validate-plugin-structure.js +183 -172
- package/plugins/pbr/scripts/validate-task.js +106 -0
- package/plugins/pbr/skills/begin/SKILL.md +594 -545
- package/plugins/pbr/skills/begin/templates/PROJECT.md.tmpl +33 -33
- package/plugins/pbr/skills/begin/templates/REQUIREMENTS.md.tmpl +18 -18
- package/plugins/pbr/skills/begin/templates/STATE.md.tmpl +49 -49
- package/plugins/pbr/skills/begin/templates/config.json.tmpl +64 -63
- package/plugins/pbr/skills/begin/templates/researcher-prompt.md.tmpl +19 -19
- package/plugins/pbr/skills/begin/templates/roadmap-prompt.md.tmpl +30 -30
- package/plugins/pbr/skills/begin/templates/synthesis-prompt.md.tmpl +16 -16
- package/plugins/pbr/skills/build/SKILL.md +943 -962
- package/plugins/pbr/skills/config/SKILL.md +256 -241
- package/plugins/pbr/skills/continue/SKILL.md +164 -127
- package/plugins/pbr/skills/debug/SKILL.md +515 -489
- package/plugins/pbr/skills/debug/templates/continuation-prompt.md.tmpl +16 -16
- package/plugins/pbr/skills/debug/templates/initial-investigation-prompt.md.tmpl +27 -27
- package/plugins/pbr/skills/discuss/SKILL.md +347 -338
- package/plugins/pbr/skills/discuss/templates/CONTEXT.md.tmpl +61 -61
- package/plugins/pbr/skills/discuss/templates/decision-categories.md +9 -9
- package/plugins/pbr/skills/explore/SKILL.md +378 -362
- package/plugins/pbr/skills/health/SKILL.md +221 -186
- package/plugins/pbr/skills/health/templates/check-pattern.md.tmpl +30 -30
- package/plugins/pbr/skills/health/templates/output-format.md.tmpl +63 -63
- package/plugins/pbr/skills/help/SKILL.md +155 -140
- package/plugins/pbr/skills/import/SKILL.md +504 -490
- package/plugins/pbr/skills/milestone/SKILL.md +704 -673
- package/plugins/pbr/skills/milestone/templates/audit-report.md.tmpl +48 -48
- package/plugins/pbr/skills/milestone/templates/stats-file.md.tmpl +30 -30
- package/plugins/pbr/skills/note/SKILL.md +231 -212
- package/plugins/pbr/skills/pause/SKILL.md +249 -235
- package/plugins/pbr/skills/pause/templates/continue-here.md.tmpl +71 -71
- package/plugins/pbr/skills/plan/SKILL.md +685 -628
- package/plugins/pbr/skills/plan/decimal-phase-calc.md +98 -98
- package/plugins/pbr/skills/plan/templates/checker-prompt.md.tmpl +21 -21
- package/plugins/pbr/skills/plan/templates/gap-closure-prompt.md.tmpl +32 -32
- package/plugins/pbr/skills/plan/templates/planner-prompt.md.tmpl +38 -38
- package/plugins/pbr/skills/plan/templates/researcher-prompt.md.tmpl +19 -19
- package/plugins/pbr/skills/plan/templates/revision-prompt.md.tmpl +23 -23
- package/plugins/pbr/skills/quick/SKILL.md +354 -335
- package/plugins/pbr/skills/resume/SKILL.md +402 -388
- package/plugins/pbr/skills/review/SKILL.md +686 -652
- package/plugins/pbr/skills/review/templates/debugger-prompt.md.tmpl +60 -60
- package/plugins/pbr/skills/review/templates/gap-planner-prompt.md.tmpl +40 -40
- package/plugins/pbr/skills/review/templates/verifier-prompt.md.tmpl +115 -115
- package/plugins/pbr/skills/scan/SKILL.md +304 -269
- package/plugins/pbr/skills/scan/templates/mapper-prompt.md.tmpl +201 -201
- package/plugins/pbr/skills/setup/SKILL.md +253 -227
- package/plugins/pbr/skills/shared/commit-planning-docs.md +35 -35
- package/plugins/pbr/skills/shared/config-loading.md +102 -102
- package/plugins/pbr/skills/shared/context-budget.md +40 -40
- package/plugins/pbr/skills/shared/context-loader-task.md +86 -86
- package/plugins/pbr/skills/shared/digest-select.md +79 -79
- package/plugins/pbr/skills/shared/domain-probes.md +125 -125
- package/plugins/pbr/skills/shared/error-reporting.md +79 -79
- package/plugins/pbr/skills/shared/gate-prompts.md +388 -388
- package/plugins/pbr/skills/shared/phase-argument-parsing.md +45 -45
- package/plugins/pbr/skills/shared/progress-display.md +53 -53
- package/plugins/pbr/skills/shared/revision-loop.md +81 -81
- package/plugins/pbr/skills/shared/state-loading.md +62 -62
- package/plugins/pbr/skills/shared/state-update.md +161 -161
- package/plugins/pbr/skills/shared/universal-anti-patterns.md +33 -33
- package/plugins/pbr/skills/status/SKILL.md +367 -353
- package/plugins/pbr/skills/todo/SKILL.md +198 -181
- package/plugins/pbr/templates/CONTEXT.md.tmpl +52 -52
- package/plugins/pbr/templates/INTEGRATION-REPORT.md.tmpl +151 -151
- package/plugins/pbr/templates/RESEARCH-SUMMARY.md.tmpl +97 -97
- package/plugins/pbr/templates/ROADMAP.md.tmpl +40 -40
- package/plugins/pbr/templates/SUMMARY.md.tmpl +81 -81
- package/plugins/pbr/templates/VERIFICATION-DETAIL.md.tmpl +116 -116
- package/plugins/pbr/templates/codebase/ARCHITECTURE.md.tmpl +98 -98
- package/plugins/pbr/templates/codebase/CONCERNS.md.tmpl +93 -93
- package/plugins/pbr/templates/codebase/CONVENTIONS.md.tmpl +104 -104
- package/plugins/pbr/templates/codebase/INTEGRATIONS.md.tmpl +78 -78
- package/plugins/pbr/templates/codebase/STACK.md.tmpl +78 -78
- package/plugins/pbr/templates/codebase/STRUCTURE.md.tmpl +80 -80
- package/plugins/pbr/templates/codebase/TESTING.md.tmpl +107 -107
- package/plugins/pbr/templates/continue-here.md.tmpl +73 -73
- package/plugins/pbr/templates/prompt-partials/phase-project-context.md.tmpl +37 -37
- package/plugins/pbr/templates/research/ARCHITECTURE.md.tmpl +124 -124
- package/plugins/pbr/templates/research/STACK.md.tmpl +71 -71
- package/plugins/pbr/templates/research/SUMMARY.md.tmpl +112 -112
- package/plugins/pbr/templates/research-outputs/phase-research.md.tmpl +81 -81
- package/plugins/pbr/templates/research-outputs/project-research.md.tmpl +99 -99
- package/plugins/pbr/templates/research-outputs/synthesis.md.tmpl +36 -36
|
@@ -1,285 +1,288 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Status line: Updates Claude Code status bar with phase progress and
|
|
5
|
-
* context usage bar.
|
|
6
|
-
*
|
|
7
|
-
* Reads STATE.md for project position. Receives session JSON on stdin
|
|
8
|
-
* from Claude Code (context_window, model, cost, etc.).
|
|
9
|
-
*
|
|
10
|
-
* Output: plain text with ANSI color codes to stdout.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
const fs = require('fs');
|
|
14
|
-
const path = require('path');
|
|
15
|
-
const cp = require('child_process');
|
|
16
|
-
const { logHook } = require('./hook-logger');
|
|
17
|
-
const { configLoad } = require('./pbr-tools');
|
|
18
|
-
|
|
19
|
-
// ANSI color codes
|
|
20
|
-
const c = {
|
|
21
|
-
reset: '\x1b[0m',
|
|
22
|
-
bold: '\x1b[1m',
|
|
23
|
-
dim: '\x1b[2m',
|
|
24
|
-
cyan: '\x1b[36m',
|
|
25
|
-
green: '\x1b[32m',
|
|
26
|
-
yellow: '\x1b[33m',
|
|
27
|
-
red: '\x1b[31m',
|
|
28
|
-
blue: '\x1b[34m',
|
|
29
|
-
magenta: '\x1b[35m',
|
|
30
|
-
white: '\x1b[37m',
|
|
31
|
-
boldCyan: '\x1b[1;36m',
|
|
32
|
-
boldGreen: '\x1b[1;32m',
|
|
33
|
-
boldYellow: '\x1b[1;33m',
|
|
34
|
-
boldRed: '\x1b[1;31m',
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
// Default status_line config — works out of the box with zero config
|
|
38
|
-
const DEFAULTS = {
|
|
39
|
-
sections: ['phase', 'plan', 'status', 'git', 'context'],
|
|
40
|
-
brand_text: '\u25C6 Plan-Build-Run',
|
|
41
|
-
max_status_length: 50,
|
|
42
|
-
context_bar: {
|
|
43
|
-
width: 10,
|
|
44
|
-
thresholds: { green: 70, yellow: 90 },
|
|
45
|
-
chars: { filled: '\u2588', empty: '\u2591' }
|
|
46
|
-
}
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Load status_line config from .planning/config.json, merged with defaults.
|
|
51
|
-
* Returns DEFAULTS if no config exists or no status_line section is present.
|
|
52
|
-
*/
|
|
53
|
-
function loadStatusLineConfig(planningDir) {
|
|
54
|
-
const config = configLoad(planningDir);
|
|
55
|
-
if (!config || !config.status_line) return DEFAULTS;
|
|
56
|
-
|
|
57
|
-
const sl = config.status_line;
|
|
58
|
-
return {
|
|
59
|
-
sections: Array.isArray(sl.sections) ? sl.sections : DEFAULTS.sections,
|
|
60
|
-
brand_text: typeof sl.brand_text === 'string' ? sl.brand_text : DEFAULTS.brand_text,
|
|
61
|
-
max_status_length: typeof sl.max_status_length === 'number' ? sl.max_status_length : DEFAULTS.max_status_length,
|
|
62
|
-
context_bar: {
|
|
63
|
-
width: (sl.context_bar && typeof sl.context_bar.width === 'number') ? sl.context_bar.width : DEFAULTS.context_bar.width,
|
|
64
|
-
thresholds: {
|
|
65
|
-
green: (sl.context_bar && sl.context_bar.thresholds && typeof sl.context_bar.thresholds.green === 'number') ? sl.context_bar.thresholds.green : DEFAULTS.context_bar.thresholds.green,
|
|
66
|
-
yellow: (sl.context_bar && sl.context_bar.thresholds && typeof sl.context_bar.thresholds.yellow === 'number') ? sl.context_bar.thresholds.yellow : DEFAULTS.context_bar.thresholds.yellow
|
|
67
|
-
},
|
|
68
|
-
chars: {
|
|
69
|
-
filled: (sl.context_bar && sl.context_bar.chars && typeof sl.context_bar.chars.filled === 'string') ? sl.context_bar.chars.filled : DEFAULTS.context_bar.chars.filled,
|
|
70
|
-
empty: (sl.context_bar && sl.context_bar.chars && typeof sl.context_bar.chars.empty === 'string') ? sl.context_bar.chars.empty : DEFAULTS.context_bar.chars.empty
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function readStdin() {
|
|
77
|
-
try {
|
|
78
|
-
const input = fs.readFileSync(0, 'utf8').trim();
|
|
79
|
-
if (input) return JSON.parse(input);
|
|
80
|
-
} catch (_e) {
|
|
81
|
-
// stdin may be empty or not JSON — that's fine
|
|
82
|
-
}
|
|
83
|
-
return {};
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function getContextPercent(stdinData) {
|
|
87
|
-
// Claude Code statusLine sends context_window.used_percentage (0-100)
|
|
88
|
-
if (stdinData.context_window && stdinData.context_window.used_percentage != null) {
|
|
89
|
-
return Math.round(stdinData.context_window.used_percentage);
|
|
90
|
-
}
|
|
91
|
-
// Legacy field name
|
|
92
|
-
if (stdinData.context_usage_fraction != null) {
|
|
93
|
-
return Math.round(stdinData.context_usage_fraction * 100);
|
|
94
|
-
}
|
|
95
|
-
return null;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Build a horizontal bar using Unicode block characters.
|
|
100
|
-
* Width is in character cells. Color shifts green -> yellow -> red.
|
|
101
|
-
*
|
|
102
|
-
* @param {number} percent - Usage percentage (0-100)
|
|
103
|
-
* @param {number} width - Bar width in characters
|
|
104
|
-
* @param {object} [opts] - Optional config overrides
|
|
105
|
-
* @param {object} [opts.thresholds] - { green: number, yellow: number }
|
|
106
|
-
* @param {object} [opts.chars] - { filled: string, empty: string }
|
|
107
|
-
*/
|
|
108
|
-
function buildContextBar(percent, width, opts) {
|
|
109
|
-
if (width < 1) return '';
|
|
110
|
-
const thresholds = (opts && opts.thresholds) || DEFAULTS.context_bar.thresholds;
|
|
111
|
-
const chars = (opts && opts.chars) || DEFAULTS.context_bar.chars;
|
|
112
|
-
|
|
113
|
-
const filled = Math.round((percent / 100) * width);
|
|
114
|
-
const empty = width - filled;
|
|
115
|
-
|
|
116
|
-
// Color based on usage threshold
|
|
117
|
-
let barColor;
|
|
118
|
-
if (percent >= thresholds.yellow) barColor = c.boldRed;
|
|
119
|
-
else if (percent >= thresholds.green) barColor = c.boldYellow;
|
|
120
|
-
else barColor = c.boldGreen;
|
|
121
|
-
|
|
122
|
-
const filledStr = chars.filled.repeat(filled);
|
|
123
|
-
const emptyStr = chars.empty.repeat(empty);
|
|
124
|
-
|
|
125
|
-
return `${barColor}${filledStr}${c.dim}${emptyStr}${c.reset}`;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Pick a color for the phase status keyword.
|
|
130
|
-
*/
|
|
131
|
-
function statusColor(statusText) {
|
|
132
|
-
const lower = statusText.toLowerCase();
|
|
133
|
-
if (lower.includes('complete') || lower.includes('verified')) return c.green;
|
|
134
|
-
if (lower.includes('
|
|
135
|
-
if (lower.includes('
|
|
136
|
-
if (lower.includes('
|
|
137
|
-
return c.
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
const
|
|
202
|
-
const
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Status line: Updates Claude Code status bar with phase progress and
|
|
5
|
+
* context usage bar.
|
|
6
|
+
*
|
|
7
|
+
* Reads STATE.md for project position. Receives session JSON on stdin
|
|
8
|
+
* from Claude Code (context_window, model, cost, etc.).
|
|
9
|
+
*
|
|
10
|
+
* Output: plain text with ANSI color codes to stdout.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const cp = require('child_process');
|
|
16
|
+
const { logHook } = require('./hook-logger');
|
|
17
|
+
const { configLoad } = require('./pbr-tools');
|
|
18
|
+
|
|
19
|
+
// ANSI color codes
|
|
20
|
+
const c = {
|
|
21
|
+
reset: '\x1b[0m',
|
|
22
|
+
bold: '\x1b[1m',
|
|
23
|
+
dim: '\x1b[2m',
|
|
24
|
+
cyan: '\x1b[36m',
|
|
25
|
+
green: '\x1b[32m',
|
|
26
|
+
yellow: '\x1b[33m',
|
|
27
|
+
red: '\x1b[31m',
|
|
28
|
+
blue: '\x1b[34m',
|
|
29
|
+
magenta: '\x1b[35m',
|
|
30
|
+
white: '\x1b[37m',
|
|
31
|
+
boldCyan: '\x1b[1;36m',
|
|
32
|
+
boldGreen: '\x1b[1;32m',
|
|
33
|
+
boldYellow: '\x1b[1;33m',
|
|
34
|
+
boldRed: '\x1b[1;31m',
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// Default status_line config — works out of the box with zero config
|
|
38
|
+
const DEFAULTS = {
|
|
39
|
+
sections: ['phase', 'plan', 'status', 'git', 'context'],
|
|
40
|
+
brand_text: '\u25C6 Plan-Build-Run',
|
|
41
|
+
max_status_length: 50,
|
|
42
|
+
context_bar: {
|
|
43
|
+
width: 10,
|
|
44
|
+
thresholds: { green: 70, yellow: 90 },
|
|
45
|
+
chars: { filled: '\u2588', empty: '\u2591' }
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Load status_line config from .planning/config.json, merged with defaults.
|
|
51
|
+
* Returns DEFAULTS if no config exists or no status_line section is present.
|
|
52
|
+
*/
|
|
53
|
+
function loadStatusLineConfig(planningDir) {
|
|
54
|
+
const config = configLoad(planningDir);
|
|
55
|
+
if (!config || !config.status_line) return DEFAULTS;
|
|
56
|
+
|
|
57
|
+
const sl = config.status_line;
|
|
58
|
+
return {
|
|
59
|
+
sections: Array.isArray(sl.sections) ? sl.sections : DEFAULTS.sections,
|
|
60
|
+
brand_text: typeof sl.brand_text === 'string' ? sl.brand_text : DEFAULTS.brand_text,
|
|
61
|
+
max_status_length: typeof sl.max_status_length === 'number' ? sl.max_status_length : DEFAULTS.max_status_length,
|
|
62
|
+
context_bar: {
|
|
63
|
+
width: (sl.context_bar && typeof sl.context_bar.width === 'number') ? sl.context_bar.width : DEFAULTS.context_bar.width,
|
|
64
|
+
thresholds: {
|
|
65
|
+
green: (sl.context_bar && sl.context_bar.thresholds && typeof sl.context_bar.thresholds.green === 'number') ? sl.context_bar.thresholds.green : DEFAULTS.context_bar.thresholds.green,
|
|
66
|
+
yellow: (sl.context_bar && sl.context_bar.thresholds && typeof sl.context_bar.thresholds.yellow === 'number') ? sl.context_bar.thresholds.yellow : DEFAULTS.context_bar.thresholds.yellow
|
|
67
|
+
},
|
|
68
|
+
chars: {
|
|
69
|
+
filled: (sl.context_bar && sl.context_bar.chars && typeof sl.context_bar.chars.filled === 'string') ? sl.context_bar.chars.filled : DEFAULTS.context_bar.chars.filled,
|
|
70
|
+
empty: (sl.context_bar && sl.context_bar.chars && typeof sl.context_bar.chars.empty === 'string') ? sl.context_bar.chars.empty : DEFAULTS.context_bar.chars.empty
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function readStdin() {
|
|
77
|
+
try {
|
|
78
|
+
const input = fs.readFileSync(0, 'utf8').trim();
|
|
79
|
+
if (input) return JSON.parse(input);
|
|
80
|
+
} catch (_e) {
|
|
81
|
+
// stdin may be empty or not JSON — that's fine
|
|
82
|
+
}
|
|
83
|
+
return {};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function getContextPercent(stdinData) {
|
|
87
|
+
// Claude Code statusLine sends context_window.used_percentage (0-100)
|
|
88
|
+
if (stdinData.context_window && stdinData.context_window.used_percentage != null) {
|
|
89
|
+
return Math.round(stdinData.context_window.used_percentage);
|
|
90
|
+
}
|
|
91
|
+
// Legacy field name
|
|
92
|
+
if (stdinData.context_usage_fraction != null) {
|
|
93
|
+
return Math.round(stdinData.context_usage_fraction * 100);
|
|
94
|
+
}
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Build a horizontal bar using Unicode block characters.
|
|
100
|
+
* Width is in character cells. Color shifts green -> yellow -> red.
|
|
101
|
+
*
|
|
102
|
+
* @param {number} percent - Usage percentage (0-100)
|
|
103
|
+
* @param {number} width - Bar width in characters
|
|
104
|
+
* @param {object} [opts] - Optional config overrides
|
|
105
|
+
* @param {object} [opts.thresholds] - { green: number, yellow: number }
|
|
106
|
+
* @param {object} [opts.chars] - { filled: string, empty: string }
|
|
107
|
+
*/
|
|
108
|
+
function buildContextBar(percent, width, opts) {
|
|
109
|
+
if (width < 1) return '';
|
|
110
|
+
const thresholds = (opts && opts.thresholds) || DEFAULTS.context_bar.thresholds;
|
|
111
|
+
const chars = (opts && opts.chars) || DEFAULTS.context_bar.chars;
|
|
112
|
+
|
|
113
|
+
const filled = Math.round((percent / 100) * width);
|
|
114
|
+
const empty = width - filled;
|
|
115
|
+
|
|
116
|
+
// Color based on usage threshold
|
|
117
|
+
let barColor;
|
|
118
|
+
if (percent >= thresholds.yellow) barColor = c.boldRed;
|
|
119
|
+
else if (percent >= thresholds.green) barColor = c.boldYellow;
|
|
120
|
+
else barColor = c.boldGreen;
|
|
121
|
+
|
|
122
|
+
const filledStr = chars.filled.repeat(filled);
|
|
123
|
+
const emptyStr = chars.empty.repeat(empty);
|
|
124
|
+
|
|
125
|
+
return `${barColor}${filledStr}${c.dim}${emptyStr}${c.reset}`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Pick a color for the phase status keyword.
|
|
130
|
+
*/
|
|
131
|
+
function statusColor(statusText) {
|
|
132
|
+
const lower = statusText.toLowerCase();
|
|
133
|
+
if (lower.includes('complete') || lower.includes('verified')) return c.green;
|
|
134
|
+
if (lower.includes('needs_fixes') || lower.includes('partial')) return c.yellow;
|
|
135
|
+
if (lower.includes('progress') || lower.includes('building') || lower.includes('executing') || lower.includes('planning')) return c.yellow;
|
|
136
|
+
if (lower.includes('verifying')) return c.blue;
|
|
137
|
+
if (lower.includes('planned') || lower.includes('ready')) return c.cyan;
|
|
138
|
+
if (lower.includes('blocked') || lower.includes('failed')) return c.red;
|
|
139
|
+
if (lower.includes('pending')) return c.dim;
|
|
140
|
+
return c.white;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Get current git branch and dirty status.
|
|
145
|
+
* Returns null if not in a git repo or git is unavailable.
|
|
146
|
+
*/
|
|
147
|
+
function getGitInfo() {
|
|
148
|
+
try {
|
|
149
|
+
const branch = cp.execSync('git branch --show-current', {
|
|
150
|
+
timeout: 500, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe']
|
|
151
|
+
}).trim();
|
|
152
|
+
if (!branch) return null;
|
|
153
|
+
const porcelain = cp.execSync('git status --porcelain', {
|
|
154
|
+
timeout: 500, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe']
|
|
155
|
+
}).trim();
|
|
156
|
+
return { branch, dirty: porcelain.length > 0 };
|
|
157
|
+
} catch (_e) {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Format milliseconds into human-readable duration (e.g. "12m", "1h23m").
|
|
164
|
+
*/
|
|
165
|
+
function formatDuration(ms) {
|
|
166
|
+
const totalSeconds = Math.floor(ms / 1000);
|
|
167
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
168
|
+
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
169
|
+
if (hours > 0) return `${hours}h${minutes > 0 ? minutes + 'm' : ''}`;
|
|
170
|
+
return `${minutes}m`;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function main() {
|
|
174
|
+
const stdinData = readStdin();
|
|
175
|
+
const cwd = process.cwd();
|
|
176
|
+
const planningDir = path.join(cwd, '.planning');
|
|
177
|
+
const stateFile = path.join(planningDir, 'STATE.md');
|
|
178
|
+
|
|
179
|
+
if (!fs.existsSync(stateFile)) {
|
|
180
|
+
process.exit(0);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
const slConfig = loadStatusLineConfig(planningDir);
|
|
185
|
+
const content = fs.readFileSync(stateFile, 'utf8');
|
|
186
|
+
const ctxPercent = getContextPercent(stdinData);
|
|
187
|
+
const status = buildStatusLine(content, ctxPercent, slConfig, stdinData);
|
|
188
|
+
|
|
189
|
+
if (status) {
|
|
190
|
+
process.stdout.write(status);
|
|
191
|
+
logHook('status-line', 'StatusLine', 'updated', { ctxPercent });
|
|
192
|
+
}
|
|
193
|
+
} catch (_e) {
|
|
194
|
+
logHook('status-line', 'StatusLine', 'error', { error: _e.message });
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
process.exit(0);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function buildStatusLine(content, ctxPercent, cfg, stdinData) {
|
|
201
|
+
const config = cfg || DEFAULTS;
|
|
202
|
+
const sections = config.sections || DEFAULTS.sections;
|
|
203
|
+
const brandText = config.brand_text || DEFAULTS.brand_text;
|
|
204
|
+
const maxLen = config.max_status_length || DEFAULTS.max_status_length;
|
|
205
|
+
const barCfg = config.context_bar || DEFAULTS.context_bar;
|
|
206
|
+
const sd = stdinData || {};
|
|
207
|
+
|
|
208
|
+
const parts = [];
|
|
209
|
+
|
|
210
|
+
// Phase section (always includes brand text)
|
|
211
|
+
if (sections.includes('phase')) {
|
|
212
|
+
const phaseMatch = content.match(/Phase:\s*(\d+)\s*of\s*(\d+)\s*(?:\(([^)]+)\))?/);
|
|
213
|
+
if (phaseMatch) {
|
|
214
|
+
parts.push(`${c.boldCyan}${brandText}${c.reset} ${c.bold}Phase ${phaseMatch[1]}/${phaseMatch[2]}${c.reset}`);
|
|
215
|
+
if (phaseMatch[3]) {
|
|
216
|
+
parts.push(`${c.magenta}${phaseMatch[3]}${c.reset}`);
|
|
217
|
+
}
|
|
218
|
+
} else {
|
|
219
|
+
parts.push(`${c.boldCyan}${brandText}${c.reset}`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Plan section
|
|
224
|
+
if (sections.includes('plan')) {
|
|
225
|
+
const planMatch = content.match(/Plan:\s*(\d+)\s*of\s*(\d+)/);
|
|
226
|
+
if (planMatch) {
|
|
227
|
+
const done = parseInt(planMatch[1], 10);
|
|
228
|
+
const total = parseInt(planMatch[2], 10);
|
|
229
|
+
const planColor = done === total ? c.green : c.white;
|
|
230
|
+
parts.push(`${planColor}Plan ${done}/${total}${c.reset}`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Status section
|
|
235
|
+
if (sections.includes('status')) {
|
|
236
|
+
const statusMatch = content.match(/Status:\s*(.+)/);
|
|
237
|
+
if (statusMatch) {
|
|
238
|
+
const text = statusMatch[1].trim();
|
|
239
|
+
const short = text.length > maxLen ? text.slice(0, maxLen - 3) + '...' : text;
|
|
240
|
+
parts.push(`${statusColor(text)}${short}${c.reset}`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Git section — branch name + dirty indicator
|
|
245
|
+
if (sections.includes('git')) {
|
|
246
|
+
const gitInfo = getGitInfo();
|
|
247
|
+
if (gitInfo) {
|
|
248
|
+
const dirtyMark = gitInfo.dirty ? `${c.yellow}*${c.reset}` : '';
|
|
249
|
+
parts.push(`${c.cyan}${gitInfo.branch}${c.reset}${dirtyMark}`);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Model section — current model display name from stdin
|
|
254
|
+
if (sections.includes('model') && sd.model && sd.model.display_name) {
|
|
255
|
+
parts.push(`${c.dim}${sd.model.display_name}${c.reset}`);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Cost section — session cost from stdin
|
|
259
|
+
if (sections.includes('cost') && sd.cost && sd.cost.total_cost_usd != null) {
|
|
260
|
+
const cost = sd.cost.total_cost_usd;
|
|
261
|
+
const costStr = `$${cost.toFixed(2)}`;
|
|
262
|
+
let costColor = c.dim;
|
|
263
|
+
if (cost > 5) costColor = c.red;
|
|
264
|
+
else if (cost > 1) costColor = c.yellow;
|
|
265
|
+
parts.push(`${costColor}${costStr}${c.reset}`);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Duration section — session wall-clock time from stdin
|
|
269
|
+
if (sections.includes('duration') && sd.cost && sd.cost.total_duration_ms != null) {
|
|
270
|
+
parts.push(`${c.dim}${formatDuration(sd.cost.total_duration_ms)}${c.reset}`);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Context bar section
|
|
274
|
+
if (sections.includes('context') && ctxPercent != null) {
|
|
275
|
+
const bar = buildContextBar(ctxPercent, barCfg.width || DEFAULTS.context_bar.width, {
|
|
276
|
+
thresholds: barCfg.thresholds || DEFAULTS.context_bar.thresholds,
|
|
277
|
+
chars: barCfg.chars || DEFAULTS.context_bar.chars
|
|
278
|
+
});
|
|
279
|
+
parts.push(`${bar} ${c.dim}${ctxPercent}%${c.reset}`);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (parts.length === 0) return null;
|
|
283
|
+
|
|
284
|
+
return parts.join(` ${c.dim}\u2502${c.reset} `);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (require.main === module) { main(); }
|
|
288
|
+
module.exports = { buildStatusLine, buildContextBar, getContextPercent, getGitInfo, formatDuration, loadStatusLineConfig, DEFAULTS };
|