valent-pipeline 0.18.0 → 0.19.15

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.
Files changed (92) hide show
  1. package/bin/cli.js +163 -4
  2. package/knowledge-seed/curated/pitfalls-cli-tool-node.md +21 -0
  3. package/package.json +1 -1
  4. package/pipeline/docs/communication-standard.md +3 -3
  5. package/pipeline/docs/cost-and-resumability-improvements.md +2 -1
  6. package/pipeline/docs/headroom-evaluation.md +144 -0
  7. package/pipeline/docs/output-discipline.md +119 -0
  8. package/pipeline/docs/pipeline-state-schema.md +2 -2
  9. package/pipeline/docs/template-skeleton.md +1 -1
  10. package/pipeline/orchestrators/claude-code/plan.workflow.js +71 -2
  11. package/pipeline/orchestrators/claude-code/sprint.workflow.js +209 -39
  12. package/pipeline/prompts/cli-dev.md +1 -3
  13. package/pipeline/prompts/libdev.md +1 -3
  14. package/pipeline/prompts/pmcp.md +21 -3
  15. package/pipeline/prompts/qa-a.md +1 -1
  16. package/pipeline/prompts/qa-b.md +1 -1
  17. package/pipeline/steps/bend/handoff.md +1 -0
  18. package/pipeline/steps/bend/write-tests.md +2 -0
  19. package/pipeline/steps/cli-dev/handoff.md +1 -0
  20. package/pipeline/steps/cli-dev/read-inputs.md +1 -1
  21. package/pipeline/steps/cli-dev/write-tests.md +2 -0
  22. package/pipeline/steps/critic/cli-tool.md +1 -1
  23. package/pipeline/steps/data/handoff.md +1 -0
  24. package/pipeline/steps/data/write-tests.md +2 -0
  25. package/pipeline/steps/docgen/handoff.md +1 -0
  26. package/pipeline/steps/docgen/write-tests.md +2 -0
  27. package/pipeline/steps/fend/handoff.md +1 -0
  28. package/pipeline/steps/fend/write-tests.md +2 -0
  29. package/pipeline/steps/iac/handoff.md +1 -0
  30. package/pipeline/steps/iac/write-tests.md +2 -0
  31. package/pipeline/steps/judge/evidence-review.md +3 -0
  32. package/pipeline/steps/libdev/handoff.md +1 -0
  33. package/pipeline/steps/libdev/read-inputs.md +1 -1
  34. package/pipeline/steps/libdev/write-tests.md +2 -0
  35. package/pipeline/steps/mcp-dev/handoff.md +1 -0
  36. package/pipeline/steps/mcp-dev/write-tests.md +2 -0
  37. package/pipeline/steps/mobile/handoff.md +1 -0
  38. package/pipeline/steps/mobile/write-tests.md +2 -0
  39. package/pipeline/steps/orchestration/resolve-next-work-item.md +21 -14
  40. package/pipeline/steps/qa-b/api.md +3 -3
  41. package/pipeline/steps/qa-b/cli-tool.md +1 -1
  42. package/pipeline/steps/qa-b/execute-tests.md +6 -2
  43. package/pipeline/steps/qa-b/library.md +1 -1
  44. package/pipeline/steps/qa-b/mobile-app.md +1 -1
  45. package/pipeline/steps/qa-b/ui.md +1 -1
  46. package/pipeline/task-graphs/backend-api.yaml +13 -1
  47. package/pipeline/task-graphs/cli-tool.yaml +13 -1
  48. package/pipeline/task-graphs/data-pipeline.yaml +13 -1
  49. package/pipeline/task-graphs/document-generation.yaml +13 -1
  50. package/pipeline/task-graphs/frontend-only.yaml +13 -1
  51. package/pipeline/task-graphs/fullstack-web.yaml +13 -1
  52. package/pipeline/task-graphs/library.yaml +13 -1
  53. package/pipeline/task-graphs/mcp-server.yaml +13 -1
  54. package/pipeline/task-graphs/mobile-app.yaml +13 -1
  55. package/pipeline/templates/escalation-log.template.md +5 -4
  56. package/pipeline/templates/story-scaffold.template.md +23 -0
  57. package/pipeline/templates/visual-review.template.md +67 -0
  58. package/skills/valent-configure/SKILL.md +1 -0
  59. package/skills/valent-run-epic-workflow/SKILL.md +49 -21
  60. package/skills/valent-run-project-workflow/SKILL.md +45 -28
  61. package/skills/valent-run-story-workflow/SKILL.md +12 -4
  62. package/skills/valent-setup-backlog/SKILL.md +1 -0
  63. package/src/board/actions.js +147 -0
  64. package/src/board/public/app.js +937 -40
  65. package/src/board/public/index.html +42 -0
  66. package/src/board/public/markdown.js +143 -0
  67. package/src/board/public/styles.css +439 -2
  68. package/src/board/server.js +243 -17
  69. package/src/commands/audit.js +20 -0
  70. package/src/commands/backlog.js +261 -9
  71. package/src/commands/board.js +13 -2
  72. package/src/commands/config.js +133 -0
  73. package/src/commands/evidence.js +64 -29
  74. package/src/commands/init.js +42 -14
  75. package/src/commands/launch.js +174 -0
  76. package/src/commands/resolve-eligible.js +17 -1
  77. package/src/commands/resolve-graph.js +20 -3
  78. package/src/commands/validate.js +5 -65
  79. package/src/lib/backlog-hash.js +21 -0
  80. package/src/lib/backlog.js +121 -2
  81. package/src/lib/board-source.js +268 -7
  82. package/src/lib/board.js +17 -1
  83. package/src/lib/config-edit.js +75 -0
  84. package/src/lib/config-presets.js +65 -0
  85. package/src/lib/config-schema.js +45 -0
  86. package/src/lib/detect.js +18 -2
  87. package/src/lib/escalation-thread.js +77 -0
  88. package/src/lib/evidence.js +0 -0
  89. package/src/lib/launch.js +146 -0
  90. package/src/lib/locked-file.js +22 -3
  91. package/src/lib/sprint.js +29 -0
  92. package/src/lib/story-timeline.js +234 -0
package/bin/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { Command } from 'commander';
4
- import { readFileSync } from 'fs';
4
+ import { readFileSync, realpathSync } from 'fs';
5
5
  import { fileURLToPath } from 'url';
6
6
  import { dirname, join } from 'path';
7
7
 
@@ -74,6 +74,7 @@ program
74
74
  .option('--root <dir>', 'Project root to project the board over (defaults to the current directory)')
75
75
  .option('--open', 'Open the board in the default browser once it is listening')
76
76
  .option('--no-audit', 'Skip merging the per-agent token/wall-clock cost trail into the board')
77
+ .option('--actions', 'Enable the write bridge (POST /api/actions/* -> allowlisted `valent backlog` commands via execFile). Loopback-only; hardened with global Host validation + Origin checks + a per-process token embedded in the served shell')
77
78
  .action(async (options) => {
78
79
  const { boardCmd } = await import('../src/commands/board.js');
79
80
  await boardCmd(options);
@@ -95,6 +96,22 @@ program
95
96
  await auditCmd(options);
96
97
  });
97
98
 
