@ijfw/memory-server 1.4.4 → 1.5.0

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 (232) hide show
  1. package/fixtures/truncation-corpus/_generate-corpus.js +367 -0
  2. package/fixtures/truncation-corpus/fx-01-clean-exit-01/events.jsonl +2 -0
  3. package/fixtures/truncation-corpus/fx-01-clean-exit-01/intent-journal.jsonl +2 -0
  4. package/fixtures/truncation-corpus/fx-01-clean-exit-01/meta.json +18 -0
  5. package/fixtures/truncation-corpus/fx-01-clean-exit-01/target/.ijfw/state/workflow.json +1 -0
  6. package/fixtures/truncation-corpus/fx-01-clean-exit-02/events.jsonl +2 -0
  7. package/fixtures/truncation-corpus/fx-01-clean-exit-02/intent-journal.jsonl +2 -0
  8. package/fixtures/truncation-corpus/fx-01-clean-exit-02/meta.json +18 -0
  9. package/fixtures/truncation-corpus/fx-01-clean-exit-02/target/.ijfw/state/workflow.json +1 -0
  10. package/fixtures/truncation-corpus/fx-01-clean-exit-03/events.jsonl +2 -0
  11. package/fixtures/truncation-corpus/fx-01-clean-exit-03/intent-journal.jsonl +2 -0
  12. package/fixtures/truncation-corpus/fx-01-clean-exit-03/meta.json +18 -0
  13. package/fixtures/truncation-corpus/fx-01-clean-exit-03/target/.ijfw/state/workflow.json +1 -0
  14. package/fixtures/truncation-corpus/fx-01-clean-exit-04/events.jsonl +2 -0
  15. package/fixtures/truncation-corpus/fx-01-clean-exit-04/intent-journal.jsonl +2 -0
  16. package/fixtures/truncation-corpus/fx-01-clean-exit-04/meta.json +18 -0
  17. package/fixtures/truncation-corpus/fx-01-clean-exit-04/target/.ijfw/state/workflow.json +1 -0
  18. package/fixtures/truncation-corpus/fx-01-clean-exit-05/events.jsonl +2 -0
  19. package/fixtures/truncation-corpus/fx-01-clean-exit-05/intent-journal.jsonl +2 -0
  20. package/fixtures/truncation-corpus/fx-01-clean-exit-05/meta.json +18 -0
  21. package/fixtures/truncation-corpus/fx-01-clean-exit-05/target/.ijfw/state/workflow.json +1 -0
  22. package/fixtures/truncation-corpus/fx-02-mid-overwrite-01/events.jsonl +1 -0
  23. package/fixtures/truncation-corpus/fx-02-mid-overwrite-01/intent-journal.jsonl +3 -0
  24. package/fixtures/truncation-corpus/fx-02-mid-overwrite-01/meta.json +18 -0
  25. package/fixtures/truncation-corpus/fx-02-mid-overwrite-01/snapshots/v-midO-1-advance.json +11 -0
  26. package/fixtures/truncation-corpus/fx-02-mid-overwrite-01/target/.ijfw/state/workflow.json +1 -0
  27. package/fixtures/truncation-corpus/fx-02-mid-overwrite-02/events.jsonl +1 -0
  28. package/fixtures/truncation-corpus/fx-02-mid-overwrite-02/intent-journal.jsonl +3 -0
  29. package/fixtures/truncation-corpus/fx-02-mid-overwrite-02/meta.json +18 -0
  30. package/fixtures/truncation-corpus/fx-02-mid-overwrite-02/snapshots/v-midO-2-advance.json +11 -0
  31. package/fixtures/truncation-corpus/fx-02-mid-overwrite-02/target/.ijfw/state/workflow.json +1 -0
  32. package/fixtures/truncation-corpus/fx-02-mid-overwrite-03/events.jsonl +1 -0
  33. package/fixtures/truncation-corpus/fx-02-mid-overwrite-03/intent-journal.jsonl +3 -0
  34. package/fixtures/truncation-corpus/fx-02-mid-overwrite-03/meta.json +18 -0
  35. package/fixtures/truncation-corpus/fx-02-mid-overwrite-03/snapshots/v-midO-3-advance.json +11 -0
  36. package/fixtures/truncation-corpus/fx-02-mid-overwrite-03/target/.ijfw/state/workflow.json +1 -0
  37. package/fixtures/truncation-corpus/fx-02-mid-overwrite-04/events.jsonl +1 -0
  38. package/fixtures/truncation-corpus/fx-02-mid-overwrite-04/intent-journal.jsonl +3 -0
  39. package/fixtures/truncation-corpus/fx-02-mid-overwrite-04/meta.json +18 -0
  40. package/fixtures/truncation-corpus/fx-02-mid-overwrite-04/snapshots/v-midO-4-advance.json +11 -0
  41. package/fixtures/truncation-corpus/fx-02-mid-overwrite-04/target/.ijfw/state/workflow.json +1 -0
  42. package/fixtures/truncation-corpus/fx-02-mid-overwrite-05/events.jsonl +1 -0
  43. package/fixtures/truncation-corpus/fx-02-mid-overwrite-05/intent-journal.jsonl +3 -0
  44. package/fixtures/truncation-corpus/fx-02-mid-overwrite-05/meta.json +18 -0
  45. package/fixtures/truncation-corpus/fx-02-mid-overwrite-05/snapshots/v-midO-5-advance.json +11 -0
  46. package/fixtures/truncation-corpus/fx-02-mid-overwrite-05/target/.ijfw/state/workflow.json +1 -0
  47. package/fixtures/truncation-corpus/fx-03-mid-append-01/events.jsonl +1 -0
  48. package/fixtures/truncation-corpus/fx-03-mid-append-01/intent-journal.jsonl +3 -0
  49. package/fixtures/truncation-corpus/fx-03-mid-append-01/meta.json +18 -0
  50. package/fixtures/truncation-corpus/fx-03-mid-append-01/target/.ijfw/blackboard/decisions.jsonl +1 -0
  51. package/fixtures/truncation-corpus/fx-03-mid-append-02/events.jsonl +1 -0
  52. package/fixtures/truncation-corpus/fx-03-mid-append-02/intent-journal.jsonl +3 -0
  53. package/fixtures/truncation-corpus/fx-03-mid-append-02/meta.json +18 -0
  54. package/fixtures/truncation-corpus/fx-03-mid-append-02/target/.ijfw/blackboard/decisions.jsonl +1 -0
  55. package/fixtures/truncation-corpus/fx-03-mid-append-03/events.jsonl +1 -0
  56. package/fixtures/truncation-corpus/fx-03-mid-append-03/intent-journal.jsonl +3 -0
  57. package/fixtures/truncation-corpus/fx-03-mid-append-03/meta.json +18 -0
  58. package/fixtures/truncation-corpus/fx-03-mid-append-03/target/.ijfw/blackboard/decisions.jsonl +1 -0
  59. package/fixtures/truncation-corpus/fx-03-mid-append-04/events.jsonl +1 -0
  60. package/fixtures/truncation-corpus/fx-03-mid-append-04/intent-journal.jsonl +3 -0
  61. package/fixtures/truncation-corpus/fx-03-mid-append-04/meta.json +18 -0
  62. package/fixtures/truncation-corpus/fx-03-mid-append-04/target/.ijfw/blackboard/decisions.jsonl +1 -0
  63. package/fixtures/truncation-corpus/fx-03-mid-append-05/events.jsonl +1 -0
  64. package/fixtures/truncation-corpus/fx-03-mid-append-05/intent-journal.jsonl +3 -0
  65. package/fixtures/truncation-corpus/fx-03-mid-append-05/meta.json +18 -0
  66. package/fixtures/truncation-corpus/fx-03-mid-append-05/target/.ijfw/blackboard/decisions.jsonl +1 -0
  67. package/fixtures/truncation-corpus/fx-04-no-events-01/events.jsonl +0 -0
  68. package/fixtures/truncation-corpus/fx-04-no-events-01/intent-journal.jsonl +1 -0
  69. package/fixtures/truncation-corpus/fx-04-no-events-01/meta.json +18 -0
  70. package/fixtures/truncation-corpus/fx-04-no-events-01/snapshots/v-noEv-1-set-phase.json +11 -0
  71. package/fixtures/truncation-corpus/fx-04-no-events-01/target/.ijfw/state/workflow.json +1 -0
  72. package/fixtures/truncation-corpus/fx-04-no-events-02/events.jsonl +0 -0
  73. package/fixtures/truncation-corpus/fx-04-no-events-02/intent-journal.jsonl +1 -0
  74. package/fixtures/truncation-corpus/fx-04-no-events-02/meta.json +18 -0
  75. package/fixtures/truncation-corpus/fx-04-no-events-02/snapshots/v-noEv-2-set-phase.json +11 -0
  76. package/fixtures/truncation-corpus/fx-04-no-events-02/target/.ijfw/state/workflow.json +1 -0
  77. package/fixtures/truncation-corpus/fx-04-no-events-03/events.jsonl +0 -0
  78. package/fixtures/truncation-corpus/fx-04-no-events-03/intent-journal.jsonl +1 -0
  79. package/fixtures/truncation-corpus/fx-04-no-events-03/meta.json +18 -0
  80. package/fixtures/truncation-corpus/fx-04-no-events-03/snapshots/v-noEv-3-set-phase.json +11 -0
  81. package/fixtures/truncation-corpus/fx-04-no-events-03/target/.ijfw/state/workflow.json +1 -0
  82. package/fixtures/truncation-corpus/fx-04-no-events-04/events.jsonl +0 -0
  83. package/fixtures/truncation-corpus/fx-04-no-events-04/intent-journal.jsonl +1 -0
  84. package/fixtures/truncation-corpus/fx-04-no-events-04/meta.json +18 -0
  85. package/fixtures/truncation-corpus/fx-04-no-events-04/snapshots/v-noEv-4-set-phase.json +11 -0
  86. package/fixtures/truncation-corpus/fx-04-no-events-04/target/.ijfw/state/workflow.json +1 -0
  87. package/fixtures/truncation-corpus/fx-04-no-events-05/events.jsonl +0 -0
  88. package/fixtures/truncation-corpus/fx-04-no-events-05/intent-journal.jsonl +1 -0
  89. package/fixtures/truncation-corpus/fx-04-no-events-05/meta.json +18 -0
  90. package/fixtures/truncation-corpus/fx-04-no-events-05/snapshots/v-noEv-5-set-phase.json +11 -0
  91. package/fixtures/truncation-corpus/fx-04-no-events-05/target/.ijfw/state/workflow.json +1 -0
  92. package/fixtures/truncation-corpus/fx-05-error-terminated-01/events.jsonl +2 -0
  93. package/fixtures/truncation-corpus/fx-05-error-terminated-01/intent-journal.jsonl +3 -0
  94. package/fixtures/truncation-corpus/fx-05-error-terminated-01/meta.json +18 -0
  95. package/fixtures/truncation-corpus/fx-05-error-terminated-01/snapshots/v-errT-1-partial.json +11 -0
  96. package/fixtures/truncation-corpus/fx-05-error-terminated-01/target/.ijfw/state/workflow.json +1 -0
  97. package/fixtures/truncation-corpus/fx-05-error-terminated-02/events.jsonl +2 -0
  98. package/fixtures/truncation-corpus/fx-05-error-terminated-02/intent-journal.jsonl +3 -0
  99. package/fixtures/truncation-corpus/fx-05-error-terminated-02/meta.json +18 -0
  100. package/fixtures/truncation-corpus/fx-05-error-terminated-02/target/.ijfw/blackboard/decisions.jsonl +1 -0
  101. package/fixtures/truncation-corpus/fx-05-error-terminated-03/events.jsonl +2 -0
  102. package/fixtures/truncation-corpus/fx-05-error-terminated-03/intent-journal.jsonl +3 -0
  103. package/fixtures/truncation-corpus/fx-05-error-terminated-03/meta.json +18 -0
  104. package/fixtures/truncation-corpus/fx-05-error-terminated-03/snapshots/v-errT-3-partial.json +11 -0
  105. package/fixtures/truncation-corpus/fx-05-error-terminated-03/target/.ijfw/state/workflow.json +1 -0
  106. package/fixtures/truncation-corpus/fx-05-error-terminated-04/events.jsonl +2 -0
  107. package/fixtures/truncation-corpus/fx-05-error-terminated-04/intent-journal.jsonl +3 -0
  108. package/fixtures/truncation-corpus/fx-05-error-terminated-04/meta.json +18 -0
  109. package/fixtures/truncation-corpus/fx-05-error-terminated-04/target/.ijfw/blackboard/decisions.jsonl +1 -0
  110. package/fixtures/truncation-corpus/fx-05-error-terminated-05/events.jsonl +2 -0
  111. package/fixtures/truncation-corpus/fx-05-error-terminated-05/intent-journal.jsonl +3 -0
  112. package/fixtures/truncation-corpus/fx-05-error-terminated-05/meta.json +18 -0
  113. package/fixtures/truncation-corpus/fx-05-error-terminated-05/snapshots/v-errT-5-partial.json +11 -0
  114. package/fixtures/truncation-corpus/fx-05-error-terminated-05/target/.ijfw/state/workflow.json +1 -0
  115. package/package.json +1 -1
  116. package/src/active-extension-writer.js +144 -64
  117. package/src/api-client.js +43 -5
  118. package/src/audit-roster.js +80 -5
  119. package/src/blackboard.js +298 -6
  120. package/src/cli-run.js +33 -5
  121. package/src/codex-agents.js +96 -5
  122. package/src/cost/aggregator.js +39 -9
  123. package/src/cost/pricing.js +57 -0
  124. package/src/cost/readers/gemini.js +1 -1
  125. package/src/cross-audit-chunker.js +189 -0
  126. package/src/cross-dispatcher.js +124 -21
  127. package/src/cross-orchestrator-cli.js +550 -14
  128. package/src/cross-orchestrator.js +1016 -17
  129. package/src/cross-project-search.js +195 -9
  130. package/src/dashboard-client-waves.html +304 -0
  131. package/src/dashboard-client.html +5 -1
  132. package/src/dashboard-server.js +73 -0
  133. package/src/deploy-alerts.js +150 -0
  134. package/src/design/iframe-bridge.js +242 -0
  135. package/src/design-companion.js +144 -0
  136. package/src/dispatch/checkpoint-cli.js +97 -0
  137. package/src/dispatch/colon-syntax.js +81 -1
  138. package/src/dispatch/extension.js +26 -2
  139. package/src/dispatch/registry-cli.js +4 -1
  140. package/src/dispatch/wave-cli.js +201 -6
  141. package/src/dispatch/worktree-cli.js +40 -0
  142. package/src/dispatch-planner.js +97 -2
  143. package/src/dream/runner.mjs +47 -11
  144. package/src/dream/stage-runner.js +40 -0
  145. package/src/dream/state-file.js +102 -0
  146. package/src/extension-installer.js +70 -24
  147. package/src/extension-quota-tracker.js +4 -2
  148. package/src/extension-registry.js +289 -35
  149. package/src/feedback-detector.js +26 -0
  150. package/src/fs-lock.js +259 -7
  151. package/src/gate-result.js +95 -1
  152. package/src/hero-line.js +86 -5
  153. package/src/intent-router.js +35 -0
  154. package/src/lib/a11y-contract.js +117 -0
  155. package/src/lib/atomic-io.js +29 -8
  156. package/src/lib/cache-keepalive.js +150 -0
  157. package/src/lib/jsonl-rotation.js +104 -0
  158. package/src/lib/lighthouse-pillar.js +121 -0
  159. package/src/lib/llm-call.js +121 -0
  160. package/src/lib/playwright-baseline.js +205 -0
  161. package/src/lib/rekor-bridge.js +221 -0
  162. package/src/lib/repo-map.js +392 -0
  163. package/src/lib/shasum-verify.js +164 -0
  164. package/src/lib/sketches-gc.js +132 -0
  165. package/src/lib/tmp-suffix.js +62 -0
  166. package/src/lib/ui-review-runner.js +554 -0
  167. package/src/lib/uispec-drift.js +301 -0
  168. package/src/lib/uispec-intake.js +381 -0
  169. package/src/lib/worktree-guards.js +118 -0
  170. package/src/lib/worktree-recovery.js +100 -0
  171. package/src/memory/auto-linker.js +152 -0
  172. package/src/memory/benchmark.js +498 -0
  173. package/src/memory/dedup.js +126 -0
  174. package/src/memory/embedding-cache.js +136 -0
  175. package/src/memory/fact-extractor.js +168 -0
  176. package/src/memory/fts5.js +65 -1
  177. package/src/memory/migrations/004-bitemporal.js +91 -0
  178. package/src/memory/migrations/005-vector-cache.js +61 -0
  179. package/src/memory/migrations/006-obsidian-graph.js +46 -0
  180. package/src/memory/migrations/007-skill-telemetry.js +24 -0
  181. package/src/memory/migrations/008-write-provenance.js +41 -0
  182. package/src/memory/obsidian-parser.js +91 -0
  183. package/src/memory/query-dataview.js +86 -0
  184. package/src/memory/search.js +10 -0
  185. package/src/memory/temporal.js +529 -0
  186. package/src/memory/tokenize.js +10 -0
  187. package/src/memory-facts-handler.js +37 -0
  188. package/src/memory-feedback.js +260 -2
  189. package/src/model-refresh.js +292 -0
  190. package/src/observability/cost-anomaly.js +166 -0
  191. package/src/observability/evaluator-checkpoint-contract.js +117 -0
  192. package/src/observability/trace-id.js +163 -0
  193. package/src/orchestrator/agents-md-blackboard.js +152 -0
  194. package/src/orchestrator/checkpoint-contract.md +140 -0
  195. package/src/orchestrator/debug-trident.js +570 -0
  196. package/src/orchestrator/merge-block-aware.js +350 -0
  197. package/src/orchestrator/plan-checker.js +475 -0
  198. package/src/orchestrator/post-done-runner.js +249 -0
  199. package/src/orchestrator/review.js +38 -3
  200. package/src/orchestrator/runtime-loop.js +430 -0
  201. package/src/orchestrator/skill-telemetry-sink.js +29 -0
  202. package/src/orchestrator/skill-telemetry.js +37 -0
  203. package/src/orchestrator/state-events.js +459 -0
  204. package/src/orchestrator/state-sdk.js +1764 -0
  205. package/src/orchestrator/status-protocol.js +84 -17
  206. package/src/orchestrator/subagent-telemetry.js +452 -0
  207. package/src/orchestrator/termination.js +160 -0
  208. package/src/orchestrator/verification-gate.js +200 -16
  209. package/src/orchestrator/wave-state.js +332 -23
  210. package/src/orchestrator/worktree-provision.js +77 -0
  211. package/src/override-use-registry.js +111 -5
  212. package/src/receipts.js +36 -4
  213. package/src/recovery/checkpoint.js +56 -3
  214. package/src/recovery/code-fixer.js +656 -0
  215. package/src/recovery/truncation.js +317 -0
  216. package/src/redactor.js +75 -6
  217. package/src/runtime-mediator.js +15 -0
  218. package/src/sanitizer.js +10 -0
  219. package/src/search-hybrid.js +139 -0
  220. package/src/server.js +603 -59
  221. package/src/swarm/worktree.js +27 -4
  222. package/src/swarm-config.js +94 -17
  223. package/src/team/domain-templates/book.json +51 -0
  224. package/src/team/domain-templates/business.json +41 -0
  225. package/src/team/domain-templates/content.json +50 -0
  226. package/src/team/domain-templates/design.json +44 -0
  227. package/src/team/domain-templates/research.json +41 -0
  228. package/src/team/domain-templates/software.json +40 -0
  229. package/src/team/generator.js +278 -3
  230. package/src/team/modify.js +203 -0
  231. package/src/team/schemas.js +48 -0
  232. package/src/update-apply.js +19 -3
