@sienklogic/plan-build-run 2.19.1 → 2.19.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 +39 -0
- package/CLAUDE.md +29 -16
- package/README.md +3 -3
- package/dashboard/server/index.js +10 -1
- package/dashboard/server/routes/agents.js +23 -2
- package/dashboard/server/routes/health.js +7 -4
- package/dashboard/server/routes/telemetry.js +20 -1
- package/dashboard/server/services/planning-reader.js +3 -17
- package/package.json +1 -1
- package/plan-build-run/bin/config-schema.json +23 -145
- package/plugins/pbr/.claude-plugin/plugin.json +1 -1
- package/plugins/pbr/agents/advisor-researcher.md +1 -0
- package/plugins/pbr/agents/debugger.md +0 -4
- package/plugins/pbr/agents/researcher.md +0 -4
- package/plugins/pbr/agents/synthesizer.md +0 -4
- package/plugins/pbr/dist/check-config-change.js +0 -7
- package/plugins/pbr/dist/check-cross-plugin-sync.js +1 -1
- package/plugins/pbr/dist/check-plan-format.js +0 -32
- package/plugins/pbr/dist/check-roadmap-sync.js +15 -11
- package/plugins/pbr/dist/check-subagent-output.js +4 -60
- package/plugins/pbr/dist/check-summary-gate.js +3 -14
- package/plugins/pbr/dist/feedback-loop.js +12 -29
- package/plugins/pbr/dist/hook-server.js +58 -6
- package/plugins/pbr/dist/milestone-learnings.js +6 -56
- package/plugins/pbr/dist/pbr-tools.js +8 -91
- package/plugins/pbr/dist/post-bash-triage.js +5 -63
- package/plugins/pbr/dist/post-hoc.js +3 -52
- package/plugins/pbr/dist/post-write-dispatch.js +0 -36
- package/plugins/pbr/dist/pre-bash-dispatch.js +1 -7
- package/plugins/pbr/dist/pre-task-dispatch.js +0 -28
- package/plugins/pbr/dist/progress-tracker.js +2 -27
- package/plugins/pbr/dist/session-cleanup.js +1 -31
- package/plugins/pbr/dist/status-line.js +13 -11
- package/plugins/pbr/dist/suggest-compact.js +2 -10
- package/plugins/pbr/dist/validate-commit.js +8 -64
- package/plugins/pbr/dist/validate-task.js +0 -30
- package/plugins/pbr/references/config-reference.md +0 -96
- package/plugins/pbr/scripts/audit-checks/si-agent-hook-config-checks.js +2 -72
- package/plugins/pbr/scripts/audit-checks/workflow-compliance.js +5 -41
- package/plugins/pbr/scripts/check-config-change.js +0 -7
- package/plugins/pbr/scripts/check-cross-plugin-sync.js +1 -1
- package/plugins/pbr/scripts/check-plan-format.js +0 -32
- package/plugins/pbr/scripts/check-roadmap-sync.js +15 -11
- package/plugins/pbr/scripts/check-subagent-output.js +4 -60
- package/plugins/pbr/scripts/check-summary-gate.js +3 -14
- package/plugins/pbr/scripts/config-schema.json +16 -129
- package/plugins/pbr/scripts/feedback-loop.js +12 -29
- package/plugins/pbr/scripts/hook-server.js +58 -6
- package/plugins/pbr/scripts/lib/config.js +4 -11
- package/plugins/pbr/scripts/lib/contextual-help.js +5 -29
- package/plugins/pbr/scripts/lib/format-validators.js +1 -26
- package/plugins/pbr/scripts/lib/frontmatter.js +4 -4
- package/plugins/pbr/scripts/lib/gates/rich-agent-context.js +13 -19
- package/plugins/pbr/scripts/lib/health.js +4 -5
- package/plugins/pbr/scripts/lib/help.js +3 -54
- package/plugins/pbr/scripts/lib/phase.js +2 -4
- package/plugins/pbr/scripts/lib/pre-commit-checks.js +1 -1
- package/plugins/pbr/scripts/lib/pre-research.js +10 -17
- package/plugins/pbr/scripts/lib/roadmap.js +11 -35
- package/plugins/pbr/scripts/lib/smart-next-task.js +11 -20
- package/plugins/pbr/scripts/lib/spot-check.js +3 -106
- package/plugins/pbr/scripts/lib/state.js +25 -130
- package/plugins/pbr/scripts/lib/verify.js +56 -46
- package/plugins/pbr/scripts/milestone-learnings.js +6 -56
- package/plugins/pbr/scripts/pbr-tools.js +8 -91
- package/plugins/pbr/scripts/post-bash-triage.js +5 -63
- package/plugins/pbr/scripts/post-hoc.js +3 -52
- package/plugins/pbr/scripts/post-write-dispatch.js +0 -36
- package/plugins/pbr/scripts/pre-bash-dispatch.js +1 -7
- package/plugins/pbr/scripts/pre-task-dispatch.js +0 -28
- package/plugins/pbr/scripts/progress-tracker.js +2 -27
- package/plugins/pbr/scripts/session-cleanup.js +1 -31
- package/plugins/pbr/scripts/status-line.js +13 -11
- package/plugins/pbr/scripts/suggest-compact.js +2 -10
- package/plugins/pbr/scripts/test/state.test.js +5 -13
- package/plugins/pbr/scripts/validate-commit.js +8 -64
- package/plugins/pbr/scripts/validate-task.js +0 -30
- package/plugins/pbr/skills/begin/SKILL.md +1 -0
- package/plugins/pbr/skills/begin/templates/config.json.tmpl +0 -4
- package/plugins/pbr/skills/build/SKILL.md +6 -6
- package/plugins/pbr/skills/config/SKILL.md +1 -0
- package/plugins/pbr/skills/help/SKILL.md +1 -0
- package/plugins/pbr/skills/pause/SKILL.md +1 -0
- package/plugins/pbr/skills/profile-user/SKILL.md +1 -0
- package/plugins/pbr/skills/quick/SKILL.md +2 -1
- package/plugins/pbr/skills/resume/SKILL.md +1 -0
- package/plugins/pbr/skills/scan/SKILL.md +1 -0
- package/plugins/pbr/skills/setup/SKILL.md +1 -0
- package/plugins/pbr/skills/shared/state-update.md +2 -2
- package/plugins/pbr/skills/status/SKILL.md +1 -0
- package/plugins/pbr/references/behavioral-contexts.md +0 -53
- package/plugins/pbr/scripts/lib/autonomy.js +0 -91
- package/plugins/pbr/scripts/lib/circuit-state.js +0 -133
- package/plugins/pbr/scripts/lib/completion.js +0 -377
- package/plugins/pbr/scripts/lib/hypothesis-runner.js +0 -127
- package/plugins/pbr/scripts/lib/local-llm/client.js +0 -237
- package/plugins/pbr/scripts/lib/local-llm/health.js +0 -12
- package/plugins/pbr/scripts/lib/local-llm/index.js +0 -89
- package/plugins/pbr/scripts/lib/local-llm/metrics.js +0 -20
- package/plugins/pbr/scripts/lib/local-llm/operations/classify-artifact.js +0 -4
- package/plugins/pbr/scripts/lib/local-llm/operations/classify-commit.js +0 -4
- package/plugins/pbr/scripts/lib/local-llm/operations/classify-error.js +0 -4
- package/plugins/pbr/scripts/lib/local-llm/operations/classify-file-intent.js +0 -4
- package/plugins/pbr/scripts/lib/local-llm/operations/score-source.js +0 -72
- package/plugins/pbr/scripts/lib/local-llm/operations/summarize-context.js +0 -62
- package/plugins/pbr/scripts/lib/local-llm/operations/triage-test-output.js +0 -12
- package/plugins/pbr/scripts/lib/local-llm/operations/validate-task.js +0 -4
- package/plugins/pbr/scripts/lib/local-llm/router.js +0 -101
- package/plugins/pbr/scripts/lib/local-llm/shadow.js +0 -60
- package/plugins/pbr/scripts/lib/local-llm/threshold-tuner.js +0 -118
- package/plugins/pbr/scripts/lib/team-composer.js +0 -87
- package/plugins/pbr/scripts/lib/team-coordinator.js +0 -153
- package/plugins/pbr/scripts/lib/template.js +0 -222
- package/plugins/pbr/scripts/lib/test-cache.js +0 -54
- package/plugins/pbr/scripts/lib/trust-gate.js +0 -84
- package/plugins/pbr/scripts/lib/wiring-check.js +0 -196
|
@@ -43,8 +43,6 @@
|
|
|
43
43
|
* auto-cleanup --phase N | --milestone vN — Auto-close todos and archive notes matching phase/milestone deliverables
|
|
44
44
|
* state reconcile — Detect and repair STATE.md/ROADMAP.md desync
|
|
45
45
|
* state backup — Create timestamped backup of STATE.md and ROADMAP.md
|
|
46
|
-
* llm <subcommand> — DEPRECATED: local_llm removed (no-op)
|
|
47
|
-
* validate-project — Comprehensive .planning/ integrity check
|
|
48
46
|
* phase add <slug> [--after N] [--goal "..."] [--depends-on N] — Add phase with ROADMAP.md integration
|
|
49
47
|
* phase remove <N> — Remove an empty phase directory (with renumbering)
|
|
50
48
|
* phase list [--status S] [--before N] — List phase directories with optional status/before filters
|
|
@@ -62,14 +60,9 @@
|
|
|
62
60
|
* learnings query-global [--tags X] [--project P] — Query global knowledge files
|
|
63
61
|
* data status — Freshness report for research/, intel/, codebase/ directories
|
|
64
62
|
* data prune --before <ISO-date> [--dry-run] — Archive stale research/codebase files
|
|
65
|
-
* incidents list [--limit N] — List recent incidents as JSON array
|
|
66
|
-
* incidents summary — Aggregate incident stats by type/severity/source
|
|
67
|
-
* incidents query [--type T] [--severity S] [--source S] [--last Nd|Nh] — Filter incidents
|
|
68
63
|
* nk record --title "..." --category "..." --files "f1,f2" --tried "..." --failed "..." — Record negative knowledge entry
|
|
69
64
|
* nk list [--category X] [--phase X] [--status X] — List negative knowledge entries
|
|
70
65
|
* nk resolve <slug> — Mark a negative knowledge entry as resolved
|
|
71
|
-
* insights import <html-path> [--project <name>] — Parse insights HTML report into learnings
|
|
72
|
-
* audit plan-checks [--last N] — List all plan-check results from hooks.jsonl (default: last 30 days)
|
|
73
66
|
* hooks perf [--last N] [--json] — Show P50/P95/P99 hook performance report from hooks-*.jsonl
|
|
74
67
|
* spot-check <phaseSlug> <planId> — Verify SUMMARY, key_files, and commits exist for a plan
|
|
75
68
|
* staleness-check <phase-slug> — Check if phase plans are stale vs dependencies
|
|
@@ -143,7 +136,6 @@ const {
|
|
|
143
136
|
|
|
144
137
|
const {
|
|
145
138
|
parseStateMd,
|
|
146
|
-
updateLegacyStateField,
|
|
147
139
|
updateFrontmatterField,
|
|
148
140
|
stateLoad: _stateLoad,
|
|
149
141
|
stateCheckProgress: _stateCheckProgress,
|
|
@@ -231,7 +223,6 @@ const {
|
|
|
231
223
|
} = require('./lib/migrate');
|
|
232
224
|
|
|
233
225
|
const {
|
|
234
|
-
spotCheck: _spotCheck,
|
|
235
226
|
verifySpotCheck: _verifySpotCheck
|
|
236
227
|
} = require('./lib/spot-check');
|
|
237
228
|
|
|
@@ -253,9 +244,7 @@ const {
|
|
|
253
244
|
queryGlobal: _queryGlobal
|
|
254
245
|
} = require('./lib/learnings');
|
|
255
246
|
|
|
256
|
-
|
|
257
|
-
insightsImport: _insightsImport
|
|
258
|
-
} = require('./lib/insights-parser');
|
|
247
|
+
// insights-parser.js CLI subcommand removed as dead (plan 140-01)
|
|
259
248
|
|
|
260
249
|
const {
|
|
261
250
|
referenceGet: _referenceGet
|
|
@@ -318,18 +307,13 @@ const {
|
|
|
318
307
|
quickStatus: _quickStatus
|
|
319
308
|
} = require('./quick-status');
|
|
320
309
|
|
|
321
|
-
// --- Local LLM imports removed (feature archived in phase 53) ---
|
|
322
|
-
|
|
323
310
|
const {
|
|
324
311
|
dataStatus: _dataStatus,
|
|
325
312
|
dataPrune: _dataPrune
|
|
326
313
|
} = require('./lib/data-hygiene');
|
|
327
314
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
query: _incidentsQuery,
|
|
331
|
-
summary: _incidentsSummary
|
|
332
|
-
} = require('./lib/incidents');
|
|
315
|
+
// incidents.js lib retained (used by error-analysis.js, record-incident.js, dashboard)
|
|
316
|
+
// but CLI subcommands removed as dead (plan 140-01)
|
|
333
317
|
|
|
334
318
|
// --- Module-level state (for backwards compatibility) ---
|
|
335
319
|
|
|
@@ -360,10 +344,6 @@ function configValidate(preloadedConfig) {
|
|
|
360
344
|
return _configValidate(preloadedConfig, planningDir);
|
|
361
345
|
}
|
|
362
346
|
|
|
363
|
-
function insightsImport(htmlPath, projectName) {
|
|
364
|
-
return _insightsImport(htmlPath, projectName, planningDir);
|
|
365
|
-
}
|
|
366
|
-
|
|
367
347
|
function stateLoad() {
|
|
368
348
|
return _stateLoad(planningDir);
|
|
369
349
|
}
|
|
@@ -556,16 +536,6 @@ function autoArchiveNotes(context) {
|
|
|
556
536
|
return _autoArchiveNotes(planningDir, context);
|
|
557
537
|
}
|
|
558
538
|
|
|
559
|
-
function incidentsList(opts) {
|
|
560
|
-
return _incidentsList({ ...opts, planningDir });
|
|
561
|
-
}
|
|
562
|
-
function incidentsQuery(filter, opts) {
|
|
563
|
-
return _incidentsQuery(filter, { ...opts, planningDir });
|
|
564
|
-
}
|
|
565
|
-
function incidentsSummary(opts) {
|
|
566
|
-
return _incidentsSummary({ ...opts, planningDir });
|
|
567
|
-
}
|
|
568
|
-
|
|
569
539
|
function intelQuery(term) {
|
|
570
540
|
const { intelQuery: _intelQuery } = require('./lib/intel');
|
|
571
541
|
return _intelQuery(term, planningDir);
|
|
@@ -602,9 +572,6 @@ function migrate(options) {
|
|
|
602
572
|
return _applyMigrations(planningDir, options);
|
|
603
573
|
}
|
|
604
574
|
|
|
605
|
-
function spotCheck(phaseDir, planId) {
|
|
606
|
-
return _spotCheck(planningDir, phaseDir, planId);
|
|
607
|
-
}
|
|
608
575
|
|
|
609
576
|
function verifySpotCheck(type, dirPath) {
|
|
610
577
|
return _verifySpotCheck(type, dirPath);
|
|
@@ -1285,9 +1252,6 @@ async function main() {
|
|
|
1285
1252
|
const { logEvent } = require('./event-logger');
|
|
1286
1253
|
logEvent(category, event, details);
|
|
1287
1254
|
output({ logged: true, category, event });
|
|
1288
|
-
} else if (command === 'llm') {
|
|
1289
|
-
// local_llm feature has been removed (phase 53). All subcommands are no-ops.
|
|
1290
|
-
output({ deprecated: true, message: 'local_llm feature has been removed. This subcommand is a no-op.' });
|
|
1291
1255
|
// --- Compound init commands ---
|
|
1292
1256
|
} else if (command === "init" && subcommand === "execute-phase") {
|
|
1293
1257
|
const phase = args[2];
|
|
@@ -1533,21 +1497,6 @@ async function main() {
|
|
|
1533
1497
|
error('Usage: learnings <ingest|query|check-thresholds|copy-global|query-global>');
|
|
1534
1498
|
process.exit(1);
|
|
1535
1499
|
}
|
|
1536
|
-
} else if (command === 'insights') {
|
|
1537
|
-
const subCmd = args[1];
|
|
1538
|
-
if (subCmd === 'import') {
|
|
1539
|
-
const htmlPath = args[2];
|
|
1540
|
-
if (!htmlPath) { error('Usage: insights import <html-file-path> [--project <name>]'); process.exit(1); }
|
|
1541
|
-
let projectName;
|
|
1542
|
-
for (let i = 3; i < args.length; i++) {
|
|
1543
|
-
if (args[i] === '--project' && args[i + 1]) { projectName = args[++i]; }
|
|
1544
|
-
}
|
|
1545
|
-
const result = insightsImport(htmlPath, projectName);
|
|
1546
|
-
output(result);
|
|
1547
|
-
} else {
|
|
1548
|
-
error('Usage: insights <import>');
|
|
1549
|
-
process.exit(1);
|
|
1550
|
-
}
|
|
1551
1500
|
} else if (command === 'verify' && subcommand === 'spot-check') {
|
|
1552
1501
|
const scType = args[2];
|
|
1553
1502
|
const scPath = args[3];
|
|
@@ -1598,10 +1547,9 @@ async function main() {
|
|
|
1598
1547
|
// Returns JSON: { ok, summary_exists, key_files_checked, commits_present, detail }
|
|
1599
1548
|
const phaseSlug = args[1];
|
|
1600
1549
|
const planId = args[2];
|
|
1601
|
-
if (!phaseSlug || !planId) {
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
output(spotCheck(phaseSlug, planId));
|
|
1550
|
+
if (!phaseSlug || !planId) { error('Usage: spot-check <phase-slug> <plan-id>'); process.exit(1); }
|
|
1551
|
+
const { verifySpotCheck } = require('./lib/spot-check');
|
|
1552
|
+
output(verifySpotCheck(phaseSlug, planId));
|
|
1605
1553
|
} else if (command === 'staleness-check') {
|
|
1606
1554
|
const slug = args[1];
|
|
1607
1555
|
if (!slug) { error('Usage: staleness-check <phase-slug>'); process.exit(1); }
|
|
@@ -1715,8 +1663,6 @@ async function main() {
|
|
|
1715
1663
|
const version = args[1];
|
|
1716
1664
|
if (!version) error('Usage: pbr-tools.js milestone-stats <version>');
|
|
1717
1665
|
output(milestoneStats(version));
|
|
1718
|
-
} else if (command === 'validate-project') {
|
|
1719
|
-
output(validateProject());
|
|
1720
1666
|
} else if (command === 'skill-section') {
|
|
1721
1667
|
// skill-section --list <skill> — list all headings
|
|
1722
1668
|
if (args[1] === '--list') {
|
|
@@ -1894,28 +1840,6 @@ async function main() {
|
|
|
1894
1840
|
const maxIterations = maxIterIdx !== -1 ? parseInt(args[maxIterIdx + 1], 10) : 3;
|
|
1895
1841
|
output(ciFix({ dryRun, maxIterations }));
|
|
1896
1842
|
|
|
1897
|
-
// ─── Incidents ───────────────────────────────────────────────────────────
|
|
1898
|
-
} else if (command === 'incidents') {
|
|
1899
|
-
const sub = args[1];
|
|
1900
|
-
if (sub === 'list') {
|
|
1901
|
-
const limitIdx = args.indexOf('--limit');
|
|
1902
|
-
const limit = limitIdx !== -1 ? parseInt(args[limitIdx + 1], 10) : 50;
|
|
1903
|
-
output(incidentsList({ limit }));
|
|
1904
|
-
} else if (sub === 'summary') {
|
|
1905
|
-
output(incidentsSummary());
|
|
1906
|
-
} else if (sub === 'query') {
|
|
1907
|
-
const filter = {};
|
|
1908
|
-
for (let i = 2; i < args.length; i++) {
|
|
1909
|
-
if (args[i] === '--type' && args[i + 1]) { filter.type = args[++i]; }
|
|
1910
|
-
else if (args[i] === '--severity' && args[i + 1]) { filter.severity = args[++i]; }
|
|
1911
|
-
else if (args[i] === '--source' && args[i + 1]) { filter.source = args[++i]; }
|
|
1912
|
-
else if (args[i] === '--last' && args[i + 1]) { filter.last = args[++i]; }
|
|
1913
|
-
}
|
|
1914
|
-
output(incidentsQuery(filter));
|
|
1915
|
-
} else {
|
|
1916
|
-
error('Usage: incidents <list|summary|query> [--type T] [--severity S] [--source S] [--last Nd|Nh] [--limit N]');
|
|
1917
|
-
}
|
|
1918
|
-
|
|
1919
1843
|
} else if (command === 'nk') {
|
|
1920
1844
|
// Lazy require to avoid loading for unrelated commands
|
|
1921
1845
|
const nkLib = require(path.join(__dirname, 'lib', 'negative-knowledge'));
|
|
@@ -1992,13 +1916,6 @@ async function main() {
|
|
|
1992
1916
|
const graphCli = require('./lib/graph-cli');
|
|
1993
1917
|
graphCli.handleGraphCommand(subcommand, args, planningDir, cwd, output, error);
|
|
1994
1918
|
|
|
1995
|
-
// --- Audit Operations ---
|
|
1996
|
-
} else if (command === 'audit' && subcommand === 'plan-checks') {
|
|
1997
|
-
const { auditPlanChecks } = require('./lib/audit');
|
|
1998
|
-
const lastIdx = args.indexOf('--last');
|
|
1999
|
-
const last = lastIdx !== -1 ? parseInt(args[lastIdx + 1], 10) : 30;
|
|
2000
|
-
output(auditPlanChecks({ last }));
|
|
2001
|
-
|
|
2002
1919
|
// --- Hooks Perf Report ---
|
|
2003
1920
|
} else if (command === 'hooks' && subcommand === 'perf') {
|
|
2004
1921
|
const { summarizeHookPerf, formatPerfTable, loadPerfEntries } = require('./lib/perf');
|
|
@@ -2031,7 +1948,7 @@ async function main() {
|
|
|
2031
1948
|
if (result.error) process.exit(1);
|
|
2032
1949
|
|
|
2033
1950
|
} else {
|
|
2034
|
-
error(`Unknown command: ${args.join(' ')}\nCommands: state load|check-progress|update|patch|advance-plan|record-metric, config validate|load-defaults|save-defaults|resolve-depth, validate health,
|
|
1951
|
+
error(`Unknown command: ${args.join(' ')}\nCommands: state load|check-progress|update|patch|advance-plan|record-metric, config validate|load-defaults|save-defaults|resolve-depth, validate health, verify summary|plan-structure|phase-completeness|artifacts|key-links|commits|references|spot-check, migrate [--dry-run] [--force], init execute-phase|plan-phase|quick|verify-work|resume|progress|map-codebase, state-bundle <phase>, plan-index, frontmatter, must-haves, phase-info, phase add|remove|list|complete, roadmap update-status|update-plans, history append|load, todo list|get|add|done, auto-cleanup --phase N|--milestone vN, event, llm health|status|classify|score-source|classify-error|summarize|metrics [--session <ISO>]|adjust-thresholds, learnings ingest|query|check-thresholds, nk record|list|resolve, data status|prune, graph build|query|impact|stats, hooks perf [--last N] [--json], spec parse|diff|reverse|impact, milestone-stats <version>, context-triage [--agents-done N] [--plans-total N] [--step NAME], ci-poll <run-id> [--timeout <seconds>], ci-fix [--dry-run] [--max-iterations N], rollback <manifest-path>, session get|set|clear|dump, claim acquire|release|list, skill-section <skill> <section>|--list <skill>, step-verify <skill> <step> <checklist-json>, suggest-alternatives phase-not-found|missing-prereq|config-invalid [args], tmux detect, quick init, generate-slug|slug-generate, parse-args plan|quick, status fingerprint, quick-status, help, skill-metadata <name>`);
|
|
2035
1952
|
}
|
|
2036
1953
|
} catch (e) {
|
|
2037
1954
|
error(e.message);
|
|
@@ -2039,6 +1956,6 @@ async function main() {
|
|
|
2039
1956
|
}
|
|
2040
1957
|
|
|
2041
1958
|
if (require.main === module || process.argv[1] === __filename) { main().catch(err => { process.stderr.write(err.message + '\n'); process.exit(1); }); }
|
|
2042
|
-
module.exports = { KNOWN_AGENTS, initExecutePhase, initPlanPhase, initQuick, initVerifyWork, initResume, initProgress, initStateBundle: stateBundle, stateBundle, statePatch, stateAdvancePlan, stateRecordMetric, stateRecordActivity, stateUpdateProgress, parseStateMd, parseRoadmapMd, parseYamlFrontmatter, parseMustHaves, countMustHaves, stateLoad, stateCheckProgress, configLoad, configClearCache, configValidate, lockedFileUpdate, planIndex, determinePhaseStatus, findFiles, atomicWrite, tailLines, frontmatter, mustHavesCollect, phaseInfo, stateUpdate, roadmapUpdateStatus, roadmapUpdatePlans, roadmapAnalyze,
|
|
1959
|
+
module.exports = { KNOWN_AGENTS, initExecutePhase, initPlanPhase, initQuick, initVerifyWork, initResume, initProgress, initStateBundle: stateBundle, stateBundle, statePatch, stateAdvancePlan, stateRecordMetric, stateRecordActivity, stateUpdateProgress, parseStateMd, parseRoadmapMd, parseYamlFrontmatter, parseMustHaves, countMustHaves, stateLoad, stateCheckProgress, configLoad, configClearCache, configValidate, lockedFileUpdate, planIndex, determinePhaseStatus, findFiles, atomicWrite, tailLines, frontmatter, mustHavesCollect, phaseInfo, stateUpdate, roadmapUpdateStatus, roadmapUpdatePlans, roadmapAnalyze, updateFrontmatterField, updateTableRow, findRoadmapRow, resolveDepthProfile, DEPTH_PROFILE_DEFAULTS, historyAppend, historyLoad, VALID_STATUS_TRANSITIONS, validateStatusTransition, writeActiveSkill, validateProject, phaseAdd, phaseRemove, phaseList, loadUserDefaults, saveUserDefaults, mergeUserDefaults, USER_DEFAULTS_PATH, todoList, todoGet, todoAdd, todoDone, migrate, verifySpotCheck, referenceGet, milestoneStats, contextTriage, stalenessCheck, summaryGate, checkpointInit, checkpointUpdate, seedsMatch, ciPoll, ciFix, parseJestOutput: _parseJestOutput, parseLintOutput: _parseLintOutput, autoFixLint: _autoFixLint, runCiFixLoop: _runCiFixLoop, rollbackPlan, sessionLoad, sessionSave, SESSION_ALLOWED_KEYS, claimAcquire, claimRelease, claimList, skillSectionGet, listSkillHeadings, stepVerify: _stepVerify, phaseAlternatives: _phaseAlternatives, prerequisiteAlternatives: _prereqAlternatives, configAlternatives: _configAlternatives, phaseComplete, phaseInsert, quickStatus, autoCloseTodos, autoArchiveNotes, buildCleanupContext, helpListCmd, skillMetadataCmd, initMapCodebase };
|
|
2043
1960
|
// NOTE: validateProject, phaseAdd, phaseRemove, phaseList were previously CLI-only (not exported).
|
|
2044
1961
|
// They are now exported for testability. This is additive and backwards-compatible.
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* PostToolUse hook for Bash:
|
|
5
|
-
*
|
|
6
|
-
* When a Bash command that looks like a test invocation exits with a non-zero
|
|
7
|
-
* exit code, this hook sends the output to the local LLM for classification.
|
|
8
|
-
* The triage result is returned as advisory context to help the frontier model
|
|
9
|
-
* focus on the right failure category without re-parsing raw test output.
|
|
4
|
+
* PostToolUse hook for Bash: Previously triaged test failures via local LLM.
|
|
5
|
+
* Now a no-op stub — local LLM infrastructure has been removed.
|
|
10
6
|
*
|
|
11
7
|
* Exit codes:
|
|
12
8
|
* 0 = always (PostToolUse hook, never blocks)
|
|
@@ -14,11 +10,7 @@
|
|
|
14
10
|
|
|
15
11
|
'use strict';
|
|
16
12
|
|
|
17
|
-
const fs = require('fs');
|
|
18
|
-
const path = require('path');
|
|
19
13
|
const { logHook } = require('./hook-logger');
|
|
20
|
-
const { resolveConfig } = require('./lib/local-llm/health');
|
|
21
|
-
const { triageTestOutput } = require('./lib/local-llm/operations/triage-test-output');
|
|
22
14
|
|
|
23
15
|
const TEST_COMMAND_PATTERNS = [
|
|
24
16
|
/\bnpm\s+test\b/,
|
|
@@ -48,63 +40,13 @@ function detectTestRunner(command) {
|
|
|
48
40
|
return null;
|
|
49
41
|
}
|
|
50
42
|
|
|
51
|
-
/**
|
|
52
|
-
* Load and resolve the local_llm config block from .planning/config.json.
|
|
53
|
-
*/
|
|
54
|
-
function loadLocalLlmConfig(cwd) {
|
|
55
|
-
try {
|
|
56
|
-
const configPath = path.join(cwd || process.cwd(), '.planning', 'config.json');
|
|
57
|
-
const parsed = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
58
|
-
return resolveConfig(parsed.local_llm);
|
|
59
|
-
} catch (_e) {
|
|
60
|
-
return resolveConfig(undefined);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
43
|
/**
|
|
65
44
|
* Check if Bash output contains test failure and triage it.
|
|
66
|
-
*
|
|
45
|
+
* Previously used local LLM for classification — now a no-op stub.
|
|
46
|
+
* @param {object} _data - parsed hook data
|
|
67
47
|
* @returns {Promise<{output: object}|null>}
|
|
68
48
|
*/
|
|
69
|
-
async function checkTestTriage(
|
|
70
|
-
const command = data.tool_input?.command || '';
|
|
71
|
-
const toolOutput = data.tool_output || '';
|
|
72
|
-
const exitCode = data.tool_exit_code;
|
|
73
|
-
|
|
74
|
-
// Only triage test commands that failed
|
|
75
|
-
if (exitCode === 0 || exitCode === undefined) return null;
|
|
76
|
-
if (!TEST_COMMAND_PATTERNS.some(p => p.test(command))) return null;
|
|
77
|
-
if (!toolOutput || toolOutput.length < 20) return null;
|
|
78
|
-
|
|
79
|
-
const cwd = process.cwd();
|
|
80
|
-
const llmConfig = loadLocalLlmConfig(cwd);
|
|
81
|
-
const planningDir = path.join(cwd, '.planning');
|
|
82
|
-
const testRunner = detectTestRunner(command);
|
|
83
|
-
|
|
84
|
-
// Truncate to last 2000 chars — test failures are usually at the end
|
|
85
|
-
const tail = toolOutput.length > 2000 ? toolOutput.slice(-2000) : toolOutput;
|
|
86
|
-
|
|
87
|
-
try {
|
|
88
|
-
const llmResult = await triageTestOutput(llmConfig, planningDir, tail, testRunner, data.session_id);
|
|
89
|
-
if (llmResult && llmResult.category && llmResult.category !== 'unknown') {
|
|
90
|
-
logHook('post-bash-triage', 'PostToolUse', 'triage', {
|
|
91
|
-
category: llmResult.category,
|
|
92
|
-
file_hint: llmResult.file_hint,
|
|
93
|
-
runner: testRunner
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
let msg = `[pbr] Test failure triage: ${llmResult.category}`;
|
|
97
|
-
if (llmResult.file_hint) {
|
|
98
|
-
msg += ` (likely: ${llmResult.file_hint})`;
|
|
99
|
-
}
|
|
100
|
-
msg += ` (confidence: ${(llmResult.confidence * 100).toFixed(0)}%)`;
|
|
101
|
-
|
|
102
|
-
return { output: { additionalContext: msg } };
|
|
103
|
-
}
|
|
104
|
-
} catch (_llmErr) {
|
|
105
|
-
// Never propagate LLM errors
|
|
106
|
-
}
|
|
107
|
-
|
|
49
|
+
async function checkTestTriage(_data) {
|
|
108
50
|
return null;
|
|
109
51
|
}
|
|
110
52
|
|
|
@@ -12,56 +12,7 @@
|
|
|
12
12
|
const fs = require('fs');
|
|
13
13
|
const path = require('path');
|
|
14
14
|
const { execSync } = require('child_process');
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Minimal YAML frontmatter parser. Returns key-value pairs from --- blocks.
|
|
18
|
-
* Handles arrays (lines starting with " - ") and nested objects (indented keys).
|
|
19
|
-
*/
|
|
20
|
-
function parseFrontmatter(content) {
|
|
21
|
-
if (!content) return {};
|
|
22
|
-
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
23
|
-
if (!match) return {};
|
|
24
|
-
|
|
25
|
-
const lines = match[1].split(/\r?\n/);
|
|
26
|
-
const result = {};
|
|
27
|
-
let currentKey = null;
|
|
28
|
-
let currentNested = null;
|
|
29
|
-
|
|
30
|
-
for (const line of lines) {
|
|
31
|
-
// Nested array item: " - value"
|
|
32
|
-
const arrayMatch = line.match(/^(\s{2,})- "?([^"]*)"?$/);
|
|
33
|
-
if (arrayMatch && currentKey) {
|
|
34
|
-
if (currentNested) {
|
|
35
|
-
if (!result[currentKey]) result[currentKey] = {};
|
|
36
|
-
if (!result[currentKey][currentNested]) result[currentKey][currentNested] = [];
|
|
37
|
-
result[currentKey][currentNested].push(arrayMatch[2]);
|
|
38
|
-
} else {
|
|
39
|
-
if (!Array.isArray(result[currentKey])) result[currentKey] = [];
|
|
40
|
-
result[currentKey].push(arrayMatch[2]);
|
|
41
|
-
}
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Nested key: " key:"
|
|
46
|
-
const nestedKeyMatch = line.match(/^\s{2,}(\w+):\s*$/);
|
|
47
|
-
if (nestedKeyMatch && currentKey) {
|
|
48
|
-
currentNested = nestedKeyMatch[1];
|
|
49
|
-
if (!result[currentKey]) result[currentKey] = {};
|
|
50
|
-
continue;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Top-level key-value: "key: value" or "key:"
|
|
54
|
-
const kvMatch = line.match(/^(\w[\w_]*):\s*(.*)$/);
|
|
55
|
-
if (kvMatch) {
|
|
56
|
-
currentKey = kvMatch[1];
|
|
57
|
-
currentNested = null;
|
|
58
|
-
const val = kvMatch[2].replace(/^"(.*)"$/, '$1').trim();
|
|
59
|
-
result[currentKey] = val || null;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return result;
|
|
64
|
-
}
|
|
15
|
+
const { extractFrontmatter } = require('./lib/frontmatter');
|
|
65
16
|
|
|
66
17
|
/**
|
|
67
18
|
* Read all PLAN-*.md files from phaseDir and parse their frontmatter.
|
|
@@ -72,7 +23,7 @@ function readPlanFiles(phaseDir) {
|
|
|
72
23
|
const files = fs.readdirSync(phaseDir).filter(f => /^PLAN.*\.md$/i.test(f));
|
|
73
24
|
for (const file of files) {
|
|
74
25
|
const content = fs.readFileSync(path.join(phaseDir, file), 'utf-8');
|
|
75
|
-
const fm =
|
|
26
|
+
const fm = extractFrontmatter(content);
|
|
76
27
|
plans.push(fm);
|
|
77
28
|
}
|
|
78
29
|
} catch (_e) {
|
|
@@ -207,7 +158,7 @@ function generateLearnings(phaseDir, _planningDir) {
|
|
|
207
158
|
|
|
208
159
|
try {
|
|
209
160
|
const verContent = fs.readFileSync(verPath, 'utf-8');
|
|
210
|
-
const fm =
|
|
161
|
+
const fm = extractFrontmatter(verContent);
|
|
211
162
|
verStatus = fm.status || '';
|
|
212
163
|
if (Array.isArray(fm.gaps)) {
|
|
213
164
|
gaps = fm.gaps;
|
|
@@ -185,42 +185,6 @@ async function processEvent(data, planningDir) {
|
|
|
185
185
|
logHook('post-write-dispatch', 'PostToolUse', 'error', { check: 'checkReadFirst', error: e.message });
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
-
// LLM file intent classification — advisory enrichment for non-planning files
|
|
189
|
-
const filePath = data.tool_input?.file_path || data.tool_input?.path || '';
|
|
190
|
-
const normalizedPath = filePath.replace(/\\/g, '/');
|
|
191
|
-
if (filePath && !normalizedPath.includes('.planning/') && !normalizedPath.includes('.planning\\')) {
|
|
192
|
-
try {
|
|
193
|
-
const { resolveConfig } = require('./lib/local-llm/health');
|
|
194
|
-
const { classifyFileIntent } = require('./lib/local-llm/operations/classify-file-intent');
|
|
195
|
-
const llmConfig = (() => {
|
|
196
|
-
try {
|
|
197
|
-
const configPath = path.join(planningDir, 'config.json');
|
|
198
|
-
const parsed = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
199
|
-
return resolveConfig(parsed.local_llm);
|
|
200
|
-
} catch (_e) {
|
|
201
|
-
return resolveConfig(undefined);
|
|
202
|
-
}
|
|
203
|
-
})();
|
|
204
|
-
|
|
205
|
-
let contentSnippet = '';
|
|
206
|
-
try {
|
|
207
|
-
const content = data.tool_input?.content || data.tool_input?.new_string || '';
|
|
208
|
-
contentSnippet = content.slice(0, 400);
|
|
209
|
-
} catch (_e) {
|
|
210
|
-
// No content available
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
if (contentSnippet) {
|
|
214
|
-
const llmResult = await classifyFileIntent(llmConfig, planningDir, filePath, contentSnippet, data.session_id);
|
|
215
|
-
if (llmResult && llmResult.file_type) {
|
|
216
|
-
results.push(`[pbr] File classified: ${llmResult.file_type}/${llmResult.intent} (confidence: ${(llmResult.confidence * 100).toFixed(0)}%)`);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
} catch (_llmErr) {
|
|
220
|
-
// Never propagate LLM errors
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
188
|
// Merge all results into a single response
|
|
225
189
|
if (results.length > 0) {
|
|
226
190
|
return { additionalContext: results.join('\n') };
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
|
|
49
49
|
const { logHook } = require('./hook-logger');
|
|
50
50
|
const { checkDangerous } = require('./check-dangerous-commands');
|
|
51
|
-
const { checkCommit
|
|
51
|
+
const { checkCommit } = require('./validate-commit');
|
|
52
52
|
const { checkUnmanagedCommit } = require('./enforce-pbr-workflow');
|
|
53
53
|
const { checkRequirePaths, checkMirrorSync, checkLintErrors } = require('./lib/pre-commit-checks');
|
|
54
54
|
// Cross-plugin sync disabled -- derivative plugins updated separately
|
|
@@ -138,12 +138,6 @@ async function processEvent(data) {
|
|
|
138
138
|
// warnings.push(syncResult.additionalContext);
|
|
139
139
|
// }
|
|
140
140
|
|
|
141
|
-
// LLM commit semantic classification -- advisory only
|
|
142
|
-
const llmAdvisory = await enrichCommitLlm(data);
|
|
143
|
-
if (llmAdvisory) {
|
|
144
|
-
warnings.push(llmAdvisory);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
141
|
if (warnings.length > 0) {
|
|
148
142
|
logHook('pre-bash-dispatch', 'PreToolUse', 'warn', { warnings: warnings.length, cmd: command.substring(0, 80) });
|
|
149
143
|
return {
|
|
@@ -31,8 +31,6 @@ const fs = require('fs');
|
|
|
31
31
|
const path = require('path');
|
|
32
32
|
const { logHook } = require('./hook-logger');
|
|
33
33
|
const { checkBudget } = require('./enforce-context-budget');
|
|
34
|
-
const { resolveConfig } = require('./lib/local-llm/health');
|
|
35
|
-
const { validateTask: llmValidateTask } = require('./lib/local-llm/operations/validate-task');
|
|
36
34
|
const { checkNonPbrAgent } = require('./enforce-pbr-workflow');
|
|
37
35
|
const { KNOWN_AGENTS } = require('./lib/core');
|
|
38
36
|
|
|
@@ -52,19 +50,6 @@ const { checkUserConfirmationGate } = require('./lib/gates/user-confirmation');
|
|
|
52
50
|
|
|
53
51
|
const MAX_DESCRIPTION_LENGTH = 100;
|
|
54
52
|
|
|
55
|
-
/**
|
|
56
|
-
* Load and resolve the local_llm config block from .planning/config.json.
|
|
57
|
-
*/
|
|
58
|
-
function loadLocalLlmConfig(cwd) {
|
|
59
|
-
try {
|
|
60
|
-
const configPath = path.join(cwd, '.planning', 'config.json');
|
|
61
|
-
const parsed = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
62
|
-
return resolveConfig(parsed.local_llm);
|
|
63
|
-
} catch (_e) {
|
|
64
|
-
return resolveConfig(undefined);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
53
|
/**
|
|
69
54
|
* Check a parsed hook data object for Task() validation issues.
|
|
70
55
|
* Returns an array of warning strings (empty if all good).
|
|
@@ -223,19 +208,6 @@ async function processEvent(data) {
|
|
|
223
208
|
if (nonPbrAgentResult) warnings.push(nonPbrAgentResult.output.additionalContext);
|
|
224
209
|
if (planValGate && planValGate.warning) warnings.push(planValGate.warning);
|
|
225
210
|
|
|
226
|
-
// LLM task coherence check — advisory only
|
|
227
|
-
try {
|
|
228
|
-
const llmCwd = data.cwd || process.env.PBR_PROJECT_ROOT || process.cwd();
|
|
229
|
-
const llmConfig = loadLocalLlmConfig(llmCwd);
|
|
230
|
-
const llmPlanningDir = path.join(llmCwd, '.planning');
|
|
231
|
-
const llmResult = await llmValidateTask(llmConfig, llmPlanningDir, data.tool_input || {}, data.session_id);
|
|
232
|
-
if (llmResult && !llmResult.coherent) {
|
|
233
|
-
warnings.push('LLM task coherence advisory: ' + (llmResult.issue || 'Task description may not match intended operation.') + ' (confidence: ' + (llmResult.confidence * 100).toFixed(0) + '%)');
|
|
234
|
-
}
|
|
235
|
-
} catch (_llmErr) {
|
|
236
|
-
// Never propagate LLM errors
|
|
237
|
-
}
|
|
238
|
-
|
|
239
211
|
if (warnings.length > 0) {
|
|
240
212
|
for (const warning of warnings) {
|
|
241
213
|
logHook('pre-task-dispatch', 'PreToolUse', 'warn', { warning });
|
|
@@ -18,7 +18,6 @@ const { logHook } = require('./hook-logger');
|
|
|
18
18
|
const { logEvent } = require('./event-logger');
|
|
19
19
|
const { configLoad, sessionSave } = require('./pbr-tools');
|
|
20
20
|
const { ensureSessionDir, cleanStaleSessions } = require('./lib/core');
|
|
21
|
-
const { resolveConfig, checkHealth, warmUp } = require('./lib/local-llm/health');
|
|
22
21
|
|
|
23
22
|
// Re-export from extracted modules for backward compatibility
|
|
24
23
|
const {
|
|
@@ -134,34 +133,10 @@ async function main() {
|
|
|
134
133
|
tryLaunchHookServer(config, planningDir);
|
|
135
134
|
}
|
|
136
135
|
|
|
137
|
-
// Write session-start timestamp
|
|
138
|
-
// Primary: write to .session.json (unified session state)
|
|
139
|
-
// Legacy: also write .session-start file for session-cleanup.js backward compat
|
|
136
|
+
// Write session-start timestamp to .session.json (unified session state)
|
|
140
137
|
const sessionStart = new Date().toISOString();
|
|
141
138
|
try { sessionSave(planningDir, { sessionStart }, sessionId); } catch (_e) { /* non-fatal */ }
|
|
142
|
-
const sessionStartFile = path.join(planningDir, '.session-start');
|
|
143
|
-
try {
|
|
144
|
-
fs.writeFileSync(sessionStartFile, sessionStart, 'utf8');
|
|
145
|
-
} catch (_e) { /* non-fatal */ }
|
|
146
139
|
|
|
147
|
-
// Local LLM health check (advisory only -- never blocks SessionStart)
|
|
148
|
-
let llmContext = '';
|
|
149
|
-
try {
|
|
150
|
-
const rawLlmConfig = config && config.local_llm;
|
|
151
|
-
const llmConfig = resolveConfig(rawLlmConfig);
|
|
152
|
-
if (llmConfig.enabled) {
|
|
153
|
-
const health = await checkHealth(llmConfig);
|
|
154
|
-
if (health.available) {
|
|
155
|
-
llmContext = `\nLocal LLM: ${llmConfig.model} (${health.warm ? 'warm' : 'cold start'})`;
|
|
156
|
-
if (!health.warm) {
|
|
157
|
-
// Fire warm-up without awaiting -- 23s cold start must not block hook
|
|
158
|
-
warmUp(llmConfig);
|
|
159
|
-
}
|
|
160
|
-
} else if (health.reason !== 'disabled') {
|
|
161
|
-
llmContext = `\nLocal LLM: unavailable -- ${health.detail || health.reason}`;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
} catch (_e) { /* graceful degradation -- never surface to user */ }
|
|
165
140
|
|
|
166
141
|
// Enrich context with recent session activity from hook server (advisory, fail-open)
|
|
167
142
|
let enrichedContext = '';
|
|
@@ -179,7 +154,7 @@ async function main() {
|
|
|
179
154
|
|
|
180
155
|
if (context) {
|
|
181
156
|
const output = {
|
|
182
|
-
additionalContext: context + sessionWarning +
|
|
157
|
+
additionalContext: context + sessionWarning + enrichedContext
|
|
183
158
|
};
|
|
184
159
|
process.stdout.write(JSON.stringify(output));
|
|
185
160
|
logHook('progress-tracker', 'SessionStart', 'injected', { hasState: true });
|
|
@@ -24,7 +24,6 @@ const { logHook, getLogFilename: getHooksFilename, cleanOldHookLogs } = require(
|
|
|
24
24
|
const { getLogFilename: getEventsFilename, cleanOldEventLogs } = require('./event-logger');
|
|
25
25
|
const { tailLines, configLoad } = require('./pbr-tools');
|
|
26
26
|
const { removeSessionDir, releaseSessionClaims } = require('./lib/core');
|
|
27
|
-
const { readSessionMetrics, summarizeMetrics, formatSessionSummary } = require('./lib/local-llm/metrics');
|
|
28
27
|
const { writeSnapshot } = require('./lib/snapshot-manager');
|
|
29
28
|
|
|
30
29
|
function readStdin() {
|
|
@@ -492,35 +491,6 @@ function main() {
|
|
|
492
491
|
}
|
|
493
492
|
} catch (_e) { /* metrics display is best-effort */ }
|
|
494
493
|
|
|
495
|
-
// Local LLM metrics summary (SessionEnd — sync reads only, never throws)
|
|
496
|
-
let llmAdditionalContext = null;
|
|
497
|
-
try {
|
|
498
|
-
const sessionStartFile = path.join(planningDir, '.session-start');
|
|
499
|
-
if (fs.existsSync(sessionStartFile)) {
|
|
500
|
-
const sessionStartTime = fs.readFileSync(sessionStartFile, 'utf8').trim();
|
|
501
|
-
const entries = readSessionMetrics(planningDir, sessionStartTime);
|
|
502
|
-
if (entries.length > 0) {
|
|
503
|
-
const summary = summarizeMetrics(entries);
|
|
504
|
-
logHook('session-cleanup', 'SessionEnd', 'llm-metrics', {
|
|
505
|
-
total_calls: summary.total_calls,
|
|
506
|
-
fallback_count: summary.fallback_count,
|
|
507
|
-
avg_latency_ms: summary.avg_latency_ms,
|
|
508
|
-
tokens_saved: summary.tokens_saved,
|
|
509
|
-
cost_saved_usd: summary.cost_saved_usd
|
|
510
|
-
});
|
|
511
|
-
if (summary.total_calls > 0) {
|
|
512
|
-
let modelName = null;
|
|
513
|
-
try {
|
|
514
|
-
const rawConfig = configLoad(planningDir) || {};
|
|
515
|
-
modelName = (rawConfig.local_llm && rawConfig.local_llm.model) || null;
|
|
516
|
-
} catch (_e) { /* config read failure is non-fatal */ }
|
|
517
|
-
llmAdditionalContext = formatSessionSummary(summary, modelName);
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
// Clean up session-start file
|
|
521
|
-
try { fs.unlinkSync(sessionStartFile); } catch (_e) { /* non-fatal */ }
|
|
522
|
-
}
|
|
523
|
-
} catch (_e) { /* metrics never crash the hook */ }
|
|
524
494
|
|
|
525
495
|
// Surface compliance violations from this session
|
|
526
496
|
let complianceContext = null;
|
|
@@ -568,7 +538,7 @@ function main() {
|
|
|
568
538
|
// Snapshot failure must never crash SessionEnd
|
|
569
539
|
}
|
|
570
540
|
|
|
571
|
-
const combinedContext = [metricsContext,
|
|
541
|
+
const combinedContext = [metricsContext, complianceContext].filter(Boolean).join('\n');
|
|
572
542
|
if (combinedContext) {
|
|
573
543
|
process.stdout.write(JSON.stringify({ additionalContext: combinedContext }) + '\n');
|
|
574
544
|
}
|
|
@@ -16,6 +16,7 @@ const cp = require('child_process');
|
|
|
16
16
|
const { logHook } = require('./hook-logger');
|
|
17
17
|
const { configLoad } = require('./pbr-tools');
|
|
18
18
|
const { resolveSessionPath } = require('./lib/core');
|
|
19
|
+
const { extractFrontmatter } = require('./lib/frontmatter');
|
|
19
20
|
// ANSI color codes
|
|
20
21
|
const c = {
|
|
21
22
|
reset: '\x1b[0m',
|
|
@@ -542,26 +543,27 @@ function main() {
|
|
|
542
543
|
|
|
543
544
|
/**
|
|
544
545
|
* Parse YAML frontmatter from STATE.md content.
|
|
545
|
-
*
|
|
546
|
+
* Thin adapter around canonical extractFrontmatter preserving null-on-missing
|
|
547
|
+
* and filtering out null/undefined string values for status-line display.
|
|
546
548
|
*/
|
|
547
|
-
|
|
549
|
+
const parseFrontmatter = (content) => {
|
|
548
550
|
if (!content.startsWith('---')) return null;
|
|
549
551
|
const endIdx = content.indexOf('---', 3);
|
|
550
552
|
if (endIdx === -1) return null;
|
|
551
|
-
const
|
|
553
|
+
const raw = extractFrontmatter(content);
|
|
552
554
|
const result = {};
|
|
553
|
-
for (const
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
if (val && val !== 'null' && val !== 'undefined') {
|
|
559
|
-
result[m[1]] = val;
|
|
555
|
+
for (const [key, val] of Object.entries(raw)) {
|
|
556
|
+
if (typeof val === 'object') {
|
|
557
|
+
// Keep objects/arrays (non-empty)
|
|
558
|
+
if (Array.isArray(val) ? val.length > 0 : Object.keys(val).length > 0) {
|
|
559
|
+
result[key] = val;
|
|
560
560
|
}
|
|
561
|
+
} else if (typeof val === 'string' && val && val !== 'null' && val !== 'undefined') {
|
|
562
|
+
result[key] = val;
|
|
561
563
|
}
|
|
562
564
|
}
|
|
563
565
|
return result;
|
|
564
|
-
}
|
|
566
|
+
};
|
|
565
567
|
|
|
566
568
|
function buildStatusLine(content, ctxPercent, cfg, stdinData, planningDir) {
|
|
567
569
|
const config = cfg || DEFAULTS;
|