99
+ // launch command — preflight + the exact /valent-run-… command to paste (NEVER spawns: the
100
+ // Claude Code window is the run mutex). Powers the board's launcher panel with --json.
101
+ program
102
+ .command('launch')
103
+ .description('Preflight a run and print the exact /valent-run-… command to paste into your Claude Code window. Never spawns — the window is the run mutex (one window = one runner)')
104
+ .option('--story [id]', 'Launch a single story (id optional — omitted resolves the next eligible story)')
105
+ .option('--epic <id>', 'Launch an epic loop (plan → sprint → retro)')
106
+ .option('--project', 'Launch the full-backlog (cross-epic) loop')
107
+ .option('--spike [id]', 'Launch a spike de-risking session')
108
+ .option('--root <dir>', 'Project root (defaults to cwd)')
109
+ .option('--json', 'Emit the launch plan as JSON (for the board launcher)')
110
+ .action(async (options) => {
111
+ const { launchCmd } = await import('../src/commands/launch.js');
112
+ await launchCmd(options);
113
+ });
114
+
98
115
  // analyze command — evidence-per-token rollups (gate / model tier / profile / rework tax / window fit)
99
116
  program
100
117
  .command('analyze')
@@ -127,6 +144,36 @@ configCmd
127
144
  await validate();
128
145
  });
129
146
 
147
+ configCmd
148
+ .command('set <path> <value>')
149
+ .description('Set one dot-path setting (board M5), comment-preserving, with validate-after-set ROLLBACK — an edit that invalidates the config is refused and not written. Value is JSON when it parses (true/5/[..]), else a bare string. e.g. `config set quality.max_rejection_cycles 4`')
150
+ .option('--root <dir>', 'Project root (defaults to cwd; resolves .valent-pipeline/pipeline-config.yaml)')
151
+ .option('--config <path>', 'Explicit config file path (overrides --root)')
152
+ .action(async (path, value, options) => {
153
+ const { configSet } = await import('../src/commands/config.js');
154
+ await configSet(options, path, value);
155
+ });
156
+
157
+ configCmd
158
+ .command('set-raw')
159
+ .description('Replace the whole pipeline-config.yaml with YAML read from STDIN, validate-before-write (board M5 raw editor). Refused if the posted YAML is invalid')
160
+ .option('--root <dir>', 'Project root (defaults to cwd)')
161
+ .option('--config <path>', 'Explicit config file path (overrides --root)')
162
+ .action(async (options) => {
163
+ const { configSetRaw } = await import('../src/commands/config.js');
164
+ await configSetRaw(options);
165
+ });
166
+
167
+ configCmd
168
+ .command('apply-preset <name>')
169
+ .description('Apply a coherent preset (fast-cheap | balanced | max-quality) — model tiers + rejection cycles + reasoning levels — comment-preserving, validated before write (board M5)')
170
+ .option('--root <dir>', 'Project root (defaults to cwd)')
171
+ .option('--config <path>', 'Explicit config file path (overrides --root)')
172
+ .action(async (name, options) => {
173
+ const { configApplyPreset } = await import('../src/commands/config.js');
174
+ await configApplyPreset(options, name);
175
+ });
176
+
130
177
  // validate-handoff command
131
178
  program
132
179
  .command('validate-handoff')
@@ -146,6 +193,7 @@ program
146
193
  .option('--file <path>', 'Explicit path to a task-graph YAML (overrides --type)')
147
194
  .option('--profiles <list>', 'Comma-separated testing profiles, e.g. api,ui,iac', '')
148
195
  .option('--validate-only', 'Validate the graph shape and references without resolving')