@@ -0,0 +1,249 @@
1
+ /**
2
+ * post-done-runner.js — v1.5.0-major S02: enforced post-DONE pipeline.
3
+ *
4
+ * Runs after a subagent's DONE has been verified by runtime-loop.js. Wraps
5
+ * reviewTask (v1.4.4 N3 two-stage review) and checkVerificationGate
6
+ * (v1.4.4 N5) into a single callable the orchestrator-LLM invokes via MCP,
7
+ * so the post-DONE contract isn't satisfied by markdown prose.
8
+ *
9
+ * v1.5.0 T13: the standalone `ijfw_subagent_post_done` MCP tool was retired and
10
+ * absorbed into the single `ijfw_state` MCP tool as the `subagent.post-done`
11
+ * verb (see STATE-SDK-CONTRACT §7). `runSelfCheck` is re-exported through
12
+ * `state-sdk.js` for that verb; `runPostDone` is still exported here for the
13
+ * direct-import test path (`test-orchestrator-post-done-runner.js`).
14
+ *
15
+ * Outcome shape (uniform regardless of branch taken):
16
+ * {
17
+ * verdict: 'approved' | 'spec_failed' | 'quality_failed',
18
+ * reviewStage: 'spec' | 'quality',
19
+ * reviewOk: boolean,
20
+ * reviewFindings: string[],
21
+ * gatePassed: boolean,
22
+ * gateAction: 'block' | 'advise' | 'pass',
23
+ * gateViolation: object | null,
24
+ * }
25
+ *
26
+ * `gateAction` (W12-F/F4 — RT2-H1) tells the MCP tool handler what to do
27
+ * with a gate failure:
28
+ * - 'pass' → gate passed; advance normally.
29
+ * - 'block' → strict mode (default) AND gate failed. Caller MUST refuse
30
+ * to claim success. The MCP handler should surface a structured
31
+ * `block: true` so the orchestrator-LLM treats it as a hard stop.
32
+ * - 'advise' → caller opted out of strict via `strictGate: false` AND gate
33
+ * failed. Caller may proceed but should still surface the
34
+ * violation so it gets routed into memory-feedback.
35
+ *
36
+ * The `dispatch` parameter is the reviewTask injected dispatcher:
37
+ * (kind: 'spec-compliance'|'code-quality', ctx: object)
38
+ * => Promise<{ verdict: 'PASS'|'FAIL', findings: string[] }>
39
+ *
40
+ * If `dispatch` is null/undefined we still run the gate check (the orchestrator
41
+ * may invoke the reviewers itself via the Agent tool); verdict becomes
42
+ * 'no_review' to signal that.
43
+ */
44
+
45
+ import { existsSync } from 'node:fs';
46
+ import { execFileSync } from 'node:child_process';
47
+ import { reviewTask } from './review.js';
48
+ import { checkVerificationGate, recordViolation } from './verification-gate.js';
49
+
50
+ /**
51
+ * Extract paths claimed in the report. Naive but effective: looks for
52
+ * "created/modified/file: <path>" plus bullet-list `- path/...` patterns.
53
+ * Skip lines under "Self-Check" section (don't recurse into reported self-checks).
54
+ */
55
+ function extractClaimedPaths(reportText) {
56
+ const lines = String(reportText || '').split('\n');
57
+ const paths = new Set();
58
+ let inSelfCheck = false;
59
+ for (const line of lines) {
60
+ if (/^##\s*Self-Check/i.test(line)) { inSelfCheck = true; continue; }
61
+ if (inSelfCheck) continue;
62
+ const m = line.match(/(?:created|modified|file):\s*[`"]?([^\s`"]+)[`"]?/i);
63
+ if (m && m[1].includes('.')) paths.add(m[1]);
64
+ const m2 = line.match(/^\s*-\s+[`"]?([^\s`"]+\.\w+)[`"]?/);
65
+ if (m2) paths.add(m2[1]);
66
+ }
67
+ return [...paths];
68
+ }
69
+
70
+ /**
71
+ * Extract plausible commit SHAs (hex, 7-40 chars) from the report.
72
+ */
73
+ function extractClaimedCommits(reportText) {
74
+ const matches = String(reportText || '').match(/\b[0-9a-f]{7,40}\b/g) || [];
75
+ return [...new Set(matches.filter((s) => /^[0-9a-f]+$/.test(s) && s.length >= 7))];
76
+ }
77
+
78
+ /**
79
+ * runSelfCheck — verify claimed files + commits actually exist before review.
80
+ * @param {string} reportText
81
+ * @param {string} projectRoot
82
+ * @returns {{
83
+ * verdict: 'PASSED'|'FAILED',
84
+ * files_claimed: number,
85
+ * files_present: number,
86
+ * files_missing: string[],
87
+ * commits_claimed: number,
88
+ * commits_present: number,
89
+ * commits_missing: string[],
90
+ * }}
91
+ */
92
+ export function runSelfCheck(reportText, projectRoot) {
93
+ const claimedPaths = extractClaimedPaths(reportText);
94
+ const claimedCommits = extractClaimedCommits(reportText);
95
+ const filesPresent = claimedPaths.filter((p) =>
96
+ existsSync(p.startsWith('/') ? p : `${projectRoot}/${p}`),
97
+ );
98
+ let commitsPresent = [];
99
+ try {
100
+ const allShas = execFileSync('git', ['log', '--all', '--format=%H'], {
101
+ cwd: projectRoot,
102
+ encoding: 'utf8',
103
+ stdio: ['ignore', 'pipe', 'ignore'],
104
+ }).split('\n');
105
+ commitsPresent = claimedCommits.filter((c) => allShas.some((sha) => sha.startsWith(c)));
106
+ } catch {
107
+ /* not a git repo — skip commit check */
108
+ }
109
+ const verdict =
110
+ filesPresent.length === claimedPaths.length &&
111
+ commitsPresent.length === claimedCommits.length
112
+ ? 'PASSED'
113
+ : 'FAILED';
114
+ return {
115
+ verdict,
116
+ files_claimed: claimedPaths.length,
117
+ files_present: filesPresent.length,
118
+ files_missing: claimedPaths.filter((p) => !filesPresent.includes(p)),
119
+ commits_claimed: claimedCommits.length,
120
+ commits_present: commitsPresent.length,
121
+ commits_missing: claimedCommits.filter((c) => !commitsPresent.includes(c)),
122
+ };
123
+ }
124
+
125
+ /**
126
+ /**
127
+ * @param {object} params
128
+ * @param {string} params.taskId
129
+ * @param {string} [params.taskSpec]
130
+ * @param {string} params.commitSha
131
+ * @param {string} [params.branch]
132
+ * @param {string} params.reportText
133
+ * @param {Array<{tool: string, input?: {command?: string}}>} [params.toolCallsInMessage]
134
+ * @param {Function|null} [params.dispatch] Reviewer dispatcher; null = skip review
135
+ * @param {string} params.projectRoot
136
+ * @param {string} [params.projectConventions]
137
+ * @param {boolean} [params.strictGate=true]
138
+ * W12-F/F4 — RT2-H1. When true (default) and the verification gate fails,
139
+ * the result includes `gateAction: 'block'` and the MCP handler MUST refuse
140
+ * to claim success. Pass `false` for legacy advisory-only behavior.
141
+ * @returns {Promise<{
142
+ * verdict: 'approved'|'spec_failed'|'quality_failed'|'no_review',
143
+ * reviewStage: 'spec'|'quality'|null,
144
+ * reviewOk: boolean,
145
+ * reviewFindings: string[],
146
+ * gatePassed: boolean,
147
+ * gateAction: 'block'|'advise'|'pass',
148
+ * gateViolation: object|null,
149
+ * selfCheck: {
150
+ * verdict: 'PASSED'|'FAILED',
151
+ * files_claimed: number,
152
+ * files_present: number,
153
+ * files_missing: string[],
154
+ * commits_claimed: number,
155
+ * commits_present: number,
156
+ * commits_missing: string[],
157
+ * },
158
+ * }>}
159
+ */
160
+ export async function runPostDone({
161
+ taskId,
162
+ taskSpec = '',
163
+ commitSha,
164
+ branch = '',
165
+ reportText,
166
+ toolCallsInMessage,
167
+ dispatch,
168
+ projectRoot,
169
+ projectConventions = '',
170
+ strictGate = true,
171
+ }) {
172
+ if (typeof projectRoot !== 'string' || projectRoot.length === 0) {
173
+ throw new TypeError('runPostDone: projectRoot is required');
174
+ }
175
+ if (typeof reportText !== 'string') {
176
+ throw new TypeError('runPostDone: reportText must be a string');
177
+ }
178
+
179
+ // ---- Self-Check (S09) ------------------------------------------------
180
+ // Verify claimed files + commits exist before spending review tokens.
181
+ // Additive: doesn't gate downstream — surfaces the divergence in result.
182
+ const selfCheck = runSelfCheck(reportText, projectRoot);
183
+
184
+ // ---- Two-stage review (N3) -------------------------------------------
185
+ let reviewOk = false;
186
+ let reviewStage = null;
187
+ let reviewFindings = [];
188
+ let verdict = 'no_review';
189
+
190
+ if (typeof dispatch === 'function') {
191
+ const r = await reviewTask({
192
+ taskId,
193
+ taskSpec,
194
+ commitSha,
195
+ branch,
196
+ projectConventions,
197
+ dispatch,
198
+ });
199
+ reviewOk = !!r.ok;
200
+ reviewStage = r.stage ?? null;
201
+ reviewFindings = Array.isArray(r.findings) ? r.findings : [];
202
+ if (reviewOk) {
203
+ verdict = 'approved';
204
+ } else {
205
+ verdict = r.stage === 'spec' ? 'spec_failed' : 'quality_failed';
206
+ }
207
+ }
208
+
209
+ // ---- Verification gate (N5) ------------------------------------------
210
+ const gateOutcome = checkVerificationGate(
211
+ reportText,
212
+ Array.isArray(toolCallsInMessage) ? toolCallsInMessage : [],
213
+ );
214
+ if (!gateOutcome.ok) {
215
+ try {
216
+ // recordViolation signature is (violation, projectRoot) -- see verification-gate.js
217
+ await recordViolation(
218
+ { taskId, ...gateOutcome },
219
+ projectRoot,
220
+ );
221
+ } catch {
222
+ // Advisory -- never block on violation log failure (matches v1.4.4 N5 contract)
223
+ }
224
+ }
225
+
226
+ // W12-F/F4 — RT2-H1: classify the gate outcome so the caller knows whether
227
+ // to BLOCK (strict default + failure), ADVISE (caller opted out + failure),
228
+ // or PASS (gate succeeded). The MCP tool handler reads `gateAction` and
229
+ // surfaces a structured `block: true` to the orchestrator-LLM when 'block'.
230
+ let gateAction;
231
+ if (gateOutcome.ok) {
232
+ gateAction = 'pass';
233
+ } else if (strictGate === false) {
234
+ gateAction = 'advise';
235
+ } else {
236
+ gateAction = 'block';
237
+ }
238
+
239
+ return {
240
+ verdict,
241
+ reviewStage,
242
+ reviewOk,
243
+ reviewFindings,
244
+ gatePassed: gateOutcome.ok === true,
245
+ gateAction,
246
+ gateViolation: gateOutcome.ok ? null : { violation: gateOutcome.violation, claim: gateOutcome.claim },
247
+ selfCheck,
248
+ };
249
+ }
@@ -43,18 +43,26 @@ export function shouldReReview(prevVerdict, iteration) {
43
43
  /**
44
44
  * Run two-stage review for a completed task.
45
45
  *
46
+ * v1.5.0 audit-MED-work-M7: optional `bothStages: true` lets callers surface
47
+ * code-quality findings even when the spec stage FAILs. Quality findings are
48
+ * downgraded to INFO severity (prefixed `[INFO]`) so the orchestrator's
49
+ * fail-on-block path still keys off the spec failure — but the operator
50
+ * gets a fuller picture of what's wrong instead of only seeing spec gaps.
51
+ *
46
52
  * @param {object} params
47
53
  * @param {string} params.taskId Blackboard task ID
48
54
  * @param {string} params.taskSpec Full task specification text
49
55
  * @param {string} params.commitSha SHA of the implementer's commit
50
56
  * @param {string} params.branch Branch name
51
57
  * @param {string} [params.projectConventions] CLAUDE.md / AGENTS.md excerpt
58
+ * @param {boolean} [params.bothStages=false] Run quality stage even on spec FAIL (M7).
52
59
  * @param {Function} params.dispatch Injected reviewer dispatcher
53
60
  *
54
61
  * @returns {Promise<{
55
62
  * ok: boolean,
56
63
  * stage: 'spec' | 'quality',
57
- * findings: string[]
64
+ * findings: string[],
65
+ * qualityFindings?: string[]
58
66
  * }>}
59
67
  */
60
68
  export async function reviewTask({
@@ -63,6 +71,7 @@ export async function reviewTask({
63
71
  commitSha,
64
72
  branch,
65
73
  projectConventions = '',
74
+ bothStages = false,
66
75
  dispatch,
67
76
  }) {
68
77
  // ------------------------------------------------------------------
@@ -76,15 +85,41 @@ export async function reviewTask({
76
85
  });
77
86
 
78
87
  if (spec.verdict !== 'PASS') {
79
- return {
88
+ const base = {
80
89
  ok: false,
81
90
  stage: 'spec',
82
91
  findings: spec.findings ?? [],
83
92
  };
93
+
94
+ // M7: when bothStages is true, also fire the quality reviewer and
95
+ // surface its findings with an [INFO] downgrade prefix. The outer ok
96
+ // / stage remains 'spec' fail — quality verdict here is advisory only.
97
+ if (bothStages) {
98
+ try {
99
+ const quality = await dispatch('code-quality', {
100
+ taskId,
101
+ commitSha,
102
+ branch,
103
+ projectConventions,
104
+ });
105
+ const downgraded = (quality.findings ?? []).map(
106
+ (f) => (typeof f === 'string' && f.startsWith('[INFO] ') ? f : `[INFO] ${f}`),
107
+ );
108
+ return {
109
+ ...base,
110
+ qualityFindings: downgraded,
111
+ };
112
+ } catch {
113
+ // Quality dispatch failure is non-fatal when we're only running it
114
+ // for advisory surfacing — fall through to the spec-only result.
115
+ }
116
+ }
117
+
118
+ return base;
84
119
  }
85
120
 
86
121
  // ------------------------------------------------------------------
87
- // Stage 2: code-quality reviewer (only runs after spec PASS)
122
+ // Stage 2: code-quality reviewer (always after spec PASS)
88
123
  // ------------------------------------------------------------------
89
124
  const quality = await dispatch('code-quality', {
90
125
  taskId,