@ijfw/memory-server 1.4.4 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (245) hide show
  1. package/bin/ijfw-memorize +14 -7
  2. package/fixtures/team/book.json +6 -6
  3. package/fixtures/team/business.json +146 -20
  4. package/fixtures/team/content.json +6 -6
  5. package/fixtures/team/design.json +148 -20
  6. package/fixtures/team/mixed.json +206 -27
  7. package/fixtures/team/research.json +146 -20
  8. package/fixtures/team/software.json +148 -20
  9. package/fixtures/truncation-corpus/_generate-corpus.js +367 -0
  10. package/fixtures/truncation-corpus/fx-01-clean-exit-01/events.jsonl +2 -0
  11. package/fixtures/truncation-corpus/fx-01-clean-exit-01/intent-journal.jsonl +2 -0
  12. package/fixtures/truncation-corpus/fx-01-clean-exit-01/meta.json +18 -0
  13. package/fixtures/truncation-corpus/fx-01-clean-exit-01/target/.ijfw/state/workflow.json +1 -0
  14. package/fixtures/truncation-corpus/fx-01-clean-exit-02/events.jsonl +2 -0
  15. package/fixtures/truncation-corpus/fx-01-clean-exit-02/intent-journal.jsonl +2 -0
  16. package/fixtures/truncation-corpus/fx-01-clean-exit-02/meta.json +18 -0
  17. package/fixtures/truncation-corpus/fx-01-clean-exit-02/target/.ijfw/state/workflow.json +1 -0
  18. package/fixtures/truncation-corpus/fx-01-clean-exit-03/events.jsonl +2 -0
  19. package/fixtures/truncation-corpus/fx-01-clean-exit-03/intent-journal.jsonl +2 -0
  20. package/fixtures/truncation-corpus/fx-01-clean-exit-03/meta.json +18 -0
  21. package/fixtures/truncation-corpus/fx-01-clean-exit-03/target/.ijfw/state/workflow.json +1 -0
  22. package/fixtures/truncation-corpus/fx-01-clean-exit-04/events.jsonl +2 -0
  23. package/fixtures/truncation-corpus/fx-01-clean-exit-04/intent-journal.jsonl +2 -0
  24. package/fixtures/truncation-corpus/fx-01-clean-exit-04/meta.json +18 -0
  25. package/fixtures/truncation-corpus/fx-01-clean-exit-04/target/.ijfw/state/workflow.json +1 -0
  26. package/fixtures/truncation-corpus/fx-01-clean-exit-05/events.jsonl +2 -0
  27. package/fixtures/truncation-corpus/fx-01-clean-exit-05/intent-journal.jsonl +2 -0
  28. package/fixtures/truncation-corpus/fx-01-clean-exit-05/meta.json +18 -0
  29. package/fixtures/truncation-corpus/fx-01-clean-exit-05/target/.ijfw/state/workflow.json +1 -0
  30. package/fixtures/truncation-corpus/fx-02-mid-overwrite-01/events.jsonl +1 -0
  31. package/fixtures/truncation-corpus/fx-02-mid-overwrite-01/intent-journal.jsonl +3 -0
  32. package/fixtures/truncation-corpus/fx-02-mid-overwrite-01/meta.json +18 -0
  33. package/fixtures/truncation-corpus/fx-02-mid-overwrite-01/snapshots/v-midO-1-advance.json +11 -0
  34. package/fixtures/truncation-corpus/fx-02-mid-overwrite-01/target/.ijfw/state/workflow.json +1 -0
  35. package/fixtures/truncation-corpus/fx-02-mid-overwrite-02/events.jsonl +1 -0
  36. package/fixtures/truncation-corpus/fx-02-mid-overwrite-02/intent-journal.jsonl +3 -0
  37. package/fixtures/truncation-corpus/fx-02-mid-overwrite-02/meta.json +18 -0
  38. package/fixtures/truncation-corpus/fx-02-mid-overwrite-02/snapshots/v-midO-2-advance.json +11 -0
  39. package/fixtures/truncation-corpus/fx-02-mid-overwrite-02/target/.ijfw/state/workflow.json +1 -0
  40. package/fixtures/truncation-corpus/fx-02-mid-overwrite-03/events.jsonl +1 -0
  41. package/fixtures/truncation-corpus/fx-02-mid-overwrite-03/intent-journal.jsonl +3 -0
  42. package/fixtures/truncation-corpus/fx-02-mid-overwrite-03/meta.json +18 -0
  43. package/fixtures/truncation-corpus/fx-02-mid-overwrite-03/snapshots/v-midO-3-advance.json +11 -0
  44. package/fixtures/truncation-corpus/fx-02-mid-overwrite-03/target/.ijfw/state/workflow.json +1 -0
  45. package/fixtures/truncation-corpus/fx-02-mid-overwrite-04/events.jsonl +1 -0
  46. package/fixtures/truncation-corpus/fx-02-mid-overwrite-04/intent-journal.jsonl +3 -0
  47. package/fixtures/truncation-corpus/fx-02-mid-overwrite-04/meta.json +18 -0
  48. package/fixtures/truncation-corpus/fx-02-mid-overwrite-04/snapshots/v-midO-4-advance.json +11 -0
  49. package/fixtures/truncation-corpus/fx-02-mid-overwrite-04/target/.ijfw/state/workflow.json +1 -0
  50. package/fixtures/truncation-corpus/fx-02-mid-overwrite-05/events.jsonl +1 -0
  51. package/fixtures/truncation-corpus/fx-02-mid-overwrite-05/intent-journal.jsonl +3 -0
  52. package/fixtures/truncation-corpus/fx-02-mid-overwrite-05/meta.json +18 -0
  53. package/fixtures/truncation-corpus/fx-02-mid-overwrite-05/snapshots/v-midO-5-advance.json +11 -0
  54. package/fixtures/truncation-corpus/fx-02-mid-overwrite-05/target/.ijfw/state/workflow.json +1 -0
  55. package/fixtures/truncation-corpus/fx-03-mid-append-01/events.jsonl +1 -0
  56. package/fixtures/truncation-corpus/fx-03-mid-append-01/intent-journal.jsonl +3 -0
  57. package/fixtures/truncation-corpus/fx-03-mid-append-01/meta.json +18 -0
  58. package/fixtures/truncation-corpus/fx-03-mid-append-01/target/.ijfw/blackboard/decisions.jsonl +1 -0
  59. package/fixtures/truncation-corpus/fx-03-mid-append-02/events.jsonl +1 -0
  60. package/fixtures/truncation-corpus/fx-03-mid-append-02/intent-journal.jsonl +3 -0
  61. package/fixtures/truncation-corpus/fx-03-mid-append-02/meta.json +18 -0
  62. package/fixtures/truncation-corpus/fx-03-mid-append-02/target/.ijfw/blackboard/decisions.jsonl +1 -0
  63. package/fixtures/truncation-corpus/fx-03-mid-append-03/events.jsonl +1 -0
  64. package/fixtures/truncation-corpus/fx-03-mid-append-03/intent-journal.jsonl +3 -0
  65. package/fixtures/truncation-corpus/fx-03-mid-append-03/meta.json +18 -0
  66. package/fixtures/truncation-corpus/fx-03-mid-append-03/target/.ijfw/blackboard/decisions.jsonl +1 -0
  67. package/fixtures/truncation-corpus/fx-03-mid-append-04/events.jsonl +1 -0
  68. package/fixtures/truncation-corpus/fx-03-mid-append-04/intent-journal.jsonl +3 -0
  69. package/fixtures/truncation-corpus/fx-03-mid-append-04/meta.json +18 -0
  70. package/fixtures/truncation-corpus/fx-03-mid-append-04/target/.ijfw/blackboard/decisions.jsonl +1 -0
  71. package/fixtures/truncation-corpus/fx-03-mid-append-05/events.jsonl +1 -0
  72. package/fixtures/truncation-corpus/fx-03-mid-append-05/intent-journal.jsonl +3 -0
  73. package/fixtures/truncation-corpus/fx-03-mid-append-05/meta.json +18 -0
  74. package/fixtures/truncation-corpus/fx-03-mid-append-05/target/.ijfw/blackboard/decisions.jsonl +1 -0
  75. package/fixtures/truncation-corpus/fx-04-no-events-01/events.jsonl +0 -0
  76. package/fixtures/truncation-corpus/fx-04-no-events-01/intent-journal.jsonl +1 -0
  77. package/fixtures/truncation-corpus/fx-04-no-events-01/meta.json +18 -0
  78. package/fixtures/truncation-corpus/fx-04-no-events-01/snapshots/v-noEv-1-set-phase.json +11 -0
  79. package/fixtures/truncation-corpus/fx-04-no-events-01/target/.ijfw/state/workflow.json +1 -0
  80. package/fixtures/truncation-corpus/fx-04-no-events-02/events.jsonl +0 -0
  81. package/fixtures/truncation-corpus/fx-04-no-events-02/intent-journal.jsonl +1 -0
  82. package/fixtures/truncation-corpus/fx-04-no-events-02/meta.json +18 -0
  83. package/fixtures/truncation-corpus/fx-04-no-events-02/snapshots/v-noEv-2-set-phase.json +11 -0
  84. package/fixtures/truncation-corpus/fx-04-no-events-02/target/.ijfw/state/workflow.json +1 -0
  85. package/fixtures/truncation-corpus/fx-04-no-events-03/events.jsonl +0 -0
  86. package/fixtures/truncation-corpus/fx-04-no-events-03/intent-journal.jsonl +1 -0
  87. package/fixtures/truncation-corpus/fx-04-no-events-03/meta.json +18 -0
  88. package/fixtures/truncation-corpus/fx-04-no-events-03/snapshots/v-noEv-3-set-phase.json +11 -0
  89. package/fixtures/truncation-corpus/fx-04-no-events-03/target/.ijfw/state/workflow.json +1 -0
  90. package/fixtures/truncation-corpus/fx-04-no-events-04/events.jsonl +0 -0
  91. package/fixtures/truncation-corpus/fx-04-no-events-04/intent-journal.jsonl +1 -0
  92. package/fixtures/truncation-corpus/fx-04-no-events-04/meta.json +18 -0
  93. package/fixtures/truncation-corpus/fx-04-no-events-04/snapshots/v-noEv-4-set-phase.json +11 -0
  94. package/fixtures/truncation-corpus/fx-04-no-events-04/target/.ijfw/state/workflow.json +1 -0
  95. package/fixtures/truncation-corpus/fx-04-no-events-05/events.jsonl +0 -0
  96. package/fixtures/truncation-corpus/fx-04-no-events-05/intent-journal.jsonl +1 -0
  97. package/fixtures/truncation-corpus/fx-04-no-events-05/meta.json +18 -0
  98. package/fixtures/truncation-corpus/fx-04-no-events-05/snapshots/v-noEv-5-set-phase.json +11 -0
  99. package/fixtures/truncation-corpus/fx-04-no-events-05/target/.ijfw/state/workflow.json +1 -0
  100. package/fixtures/truncation-corpus/fx-05-error-terminated-01/events.jsonl +2 -0
  101. package/fixtures/truncation-corpus/fx-05-error-terminated-01/intent-journal.jsonl +3 -0
  102. package/fixtures/truncation-corpus/fx-05-error-terminated-01/meta.json +18 -0
  103. package/fixtures/truncation-corpus/fx-05-error-terminated-01/snapshots/v-errT-1-partial.json +11 -0
  104. package/fixtures/truncation-corpus/fx-05-error-terminated-01/target/.ijfw/state/workflow.json +1 -0
  105. package/fixtures/truncation-corpus/fx-05-error-terminated-02/events.jsonl +2 -0
  106. package/fixtures/truncation-corpus/fx-05-error-terminated-02/intent-journal.jsonl +3 -0
  107. package/fixtures/truncation-corpus/fx-05-error-terminated-02/meta.json +18 -0
  108. package/fixtures/truncation-corpus/fx-05-error-terminated-02/target/.ijfw/blackboard/decisions.jsonl +1 -0
  109. package/fixtures/truncation-corpus/fx-05-error-terminated-03/events.jsonl +2 -0
  110. package/fixtures/truncation-corpus/fx-05-error-terminated-03/intent-journal.jsonl +3 -0
  111. package/fixtures/truncation-corpus/fx-05-error-terminated-03/meta.json +18 -0
  112. package/fixtures/truncation-corpus/fx-05-error-terminated-03/snapshots/v-errT-3-partial.json +11 -0
  113. package/fixtures/truncation-corpus/fx-05-error-terminated-03/target/.ijfw/state/workflow.json +1 -0
  114. package/fixtures/truncation-corpus/fx-05-error-terminated-04/events.jsonl +2 -0
  115. package/fixtures/truncation-corpus/fx-05-error-terminated-04/intent-journal.jsonl +3 -0
  116. package/fixtures/truncation-corpus/fx-05-error-terminated-04/meta.json +18 -0
  117. package/fixtures/truncation-corpus/fx-05-error-terminated-04/target/.ijfw/blackboard/decisions.jsonl +1 -0
  118. package/fixtures/truncation-corpus/fx-05-error-terminated-05/events.jsonl +2 -0
  119. package/fixtures/truncation-corpus/fx-05-error-terminated-05/intent-journal.jsonl +3 -0
  120. package/fixtures/truncation-corpus/fx-05-error-terminated-05/meta.json +18 -0
  121. package/fixtures/truncation-corpus/fx-05-error-terminated-05/snapshots/v-errT-5-partial.json +11 -0
  122. package/fixtures/truncation-corpus/fx-05-error-terminated-05/target/.ijfw/state/workflow.json +1 -0
  123. package/package.json +6 -3
  124. package/src/active-extension-writer.js +144 -64
  125. package/src/api-client.js +43 -5
  126. package/src/audit-roster.js +80 -5
  127. package/src/blackboard.js +298 -6
  128. package/src/cli-run.js +33 -5
  129. package/src/codex-agents.js +96 -5
  130. package/src/cost/aggregator.js +39 -9
  131. package/src/cost/pricing.js +57 -0
  132. package/src/cost/readers/gemini.js +1 -1
  133. package/src/cross-audit-chunker.js +189 -0
  134. package/src/cross-dispatcher.js +124 -21
  135. package/src/cross-orchestrator-cli.js +754 -159
  136. package/src/cross-orchestrator.js +1065 -17
  137. package/src/cross-project-search.js +195 -9
  138. package/src/dashboard-client-waves.html +304 -0
  139. package/src/dashboard-client.html +5 -1
  140. package/src/dashboard-server.js +73 -0
  141. package/src/deploy-alerts.js +150 -0
  142. package/src/design/iframe-bridge.js +242 -0
  143. package/src/design-companion.js +144 -0
  144. package/src/dispatch/checkpoint-cli.js +97 -0
  145. package/src/dispatch/colon-syntax.js +81 -1
  146. package/src/dispatch/extension.js +26 -2
  147. package/src/dispatch/registry-cli.js +4 -1
  148. package/src/dispatch/wave-cli.js +201 -6
  149. package/src/dispatch/worktree-cli.js +40 -0
  150. package/src/dispatch-planner.js +97 -2
  151. package/src/dream/runner.mjs +47 -11
  152. package/src/dream/stage-runner.js +40 -0
  153. package/src/dream/state-file.js +102 -0
  154. package/src/extension-installer.js +70 -24
  155. package/src/extension-quota-tracker.js +4 -2
  156. package/src/extension-registry.js +289 -35
  157. package/src/feedback-detector.js +26 -0
  158. package/src/fs-lock.js +259 -7
  159. package/src/gate-result.js +95 -1
  160. package/src/hardware-signer.js +4 -2
  161. package/src/hero-line.js +86 -5
  162. package/src/intent-router.js +35 -0
  163. package/src/lib/a11y-contract.js +117 -0
  164. package/src/lib/atomic-io.js +29 -8
  165. package/src/lib/cache-keepalive.js +150 -0
  166. package/src/lib/jsonl-rotation.js +104 -0
  167. package/src/lib/lighthouse-pillar.js +121 -0
  168. package/src/lib/llm-call.js +121 -0
  169. package/src/lib/playwright-baseline.js +205 -0
  170. package/src/lib/rekor-bridge.js +221 -0
  171. package/src/lib/repo-map.js +392 -0
  172. package/src/lib/shasum-verify.js +164 -0
  173. package/src/lib/sketches-gc.js +132 -0
  174. package/src/lib/tmp-suffix.js +62 -0
  175. package/src/lib/ui-review-runner.js +595 -0
  176. package/src/lib/uispec-drift.js +301 -0
  177. package/src/lib/uispec-intake.js +381 -0
  178. package/src/lib/worktree-guards.js +118 -0
  179. package/src/lib/worktree-recovery.js +100 -0
  180. package/src/memory/auto-linker.js +267 -0
  181. package/src/memory/benchmark.js +498 -0
  182. package/src/memory/dedup.js +126 -0
  183. package/src/memory/embedding-cache.js +136 -0
  184. package/src/memory/fact-extractor.js +168 -0
  185. package/src/memory/fts5.js +65 -1
  186. package/src/memory/migration-runner.js +6 -1
  187. package/src/memory/migrations/004-bitemporal.js +91 -0
  188. package/src/memory/migrations/005-vector-cache.js +61 -0
  189. package/src/memory/migrations/006-obsidian-graph.js +46 -0
  190. package/src/memory/migrations/007-skill-telemetry.js +24 -0
  191. package/src/memory/migrations/008-write-provenance.js +41 -0
  192. package/src/memory/migrations/009-obsidian-backfill.js +50 -0
  193. package/src/memory/obsidian-parser.js +152 -0
  194. package/src/memory/query-dataview.js +86 -0
  195. package/src/memory/search.js +46 -15
  196. package/src/memory/temporal.js +529 -0
  197. package/src/memory/tokenize.js +10 -0
  198. package/src/memory-facts-handler.js +37 -0
  199. package/src/memory-feedback.js +260 -2
  200. package/src/model-refresh.js +292 -0
  201. package/src/observability/cost-anomaly.js +166 -0
  202. package/src/observability/evaluator-checkpoint-contract.js +117 -0
  203. package/src/observability/trace-id.js +163 -0
  204. package/src/orchestrator/agents-md-blackboard.js +152 -0
  205. package/src/orchestrator/checkpoint-contract.md +140 -0
  206. package/src/orchestrator/debug-trident-trigger.js +374 -0
  207. package/src/orchestrator/debug-trident.js +570 -0
  208. package/src/orchestrator/merge-block-aware.js +350 -0
  209. package/src/orchestrator/plan-checker.js +475 -0
  210. package/src/orchestrator/post-done-runner.js +277 -0
  211. package/src/orchestrator/review.js +38 -3
  212. package/src/orchestrator/skill-telemetry-sink.js +29 -0
  213. package/src/orchestrator/skill-telemetry.js +37 -0
  214. package/src/orchestrator/state-events.js +459 -0
  215. package/src/orchestrator/state-sdk.js +1932 -0
  216. package/src/orchestrator/status-protocol.js +84 -17
  217. package/src/orchestrator/subagent-telemetry.js +471 -0
  218. package/src/orchestrator/termination.js +160 -0
  219. package/src/orchestrator/verification-gate.js +200 -16
  220. package/src/orchestrator/wave-state.js +332 -23
  221. package/src/orchestrator/worktree-provision.js +77 -0
  222. package/src/override-resolver.js +5 -3
  223. package/src/override-use-registry.js +111 -5
  224. package/src/receipts.js +36 -4
  225. package/src/recovery/checkpoint.js +56 -3
  226. package/src/recovery/code-fixer.js +961 -0
  227. package/src/recovery/truncation.js +317 -0
  228. package/src/redactor.js +75 -6
  229. package/src/runtime-mediator.js +15 -1
  230. package/src/sanitizer.js +10 -0
  231. package/src/search-hybrid.js +139 -0
  232. package/src/server.js +795 -112
  233. package/src/swarm/worktree.js +27 -4
  234. package/src/swarm-config.js +102 -17
  235. package/src/team/domain-templates/book.json +51 -0
  236. package/src/team/domain-templates/business.json +44 -0
  237. package/src/team/domain-templates/content.json +50 -0
  238. package/src/team/domain-templates/design.json +44 -0
  239. package/src/team/domain-templates/research.json +44 -0
  240. package/src/team/domain-templates/software.json +40 -0
  241. package/src/team/generator.js +440 -3
  242. package/src/team/modify.js +203 -0
  243. package/src/team/schemas.js +48 -0
  244. package/src/update-apply.js +19 -3
  245. package/src/dashboard-charts.js +0 -239
