@sienklogic/plan-build-run 2.26.2 → 2.27.1
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 +14 -0
- package/README.md +29 -0
- package/dashboard/public/css/layout.css +7 -283
- package/dashboard/public/css/status-colors.css +7 -0
- package/dashboard/public/css/tokens.css +3 -3
- package/dashboard/public/js/sidebar-toggle.js +9 -31
- package/dashboard/public/js/theme-toggle.js +4 -4
- package/dashboard/src/views/partials/activity-feed.ejs +17 -9
- package/dashboard/src/views/partials/analytics-content.ejs +178 -88
- package/dashboard/src/views/partials/audit-detail-content.ejs +6 -4
- package/dashboard/src/views/partials/audits-content.ejs +28 -26
- package/dashboard/src/views/partials/breadcrumbs.ejs +8 -4
- package/dashboard/src/views/partials/config-content.ejs +98 -95
- package/dashboard/src/views/partials/dashboard-content.ejs +69 -60
- package/dashboard/src/views/partials/dependencies-content.ejs +5 -3
- package/dashboard/src/views/partials/empty-state.ejs +10 -5
- package/dashboard/src/views/partials/footer.ejs +8 -2
- package/dashboard/src/views/partials/head.ejs +2 -1
- package/dashboard/src/views/partials/header.ejs +16 -19
- package/dashboard/src/views/partials/layout-bottom.ejs +5 -40
- package/dashboard/src/views/partials/layout-top.ejs +6 -5
- package/dashboard/src/views/partials/logs-content.ejs +26 -29
- package/dashboard/src/views/partials/milestone-detail-content.ejs +5 -5
- package/dashboard/src/views/partials/milestones-content.ejs +40 -31
- package/dashboard/src/views/partials/note-detail-content.ejs +7 -5
- package/dashboard/src/views/partials/notes-content.ejs +4 -4
- package/dashboard/src/views/partials/phase-content.ejs +6 -8
- package/dashboard/src/views/partials/phase-doc-content.ejs +13 -15
- package/dashboard/src/views/partials/phase-timeline.ejs +22 -15
- package/dashboard/src/views/partials/phases-content.ejs +98 -84
- package/dashboard/src/views/partials/quick-content.ejs +34 -32
- package/dashboard/src/views/partials/quick-detail-content.ejs +20 -19
- package/dashboard/src/views/partials/requirements-content.ejs +6 -6
- package/dashboard/src/views/partials/research-content.ejs +14 -14
- package/dashboard/src/views/partials/research-detail-content.ejs +13 -11
- package/dashboard/src/views/partials/roadmap-content.ejs +145 -128
- package/dashboard/src/views/partials/sidebar.ejs +86 -140
- package/dashboard/src/views/partials/todo-create-content.ejs +51 -46
- package/dashboard/src/views/partials/todo-detail-content.ejs +26 -25
- package/dashboard/src/views/partials/todos-content.ejs +65 -62
- package/dashboard/src/views/partials/todos-done-content.ejs +33 -31
- package/package.json +1 -1
- package/plugins/copilot-pbr/plugin.json +1 -1
- package/plugins/copilot-pbr/skills/build/SKILL.md +12 -0
- package/plugins/copilot-pbr/skills/quick/SKILL.md +12 -0
- package/plugins/copilot-pbr/skills/review/SKILL.md +14 -0
- package/plugins/cursor-pbr/.cursor-plugin/plugin.json +1 -1
- package/plugins/cursor-pbr/README.md +20 -0
- package/plugins/cursor-pbr/skills/build/SKILL.md +12 -0
- package/plugins/cursor-pbr/skills/quick/SKILL.md +12 -0
- package/plugins/cursor-pbr/skills/review/SKILL.md +14 -0
- package/plugins/pbr/.claude-plugin/plugin.json +1 -1
- package/plugins/pbr/scripts/local-llm/metrics.js +121 -33
- package/plugins/pbr/skills/build/SKILL.md +12 -0
- package/plugins/pbr/skills/quick/SKILL.md +12 -0
- package/plugins/pbr/skills/review/SKILL.md +14 -0
|
@@ -29,6 +29,15 @@ function logMetric(planningDir, entry) {
|
|
|
29
29
|
const logFile = path.join(logsDir, 'local-llm-metrics.jsonl');
|
|
30
30
|
|
|
31
31
|
fs.mkdirSync(logsDir, { recursive: true });
|
|
32
|
+
|
|
33
|
+
// Update lifetime totals BEFORE appending to JSONL so seeding
|
|
34
|
+
// (which reads the JSONL) doesn't double-count this entry
|
|
35
|
+
try {
|
|
36
|
+
updateLifetimeTotals(logsDir, entry);
|
|
37
|
+
} catch (_) {
|
|
38
|
+
// Totals update failure is non-fatal
|
|
39
|
+
}
|
|
40
|
+
|
|
32
41
|
fs.appendFileSync(logFile, JSON.stringify(entry) + '\n', 'utf8');
|
|
33
42
|
|
|
34
43
|
// Rotate if over MAX_ENTRIES
|
|
@@ -47,6 +56,63 @@ function logMetric(planningDir, entry) {
|
|
|
47
56
|
}
|
|
48
57
|
}
|
|
49
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Atomically increments the lifetime-totals.json running counters.
|
|
61
|
+
* This file persists across JSONL log rotations so lifetime metrics never plateau.
|
|
62
|
+
*
|
|
63
|
+
* @param {string} logsDir - path to the .planning/logs directory
|
|
64
|
+
* @param {object} entry - the metric entry being logged
|
|
65
|
+
*/
|
|
66
|
+
function updateLifetimeTotals(logsDir, entry) {
|
|
67
|
+
const totalsFile = path.join(logsDir, 'lifetime-totals.json');
|
|
68
|
+
let totals = null;
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
totals = JSON.parse(fs.readFileSync(totalsFile, 'utf8'));
|
|
72
|
+
} catch (_) {
|
|
73
|
+
// File doesn't exist yet or is corrupt — seed from existing JSONL
|
|
74
|
+
totals = seedTotalsFromJsonl(logsDir);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
totals.total_calls = (totals.total_calls || 0) + 1;
|
|
78
|
+
totals.fallback_count = (totals.fallback_count || 0) + (entry.fallback_used ? 1 : 0);
|
|
79
|
+
totals.tokens_saved = (totals.tokens_saved || 0) + (entry.tokens_saved_frontier || 0);
|
|
80
|
+
totals.total_latency_ms = (totals.total_latency_ms || 0) + (entry.latency_ms || 0);
|
|
81
|
+
|
|
82
|
+
fs.writeFileSync(totalsFile, JSON.stringify(totals) + '\n', 'utf8');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Seeds lifetime totals by scanning the existing JSONL log.
|
|
87
|
+
* Called once when lifetime-totals.json doesn't exist yet (migration from pre-totals installs).
|
|
88
|
+
* Returns the accumulated totals from whatever entries remain in the JSONL.
|
|
89
|
+
*
|
|
90
|
+
* @param {string} logsDir - path to the .planning/logs directory
|
|
91
|
+
* @returns {{ total_calls: number, fallback_count: number, tokens_saved: number, total_latency_ms: number }}
|
|
92
|
+
*/
|
|
93
|
+
function seedTotalsFromJsonl(logsDir) {
|
|
94
|
+
const seed = { total_calls: 0, fallback_count: 0, tokens_saved: 0, total_latency_ms: 0 };
|
|
95
|
+
try {
|
|
96
|
+
const logFile = path.join(logsDir, 'local-llm-metrics.jsonl');
|
|
97
|
+
const contents = fs.readFileSync(logFile, 'utf8');
|
|
98
|
+
const lines = contents.split(/\r?\n/).filter((l) => l.trim() !== '');
|
|
99
|
+
for (const line of lines) {
|
|
100
|
+
try {
|
|
101
|
+
const e = JSON.parse(line);
|
|
102
|
+
seed.total_calls += 1;
|
|
103
|
+
seed.fallback_count += e.fallback_used ? 1 : 0;
|
|
104
|
+
seed.tokens_saved += e.tokens_saved_frontier || 0;
|
|
105
|
+
seed.total_latency_ms += e.latency_ms || 0;
|
|
106
|
+
} catch (_) {
|
|
107
|
+
// Skip malformed lines
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
} catch (_) {
|
|
111
|
+
// No JSONL file — start at zero
|
|
112
|
+
}
|
|
113
|
+
return seed;
|
|
114
|
+
}
|
|
115
|
+
|
|
50
116
|
/**
|
|
51
117
|
* Reads metric entries from the JSONL log that occurred at or after sessionStartTime.
|
|
52
118
|
*
|
|
@@ -131,47 +197,69 @@ function computeLifetimeMetrics(planningDir, frontierTokenRate) {
|
|
|
131
197
|
};
|
|
132
198
|
|
|
133
199
|
try {
|
|
134
|
-
const
|
|
135
|
-
|
|
200
|
+
const rate = frontierTokenRate != null ? frontierTokenRate : 3.0;
|
|
201
|
+
const logsDir = path.join(planningDir, 'logs');
|
|
202
|
+
const totalsFile = path.join(logsDir, 'lifetime-totals.json');
|
|
203
|
+
|
|
204
|
+
// Primary path: read from lifetime-totals.json (survives log rotation)
|
|
205
|
+
let totals = null;
|
|
136
206
|
try {
|
|
137
|
-
|
|
207
|
+
totals = JSON.parse(fs.readFileSync(totalsFile, 'utf8'));
|
|
138
208
|
} catch (_) {
|
|
139
|
-
|
|
209
|
+
// No totals file — fall back to JSONL scan (migration path for existing installs)
|
|
140
210
|
}
|
|
141
211
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
212
|
+
// Build by_operation from the JSONL (only covers recent entries, but still useful)
|
|
213
|
+
const by_operation = {};
|
|
214
|
+
const logFile = path.join(logsDir, 'local-llm-metrics.jsonl');
|
|
215
|
+
try {
|
|
216
|
+
const contents = fs.readFileSync(logFile, 'utf8');
|
|
217
|
+
const entries = contents
|
|
218
|
+
.split(/\r?\n/)
|
|
219
|
+
.filter((l) => l.trim() !== '')
|
|
220
|
+
.map((l) => {
|
|
221
|
+
try {
|
|
222
|
+
return JSON.parse(l);
|
|
223
|
+
} catch (_) {
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
})
|
|
227
|
+
.filter((e) => e !== null);
|
|
155
228
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
229
|
+
for (const e of entries) {
|
|
230
|
+
const op = e.operation || 'unknown';
|
|
231
|
+
if (!by_operation[op]) {
|
|
232
|
+
by_operation[op] = { calls: 0, fallbacks: 0, tokens_saved: 0 };
|
|
233
|
+
}
|
|
234
|
+
by_operation[op].calls += 1;
|
|
235
|
+
if (e.fallback_used) by_operation[op].fallbacks += 1;
|
|
236
|
+
by_operation[op].tokens_saved += e.tokens_saved_frontier || 0;
|
|
237
|
+
}
|
|
163
238
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
239
|
+
// If no totals file, compute from JSONL (legacy/migration path)
|
|
240
|
+
if (!totals) {
|
|
241
|
+
if (entries.length === 0) return zero;
|
|
242
|
+
const total_calls = entries.length;
|
|
243
|
+
const fallback_count = entries.filter((e) => e.fallback_used).length;
|
|
244
|
+
const totalLatency = entries.reduce((sum, e) => sum + (e.latency_ms || 0), 0);
|
|
245
|
+
const avg_latency_ms = total_calls > 0 ? totalLatency / total_calls : 0;
|
|
246
|
+
const tokens_saved = entries.reduce((sum, e) => sum + (e.tokens_saved_frontier || 0), 0);
|
|
247
|
+
const cost_saved_usd = tokens_saved * (rate / 1_000_000);
|
|
248
|
+
return { total_calls, fallback_count, avg_latency_ms, tokens_saved, cost_saved_usd, by_operation };
|
|
169
249
|
}
|
|
170
|
-
|
|
171
|
-
if
|
|
172
|
-
|
|
250
|
+
} catch (_) {
|
|
251
|
+
// No JSONL file — if we have totals, continue; otherwise return zero
|
|
252
|
+
if (!totals) return zero;
|
|
173
253
|
}
|
|
174
254
|
|
|
255
|
+
// Use lifetime totals for the headline numbers
|
|
256
|
+
const total_calls = totals.total_calls || 0;
|
|
257
|
+
const fallback_count = totals.fallback_count || 0;
|
|
258
|
+
const tokens_saved = totals.tokens_saved || 0;
|
|
259
|
+
const total_latency_ms = totals.total_latency_ms || 0;
|
|
260
|
+
const avg_latency_ms = total_calls > 0 ? total_latency_ms / total_calls : 0;
|
|
261
|
+
const cost_saved_usd = tokens_saved * (rate / 1_000_000);
|
|
262
|
+
|
|
175
263
|
return { total_calls, fallback_count, avg_latency_ms, tokens_saved, cost_saved_usd, by_operation };
|
|
176
264
|
} catch (_) {
|
|
177
265
|
return zero;
|
|
@@ -249,4 +337,4 @@ function logAgreement(planningDir, entry) {
|
|
|
249
337
|
}
|
|
250
338
|
}
|
|
251
339
|
|
|
252
|
-
module.exports = { logMetric, readSessionMetrics, summarizeMetrics, computeLifetimeMetrics, formatSessionSummary, logAgreement };
|
|
340
|
+
module.exports = { logMetric, readSessionMetrics, summarizeMetrics, computeLifetimeMetrics, formatSessionSummary, logAgreement, updateLifetimeTotals, seedTotalsFromJsonl };
|
|
@@ -244,6 +244,18 @@ For each wave, in order (Wave 1, then Wave 2, etc.):
|
|
|
244
244
|
|
|
245
245
|
For each plan in the current wave (excluding skipped plans):
|
|
246
246
|
|
|
247
|
+
**Local LLM plan quality check (optional, advisory):**
|
|
248
|
+
|
|
249
|
+
Before spawning executors for this wave, if `config.local_llm.enabled` is `true`, run a quick classification on each plan to catch stubs before wasting an executor spawn:
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
node ${CLAUDE_PLUGIN_ROOT}/scripts/pbr-tools.js llm classify PLAN ".planning/phases/{NN}-{slug}/{plan_id}-PLAN.md"
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
- If classification is `"stub"` or `"partial"` with confidence >= 0.7: warn the user before spawning: `"⚠ Plan {plan_id} classified as {classification} (confidence {conf}) — consider refining before building."`
|
|
256
|
+
- If the command fails or returns null: skip silently (local LLM unavailable — not an error)
|
|
257
|
+
- This is advisory only — never block on the result
|
|
258
|
+
|
|
247
259
|
**Present plan narrative before spawning:**
|
|
248
260
|
|
|
249
261
|
Display to the user before spawning:
|
|
@@ -148,6 +148,18 @@ Before proceeding to Step 7, confirm these exist on disk:
|
|
|
148
148
|
|
|
149
149
|
If either check fails, you have skipped steps. Go back and complete Steps 4-6. Do NOT proceed to spawning an executor.
|
|
150
150
|
|
|
151
|
+
### Step 6b: Local LLM Task Validation (optional, advisory)
|
|
152
|
+
|
|
153
|
+
If `config.local_llm.enabled` is `true`, run a quick scope validation before spawning:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
node ${CLAUDE_PLUGIN_ROOT}/scripts/pbr-tools.js llm classify PLAN ".planning/quick/{NNN}-{slug}/PLAN.md"
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
- If classification is `"stub"` with confidence >= 0.7: warn `"⚠ Plan looks like a stub — executor may struggle. Consider adding more detail to task descriptions."`
|
|
160
|
+
- If the command fails or returns null: skip silently (local LLM unavailable)
|
|
161
|
+
- This is advisory only — never block on the result
|
|
162
|
+
|
|
151
163
|
### Step 7: Spawn Executor
|
|
152
164
|
|
|
153
165
|
**Pre-spawn check** — Verify `.planning/quick/{NNN}-{slug}/PLAN.md` exists and contains at least one `<task>` block. If missing, STOP and complete Steps 4-6 first.
|
|
@@ -202,6 +202,20 @@ Then display the overall verdict (`PASSED`, `GAPS FOUND`, or `HUMAN NEEDED`) bef
|
|
|
202
202
|
|
|
203
203
|
---
|
|
204
204
|
|
|
205
|
+
### Step 3b: Local LLM Verification Quality Check (optional, advisory)
|
|
206
|
+
|
|
207
|
+
After the verifier completes and writes VERIFICATION.md, if `config.local_llm.enabled` is `true`, run a quality classification:
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
node ${CLAUDE_PLUGIN_ROOT}/scripts/pbr-tools.js llm classify SUMMARY ".planning/phases/{NN}-{slug}/VERIFICATION.md"
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
- If classification is `"thin"` with confidence >= 0.7: warn `"⚠ Verification report appears thin on details — UAT may not catch all gaps. Consider re-running with /pbr:review {N}."`
|
|
214
|
+
- If the command fails or returns null: skip silently (local LLM unavailable)
|
|
215
|
+
- This is advisory only — never block on the result
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
205
219
|
### Step 4: Present Verification Results (inline)
|
|
206
220
|
|
|
207
221
|
Read the VERIFICATION.md frontmatter. Check the `attempt` counter.
|