@ijfw/memory-server 1.4.3 → 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 (233) 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 +1171 -10
  129. package/src/cross-project-search.js +195 -9
  130. package/src/dashboard-client-planning.html +273 -0
  131. package/src/dashboard-client-waves.html +304 -0
  132. package/src/dashboard-client.html +17 -2
  133. package/src/dashboard-server.js +152 -0
  134. package/src/deploy-alerts.js +150 -0
  135. package/src/design/iframe-bridge.js +242 -0
  136. package/src/design-companion.js +144 -0
  137. package/src/dispatch/checkpoint-cli.js +97 -0
  138. package/src/dispatch/colon-syntax.js +81 -1
  139. package/src/dispatch/extension.js +27 -1
  140. package/src/dispatch/registry-cli.js +4 -1
  141. package/src/dispatch/wave-cli.js +323 -0
  142. package/src/dispatch/worktree-cli.js +40 -0
  143. package/src/dispatch-planner.js +97 -2
  144. package/src/dream/runner.mjs +47 -11
  145. package/src/dream/stage-runner.js +40 -0
  146. package/src/dream/state-file.js +102 -0
  147. package/src/extension-installer.js +70 -24
  148. package/src/extension-quota-tracker.js +4 -2
  149. package/src/extension-registry.js +289 -35
  150. package/src/feedback-detector.js +26 -0
  151. package/src/fs-lock.js +259 -7
  152. package/src/gate-result.js +95 -1
  153. package/src/hero-line.js +86 -5
  154. package/src/intent-router.js +35 -0
  155. package/src/lib/a11y-contract.js +117 -0
  156. package/src/lib/atomic-io.js +29 -8
  157. package/src/lib/cache-keepalive.js +150 -0
  158. package/src/lib/jsonl-rotation.js +104 -0
  159. package/src/lib/lighthouse-pillar.js +121 -0
  160. package/src/lib/llm-call.js +121 -0
  161. package/src/lib/playwright-baseline.js +205 -0
  162. package/src/lib/rekor-bridge.js +221 -0
  163. package/src/lib/repo-map.js +392 -0
  164. package/src/lib/shasum-verify.js +164 -0
  165. package/src/lib/sketches-gc.js +132 -0
  166. package/src/lib/tmp-suffix.js +62 -0
  167. package/src/lib/ui-review-runner.js +554 -0
  168. package/src/lib/uispec-drift.js +301 -0
  169. package/src/lib/uispec-intake.js +381 -0
  170. package/src/lib/worktree-guards.js +118 -0
  171. package/src/lib/worktree-recovery.js +100 -0
  172. package/src/memory/auto-linker.js +152 -0
  173. package/src/memory/benchmark.js +498 -0
  174. package/src/memory/dedup.js +126 -0
  175. package/src/memory/embedding-cache.js +136 -0
  176. package/src/memory/fact-extractor.js +168 -0
  177. package/src/memory/fts5.js +65 -1
  178. package/src/memory/migrations/004-bitemporal.js +91 -0
  179. package/src/memory/migrations/005-vector-cache.js +61 -0
  180. package/src/memory/migrations/006-obsidian-graph.js +46 -0
  181. package/src/memory/migrations/007-skill-telemetry.js +24 -0
  182. package/src/memory/migrations/008-write-provenance.js +41 -0
  183. package/src/memory/obsidian-parser.js +91 -0
  184. package/src/memory/query-dataview.js +86 -0
  185. package/src/memory/search.js +10 -0
  186. package/src/memory/temporal.js +529 -0
  187. package/src/memory/tokenize.js +10 -0
  188. package/src/memory-facts-handler.js +37 -0
  189. package/src/memory-feedback.js +260 -2
  190. package/src/model-refresh.js +292 -0
  191. package/src/observability/cost-anomaly.js +166 -0
  192. package/src/observability/evaluator-checkpoint-contract.js +117 -0
  193. package/src/observability/trace-id.js +163 -0
  194. package/src/orchestrator/agents-md-blackboard.js +152 -0
  195. package/src/orchestrator/checkpoint-contract.md +140 -0
  196. package/src/orchestrator/debug-trident.js +570 -0
  197. package/src/orchestrator/merge-block-aware.js +350 -0
  198. package/src/orchestrator/plan-checker.js +475 -0
  199. package/src/orchestrator/post-done-runner.js +249 -0
  200. package/src/orchestrator/review.js +136 -0
  201. package/src/orchestrator/runtime-loop.js +430 -0
  202. package/src/orchestrator/skill-telemetry-sink.js +29 -0
  203. package/src/orchestrator/skill-telemetry.js +37 -0
  204. package/src/orchestrator/state-events.js +459 -0
  205. package/src/orchestrator/state-sdk.js +1764 -0
  206. package/src/orchestrator/status-protocol.js +235 -0
  207. package/src/orchestrator/subagent-telemetry.js +452 -0
  208. package/src/orchestrator/termination.js +160 -0
  209. package/src/orchestrator/verification-gate.js +281 -0
  210. package/src/orchestrator/wave-state.js +564 -0
  211. package/src/orchestrator/worktree-provision.js +77 -0
  212. package/src/override-use-registry.js +111 -5
  213. package/src/receipts.js +36 -4
  214. package/src/recovery/checkpoint.js +56 -3
  215. package/src/recovery/code-fixer.js +656 -0
  216. package/src/recovery/truncation.js +317 -0
  217. package/src/redactor.js +75 -6
  218. package/src/runtime-mediator.js +15 -0
  219. package/src/sanitizer.js +10 -0
  220. package/src/search-hybrid.js +139 -0
  221. package/src/server.js +603 -59
  222. package/src/swarm/worktree.js +27 -4
  223. package/src/swarm-config.js +113 -12
  224. package/src/team/domain-templates/book.json +51 -0
  225. package/src/team/domain-templates/business.json +41 -0
  226. package/src/team/domain-templates/content.json +50 -0
  227. package/src/team/domain-templates/design.json +44 -0
  228. package/src/team/domain-templates/research.json +41 -0
  229. package/src/team/domain-templates/software.json +40 -0
  230. package/src/team/generator.js +278 -3
  231. package/src/team/modify.js +203 -0
  232. package/src/team/schemas.js +48 -0
  233. 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
