@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.
- package/bin/ijfw-memorize +14 -7
- package/fixtures/team/book.json +6 -6
- package/fixtures/team/business.json +146 -20
- package/fixtures/team/content.json +6 -6
- package/fixtures/team/design.json +148 -20
- package/fixtures/team/mixed.json +206 -27
- package/fixtures/team/research.json +146 -20
- package/fixtures/team/software.json +148 -20
- package/fixtures/truncation-corpus/_generate-corpus.js +367 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-01/events.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-01/intent-journal.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-01/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-01/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-02/events.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-02/intent-journal.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-02/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-02/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-03/events.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-03/intent-journal.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-03/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-03/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-04/events.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-04/intent-journal.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-04/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-04/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-05/events.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-05/intent-journal.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-05/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-05/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-01/events.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-01/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-01/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-01/snapshots/v-midO-1-advance.json +11 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-01/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-02/events.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-02/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-02/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-02/snapshots/v-midO-2-advance.json +11 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-02/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-03/events.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-03/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-03/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-03/snapshots/v-midO-3-advance.json +11 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-03/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-04/events.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-04/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-04/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-04/snapshots/v-midO-4-advance.json +11 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-04/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-05/events.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-05/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-05/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-05/snapshots/v-midO-5-advance.json +11 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-05/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-01/events.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-01/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-01/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-01/target/.ijfw/blackboard/decisions.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-02/events.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-02/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-02/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-02/target/.ijfw/blackboard/decisions.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-03/events.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-03/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-03/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-03/target/.ijfw/blackboard/decisions.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-04/events.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-04/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-04/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-04/target/.ijfw/blackboard/decisions.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-05/events.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-05/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-05/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-05/target/.ijfw/blackboard/decisions.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-04-no-events-01/events.jsonl +0 -0
- package/fixtures/truncation-corpus/fx-04-no-events-01/intent-journal.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-04-no-events-01/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-04-no-events-01/snapshots/v-noEv-1-set-phase.json +11 -0
- package/fixtures/truncation-corpus/fx-04-no-events-01/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-04-no-events-02/events.jsonl +0 -0
- package/fixtures/truncation-corpus/fx-04-no-events-02/intent-journal.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-04-no-events-02/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-04-no-events-02/snapshots/v-noEv-2-set-phase.json +11 -0
- package/fixtures/truncation-corpus/fx-04-no-events-02/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-04-no-events-03/events.jsonl +0 -0
- package/fixtures/truncation-corpus/fx-04-no-events-03/intent-journal.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-04-no-events-03/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-04-no-events-03/snapshots/v-noEv-3-set-phase.json +11 -0
- package/fixtures/truncation-corpus/fx-04-no-events-03/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-04-no-events-04/events.jsonl +0 -0
- package/fixtures/truncation-corpus/fx-04-no-events-04/intent-journal.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-04-no-events-04/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-04-no-events-04/snapshots/v-noEv-4-set-phase.json +11 -0
- package/fixtures/truncation-corpus/fx-04-no-events-04/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-04-no-events-05/events.jsonl +0 -0
- package/fixtures/truncation-corpus/fx-04-no-events-05/intent-journal.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-04-no-events-05/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-04-no-events-05/snapshots/v-noEv-5-set-phase.json +11 -0
- package/fixtures/truncation-corpus/fx-04-no-events-05/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-01/events.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-01/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-01/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-01/snapshots/v-errT-1-partial.json +11 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-01/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-02/events.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-02/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-02/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-02/target/.ijfw/blackboard/decisions.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-03/events.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-03/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-03/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-03/snapshots/v-errT-3-partial.json +11 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-03/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-04/events.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-04/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-04/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-04/target/.ijfw/blackboard/decisions.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-05/events.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-05/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-05/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-05/snapshots/v-errT-5-partial.json +11 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-05/target/.ijfw/state/workflow.json +1 -0
- package/package.json +6 -3
- package/src/active-extension-writer.js +144 -64
- package/src/api-client.js +43 -5
- package/src/audit-roster.js +80 -5
- package/src/blackboard.js +298 -6
- package/src/cli-run.js +33 -5
- package/src/codex-agents.js +96 -5
- package/src/cost/aggregator.js +39 -9
- package/src/cost/pricing.js +57 -0
- package/src/cost/readers/gemini.js +1 -1
- package/src/cross-audit-chunker.js +189 -0
- package/src/cross-dispatcher.js +124 -21
- package/src/cross-orchestrator-cli.js +754 -159
- package/src/cross-orchestrator.js +1065 -17
- package/src/cross-project-search.js +195 -9
- package/src/dashboard-client-waves.html +304 -0
- package/src/dashboard-client.html +5 -1
- package/src/dashboard-server.js +73 -0
- package/src/deploy-alerts.js +150 -0
- package/src/design/iframe-bridge.js +242 -0
- package/src/design-companion.js +144 -0
- package/src/dispatch/checkpoint-cli.js +97 -0
- package/src/dispatch/colon-syntax.js +81 -1
- package/src/dispatch/extension.js +26 -2
- package/src/dispatch/registry-cli.js +4 -1
- package/src/dispatch/wave-cli.js +201 -6
- package/src/dispatch/worktree-cli.js +40 -0
- package/src/dispatch-planner.js +97 -2
- package/src/dream/runner.mjs +47 -11
- package/src/dream/stage-runner.js +40 -0
- package/src/dream/state-file.js +102 -0
- package/src/extension-installer.js +70 -24
- package/src/extension-quota-tracker.js +4 -2
- package/src/extension-registry.js +289 -35
- package/src/feedback-detector.js +26 -0
- package/src/fs-lock.js +259 -7
- package/src/gate-result.js +95 -1
- package/src/hardware-signer.js +4 -2
- package/src/hero-line.js +86 -5
- package/src/intent-router.js +35 -0
- package/src/lib/a11y-contract.js +117 -0
- package/src/lib/atomic-io.js +29 -8
- package/src/lib/cache-keepalive.js +150 -0
- package/src/lib/jsonl-rotation.js +104 -0
- package/src/lib/lighthouse-pillar.js +121 -0
- package/src/lib/llm-call.js +121 -0
- package/src/lib/playwright-baseline.js +205 -0
- package/src/lib/rekor-bridge.js +221 -0
- package/src/lib/repo-map.js +392 -0
- package/src/lib/shasum-verify.js +164 -0
- package/src/lib/sketches-gc.js +132 -0
- package/src/lib/tmp-suffix.js +62 -0
- package/src/lib/ui-review-runner.js +595 -0
- package/src/lib/uispec-drift.js +301 -0
- package/src/lib/uispec-intake.js +381 -0
- package/src/lib/worktree-guards.js +118 -0
- package/src/lib/worktree-recovery.js +100 -0
- package/src/memory/auto-linker.js +267 -0
- package/src/memory/benchmark.js +498 -0
- package/src/memory/dedup.js +126 -0
- package/src/memory/embedding-cache.js +136 -0
- package/src/memory/fact-extractor.js +168 -0
- package/src/memory/fts5.js +65 -1
- package/src/memory/migration-runner.js +6 -1
- package/src/memory/migrations/004-bitemporal.js +91 -0
- package/src/memory/migrations/005-vector-cache.js +61 -0
- package/src/memory/migrations/006-obsidian-graph.js +46 -0
- package/src/memory/migrations/007-skill-telemetry.js +24 -0
- package/src/memory/migrations/008-write-provenance.js +41 -0
- package/src/memory/migrations/009-obsidian-backfill.js +50 -0
- package/src/memory/obsidian-parser.js +152 -0
- package/src/memory/query-dataview.js +86 -0
- package/src/memory/search.js +46 -15
- package/src/memory/temporal.js +529 -0
- package/src/memory/tokenize.js +10 -0
- package/src/memory-facts-handler.js +37 -0
- package/src/memory-feedback.js +260 -2
- package/src/model-refresh.js +292 -0
- package/src/observability/cost-anomaly.js +166 -0
- package/src/observability/evaluator-checkpoint-contract.js +117 -0
- package/src/observability/trace-id.js +163 -0
- package/src/orchestrator/agents-md-blackboard.js +152 -0
- package/src/orchestrator/checkpoint-contract.md +140 -0
- package/src/orchestrator/debug-trident-trigger.js +374 -0
- package/src/orchestrator/debug-trident.js +570 -0
- package/src/orchestrator/merge-block-aware.js +350 -0
- package/src/orchestrator/plan-checker.js +475 -0
- package/src/orchestrator/post-done-runner.js +277 -0
- package/src/orchestrator/review.js +38 -3
- package/src/orchestrator/skill-telemetry-sink.js +29 -0
- package/src/orchestrator/skill-telemetry.js +37 -0
- package/src/orchestrator/state-events.js +459 -0
- package/src/orchestrator/state-sdk.js +1932 -0
- package/src/orchestrator/status-protocol.js +84 -17
- package/src/orchestrator/subagent-telemetry.js +471 -0
- package/src/orchestrator/termination.js +160 -0
- package/src/orchestrator/verification-gate.js +200 -16
- package/src/orchestrator/wave-state.js +332 -23
- package/src/orchestrator/worktree-provision.js +77 -0
- package/src/override-resolver.js +5 -3
- package/src/override-use-registry.js +111 -5
- package/src/receipts.js +36 -4
- package/src/recovery/checkpoint.js +56 -3
- package/src/recovery/code-fixer.js +961 -0
- package/src/recovery/truncation.js +317 -0
- package/src/redactor.js +75 -6
- package/src/runtime-mediator.js +15 -1
- package/src/sanitizer.js +10 -0
- package/src/search-hybrid.js +139 -0
- package/src/server.js +795 -112
- package/src/swarm/worktree.js +27 -4
- package/src/swarm-config.js +102 -17
- package/src/team/domain-templates/book.json +51 -0
- package/src/team/domain-templates/business.json +44 -0
- package/src/team/domain-templates/content.json +50 -0
- package/src/team/domain-templates/design.json +44 -0
- package/src/team/domain-templates/research.json +44 -0
- package/src/team/domain-templates/software.json +40 -0
- package/src/team/generator.js +440 -3
- package/src/team/modify.js +203 -0
- package/src/team/schemas.js +48 -0
- package/src/update-apply.js +19 -3
- 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
|
-
|
|
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 (
|
|
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 };
|