196
+ .option('--out <path>', 'Also write the resolved graph JSON to this file (e.g. stories/<id>/evidence/resolved-graph.json — the durable stage-list record the status board reads); stdout is unchanged')
149
197
  .action(async (options) => {
150
198
  const { resolveGraphCmd } = await import('../src/commands/resolve-graph.js');
151
199
  await resolveGraphCmd(options);
@@ -171,6 +219,8 @@ program
171
219
  .option('--backlog <path>', 'Backlog file (YAML/JSON); resolves its `items`')
172
220
  .option('--stories <path>', 'Explicit item array (YAML/JSON); overrides --backlog')
173
221
  .option('--epic <id>', 'Scope candidates to one epic (deps still resolve against the whole backlog; cross-epic prerequisites must be shipped)')
222
+ .option('--full', 'Emit eligible/groomedBuffer as full workflow-args candidate objects instead of id lists')
223
+ .option('--project-type <type>', 'Stamp every --full candidate with this projectType (project config value; only meaningful with --full)')
174
224
  .action(async (options) => {
175
225
  const { resolveEligibleCmd } = await import('../src/commands/resolve-eligible.js');
176
226
  await resolveEligibleCmd(options);
@@ -243,6 +293,7 @@ backlogCmd
243
293
  .requiredOption('--story <id>', 'Story identifier')
244
294
  .requiredOption('--reason <text>', 'One-line escalation reason')
245
295
  .option('--escalation-type <type>', 'skippable | blocking (default skippable)')
296
+ .option('--origin <origin>', 'Escalation origin the inbox branches on: manual | reject-cap | spike-halt | … (default manual)')
246
297
  .option('--state <path>', 'pipeline-state.json path (default ./pipeline-state.json)')
247
298
  .option('--date <YYYY-MM-DD>', 'blocked_at date (defaults to today)')
248
299
  .action(async (options) => {
@@ -272,12 +323,78 @@ backlogCmd
272
323
  .requiredOption('--story <id>', 'Item to cancel')
273
324
  .requiredOption('--reason <text>', 'Why (e.g. "superseded by KANBAN-TESTINFRA-002: one root cause, N symptom bugs")')
274
325
  .option('--superseded-by <id>', 'Replacing item (usually the priority-0 remediation story); re-points dependents')
326
+ .option('--state <path>', 'pipeline-state.json path whose blocked_stories entry is cleared (default ./pipeline-state.json)')
275
327
  .option('--date <YYYY-MM-DD>', 'cancelled_at date (defaults to today)')
276
328
  .action(async (options) => {
277
329
  const { backlogCancel } = await import('../src/commands/backlog.js');
278
330
  await backlogCancel(options);
279
331
  });
280
332
 
333
+ backlogCmd
334
+ .command('reorder')
335
+ .description('Stack-rank (board M3a): renumber `priority` dense + unique (1..N) across ALL live items — --order must list exactly the non-shipped/cancelled ids (the packer sorts by priority; partial renumbers leave two tie-break regimes). Dependency-order violations warn, never block')
336
+ .requiredOption('--backlog <path>', 'Backlog file (YAML)')
337
+ .requiredOption('--order <json>', 'JSON array of every live item id, highest priority first')
338
+ .option('--expect-hash <hash>', 'Compare-and-swap: refuse the renumber if the backlog file hash differs from this (the board\'s conflict guard — model.backlog_hash; omit to skip)')
339
+ .action(async (options) => {
340
+ const { backlogReorder } = await import('../src/commands/backlog.js');
341
+ await backlogReorder(options);
342
+ });
343
+
344
+ backlogCmd
345
+ .command('set-priority')
346
+ .description('Single-item move (board M3a): set one item\'s numeric `priority` (lower = higher rank; ties resolve by file position)')
347
+ .requiredOption('--backlog <path>', 'Backlog file (YAML)')
348
+ .requiredOption('--story <id>', 'Item to move')
349
+ .requiredOption('--priority <n>', 'New numeric priority')
350
+ .action(async (options) => {
351
+ const { backlogSetPriority } = await import('../src/commands/backlog.js');
352
+ await backlogSetPriority(options);
353
+ });
354
+
355
+ backlogCmd
356
+ .command('add')
357
+ .description('New story slot (board M3a): append a `pending` item at the BOTTOM of the ranking (unless --priority) and scaffold stories/<id>/story.md from the story-scaffold template (AC stubs — authoring stays in story files/BMAD). Never overwrites an existing story.md')
358
+ .requiredOption('--backlog <path>', 'Backlog file (YAML)')
359
+ .requiredOption('--story <id>', 'New item id (names the stories/<id>/ directory)')
360
+ .option('--title <text>', 'Human title (also templated into the scaffold)')
361
+ .option('--points <n>', 'story_points estimate')
362
+ .option('--depends-on <ids>', 'Comma-separated prerequisite item ids')
363
+ .option('--profiles <list>', 'Comma-separated testing_profiles, e.g. api,ui')
364
+ .option('--epic <id>', 'Epic the story belongs to')
365
+ .option('--priority <n>', 'Explicit priority (default: bottom of the current ranking)')
366
+ .option('--root <dir>', 'Project root for the story scaffold (defaults to cwd)')
367
+ .option('--no-scaffold', 'Backlog entry only — skip writing stories/<id>/story.md')
368
+ .option('--date <YYYY-MM-DD>', 'created_at date (defaults to today)')
369
+ .action(async (options) => {
370
+ const { backlogAdd } = await import('../src/commands/backlog.js');
371
+ await backlogAdd(options);
372
+ });
373
+
374
+ backlogCmd
375
+ .command('comment')
376
+ .description('Append to a story\'s escalation thread in stories/<id>/output/escalation-log.md (creates the dir + a minimal log on first use) and stamp blocked_stories[].last_comment. --author is a caller convention: the board sends human, run-intake triage sends pipeline')
377
+ .requiredOption('--story <id>', 'Story identifier')
378
+ .requiredOption('--author <who>', 'human | pipeline')
379
+ .requiredOption('--text <text>', 'The comment body (markdown; heading-like lines are escaped)')
380
+ .option('--root <dir>', 'Project root (defaults to cwd)')
381
+ .option('--state <path>', 'pipeline-state.json path for the last_comment pointer (default ./pipeline-state.json)')
382
+ .option('--at <iso>', 'Entry timestamp (defaults to now)')
383
+ .action(async (options) => {
384
+ const { backlogComment } = await import('../src/commands/backlog.js');
385
+ await backlogComment(options);
386
+ });
387
+
388
+ backlogCmd
389
+ .command('triage-scan')
390
+ .description('Read-only run-intake scan (board M3b): every blocked story whose escalation thread ends with a HUMAN entry (awaiting the pipeline), with origin/reason + size-bounded thread, as JSON — the deterministic half of intake triage (the session judges accept/needs-info/acknowledge and replies via `backlog comment --author pipeline`)')
391
+ .option('--root <dir>', 'Project root (defaults to cwd)')
392
+ .option('--state <path>', 'pipeline-state.json path (default ./pipeline-state.json)')
393
+ .action(async (options) => {
394
+ const { backlogTriageScan } = await import('../src/commands/backlog.js');
395
+ await backlogTriageScan(options);
396
+ });
397
+
281
398
  backlogCmd
282
399
  .command('lint')
283
400
  .description('Structural backlog validation BEFORE a run (PUR-001 R-6.3): duplicate/missing ids, self-deps, depends_on refs to nonexistent items, dependency cycles, non-array workspaces, case-variant types (errors, exit 1); unknown statuses, missing bug refs, non-numeric priority/points, undetected workspace dirs (warnings)')
@@ -514,6 +631,7 @@ evidenceCmd
514
631
  .option('--hash-files-from-spec', 'Also snapshot every acceptance source the spec manifest declares (files[] + per-case testFile) — helpers outside the acceptance dir stay frozen too')
515
632
  .option('--timeout <ms>', 'Kill the wrapped command after this many milliseconds')
516
633
  .option('--quiet', 'Suppress live echo of the child output (still captured to output.log)')
634
+ .option('--digest', 'Append failing case names (junit-derived) + a bounded output.log tail after the trailer')
517
635
  .action(async (commandArgs, options) => {
518
636
  const { evidenceRunCmd } = await import('../src/commands/evidence.js');
519
637
  await evidenceRunCmd(commandArgs, options);
@@ -574,6 +692,7 @@ evidenceCmd
574
692
  .option('--root <dir>', 'Project root (defaults to cwd)')
575
693
  .option('--label <name>', 'Assert label to bind to (defaults to evidence_gate.label, then qa-b-tests)')
576
694
  .option('--tip <ref>', 'Git ref the evidence must cover (defaults to HEAD)')
695
+ .option('--ancestry-only', 'Skip the post-evidence escape diff (the backlog ledger-door posture, review pass-4 #1); ancestry + green assert + proof only')
577
696
  .action(async (options) => {
578
697
  const { evidenceVerifyShipCmd } = await import('../src/commands/evidence.js');
579
698
  await evidenceVerifyShipCmd(options);
@@ -591,6 +710,18 @@ evidenceCmd
591
710
  await evidenceProofCmd(options);
592
711
  });
593
712
 
713
+ evidenceCmd
714
+ .command('stage-record')
715
+ .description('Persist the workflow\'s per-stage verdict ledger (+ optionally the resolved task graph) into stories/<id>/evidence/ — the durable record the status board\'s gate rail reads after run journals are gone. Merge-by-stage: a later record replaces matching stages and keeps the rest.')
716
+ .requiredOption('--story <id>', 'Story identifier')
717
+ .requiredOption('--stages <json>', 'JSON array of { stage, verdict, cycles?, disposition?, reason? } — the workflow\'s post-coercion verdicts, verbatim')
718
+ .option('--graph <json>', 'Resolved task graph JSON ({ tasks, skipped, ... }) to persist as resolved-graph.json alongside')
719
+ .option('--root <dir>', 'Project root (defaults to cwd)')
720
+ .action(async (options) => {
721
+ const { evidenceStageRecordCmd } = await import('../src/commands/evidence.js');
722
+ await evidenceStageRecordCmd(options);
723
+ });
724
+
594
725
  evidenceCmd
595
726
  .command('pin')
596
727
  .description('Record which git SHA a gate reviewed (the CLI captures the SHA — there is no flag to type one)')
@@ -829,7 +960,35 @@ dbCmd
829
960
  // Introspection hook (review pass-3 #35): scripts/test-docs.js imports the registered command
830
961
  // tree to assert every `bin/cli.js …` invocation in pipeline/** and skills/** against the live
831
962
  // commander registry — a prompt referencing a command/flag that does not exist fails `npm test`.
832
- // Building the program executes nothing (all actions are lazy dynamic imports); the env guard
833
- // (not an argv heuristic) keeps every real invocation, npx shim included, behaving identically.
963
+ // Building the program executes nothing (all actions are lazy dynamic imports).
964
+ //
965
+ // The guard fails CLOSED (review pass-4 #6): the old truthiness check silently no-opped the
966
+ // ENTIRE CLI with exit 0 under ANY value of the var — a one-env-var trust-chain bypass had it
967
+ // ever leaked into an agent's shell (every evidence/crosscheck/ship-clamp gate would have
968
+ // returned success vacuously, with zero output to notice). Now: imported as a module
969
+ // (test-docs.js) => parse() skipped as before; run as the ENTRYPOINT with the var set => loud
970
+ // stderr + non-zero exit, never silence. realpath survives the npx/.bin shim symlinks; the
971
+ // case-fold handles Windows drive-letter casing.
834
972
  export { program };
835
- if (!process.env.VALENT_CLI_INTROSPECT) program.parse();
973
+ if (process.env.VALENT_CLI_INTROSPECT) {
974
+ const isEntrypoint = (() => {
975
+ try {
976
+ const argv1 = realpathSync(process.argv[1]);
977
+ return process.platform === 'win32'
978
+ ? argv1.toLowerCase() === __filename.toLowerCase()
979
+ : argv1 === __filename;
980
+ } catch {
981
+ return false; // no/unresolvable argv[1] (REPL, embedded import) — not a CLI invocation
982
+ }
983
+ })();
984
+ if (isEntrypoint) {
985
+ console.error('VALENT_CLI_INTROSPECT is set — this variable exists ONLY for scripts/test-docs.js module introspection. '
986
+ + 'Refusing to run the CLI under it: a silently no-op CLI would fake every evidence gate green. Unset it and re-run.');
987
+ process.exit(78); // EX_CONFIG
988
+ }
989
+ // Imported as a module under the var (the test-docs contract): build the tree, never parse.
990
+ } else {
991
+ // The var is unset on every real invocation: parse UNCONDITIONALLY, exactly as before pass-4 #6
992
+ // — entrypoint detection must never be able to silently no-op a genuine CLI run.
993
+ program.parse();
994
+ }
@@ -0,0 +1,21 @@
1
+ # Pitfalls Primer — cli-tool (Node)
2
+
3
+ **Scope:** stories with the `cli-tool` profile (command-line surfaces: subcommands, flags, exit codes, terminal output contracts).
4
+ **How to use:** This is a *primer, not a checklist to satisfy.* Scan your diff against each item and fix gaps now. It is deliberately short — also handle obvious things *like* these that aren't listed. CRITIC is still the grader; this is just wiping the obvious smudges off first.
5
+
6
+ ## The usual suspects
7
+
8
+ 1. **Exit 0 on failure.** Any path that prints an error (or catches and swallows one) and still exits 0. Scripts and CI branch on exit codes — this is the CLI equivalent of a swallowed exception.
9
+ 2. **Contract output polluted.** Logs, banners, progress, deprecation warnings, or color escapes on stdout in a machine-readable mode (or interleaved with pipeable output). Diagnostics go to stderr; stdout is the contract.
10
+ 3. **Hidden interactivity.** A code path that can reach a prompt (readline, inquirer, confirm) with no flag/env override and no non-TTY guard hangs CI forever. Guard `process.stdin.isTTY`; every prompt needs a non-interactive escape hatch.
11
+ 4. **Unknown input passes silently.** Unknown flags or subcommands ignored instead of erroring non-zero — typos become no-ops that look like success.
12
+ 5. **Windows paths and quoting.** Spaces in paths, backslash separators, `cmd.exe` quoting, drive-letter casing. Use `path` APIs and array-form `spawn` (never string-concatenated shell commands) for anything that touches the filesystem or subprocesses.
13
+ 6. **Accidental breaking changes.** Renamed/removed flags or subcommands, changed defaults, changed exit codes, or changed output shape that existing scripts depend on — without a deprecation path. Compare against the prior surface before you ship.
14
+ 7. **Partial-failure state.** A command that mutates files/state, fails midway, and leaves the mutation half-applied with exit 0 or no resume/rollback. Write-to-temp + rename; report what was and wasn't done.
15
+ 8. **Help text drift.** Behavior that contradicts `--help` — missing flags, wrong defaults, stale examples. The help IS documentation; keep it true in the same diff.
16
+ 9. **Argument-parsing edges.** Values that look like flags (`--name --verbose`), empty strings, the `--` separator, repeated flags. Probe every option you touched.
17
+ 10. **stdin/pipe behavior.** Reading stdin when none was intended (hangs in pipelines), or ignoring piped input a consumer would expect; `EPIPE` on `head`-style consumers crashes instead of exiting quietly.
18
+ 11. **Signal handling for long-running commands.** Ctrl-C mid-run leaves temp files, locks, or spinners that corrupt the terminal; cleanup handlers belong on SIGINT/SIGTERM.
19
+ 12. **Tests spawn the real binary.** Behavioral coverage spawns the bin entry as a subprocess and asserts exit code + stdout + stderr; failure tests assert the non-zero CODE, not just the message; `--json` modes are parsed with a real parser, not regexed.
20
+
21
+ *Not exhaustive. If you know an obvious one for this stack that isn't here, handle it anyway.*
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "valent-pipeline",
3
- "version": "0.18.0",
3
+ "version": "0.19.15",
4
4
  "description": "v3 multi-agent AI pipeline for software development lifecycle",
5
5
  "type": "module",
6
6
  "bin": {
@@ -354,7 +354,7 @@ The decision block is written to `decisions.md` in the story directory. Any agen
354
354
 
355
355
  ## 7. Headless Escalation Protocol
356
356
 
357
- When a blocker requires human input, the workflow orchestrator classifies it, logs it to `{story_output_dir}/escalation-log.md`, outputs the structured escalation block to CLI for visibility, marks the story `blocked-on-user`, and moves to the next unblocked story. The pipeline does not pause for skippable blockers. Blocking escalations (quality gate failures) stop the current story cleanly before moving on.
357
+ When a blocker requires human input, the workflow orchestrator classifies it, outputs the structured escalation block to CLI for visibility, marks the story `blocked-on-user` (recording it in `pipeline-state.json blocked_stories` with its `origin`), and moves to the next unblocked story. The durable conversation lives in `{story_output_dir}/escalation-log.md`: the board inbox and `valent backlog comment --author human|pipeline` append to its `## Thread` section, and `valent backlog triage-scan` reads it at the next run's intake. The pipeline does not pause for skippable blockers. Blocking escalations (quality gate failures) stop the current story cleanly before moving on.
358
358
 
359
359
  ### Escalation block format
360
360
 
@@ -395,7 +395,7 @@ Need: Pick an option or provide guidance
395
395
  - **Blocking** failures (quality gate exhaustion): pipeline stops cleanly on this story, persists state, then moves to the next unblocked story if safe
396
396
  - If no unblocked stories remain, the pipeline stops cleanly with persisted state
397
397
  - Escalation is the **last resort** — the workflow should exhaust autonomous resolution first (e.g., the code-owned rework cap routing rejections upstream, or Design Council deliberation)
398
- - All escalations are logged in both `{story_output_dir}/escalation-log.md` and `story-report.md`
398
+ - All escalations surface in `pipeline-state.json blocked_stories` and `story-report.md`; the human<->pipeline conversation about one lives in `{story_output_dir}/escalation-log.md` (`backlog comment`)
399
399
  - **Resume:** user fixes inputs, sets story status to `pending` in backlog, re-runs pipeline
400
400
 
401
401
  > **Note:** Headless escalation is implemented (skip-and-log; blocked stories surface with status `blocked-on-user`). For async notification when escalations occur during headless runs, scheduled remote agents and Slack integration (via MCP) are candidate notification channels.
@@ -453,4 +453,4 @@ Agents should verify their output against this checklist before finalizing.
453
453
  - [ ] Context references a file, not inline detail
454
454
  - [ ] Options are numbered, concrete, and actionable
455
455
  - [ ] Need statement tells the user exactly what is required
456
- - [ ] Escalation entry written to `{story_output_dir}/escalation-log.md`
456
+ - [ ] Escalation recorded in `pipeline-state.json blocked_stories` (origin + reason); replies via `backlog comment`
@@ -191,8 +191,9 @@ Resume = relaunch `Workflow({ scriptPath, resumeFromRunId })`; the journal repla
191
191
 
192
192
  ### Fix A — Idempotent, on-disk phase checkpoints (cross-session resume)
193
193
 
194
- Persist a durable run ledger in the existing SQLite DB (extends the `artifacts`/`calibration` tables in `src/lib/db.js`). Add a CLI command:
194
+ Persist a durable run ledger in the existing SQLite DB (extends the `artifacts`/`calibration` tables in `src/lib/db.js`). Add a CLI command (PROPOSED — `checkpoint` does not exist yet):
195
195
 
196
+ <!-- cli-drift: proposed -->
196
197
  ```
197
198
  node .valent-pipeline/bin/cli.js checkpoint --sprint <id> --story <id> --phase <spec|build|critic|qa|judge> --verdict <pass|ship|reject>
198
199
  ```
@@ -0,0 +1,144 @@
1
+ # Headroom Evaluation — Context Compression for the Pipeline
2
+
3
+ **Date:** 2026-06-11
4
+ **Status:** Research / recommendation. Not yet piloted. **Superseded in part:** integration point 1 (MCP-mode compression) was rejected on token-accounting grounds — `headroom_compress` only accepts inline `content`, so the bulk is already in agent context (and gets re-echoed as output tokens) before compression can act. See `output-discipline.md` for the verified finding and the zero-dependency counter-proposal. The `learn` analysis (integration point 2) stands.
5
+ **Verdict (short):** **Worth a scoped pilot.** Adopt **one dependency** (Dockerized Headroom) for **two integration points**: (1) in-run **compression** via MCP — the primary justification, because it's the piece we couldn't cheaply rebuild; and (2) a post-run **failure-mining** signal (`headroom learn`, dry-run) that rides along for ~zero marginal cost and feeds our Retrospective as *gated proposals*. Two guardrails: keep lossy compression away from gate-critical evidence, and **never** let `learn` auto-write (`--apply`) into our live `~/.claude` / `MEMORY.md`. And reframe the goal: Headroom cuts per-agent context pressure and token cost; it does **not** fix the thing that actually limits our "long runs" (cross-session resumability).
6
+
7
+ ---
8
+
9
+ ## What Headroom is
10
+
11
+ Open-source (Apache-2.0) "context optimization layer" that compresses verbose content — tool outputs, logs, API responses, RAG chunks, big files — **before it reaches the LLM**. Claims 60–95% token reduction with negligible accuracy delta (GSM8K 0.870→0.870, TruthfulQA 0.530→0.560 on their benchmarks).
12
+
13
+ - Written in Python (77%) / Rust (18%) / TS. Requires Python 3.10+.
14
+ - ~22.7k stars, 154 releases, v0.24.0 (2026-06-09), active. Apache-2.0.
15
+ - Three deployment modes: **library**, **proxy** (sits between agent and provider, auto-compresses), **MCP server** (exposes tools the agent calls).
16
+ - Ships a Docker image: `ghcr.io/chopratejas/headroom:latest`. So "dockerize it" is already done upstream.
17
+
18
+ ### How the compression works
19
+ - **ContentRouter** auto-detects content type (JSON / source / logs / diffs / HTML) and routes to a purpose-built compressor (SmartCrusher for JSON, AST-aware CodeCompressor, log compressor, etc.).
20
+ - **Selectively lossy**: preserves structural/high-signal tokens (JSON keys, function signatures, error messages, identifiers), compresses low-signal bulk (long string values, function bodies, repeated patterns).
21
+ - **CCR store** (Compress-Cache-Retrieve): keeps the original locally; the LLM gets a `headroom_retrieve` tool to pull full detail on demand. This is the reversibility safety net.
22
+ - **IntelligentContext**: importance-scores messages (recency, semantic relevance, error indicators) and drops the lowest-value ones to fit a token budget.
23
+
24
+ ### MCP mode specifics (the mode you proposed)
25
+ - `headroom mcp install`, or run the Docker image as a local MCP server.
26
+ - Exposes three tools: `headroom_compress`, `headroom_retrieve`, `headroom_stats`.
27
+ - **Key property:** in MCP mode compression is **opt-in per call** — an agent only benefits from content it explicitly pipes through `headroom_compress`. It is *not* automatic interception (that's proxy mode).
28
+
29
+ ### The `learn` feature (separate runtime surface from the MCP server)
30
+ `learn` is **not** something the MCP server "turns on" — it's a standalone offline CLI batch step. Mechanics, confirmed from the docs/source:
31
+ - **Input:** reads Claude Code's **native** session logs at `~/.claude/projects/*.jsonl`. It does *not* require Headroom's own proxy/recording to have been active. **This means it already sees our pipeline runs** — our agents are Claude Code subagents writing exactly those jsonl logs.
32
+ - **What it extracts:** correlates each failed tool call with the later call that fixed it, and emits *operational/environmental* corrections — commands that work, wrong→right file paths, search-scope guidance, large-file offset/limit hints. (Operational, not quality/spec knowledge.)
33
+ - **Output:** prose inside a delimited block `<!-- headroom:learn:start -->`…`<!-- headroom:learn:end -->` in `CLAUDE.md` (project) and `MEMORY.md` (`~/.claude/projects/*/memory/`). On re-run only the marked block is replaced.
34
+ - **Dry-run is the default.** `headroom learn` (no `--apply`) **prints recommendations to stdout and writes nothing.** `--apply` is what mutates files.
35
+ - **Flags:** `--project PATH` (which project to analyze/write, default cwd), `--all` (every discovered project), `--apply` (write; omit for dry-run), `--claude-dir PATH` (override `~/.claude`). Output *file targets* are otherwise fixed (not configurable).
36
+
37
+ The dry-run-prints-to-stdout behavior is the whole hijack: **stdout is the API.** We don't need a programmatic interface to capture the mined corrections as data.
38
+
39
+ **Sources:** [docs](https://headroom-docs.vercel.app/docs) · [GitHub](https://github.com/chopratejas/headroom) · [how-compression-works](https://headroom-docs.vercel.app/docs/how-compression-works) · [failure-learning](https://github.com/chopratejas/headroom/blob/main/docs/content/docs/failure-learning.mdx) · [CLI reference](https://chopratejas.github.io/headroom/cli/) · [AIToolly writeup](https://aitoolly.com/ai-news/article/2026-06-07-headroom-new-open-source-tool-reduces-llm-token-consumption-by-60-95-for-rag-and-logs) · [Tosea guide](https://tosea.ai/blog/how-to-use-headroom-context-compression-guide)
40
+
41
+ ---
42
+
43
+ ## How it maps onto our pipeline
44
+
45
+ Our agents are Claude Code **Workflow subagents** spawned via `agent()` in `sprint.workflow.js`. They self-serve from whatever MCP tools are present in the session (same way Ref MCP / Lever 6 works today). So **integration cost ≈ Lever 6**: register the MCP server in session config, then add per-role prompt hints telling the agents that ingest big outputs to route them through `headroom_compress`.
46
+
47
+ Where it would actually move the needle, and where it wouldn't:
48
+
49
+ | Pipeline context cost | Already addressed? | Headroom adds? |
50
+ |---|---|---|
51
+ | 40K-token doc-page pastes | **Yes** — Ref MCP (Lever 6) | No incremental gain |
52
+ | Inter-agent prose bloat in handoffs | **Yes** — distilled handoff format (`communication-standard.md`) | Minimal |
53
+ | Cache misses | **Yes** — byte-identical static-prefix discipline | Minimal in MCP mode |
54
+ | **Raw test/build/lint logs** ingested by QA/EVIDENCE | No | **Strong fit** (logs compress 85–95%) |
55
+ | **Large JSON / API responses** read by DATA/BEND | No | **Strong fit** (70–90%) |
56
+ | **Large source-file reads** by dev agents | Partially | **Moderate fit** (AST-aware, 40–70%) |
57
+ | Reversible "drop now, fetch later" | No | **New capability** (CCR + `headroom_retrieve`) |
58
+
59
+ So the honest incremental value is **narrower than the headline 60–95%**: it's concentrated on agents that currently swallow raw, machine-generated bulk (logs, JSON, big files). For the rest of the pipeline we've already paid down most of the bloat with Ref + distilled handoffs.
60
+
61
+ ### Build vs. borrow — why this is one dependency, not two
62
+
63
+ The two features differ sharply in how hard they'd be to rebuild ourselves, and that's what drives the recommendation:
64
+
65
+ | Capability | Rebuild cost if we DIY | Decision |
66
+ |---|---|---|
67
+ | **Compression** (content-aware routers, AST-aware code compressor, CCR store + reversible `headroom_retrieve`, importance scoring) | **High** — months to do well | **Borrow.** This is the piece we can't cheaply replicate. |
68
+ | **Failure-mining** (`learn`: parse jsonl, correlate fail→fix) | **Low** — our Retrospective could parse the same `~/.claude/projects/*.jsonl` itself | Would not justify a dependency *alone*. |
69
+
70
+ The synthesis: **we take the dependency for the compression we can't rebuild; once it's in, the `learn` signal comes along at ~zero marginal cost.** That's a materially better cost story than evaluating either feature in isolation — one container amortized across two wins — and it's why reusing Headroom's miner beats writing our own. (If we ever drop the compression dependency, revisit owning the miner ourselves; on its own it's cheap.)
71
+
72
+ ---
73
+
74
+ ## Assessment of your specific proposal
75
+
76
+ > "Dockerize it and install it as an MCP with the learning feature turned on → reduce context, allow longer runs."
77
+
78
+ **Dockerize + MCP (compression):** ✅ Sound and low-cost. Upstream Docker image exists; MCP registration mirrors our existing Ref MCP pattern. No orchestrator changes needed — the Workflow script has no FS access, and tools activate from session config. This is the primary justification.
79
+
80
+ **"Allow longer runs":** ⚠️ **Partial mismatch.** Each `agent()` is a fresh subagent with its own context window, so the real "long run" blocker is **cross-session resumability** — our journal is in-memory and same-session (`pipeline-state-schema.md`, and Problem 1 in `cost-and-resumability-improvements.md`). Headroom does nothing for that. What it *does* do is relieve **per-agent window pressure**: a QA/EVIDENCE agent chewing through huge logs hits its window later, and a dev agent can read more files before saturating. That's real, but it's "each agent does more before saturating," not "the overall run survives longer."
81
+
82
+ **"Learning feature turned on":** ✅ **Yes — but as a gated proposal feed, not an auto-writer, and it's a separate step from the MCP server.** Worth correcting the framing first: installing the MCP server does **not** turn on `learn`. Compression is the live MCP server; `learn` is an offline CLI you invoke *after* a run. So "MCP with learning on" is really **two integration points from one dependency**, not one toggle.
83
+
84
+ The right shape for `learn`:
85
+ - Run it in the **Retrospective phase**, **dry-run** (`headroom learn --project <repo>`, no `--apply`), scoped to this project.
86
+ - Capture **stdout** (the mined fail→fix corrections) and feed them as **candidate directives** into our existing gating — impact gating, invariant guards, stale-reissue guards. Our governance still decides what becomes active.
87
+ - These corrections are *operational/environmental* (commands, paths, scopes), so they land in the **curated pitfall/environment layer** — which today is hand-seeded — not the retrospective *quality*-directive layer. Good fit for auto-proposing additions to a layer we currently maintain by hand.
88
+
89
+ What we must **not** do — and why the naive "turn learning on" is dangerous on *this* machine specifically:
90
+ - `headroom learn --apply` writes into `CLAUDE.md` **and `~/.claude/projects/*/memory/MEMORY.md`** — the exact auto-memory file this pipeline depends on. An uncontrolled `--apply` could clobber/append into live memory, bypassing our `correction-directives.yaml` lifecycle and once-per-batch curation discipline.
91
+ - It reads `~/.claude` globally (`--all` spans every project; even scoped runs read the shared jsonl), so cross-project bleed is real — it could surface "corrections" mined from unrelated work on this box.
92
+ - The fail→fix correlation is a **heuristic** — the "fix" may be coincidental, not causal. That's *why* it must enter as gated proposals, never raw auto-writes.
93
+
94
+ **Rule:** dry-run only against the real `~/.claude` (or sandbox `--claude-dir`); never `--apply` to the live home dir; everything routes through our gating.
95
+
96
+ ---
97
+
98
+ ## Risks / caveats
99
+
100
+ 1. **MCP mode is opt-in.** Compression only happens on content agents deliberately pass to `headroom_compress`. Requires per-role prompt hints + the agents actually complying. Same adoption risk Ref MCP has.
101
+ 2. **Selectively lossy.** A compressor that drops "low-signal" content from a build log could drop the one stack frame a CRITIC/EVIDENCE agent needed. CCR/`headroom_retrieve` is the mitigation, but only if the agent knows to retrieve. For **gate-critical evidence** (EVIDENCE, JUDGE inputs) I'd be cautious — these are the artifacts we least want quietly summarized.
102
+ 3. **Caching interaction.** Our biggest existing lever is byte-identical static prefixes for Anthropic prompt-cache hits. MCP mode doesn't touch prompt structure, so low risk. **Proxy mode would** rewrite message contents and must be validated against our cache discipline before considering it.
103
+ 4. **Overlap.** Ref (Lever 6) + distilled handoffs already capture much of the easy savings. Don't double-count.
104
+ 5. **State/persistence.** CCR cache lives in the container (single-host fine). True cross-agent `SharedContext` wants the heavier memory stack (Qdrant/Neo4j devcontainer) — out of scope for a first pilot.
105
+ 6. **`learn` `--apply` collides with our live memory.** It writes into `CLAUDE.md` and `~/.claude/projects/*/memory/MEMORY.md` — the auto-memory this pipeline depends on. Mitigation: dry-run only / sandbox `--claude-dir`; never `--apply` to the live home dir.
106
+ 7. **`learn` cross-project bleed.** It reads `~/.claude` globally (`--all`, and shared jsonl even when scoped), so it can surface "corrections" mined from unrelated work on this box. Mitigation: `--project`-scope tightly and gate everything.
107
+ 8. **`learn` heuristic quality.** Fail→fix correlation can be coincidental, not causal. Mitigation: enter as gated proposals, measure true-positive rate at the gate.
108
+ 9. **Their own "skip it" advice:** Headroom docs say skip it if you rely solely on a provider's native compaction or run in sandboxes where local processes can't run. We're neither, but worth noting.
109
+
110
+ ---
111
+
112
+ ## Recommendation
113
+
114
+ **Run a scoped pilot: one dependency, two integration points.** Compression is the reason to adopt; `learn` is the bundled bonus, gated.
115
+
116
+ Sequence it with the **RAG / Knowledge Retrieval Audit we already owe** (`knowledge-system.md` §4) — both are "is the savings real on *our* workload?" questions and share an evaluation harness.
117
+
118
+ **Dependency:** Dockerized Headroom (`ghcr.io/chopratejas/headroom:latest`), one container.
119
+
120
+ **Integration point 1 — compression (MCP, in-run):**
121
+ 1. Wire `headroom_compress`/`headroom_retrieve` to **QA-B, EVIDENCE, and dev agents (BEND/DATA)** only — the large-raw-output ingesters. Mirror the Lever-6 config surface: `headroom: { enabled?: true, roles?: [...] }`.
122
+ 2. **Guardrail:** exclude gate-critical evidence artifacts from lossy compression, or require `headroom_retrieve` before any ship/reject decision references compressed content.
123
+
124
+ **Integration point 2 — failure-mining (`learn` CLI, post-run, gated):**
125
+ 3. Add a **Retrospective sub-step** that shells `headroom learn --project <repo>` in **dry-run**, captures stdout, and submits the mined corrections as **candidate directives** into our existing impact/invariant/stale gating → curated pitfall/environment layer.
126
+ 4. **Guardrail:** never `--apply` against the live `~/.claude`; dry-run only (or sandbox `--claude-dir`); everything flows through our gating. Protects `MEMORY.md` and `correction-directives.yaml` discipline.
127
+
128
+ **Measurement (both, over 5–10 stories):** tokens/story and cache-hit rate via `valent-review-cost`/`/audit`; whether any CRITIC/JUDGE/EVIDENCE decision degraded because signal was compressed away; and the signal-quality / false-positive rate of `learn`'s proposed corrections at the gate. Compare against today's Ref+distilled baseline, not an un-optimized strawman.
129
+
130
+ **Decision:** keep compression for roles where net token savings is material **and** zero evidence-quality regressions; keep `learn` if its gated proposals show a useful true-positive rate. Revisit **proxy mode** (auto-compression) only if the MCP pilot proves out and we've validated it against our prompt-cache discipline.
131
+
132
+ **Bottom line:** Not dumb. The headline framing oversells it — it's a **narrower, per-agent win** than 60–95%, and it doesn't extend run *length* (that's our resumability problem, untouched). But the build-vs-borrow math is the real story: **compression is the piece we couldn't cheaply rebuild, and taking that one dependency gets the failure-mining signal essentially for free.** Adopt as a scoped pilot — compression wired to the bulk-ingesting agents, `learn` running dry-run into our gating — measured honestly, with `--apply` kept away from our live home dir.
133
+
134
+ ---
135
+
136
+ ### References
137
+ - Headroom docs — https://headroom-docs.vercel.app/docs
138
+ - GitHub `chopratejas/headroom` — https://github.com/chopratejas/headroom
139
+ - How compression works — https://headroom-docs.vercel.app/docs/how-compression-works
140
+ - Failure-learning (`learn`) — https://github.com/chopratejas/headroom/blob/main/docs/content/docs/failure-learning.mdx
141
+ - CLI reference — https://chopratejas.github.io/headroom/cli/
142
+ - AIToolly overview — https://aitoolly.com/ai-news/article/2026-06-07-headroom-new-open-source-tool-reduces-llm-token-consumption-by-60-95-for-rag-and-logs
143
+ - Tosea usage guide — https://tosea.ai/blog/how-to-use-headroom-context-compression-guide
144
+ - Related internal docs: `cost-and-resumability-improvements.md` (Levers 1–7), `knowledge-system.md` (§4 RAG audit), `communication-standard.md`, `pipeline-state-schema.md`
@@ -0,0 +1,119 @@
1
+ # Output Discipline v2 — Bulk-Log Containment via the Existing Evidence Wrapper
2
+
3
+ **Date:** 2026-06-11
4
+ **Status:** **Implemented (v0.19.5, 2026-06-11)** — Changes 1–2 live (`--quiet` on all mandated suite invocations; quiet-reporter + log-to-file guidance in all 9 dev roles and the QA-B live-environment steps). Change 3's `--digest` flag is **built but unwired** (`evidence run --digest`; junit-derived failing cases + bounded tail) — prompt wiring stays gated on §6 measurement. History: v2 of this doc followed a cold adversarial review that found v1 (`vp-run` wrapper + PreToolUse hook) duplicated `src/lib/evidence.js`; v1's Headroom analysis (§1) survives, its build plan does not.
5
+ **Verdict (short):** The raw-log token problem is real but **~80% of the fix is two step-file edits and one workflow-string change against infrastructure that already ships**: `evidence run` already tees full stdout+stderr to a hashed, durable `output.log` and already has a `quiet` option — it just isn't used. Add `--quiet` to the mandated QA/ATDD invocations, add quiet-reporter guidance to the dev self-run steps that run bare today, and optionally enhance the existing run trailer with a bounded failure digest. **No new wrapper, no hook, no second log store.** Honest addressable savings: ~3–8% of story spend, concentrated in QA-B — worth shipping because the cost is near zero, not because the savings are large.
6
+
7
+ ---
8
+
9
+ ## 1. Why not Headroom MCP (unchanged from v1)
10
+
11
+ `headroom-evaluation.md` proposed wiring Headroom's MCP compression into bulk-ingesting agents. Verified against the actual tool schema ([MCP docs](https://headroom-docs.vercel.app/docs/mcp)): **`headroom_compress` takes only inline `content`** — no file path, no server-side read. So the bulk is already in the agent's context as a tool result before compression can act, and compressing requires re-emitting it verbatim as **output tokens** (≈5× input price). Net: original in context twice + compressed copy + an output-token bill. Compression that works must intercept **before the tool result is constructed**. The Ref-MCP analogy fails because Ref retrieves server-side — the agent's first copy is already distilled. Proxy mode (the deployment that genuinely intercepts) stays deferred: it rewrites messages between turns, colliding with byte-identical prompt-cache discipline (Lever 7) and the OAuth'd Pro path.
12
+
13
+ ---
14
+
15
+ ## 2. What the pipeline already has (the v1 blind spot)
16
+
17
+ `evidence run` (`src/lib/evidence.js`) is already the lossless capture-and-retrieve store v1 proposed to build:
18
+
19
+ | v1 wanted (`vp-run`) | Already shipped (`evidence run`) |
20
+ |---|---|
21
+ | Tee full stdout+stderr to a log file | `output.log` per run dir (`evidence.js:425`), full interleaved stream |
22
+ | Lossless, citable artifact | SHA-256-hashed into `evidence.json` (`evidence.js:534`), plus frozen junit, git SHA, run index — *stronger* than v1's plan |
23
+ | Exit-code passthrough | Yes — gates depend on it |
24
+ | Bounded digest into context | `formatRunTrailer` (`evidence.js:1246`): exit code, test counts, evidence path |
25
+ | Suppress bulk from context | **`quiet` option already exists** (`evidence.js:399,449`) — suppresses live echo, leaves only the trailer |
26
+ | Compliance enforcement | `qa-b/execute-tests.md:35`: "**Every suite runs wrapped in `evidence run`**"; both ATDD gates invoke it from the workflow (`sprint.workflow.js:788`) |
27
+ | Durable, gate-citable store | `stories/<id>/evidence/` is tracked and survives worktree merge; `qa-b/write-report.md` already requires listing run IDs for JUDGE |
28
+
29
+ What v1 got wrong as a consequence (all confirmed by the review against code):
30
+
31
+ - **"Raw log exists only in the agent's transcript" — false.** `output.log` is the existing trust architecture; `qa-b/api.md` already calls it "the raw proof a transcribed table cannot replace."
32
+ - **A second wrapper would *break* the Evidence gate.** A suite run through `vp-run` instead of `evidence run` produces no frozen junit → `assertRules` fails → a burned rework cycle.
33
+ - **The PreToolUse hook fires on the pipeline's own gates.** STATIC runs `pre_critic_gate.commands` bare by design ("run each command… do NOT fix anything"); PACKGATE runs `pack_gate.commands` bare. The v1 default regex (`tsc`, `npm test`…) denies commands the config *orders* agents to run — and an unanchored `npx vitest` regex would deny the mandated `evidence run … -- npx vitest` itself.
34
+ - **A gitignored `.valent-pipeline/logs/` store with retention pruning is a *weaker* audit trail** than the tracked evidence dir — and under `git.parallelism` it dies with the worktree.
35
+ - **`vp-run` re-solves solved bugs** — `runEvidence` already carries the Windows tree-kill and argv re-quoting fixes from adversarial reviews #33/#34.
36
+
37
+ ---
38
+
39
+ ## 3. The actual remaining gap
40
+
41
+ With `evidence run` accounted for, what still leaks raw bulk into context today:
42
+
43
+ | Leak | Where | Size (honest) |
44
+ |---|---|---|
45
+ | `evidence run` **echoes the full child output live** by default — `quiet` exists but nothing passes it | QA-B suites (`execute-tests.md:38`), smoke curls (`api.md`), RED/GREEN ATDD gates (`sprint.workflow.js:788`) | 1–4K tokens/passing suite (default reporter, not verbose — v1's 5–30K figure assumed a verbose mode nothing specifies), more on failure; × suites × rejection cycles |
46
+ | **Dev self-runs are bare** — no wrapper, no reporter guidance | `bend/write-tests.md` ("run the full backend test suite"), `handoff.md`, FEND/DATA equivalents | 1–5K tokens/run × attempts |
47
+ | **Docker build / compose logs** during live-server QA | QA-B | 5–50K tokens, the single bulkiest item |
48
+ | Paging fragments after the 30K-char Bash truncation | any of the above on big output | multiplies the above when it bites |
49
+
50
+ Honest aggregate: **~10–30K addressable input tokens per story** against ~200–400K/story total — **roughly 3–8% of story spend**, mostly at sonnet/haiku input prices, and shrinking as Levers 1–3 reduce rejection cycles. This is a "ship it because it's nearly free," not a headline lever.
51
+
52
+ ---
53
+
54
+ ## 4. The plan (three small changes, no new infrastructure)
55
+
56
+ ### Change 1 — Pass `--quiet` on mandated `evidence run` invocations *(S, workflow + step files)*
57
+ - `qa-b/execute-tests.md` and `qa-b/api.md`: add `--quiet` to the documented commands.
58
+ - `atddRunCmd` (`sprint.workflow.js:788`): add `--quiet` — one string change. RED/GREEN consumers are haiku transcriber gates that need the trailer verdict, not the stream.
59
+ - The trailer already carries exit code + test counts + evidence path; failure detail is one `Read` of the frozen junit/`output.log` away. Step files gain one line: *on failure, `Grep` the run's `output.log` / read the junit `<failure>` entries — never re-run loud.*
60
+
61
+ ### Change 2 — Quiet-reporter guidance in dev self-run steps *(S, prompt-only)*
62
+ For `bend`/`fend`/`data` `write-tests` and `handoff` steps (these run bare and legitimately so — they're inner-loop self-checks, not evidence):
63
+ 1. Run quiet first (`--reporter=dot`, `--silent`, `tsc --pretty false`).
64
+ 2. On failure, re-run *only the failing file/test* with full output — never the whole suite verbose.
65
+ 3. For Docker: build/compose with output to a file (`> build.log 2>&1`), then `Grep`/`Read` the tail — kills the bulkiest leak (§3 row 3) with zero machinery.
66
+
67
+ ### Change 3 (optional, only if measurement demands) — `--digest` on the existing trailer *(S–M, ~30 lines)*
68
+ If §6 measurement shows agents paging `output.log` too often after Changes 1–2: extend `formatRunTrailer` to append failing case names (it already parses junit) + a bounded log tail with an explicit `+K more lines` marker. This is v1's entire `vp-run` value proposition, landed as ~30 lines in the wrapper that already exists.
69
+
70
+ **Explicitly cut from v1 (with reasons from the review):**
71
+ - ❌ `vp-run` — duplicate of `evidence run`; would fail the Evidence gate if agents obeyed it.
72
+ - ❌ PreToolUse hook — denies STATIC/PACKGATE/ATDD-mandated commands; regex needs maintenance across 9 task graphs / 9 testing profiles; `upgrade` doesn't manage `.claude/settings.json` (only `init` does), so the install story was also wrong. Compliance for QA already *is* deterministic (the Evidence gate rejects unwrapped suites); dev self-run compliance is measured first (§6), prompt-fixed second.
73
+ - ❌ `logs:` template field — `write-report.md` already requires evidence run IDs for JUDGE; add line-range granularity to that existing rule only if measurement shows JUDGE missing signal.
74
+ - ❌ `.valent-pipeline/logs/` + retention pruning — the tracked evidence dir is the log store; pruning an audit trail the gates cite is self-harm.
75
+
76
+ ---
77
+
78
+ ## 5. The "need verbose" question (unchanged answer, corrected mechanism)
79
+
80
+ `--quiet` bounds *default ingestion*, never *verbose execution* — the full stream always reaches `output.log` (hashed, durable, tracked). An agent that needs everything pages the log with `Read`/`Grep` deliberately, in the amounts it chooses. The unwrapped alternative was never "full verbose in context" anyway: Claude Code truncates at ~30K chars at an arbitrary point. Quiet-wrapped is a strict superset of unwrapped verbose.
81
+
82
+ One real caveat the review surfaced: QA-B smoke tables deliberately exercise **expected-error** paths, so `Error:`-pattern digests can fill with benign matches. That's an argument for Change 3's junit-derived digest (failing *cases*, not grepped *lines*) over v1's regex approach — and for keeping the digest junit-first if Change 3 ever ships.
83
+
84
+ ---
85
+
86
+ ## 6. Measurement (gates the optional work)
87
+
88
+ A/B over 5–10 stories vs. current baseline, via `valent analyze` / `audit` on run journals:
89
+
90
+ | Metric | Keep-criterion |
91
+ |---|---|
92
+ | Tokens/story in **QA-B + RED/GREEN gates + dev self-run turns** | ≥15% reduction *in those roles* (per the review: don't claim pipeline-wide) |
93
+ | Gate quality (CRITIC/JUDGE/Evidence outcomes) | Zero regressions |
94
+ | `output.log` paging frequency after Changes 1–2 | If high → build Change 3; if low → done |
95
+ | Rejection cycles/story | No increase |
96
+
97
+ Everything is one-line revertible (remove `--quiet`, drop prompt blocks).
98
+
99
+ ---
100
+
101
+ ## 7. Appendix — Headroom remnants (unchanged from v1)
102
+
103
+ - **`headroom learn` (failure-mining), standalone:** still piloteable for ~zero cost — offline CLI reading `~/.claude/projects/*.jsonl`; never needed the MCP server. Dry-run only, in Retrospective; stdout → candidate directives → existing gating → curated pitfall/environment layer. Never `--apply` against the live `~/.claude` (it writes into `MEMORY.md`). Evaluate on true-positive rate at the gate.
104
+ - **Proxy-mode revisit trigger:** only if raw-bulk pressure remains material after Changes 1–2 are measured, and only with explicit validation against prompt-cache discipline. Expectation: never fires.
105
+
106
+ ---
107
+
108
+ ## TL;DR
109
+
110
+ v1 diagnosed the right problem (compress-before-context, not after — the Headroom MCP rejection stands) and designed the wrong solution by not reading `evidence.js`: the pipeline already owns a hashed, durable, gate-enforced capture store with an unused `quiet` flag. The corrected plan is **flip `--quiet` on in three places, add quiet-reporter + log-to-file guidance to the bare dev/Docker runs, and optionally teach the existing trailer a junit-derived failure digest later**. ~3–8% of story spend, near-zero build cost, zero new dependencies, evidence trail untouched (it was already better than v1 knew).
111
+
112
+ ---
113
+
114
+ ### References
115
+ - Cold adversarial review (2026-06-11) — verdict "implement modified": cut `vp-run` + hook, rebuild on `evidence run`. Key citations verified: `evidence.js:399/425/449/534/1246`, `qa-b/execute-tests.md:35`, `sprint.workflow.js:788`, `upgrade.js` (no settings.json management).
116
+ - `headroom-evaluation.md` — the rejected compression-dependency alternative (its `learn` analysis stands)
117
+ - `cost-and-resumability-improvements.md` — Levers 1–7 (this is a minor sibling lever; compounds with 1–3)
118
+ - `communication-standard.md` — same principle applied to inter-agent prose
119
+ - Headroom MCP tool schema (verification source) — https://headroom-docs.vercel.app/docs/mcp