+ }
@@ -0,0 +1,136 @@
1
+ /**
2
+ * review.js — Two-stage per-task review for the IJFW orchestrator.
3
+ *
4
+ * Stage 1: spec-compliance reviewer — confirms the diff faithfully
5
+ * implements every requirement in the task spec.
6
+ * Stage 2: code-quality reviewer — checks correctness, security, and
7
+ * project-convention adherence (runs only after Stage 1 passes).
8
+ *
9
+ * The `dispatch` parameter is injected by the caller so this module is
10
+ * testable without a live Agent tool. Signature:
11
+ * (kind: 'spec-compliance' | 'code-quality', ctx: object)
12
+ * => Promise<{ verdict: 'PASS' | 'FAIL', findings: string[] }>
13
+ *
14
+ * Landed in W10-A2 (v1.4.4 — N3).
15
+ */
16
+
17
+ // ---------------------------------------------------------------------------
18
+ // Constants
19
+ // ---------------------------------------------------------------------------
20
+
21
+ /** Maximum re-review iterations before escalation. */
22
+ export const REVIEW_MAX_ITERATIONS = 3;
23
+
24
+ // ---------------------------------------------------------------------------
25
+ // Helpers
26
+ // ---------------------------------------------------------------------------
27
+
28
+ /**
29
+ * Returns true when re-review should be triggered.
30
+ *
31
+ * @param {'PASS'|'FAIL'} prevVerdict
32
+ * @param {number} iteration 1-based iteration count (1 = first attempt)
33
+ * @returns {boolean}
34
+ */
35
+ export function shouldReReview(prevVerdict, iteration) {
36
+ return prevVerdict !== 'PASS' && iteration < REVIEW_MAX_ITERATIONS;
37
+ }
38
+
39
+ // ---------------------------------------------------------------------------
40
+ // Main export
41
+ // ---------------------------------------------------------------------------
42
+
43
+ /**
44
+ * Run two-stage review for a completed task.
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
+ *
52
+ * @param {object} params
53
+ * @param {string} params.taskId Blackboard task ID
54
+ * @param {string} params.taskSpec Full task specification text
55
+ * @param {string} params.commitSha SHA of the implementer's commit
56
+ * @param {string} params.branch Branch name
57
+ * @param {string} [params.projectConventions] CLAUDE.md / AGENTS.md excerpt
58
+ * @param {boolean} [params.bothStages=false] Run quality stage even on spec FAIL (M7).
59
+ * @param {Function} params.dispatch Injected reviewer dispatcher
60
+ *
61
+ * @returns {Promise<{
62
+ * ok: boolean,
63
+ * stage: 'spec' | 'quality',
64
+ * findings: string[],
65
+ * qualityFindings?: string[]
66
+ * }>}
67
+ */
68
+ export async function reviewTask({
69
+ taskId,
70
+ taskSpec,
71
+ commitSha,
72
+ branch,
73
+ projectConventions = '',
74
+ bothStages = false,
75
+ dispatch,
76
+ }) {
77
+ // ------------------------------------------------------------------
78
+ // Stage 1: spec-compliance reviewer
79
+ // ------------------------------------------------------------------
80
+ const spec = await dispatch('spec-compliance', {
81
+ taskId,
82
+ taskSpec,
83
+ commitSha,
84
+ branch,
85
+ });
86
+
87
+ if (spec.verdict !== 'PASS') {
88
+ const base = {
89
+ ok: false,
90
+ stage: 'spec',
91
+ findings: spec.findings ?? [],
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;
119
+ }
120
+
121
+ // ------------------------------------------------------------------
122
+ // Stage 2: code-quality reviewer (always after spec PASS)
123
+ // ------------------------------------------------------------------
124
+ const quality = await dispatch('code-quality', {
125
+ taskId,
126
+ commitSha,
127
+ branch,
128
+ projectConventions,
129
+ });
130
+
131
+ return {
132
+ ok: quality.verdict === 'PASS',
133
+ stage: 'quality',
134
+ findings: quality.findings ?? [],
135
+ };
136
+ }