@@ -0,0 +1,277 @@
1
+ /**
2
+ * post-done-runner.js — v1.5.0-major S02: post-DONE pipeline primitives.
3
+ *
4
+ * WHAT IS LIVE: `runSelfCheck` is the only export on the production path. The
5
+ * live DONE-handler is the `subagent.post-done` state-SDK verb, which calls
6
+ * `runSelfCheck` directly (and fires debug-trident via debug-trident-trigger.js
7
+ * on a self-check failure). The verification gate itself is also enforced live
8
+ * — `state-sdk.js` calls `enforceVerificationGate` directly.
9
+ *
10
+ * WHAT IS NOT LIVE: `runPostDone` is a library/test surface — NOT the live
11
+ * DONE-handler. It is a convenience wrapper that bundles reviewTask (v1.4.4 N3
12
+ * two-stage review) + checkVerificationGate (v1.4.4 N5) for direct-import
13
+ * callers and the test path (`test-orchestrator-post-done-runner.js`). The
14
+ * production two-stage spec+quality review happens via agent dispatch
15
+ * (spec-reviewer + quality-reviewer agents), not through this wrapper. Its
16
+ * original S02 caller (`runtime-loop.js`) was never wired; that file is now
17
+ * removed. `runPostDone` is kept for its test surface and for any future
18
+ * caller that wants the two checks bundled — it does not carry production
19
+ * traffic today.
20
+ *
21
+ * v1.5.0 T13: the standalone `ijfw_subagent_post_done` MCP tool was retired and
22
+ * absorbed into the single `ijfw_state` MCP tool as the `subagent.post-done`
23
+ * verb (see STATE-SDK-CONTRACT §7). `runSelfCheck` is re-exported through
24
+ * `state-sdk.js` for that verb.
25
+ *
26
+ * Outcome shape (uniform regardless of branch taken):
27
+ * {
28
+ * verdict: 'approved' | 'spec_failed' | 'quality_failed',
29
+ * reviewStage: 'spec' | 'quality',
30
+ * reviewOk: boolean,
31
+ * reviewFindings: string[],
32
+ * gatePassed: boolean,
33
+ * gateAction: 'block' | 'advise' | 'pass',
34
+ * gateViolation: object | null,
35
+ * }
36
+ *
37
+ * `gateAction` (W12-F/F4 — RT2-H1) tells the MCP tool handler what to do
38
+ * with a gate failure:
39
+ * - 'pass' → gate passed; advance normally.
40
+ * - 'block' → strict mode (default) AND gate failed. Caller MUST refuse
41
+ * to claim success. The MCP handler should surface a structured
42
+ * `block: true` so the orchestrator-LLM treats it as a hard stop.
43
+ * - 'advise' → caller opted out of strict via `strictGate: false` AND gate
44
+ * failed. Caller may proceed but should still surface the
45
+ * violation so it gets routed into memory-feedback.
46
+ *
47
+ * The `dispatch` parameter is the reviewTask injected dispatcher:
48
+ * (kind: 'spec-compliance'|'code-quality', ctx: object)
49
+ * => Promise<{ verdict: 'PASS'|'FAIL', findings: string[] }>
50
+ *
51
+ * If `dispatch` is null/undefined we still run the gate check (the orchestrator
52
+ * may invoke the reviewers itself via the Agent tool); verdict becomes
53
+ * 'no_review' to signal that.
54
+ */
55
+
56
+ import { existsSync } from 'node:fs';
57
+ import { execFileSync } from 'node:child_process';
58
+ import { reviewTask } from './review.js';
59
+ import { checkVerificationGate, recordViolation } from './verification-gate.js';
60
+ // debug-trident (T29) is wired on the LIVE path only: `subagent.post-done` in
61
+ // state-sdk.js fires debug-trident fire-and-forget when its self-check gate
62
+ // fails, via `maybeFireDebugTrident` in debug-trident-trigger.js. That is the
63
+ // genuine production caller — codex+gemini are dispatched against the real
64
+ // gate-failure evidence whenever IJFW_DEBUG_TRIDENT is enabled. runPostDone
65
+ // deliberately does NOT invoke debug-trident (the earlier W2.C inline-
66
+ // annotation hook was dead — computed but never returned — and was removed).
67
+
68
+ /**
69
+ * Extract paths claimed in the report. Naive but effective: looks for
70
+ * "created/modified/file: <path>" plus bullet-list `- path/...` patterns.
71
+ * Skip lines under "Self-Check" section (don't recurse into reported self-checks).
72
+ */
73
+ function extractClaimedPaths(reportText) {
74
+ const lines = String(reportText || '').split('\n');
75
+ const paths = new Set();
76
+ let inSelfCheck = false;
77
+ for (const line of lines) {
78
+ if (/^##\s*Self-Check/i.test(line)) { inSelfCheck = true; continue; }
79
+ if (inSelfCheck) continue;
80
+ const m = line.match(/(?:created|modified|file):\s*[`"]?([^\s`"]+)[`"]?/i);
81
+ if (m && m[1].includes('.')) paths.add(m[1]);
82
+ const m2 = line.match(/^\s*-\s+[`"]?([^\s`"]+\.\w+)[`"]?/);
83
+ if (m2) paths.add(m2[1]);
84
+ }
85
+ return [...paths];
86
+ }
87
+
88
+ /**
89
+ * Extract plausible commit SHAs (hex, 7-40 chars) from the report.
90
+ */
91
+ function extractClaimedCommits(reportText) {
92
+ const matches = String(reportText || '').match(/\b[0-9a-f]{7,40}\b/g) || [];
93
+ return [...new Set(matches.filter((s) => /^[0-9a-f]+$/.test(s) && s.length >= 7))];
94
+ }
95
+
96
+ /**
97
+ * runSelfCheck — verify claimed files + commits actually exist before review.
98
+ * @param {string} reportText
99
+ * @param {string} projectRoot
100
+ * @returns {{
101
+ * verdict: 'PASSED'|'FAILED',
102
+ * files_claimed: number,
103
+ * files_present: number,
104
+ * files_missing: string[],
105
+ * commits_claimed: number,
106
+ * commits_present: number,
107
+ * commits_missing: string[],
108
+ * }}
109
+ */
110
+ export function runSelfCheck(reportText, projectRoot) {
111
+ const claimedPaths = extractClaimedPaths(reportText);
112
+ const claimedCommits = extractClaimedCommits(reportText);
113
+ const filesPresent = claimedPaths.filter((p) =>
114
+ existsSync(p.startsWith('/') ? p : `${projectRoot}/${p}`),
115
+ );
116
+ let commitsPresent = [];
117
+ try {
118
+ const allShas = execFileSync('git', ['log', '--all', '--format=%H'], {
119
+ cwd: projectRoot,
120
+ encoding: 'utf8',
121
+ stdio: ['ignore', 'pipe', 'ignore'],
122
+ }).split('\n');
123
+ commitsPresent = claimedCommits.filter((c) => allShas.some((sha) => sha.startsWith(c)));
124
+ } catch {
125
+ /* not a git repo — skip commit check */
126
+ }
127
+ const verdict =
128
+ filesPresent.length === claimedPaths.length &&
129
+ commitsPresent.length === claimedCommits.length
130
+ ? 'PASSED'
131
+ : 'FAILED';
132
+ return {
133
+ verdict,
134
+ files_claimed: claimedPaths.length,
135
+ files_present: filesPresent.length,
136
+ files_missing: claimedPaths.filter((p) => !filesPresent.includes(p)),
137
+ commits_claimed: claimedCommits.length,
138
+ commits_present: commitsPresent.length,
139
+ commits_missing: claimedCommits.filter((c) => !commitsPresent.includes(c)),
140
+ };
141
+ }
142
+
143
+ /**
144
+ * runPostDone — library/test surface. NOT the live DONE-handler.
145
+ *
146
+ * The live subagent-completion path is the `subagent.post-done` state-SDK verb
147
+ * (which runs `runSelfCheck` + fires debug-trident on failure), plus the
148
+ * verification gate enforced directly in `state-sdk.js`; the production
149
+ * two-stage spec+quality review runs via agent dispatch (spec-reviewer +
150
+ * quality-reviewer agents). This wrapper bundles reviewTask (N3) +
151
+ * checkVerificationGate (N5) + runSelfCheck (S09) for direct-import callers
152
+ * and `test-orchestrator-post-done-runner.js`. It carries no production
153
+ * traffic — keep it honest: do not describe it as the live handler.
154
+ *
155
+ * @param {object} params
156
+ * @param {string} params.taskId
157
+ * @param {string} [params.taskSpec]
158
+ * @param {string} params.commitSha
159
+ * @param {string} [params.branch]
160
+ * @param {string} params.reportText
161
+ * @param {Array<{tool: string, input?: {command?: string}}>} [params.toolCallsInMessage]
162
+ * @param {Function|null} [params.dispatch] Reviewer dispatcher; null = skip review
163
+ * @param {string} params.projectRoot
164
+ * @param {string} [params.projectConventions]
165
+ * @param {boolean} [params.strictGate=true]
166
+ * W12-F/F4 — RT2-H1. When true (default) and the verification gate fails,
167
+ * the result includes `gateAction: 'block'` and the MCP handler MUST refuse
168
+ * to claim success. Pass `false` for legacy advisory-only behavior.
169
+ * @returns {Promise<{
170
+ * verdict: 'approved'|'spec_failed'|'quality_failed'|'no_review',
171
+ * reviewStage: 'spec'|'quality'|null,
172
+ * reviewOk: boolean,
173
+ * reviewFindings: string[],
174
+ * gatePassed: boolean,
175
+ * gateAction: 'block'|'advise'|'pass',
176
+ * gateViolation: object|null,
177
+ * selfCheck: {
178
+ * verdict: 'PASSED'|'FAILED',
179
+ * files_claimed: number,
180
+ * files_present: number,
181
+ * files_missing: string[],
182
+ * commits_claimed: number,
183
+ * commits_present: number,
184
+ * commits_missing: string[],
185
+ * },
186
+ * }>}
187
+ */
188
+ export async function runPostDone({
189
+ taskId,
190
+ taskSpec = '',
191
+ commitSha,
192
+ branch = '',
193
+ reportText,
194
+ toolCallsInMessage,
195
+ dispatch,
196
+ projectRoot,
197
+ projectConventions = '',
198
+ strictGate = true,
199
+ }) {
200
+ if (typeof projectRoot !== 'string' || projectRoot.length === 0) {
201
+ throw new TypeError('runPostDone: projectRoot is required');
202
+ }
203
+ if (typeof reportText !== 'string') {
204
+ throw new TypeError('runPostDone: reportText must be a string');
205
+ }
206
+
207
+ // ---- Self-Check (S09) ------------------------------------------------
208
+ // Verify claimed files + commits exist before spending review tokens.
209
+ // Additive: doesn't gate downstream — surfaces the divergence in result.
210
+ const selfCheck = runSelfCheck(reportText, projectRoot);
211
+
212
+ // ---- Two-stage review (N3) -------------------------------------------
213
+ let reviewOk = false;
214
+ let reviewStage = null;
215
+ let reviewFindings = [];
216
+ let verdict = 'no_review';
217
+
218
+ if (typeof dispatch === 'function') {
219
+ const r = await reviewTask({
220
+ taskId,
221
+ taskSpec,
222
+ commitSha,
223
+ branch,
224
+ projectConventions,
225
+ dispatch,
226
+ });
227
+ reviewOk = !!r.ok;
228
+ reviewStage = r.stage ?? null;
229
+ reviewFindings = Array.isArray(r.findings) ? r.findings : [];
230
+ if (reviewOk) {
231
+ verdict = 'approved';
232
+ } else {
233
+ verdict = r.stage === 'spec' ? 'spec_failed' : 'quality_failed';
234
+ }
235
+ }
236
+
237
+ // ---- Verification gate (N5) ------------------------------------------
238
+ const gateOutcome = checkVerificationGate(
239
+ reportText,
240
+ Array.isArray(toolCallsInMessage) ? toolCallsInMessage : [],
241
+ );
242
+ if (!gateOutcome.ok) {
243
+ try {
244
+ // recordViolation signature is (violation, projectRoot) -- see verification-gate.js
245
+ await recordViolation(
246
+ { taskId, ...gateOutcome },
247
+ projectRoot,
248
+ );
249
+ } catch {
250
+ // Advisory -- never block on violation log failure (matches v1.4.4 N5 contract)
251
+ }
252
+ }
253
+
254
+ // W12-F/F4 — RT2-H1: classify the gate outcome so the caller knows whether
255
+ // to BLOCK (strict default + failure), ADVISE (caller opted out + failure),
256
+ // or PASS (gate succeeded). The MCP tool handler reads `gateAction` and
257
+ // surfaces a structured `block: true` to the orchestrator-LLM when 'block'.
258
+ let gateAction;
259
+ if (gateOutcome.ok) {
260
+ gateAction = 'pass';
261
+ } else if (strictGate === false) {
262
+ gateAction = 'advise';
263
+ } else {
264
+ gateAction = 'block';
265
+ }
266
+
267
+ return {
268
+ verdict,
269
+ reviewStage,
270
+ reviewOk,
271
+ reviewFindings,
272
+ gatePassed: gateOutcome.ok === true,
273
+ gateAction,
274
+ gateViolation: gateOutcome.ok ? null : { violation: gateOutcome.violation, claim: gateOutcome.claim },
275
+ selfCheck,
276
+ };
277
+ }
@@ -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,
@@ -0,0 +1,29 @@
1
+ // mcp-server/src/orchestrator/skill-telemetry-sink.js
2
+ // IJFW v1.5.0 -- state-SDK telemetry.record -> skill_telemetry shim.
3
+ //
4
+ // Maps the existing state-SDK telemetry.record payload shape into the
5
+ // skill_telemetry table. Payload shape (per state-sdk.js telemetry.record):
6
+ // { kind, dedupKey, metrics }
7
+ // When kind === 'skill.execution' we expect metrics to carry:
8
+ // { skill_id, session_id?, outcome, latency_ms?, created_at? }
9
+ // Anything else is a clean skip — the generic telemetry.record verb keeps
10
+ // its existing append-to-telemetry-file behavior regardless of this sink.
11
+
12
+ import { recordSkillExecution } from './skill-telemetry.js';
13
+
14
+ export function sinkSkillTelemetry(db, payload) {
15
+ if (!payload || payload.kind !== 'skill.execution') return { skipped: true };
16
+ const m = payload.metrics || {};
17
+ const skill_id = m.skill_id;
18
+ if (!skill_id) return { skipped: true, reason: 'no_skill_id' };
19
+ recordSkillExecution(db, {
20
+ skill_id,
21
+ session_id: m.session_id || null,
22
+ outcome: m.outcome || 'success',
23
+ latency_ms: typeof m.latency_ms === 'number' ? m.latency_ms : null,
24
+ created_at: m.created_at || Date.now(),
25
+ });
26
+ return { skipped: false };
27
+ }
28
+
29
+ export default { sinkSkillTelemetry };
@@ -0,0 +1,37 @@
1
+ // mcp-server/src/orchestrator/skill-telemetry.js
2
+ // IJFW v1.5.0 -- skills telemetry recorder + top-K reader.
3
+
4
+ export function recordSkillExecution(db, {
5
+ skill_id,
6
+ session_id = null,
7
+ outcome,
8
+ latency_ms = null,
9
+ created_at = Date.now(),
10
+ } = {}) {
11
+ if (!skill_id || !outcome) throw new Error('recordSkillExecution: skill_id and outcome required');
12
+ if (!['success', 'failure', 'aborted'].includes(outcome)) {
13
+ throw new Error(`recordSkillExecution: invalid outcome '${outcome}'`);
14
+ }
15
+ db.prepare(
16
+ `INSERT INTO skill_telemetry (skill_id, session_id, outcome, latency_ms, created_at)
17
+ VALUES (?, ?, ?, ?, ?)`,
18
+ ).run(skill_id, session_id, outcome, latency_ms, created_at);
19
+ }
20
+
21
+ export function topKSuccessfulSkills(db, { k = 5, since = null } = {}) {
22
+ const params = [];
23
+ let whereSince = '';
24
+ if (since !== null) { whereSince = 'AND created_at >= ?'; params.push(since); }
25
+ return db
26
+ .prepare(
27
+ `SELECT skill_id, COUNT(*) AS success_count, MAX(created_at) AS last_success_at
28
+ FROM skill_telemetry
29
+ WHERE outcome = 'success' ${whereSince}
30
+ GROUP BY skill_id
31
+ ORDER BY success_count DESC, last_success_at DESC
32
+ LIMIT ?`,
33
+ )
34
+ .all(...params, k);
35
+ }
36
+
37
+ export default { recordSkillExecution, topKSuccessfulSkills };