@smartmemory/compose 0.3.7 → 0.3.8
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/.compose-deps.json +1 -13
- package/README.md +72 -5
- package/bin/compose.js +470 -351
- package/bin/judgment-migrate.js +387 -0
- package/contracts/comp-obs-contract.schema.json +9 -3
- package/contracts/fluid-record.schema.json +209 -0
- package/contracts/lifecycle-backfill.schema.json +322 -0
- package/dist/assets/App-Z4MU-H_F.js +916 -0
- package/dist/assets/{_baseUniq-Bo837sRJ.js → _baseUniq-ClWoCPFl.js} +1 -1
- package/dist/assets/{arc-BafGpyqE.js → arc-DY26UIVo.js} +1 -1
- package/dist/assets/{architectureDiagram-Q4EWVU46-BOBfUsqL.js → architectureDiagram-Q4EWVU46-6Ggq4DqJ.js} +1 -1
- package/dist/assets/{blockDiagram-DXYQGD6D-Dwodev1a.js → blockDiagram-DXYQGD6D-CH3Ked0l.js} +1 -1
- package/dist/assets/{browser-1ntj1-x_.js → browser-BWkrenen.js} +1 -1
- package/dist/assets/{c4Diagram-AHTNJAMY-CU_bhYag.js → c4Diagram-AHTNJAMY-Bk8dYilu.js} +1 -1
- package/dist/assets/channel-SnZzzh7k.js +1 -0
- package/dist/assets/{chunk-4BX2VUAB-p8WsDwnO.js → chunk-4BX2VUAB-BMR0XaAQ.js} +1 -1
- package/dist/assets/{chunk-4TB4RGXK-B8h7-eR0.js → chunk-4TB4RGXK-JytR14a9.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-DxeEr98s.js → chunk-55IACEB6-B4Q97BCP.js} +1 -1
- package/dist/assets/{chunk-EDXVE4YY-BYt8F151.js → chunk-EDXVE4YY-R_qarkSf.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-DGSOVeie.js → chunk-FMBD7UC4-C9s7KR9m.js} +1 -1
- package/dist/assets/{chunk-OYMX7WX6-B-QdgYR2.js → chunk-OYMX7WX6-BySQzVxc.js} +1 -1
- package/dist/assets/{chunk-QZHKN3VN-Du5UAZLs.js → chunk-QZHKN3VN-DdpSYZsW.js} +1 -1
- package/dist/assets/{chunk-YZCP3GAM-C8JbNBSk.js → chunk-YZCP3GAM-iE_tzriw.js} +1 -1
- package/dist/assets/classDiagram-6PBFFD2Q-CBu92dSH.js +1 -0
- package/dist/assets/classDiagram-v2-HSJHXN6E-CBu92dSH.js +1 -0
- package/dist/assets/clone-DgklGjHm.js +1 -0
- package/dist/assets/{cose-bilkent-S5V4N54A-O1ESaqge.js → cose-bilkent-S5V4N54A-BdlU6ZX_.js} +1 -1
- package/dist/assets/{dagre-KV5264BT-CPTmFPHw.js → dagre-KV5264BT-Cp3F5KTn.js} +1 -1
- package/dist/assets/{diagram-5BDNPKRD-B3PNrWs5.js → diagram-5BDNPKRD-DiR6_2q_.js} +1 -1
- package/dist/assets/{diagram-G4DWMVQ6-Cscfr6vc.js → diagram-G4DWMVQ6-w0i-p5HX.js} +1 -1
- package/dist/assets/{diagram-MMDJMWI5-CSfqZ-TM.js → diagram-MMDJMWI5-tIHhwUv3.js} +1 -1
- package/dist/assets/{diagram-TYMM5635-Cg4aYS7W.js → diagram-TYMM5635-BAeY3B19.js} +1 -1
- package/dist/assets/{erDiagram-SMLLAGMA-_ZqwG5pl.js → erDiagram-SMLLAGMA-Ckx_Knko.js} +1 -1
- package/dist/assets/{flowDiagram-DWJPFMVM-C83boxFT.js → flowDiagram-DWJPFMVM-DeoNka6J.js} +1 -1
- package/dist/assets/{ganttDiagram-T4ZO3ILL-CWnIjuEi.js → ganttDiagram-T4ZO3ILL-BmGnFbEg.js} +1 -1
- package/dist/assets/{gitGraphDiagram-UUTBAWPF-DrMdxZfH.js → gitGraphDiagram-UUTBAWPF-Dk48IHsx.js} +1 -1
- package/dist/assets/{graph-RE4I7Ty7.js → graph-BNzKGvoy.js} +1 -1
- package/dist/assets/{graph-Bi99_6Yf.js → graph-CI_1htl0.js} +1 -1
- package/dist/assets/{index-Rm2RE-c0.js → index-BEfrNBp8.js} +3 -3
- package/dist/assets/index-yyrA5OZd.css +1 -0
- package/dist/assets/{infoDiagram-42DDH7IO-BLmP4Epr.js → infoDiagram-42DDH7IO-BRf827i0.js} +1 -1
- package/dist/assets/{ishikawaDiagram-UXIWVN3A-yuWWshKN.js → ishikawaDiagram-UXIWVN3A-0kCZaeCM.js} +1 -1
- package/dist/assets/{journeyDiagram-VCZTEJTY-BOfhaJov.js → journeyDiagram-VCZTEJTY-rvU7ayRt.js} +1 -1
- package/dist/assets/{kanban-definition-6JOO6SKY-Bbolde15.js → kanban-definition-6JOO6SKY-DpQwX1C5.js} +1 -1
- package/dist/assets/{layout-BSf33zm8.js → layout-BI8cXFPI.js} +1 -1
- package/dist/assets/{linear-AvSTWMqx.js → linear-a0glcDiw.js} +1 -1
- package/dist/assets/{min-QBM8H4xN.js → min-vPHfnXcC.js} +1 -1
- package/dist/assets/{mindmap-definition-QFDTVHPH-BuvgtqIc.js → mindmap-definition-QFDTVHPH-D14eF-7C.js} +1 -1
- package/dist/assets/mobile-B7m9EO9D.js +17 -0
- package/dist/assets/{pieDiagram-DEJITSTG-DIzF16vh.js → pieDiagram-DEJITSTG-Cno-gETh.js} +1 -1
- package/dist/assets/{quadrantDiagram-34T5L4WZ-D-mbUIjS.js → quadrantDiagram-34T5L4WZ-BUQM1Hfm.js} +1 -1
- package/dist/assets/{requirementDiagram-MS252O5E-CEs4kCLd.js → requirementDiagram-MS252O5E-pOXlN2-q.js} +1 -1
- package/dist/assets/{sankeyDiagram-XADWPNL6-DFsnCr9n.js → sankeyDiagram-XADWPNL6-Crynd3_b.js} +1 -1
- package/dist/assets/{sequenceDiagram-FGHM5R23-BEJYdTjQ.js → sequenceDiagram-FGHM5R23-D9fZdCM8.js} +1 -1
- package/dist/assets/{stateDiagram-FHFEXIEX-BBXs57uY.js → stateDiagram-FHFEXIEX-CW9qVec8.js} +1 -1
- package/dist/assets/stateDiagram-v2-QKLJ7IA2-DkVLzHbY.js +1 -0
- package/dist/assets/{timeline-definition-GMOUNBTQ-BGvLoVAY.js → timeline-definition-GMOUNBTQ-BcHzhm_8.js} +1 -1
- package/dist/assets/{vennDiagram-DHZGUBPP-9LaBTMe0.js → vennDiagram-DHZGUBPP-BfytJcWk.js} +1 -1
- package/dist/assets/{wardley-RL74JXVD-P4MEqMTP.js → wardley-RL74JXVD-DLj-IjyB.js} +1 -1
- package/dist/assets/{wardleyDiagram-NUSXRM2D-o-tmxnlC.js → wardleyDiagram-NUSXRM2D-Ds0Ue68c.js} +1 -1
- package/dist/assets/{xychartDiagram-5P7HB3ND-Dpn7V6qk.js → xychartDiagram-5P7HB3ND-vjWDXFL6.js} +1 -1
- package/dist/index.html +3 -3
- package/lib/agent-string.js +7 -5
- package/lib/append-integrity.js +81 -0
- package/lib/backfill-evidence.js +109 -0
- package/lib/bug-escalation.js +9 -0
- package/lib/build-stream-schema.js +3 -1
- package/lib/build-stream-writer.js +25 -0
- package/lib/build.js +874 -170
- package/lib/canon-guard.js +28 -6
- package/lib/canon-override.js +196 -0
- package/lib/canon-registry.js +104 -0
- package/lib/cli-commands.js +144 -0
- package/lib/codex-preflight.js +26 -13
- package/lib/colleague/context.js +215 -0
- package/lib/colleague/writeback.js +95 -0
- package/lib/completion-gate.js +1421 -0
- package/lib/completion-writer.js +47 -47
- package/lib/consumer-fanout.js +105 -11
- package/lib/coverage-gate.js +200 -0
- package/lib/dir-lock.js +170 -0
- package/lib/dispatch-ledger.js +3 -3
- package/lib/feature-json.js +1 -1
- package/lib/feature-reconciler.js +8 -0
- package/lib/feature-validator.js +64 -1
- package/lib/feature-writer.js +57 -2
- package/lib/fluid/factory.js +167 -0
- package/lib/fluid/ideabox-dates.js +73 -0
- package/lib/fluid/ideabox-migrate.js +154 -0
- package/lib/fluid/ideabox-ops.js +585 -0
- package/lib/fluid/ideabox-view.js +146 -0
- package/lib/fluid/import-ideabox.js +186 -0
- package/lib/fluid/local-provider.js +606 -0
- package/lib/fluid/provider.js +684 -0
- package/lib/fluid/record-shape.js +214 -0
- package/lib/fluid/record-store.js +328 -0
- package/lib/fluid/render-ideabox.js +261 -0
- package/lib/fluid/schema.js +40 -0
- package/lib/fluid/smartmemory-provider.js +1695 -0
- package/lib/gsd.js +63 -23
- package/lib/guard-cli.js +175 -0
- package/lib/guard-custody.js +141 -0
- package/lib/guard-descriptors.js +530 -0
- package/lib/guard-enrol.js +254 -0
- package/lib/health-score.js +1 -1
- package/lib/ideabox-cli.js +315 -0
- package/lib/ideabox.js +121 -21
- package/lib/judgment/store/index.js +9 -1
- package/lib/judgment/store/records.js +1 -1
- package/lib/judgment/trace.js +380 -0
- package/lib/judgment-decision-write.js +277 -0
- package/lib/judgment-decisions.js +466 -0
- package/lib/judgment-gen.js +5 -1
- package/lib/judgment-writer.js +56 -2
- package/lib/lifecycle-modes.js +4 -4
- package/lib/lineage.js +400 -0
- package/lib/local-claude-connector.js +52 -1
- package/lib/maya-client.js +302 -0
- package/lib/maya-config.js +53 -0
- package/lib/maya-identity.js +283 -0
- package/lib/migrate-anon.js +5 -0
- package/lib/migrate-roadmap.js +15 -0
- package/lib/new.js +13 -1
- package/lib/pipeline-compat.js +104 -0
- package/lib/policy-catalog.js +295 -0
- package/lib/policy-check.js +0 -0
- package/lib/process-termination.js +98 -0
- package/lib/resolve-workspace.js +5 -1
- package/lib/result-normalizer.js +396 -199
- package/lib/roadmap-errors.js +65 -0
- package/lib/roadmap-preservers.js +24 -4
- package/lib/roadmap-residue.js +299 -0
- package/lib/smartmemory-client.js +614 -78
- package/lib/smartmemory-config.js +54 -0
- package/lib/smartmemory-ingest.js +19 -2
- package/lib/step-prompt.js +7 -6
- package/lib/stratum-engine.js +53 -4
- package/lib/stratum-mcp-client.js +271 -36
- package/lib/test-bootstrap.js +31 -0
- package/lib/tool-inventory.js +122 -0
- package/lib/version-check.js +91 -19
- package/lib/vision-writer.js +88 -1
- package/package.json +7 -6
- package/pipelines/bug-fix.stratum.yaml +205 -211
- package/pipelines/build-quick.profiles.json +12 -0
- package/pipelines/build-quick.stratum.yaml +263 -350
- package/pipelines/content.stratum.yaml +81 -77
- package/pipelines/coverage-sweep.stratum.yaml +49 -30
- package/pipelines/plan.stratum.yaml +76 -86
- package/pipelines/refactor.stratum.yaml +125 -125
- package/pipelines/research.stratum.yaml +56 -58
- package/pipelines/review-fix.profiles.json +6 -0
- package/pipelines/review-fix.stratum.yaml +110 -83
- package/presets/team-feature.profiles.json +6 -0
- package/presets/team-feature.stratum.yaml +93 -66
- package/presets/team-research.profiles.json +6 -0
- package/presets/team-research.stratum.yaml +89 -80
- package/presets/team-review.profiles.json +8 -0
- package/presets/team-review.stratum.yaml +98 -80
- package/scripts/cost-census.mjs +70 -0
- package/scripts/guard-sign/compose-guard-sign.sh +62 -0
- package/server/agent-health.js +22 -0
- package/server/agent-hooks.js +14 -1
- package/server/agent-server.js +5 -248
- package/server/agent-spawn.js +3 -4
- package/server/agent-workspace.js +294 -0
- package/server/build-routes.js +6 -5
- package/server/build-stream-bridge.js +53 -0
- package/server/cc-session-watcher.js +4 -1
- package/server/coalescing-buffer.js +7 -1
- package/server/completion-projection.js +228 -0
- package/server/compose-mcp-tools.js +109 -23
- package/server/compose-mcp.js +88 -882
- package/server/decision-event-emit.js +41 -2
- package/server/decision-event-id.js +17 -0
- package/server/decision-events-snapshot.js +3 -0
- package/server/design-routes.js +14 -8
- package/server/feature-scan.js +76 -2
- package/server/file-watcher.js +170 -21
- package/server/ideabox-routes.js +166 -224
- package/server/index.js +70 -100
- package/server/lifecycle-guard.js +240 -10
- package/server/lifecycle-phase-history.js +276 -0
- package/server/maya-routes.js +507 -0
- package/server/mcp-tool-defs.js +940 -0
- package/server/mcp-tool-policy.js +34 -2
- package/server/model-tiers.js +22 -5
- package/server/pipeline-routes.js +21 -11
- package/server/project-root.js +58 -19
- package/server/remote-utils.js +3 -1
- package/server/schema-validator.js +7 -1
- package/server/session-manager.js +5 -6
- package/server/session-routes.js +3 -1
- package/server/stratum-client.js +57 -10
- package/server/stratum-sync.js +6 -3
- package/server/summarizer.js +3 -4
- package/server/supervisor.js +0 -1
- package/server/vision-routes.js +208 -98
- package/server/vision-server.js +86 -23
- package/server/vision-store.js +60 -6
- package/server/vision-utils.js +3 -4
- package/server/workspace-activity.js +18 -0
- package/server/workspace-middleware.js +2 -2
- package/server/workspace-runtime.js +243 -0
- package/server/worktree-gc.js +1 -0
- package/dist/assets/App-PkZzHeMj.js +0 -894
- package/dist/assets/channel-qVK_qn4E.js +0 -1
- package/dist/assets/classDiagram-6PBFFD2Q-B8UcfC1q.js +0 -1
- package/dist/assets/classDiagram-v2-HSJHXN6E-B8UcfC1q.js +0 -1
- package/dist/assets/clone-Pu3RyLUh.js +0 -1
- package/dist/assets/index-LIwREYgH.css +0 -1
- package/dist/assets/mobile-BnXEOE3U.js +0 -17
- package/dist/assets/stateDiagram-v2-QKLJ7IA2-BqKuX4rj.js +0 -1
- package/lib/staleness.js +0 -87
- package/server/ideabox-cache.js +0 -77
|
@@ -23,10 +23,41 @@ export const SETUP_TOOLS = new Set([
|
|
|
23
23
|
'set_workspace', 'get_workspace', 'bind_session', 'get_current_session',
|
|
24
24
|
]);
|
|
25
25
|
|
|
26
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* Management/approval/completion tools an implementer context must not wield.
|
|
28
|
+
*
|
|
29
|
+
* COMP-COVERAGE-GATE (2026-08-24) added the last two, found by
|
|
30
|
+
* `checkAuthorizationCoverage`'s C4 check — mutating tools this list had never
|
|
31
|
+
* been asked about. Both postdate COMP-MCP-ENFORCE-1's charter ("cannot
|
|
32
|
+
* self-approve, self-complete, or mutate roadmap status") and no design ever
|
|
33
|
+
* ruled that an implementer may call them:
|
|
34
|
+
*
|
|
35
|
+
* - `canon_override_grant` — the escape hatch FROM canon enforcement was
|
|
36
|
+
* callable by the profile SUBJECT to it. COMP-CANON-OVERRIDE already reasoned
|
|
37
|
+
* that the override must not be grantable for its own governance state
|
|
38
|
+
* (`overrideEligible: false`); this is the same argument one level up, at the
|
|
39
|
+
* caller instead of the target. An implementer that hits a canon block must
|
|
40
|
+
* escalate, not self-authorise.
|
|
41
|
+
* - `roadmap_xref_push` — writes EXTERNAL trackers (github issues, sibling
|
|
42
|
+
* repos). An implementer session should not be reaching outside the repo.
|
|
43
|
+
*
|
|
44
|
+
* The eight `judgment_*` writers were the other eight C4 findings and are
|
|
45
|
+
* deliberately NOT here: COMP-JUDGMENT-WRITER/design.md:137 rules that they
|
|
46
|
+
* "stay implementer/orchestrator-only", so an implementer writing judgment is a
|
|
47
|
+
* recorded decision, not an omission. That ruling is now recorded machine-side
|
|
48
|
+
* in `C4_EXCEPTIONS` (lib/coverage-gate.js) so the gate stops re-raising it.
|
|
49
|
+
*
|
|
50
|
+
* Verified before adding the two: no pipeline spec, prompt template or server
|
|
51
|
+
* flow invokes either from an implementer-profile session.
|
|
52
|
+
*/
|
|
27
53
|
const IMPLEMENTER_DENY = [
|
|
28
54
|
'approve_gate', 'complete_feature', 'kill_feature',
|
|
29
55
|
'set_feature_status', 'add_roadmap_entry', 'record_completion', 'propose_followup',
|
|
56
|
+
// COMP-LIFECYCLE-BACKFILL: a completion is a management act, backfilled or not.
|
|
57
|
+
'backfill_completion',
|
|
58
|
+
// ── added by COMP-COVERAGE-GATE C4 ──
|
|
59
|
+
'canon_override_grant',
|
|
60
|
+
'roadmap_xref_push',
|
|
30
61
|
];
|
|
31
62
|
|
|
32
63
|
/** Reviewer (read-only) may call only these. */
|
|
@@ -37,6 +68,7 @@ const REVIEWER_ALLOW = [
|
|
|
37
68
|
'validate_feature', 'validate_project', 'roadmap_diff', 'get_roadmap', 'roadmap_graph_check', 'assess_feature_artifacts',
|
|
38
69
|
'set_workspace', 'get_workspace', 'bind_session',
|
|
39
70
|
'get_judgment_state', // COMP-JUDGMENT-WRITER: read-only; the eight judgment write tools stay reviewer-denied
|
|
71
|
+
'get_judgment_trace', // COMP-JUDGMENT-PRECEDENT: read-only ancestry walk
|
|
40
72
|
];
|
|
41
73
|
|
|
42
74
|
export const PROFILE_POLICY = {
|
|
@@ -47,7 +79,7 @@ export const PROFILE_POLICY = {
|
|
|
47
79
|
|
|
48
80
|
/** phase → management tools re-permitted for DENY-mode profiles in that phase. */
|
|
49
81
|
export const PHASE_REFINEMENT = {
|
|
50
|
-
ship: new Set(['complete_feature', 'record_completion']),
|
|
82
|
+
ship: new Set(['complete_feature', 'record_completion', 'backfill_completion']),
|
|
51
83
|
};
|
|
52
84
|
|
|
53
85
|
// Strictness ordering — a bind hint may only NARROW (raise strictness), never widen.
|
package/server/model-tiers.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* model-tiers.js — Model tier routing for STRAT-TIER.
|
|
3
3
|
*
|
|
4
|
-
* Maps symbolic tier names to
|
|
4
|
+
* Maps symbolic tier names to provider-specific model IDs.
|
|
5
5
|
* Tiers let pipeline specs declare intent (critical / standard / fast)
|
|
6
6
|
* without hard-coding model strings — the map here is the single source of truth.
|
|
7
7
|
*/
|
|
@@ -13,6 +13,21 @@ export const MODEL_TIERS = {
|
|
|
13
13
|
fast: 'claude-haiku-4-5-20251001',
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
+
export const CODEX_MODEL_TIERS = {
|
|
17
|
+
critical: 'gpt-6-astra',
|
|
18
|
+
standard: 'gpt-5.6-terra',
|
|
19
|
+
fast: 'gpt-5.3-codex-spark',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// C12: codex efforts follow the routing convention — `low` is for trivial
|
|
23
|
+
// mechanical work only, and the fast tier is a model choice, not a
|
|
24
|
+
// reasoning-quality choice. Fast runs the cheap model at routine effort.
|
|
25
|
+
const CODEX_TIER_THINKING = {
|
|
26
|
+
critical: { mode: null, effort: 'high' },
|
|
27
|
+
standard: { mode: null, effort: 'high' },
|
|
28
|
+
fast: { mode: null, effort: 'medium' },
|
|
29
|
+
};
|
|
30
|
+
|
|
16
31
|
/**
|
|
17
32
|
* Default thinking config per tier.
|
|
18
33
|
* - Opus 4.7 / Sonnet 4.6 support adaptive thinking and the effort parameter.
|
|
@@ -32,9 +47,10 @@ export const TIER_THINKING = {
|
|
|
32
47
|
* @param {string|null|undefined} tier
|
|
33
48
|
* @returns {string|null} Model ID, or null if tier is unknown / not provided.
|
|
34
49
|
*/
|
|
35
|
-
export function resolveTierModel(tier) {
|
|
50
|
+
export function resolveTierModel(tier, provider = 'claude') {
|
|
36
51
|
if (!tier) return null;
|
|
37
|
-
|
|
52
|
+
const models = provider === 'codex' ? CODEX_MODEL_TIERS : provider === 'claude' ? MODEL_TIERS : {};
|
|
53
|
+
return models[tier] ?? null;
|
|
38
54
|
}
|
|
39
55
|
|
|
40
56
|
/**
|
|
@@ -43,7 +59,8 @@ export function resolveTierModel(tier) {
|
|
|
43
59
|
* @param {string|null|undefined} tier
|
|
44
60
|
* @returns {{ mode: string, effort: string|null }|null}
|
|
45
61
|
*/
|
|
46
|
-
export function resolveTierThinking(tier) {
|
|
62
|
+
export function resolveTierThinking(tier, provider = 'claude') {
|
|
47
63
|
if (!tier) return null;
|
|
48
|
-
|
|
64
|
+
const thinking = provider === 'codex' ? CODEX_TIER_THINKING : provider === 'claude' ? TIER_THINKING : {};
|
|
65
|
+
return thinking[tier] ?? null;
|
|
49
66
|
}
|
|
@@ -16,6 +16,7 @@ import { readdirSync, readFileSync, writeFileSync, unlinkSync, existsSync, mkdir
|
|
|
16
16
|
import { join, basename, dirname } from 'node:path';
|
|
17
17
|
import { randomUUID, createHash } from 'node:crypto';
|
|
18
18
|
import YAML from 'yaml';
|
|
19
|
+
import { tsCompatibilityOf } from '../lib/pipeline-compat.js';
|
|
19
20
|
|
|
20
21
|
/** sha-256 hex of a string (COMP-PIPE-EDIT-6 conflict-detection baseline). */
|
|
21
22
|
function sha256(text) {
|
|
@@ -139,7 +140,12 @@ function extractSteps(parsed) {
|
|
|
139
140
|
* The shipped specs store `metadata` as a leading `#` comment (or omit it), so
|
|
140
141
|
* the metadata-based `loadTemplates` never surfaces them. The editor discovers
|
|
141
142
|
* by filename instead.
|
|
142
|
-
*
|
|
143
|
+
* COMP-PIPELINE-QUARANTINE: each entry also carries `tsCompatible` — false for a
|
|
144
|
+
* spec left on the retired v0.3 dialect, which the engine cannot run at all. The
|
|
145
|
+
* editor lists specs it cannot execute, so the flag is what lets the surface say
|
|
146
|
+
* so instead of presenting them as ordinary choices.
|
|
147
|
+
*
|
|
148
|
+
* @returns {Array<{ file, version, flows: string[], tsCompatible: boolean, tsIncompatibleReason: string|null }>}
|
|
143
149
|
*/
|
|
144
150
|
function listSpecFiles(pipelinesDir) {
|
|
145
151
|
let files;
|
|
@@ -151,14 +157,24 @@ function listSpecFiles(pipelinesDir) {
|
|
|
151
157
|
const out = [];
|
|
152
158
|
for (const file of files) {
|
|
153
159
|
try {
|
|
154
|
-
const
|
|
160
|
+
const raw = readFileSync(join(pipelinesDir, file), 'utf-8');
|
|
161
|
+
const parsed = YAML.parse(raw);
|
|
162
|
+
const compat = tsCompatibilityOf(raw);
|
|
155
163
|
out.push({
|
|
156
164
|
file,
|
|
157
165
|
version: parsed?.version ?? null,
|
|
158
166
|
flows: flowNamesOf(parsed),
|
|
167
|
+
tsCompatible: compat.compatible,
|
|
168
|
+
tsIncompatibleReason: compat.reason,
|
|
159
169
|
});
|
|
160
170
|
} catch {
|
|
161
|
-
out.push({
|
|
171
|
+
out.push({
|
|
172
|
+
file,
|
|
173
|
+
version: null,
|
|
174
|
+
flows: [],
|
|
175
|
+
tsCompatible: false,
|
|
176
|
+
tsIncompatibleReason: 'spec is not parseable YAML',
|
|
177
|
+
});
|
|
162
178
|
}
|
|
163
179
|
}
|
|
164
180
|
return out;
|
|
@@ -603,33 +619,27 @@ function createNewFlowNode(doc, model, name) {
|
|
|
603
619
|
}
|
|
604
620
|
|
|
605
621
|
// ---------------------------------------------------------------------------
|
|
606
|
-
//
|
|
622
|
+
// Workspace-local draft state (disk is authoritative)
|
|
607
623
|
// ---------------------------------------------------------------------------
|
|
608
624
|
|
|
609
|
-
let currentDraft = null;
|
|
610
|
-
|
|
611
625
|
const DRAFT_FILE = 'pipeline-draft.json';
|
|
612
626
|
|
|
613
627
|
function saveDraft(dataDir, draft) {
|
|
614
|
-
currentDraft = draft;
|
|
615
628
|
mkdirSync(dataDir, { recursive: true });
|
|
616
629
|
writeFileSync(join(dataDir, DRAFT_FILE), JSON.stringify(draft, null, 2));
|
|
617
630
|
}
|
|
618
631
|
|
|
619
632
|
function loadDraft(dataDir) {
|
|
620
|
-
if (currentDraft) return currentDraft;
|
|
621
633
|
const p = join(dataDir, DRAFT_FILE);
|
|
622
634
|
if (!existsSync(p)) return null;
|
|
623
635
|
try {
|
|
624
|
-
|
|
625
|
-
return currentDraft;
|
|
636
|
+
return JSON.parse(readFileSync(p, 'utf-8'));
|
|
626
637
|
} catch {
|
|
627
638
|
return null;
|
|
628
639
|
}
|
|
629
640
|
}
|
|
630
641
|
|
|
631
642
|
function clearDraft(dataDir) {
|
|
632
|
-
currentDraft = null;
|
|
633
643
|
const p = join(dataDir, DRAFT_FILE);
|
|
634
644
|
try { if (existsSync(p)) unlinkSync(p); } catch { /* ignore */ }
|
|
635
645
|
}
|
package/server/project-root.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import path from 'node:path';
|
|
15
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
15
16
|
import fs from 'node:fs';
|
|
16
17
|
import { fileURLToPath } from 'node:url';
|
|
17
18
|
import { findProjectRoot } from './find-root.js';
|
|
@@ -42,15 +43,40 @@ let _targetRoot = (() => {
|
|
|
42
43
|
|
|
43
44
|
let _dataDir = path.join(_targetRoot, '.compose', 'data');
|
|
44
45
|
let _configCache = null;
|
|
46
|
+
const projectContext = new AsyncLocalStorage();
|
|
47
|
+
|
|
48
|
+
/** Pin a request and all of its asynchronous descendants to one workspace. */
|
|
49
|
+
export function withProjectContext(binding, fn) {
|
|
50
|
+
return projectContext.run(binding, fn);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Keep asynchronous work pinned even when its HTTP client disconnects. */
|
|
54
|
+
export async function trackProjectWork(operation) {
|
|
55
|
+
const binding = projectContext.getStore();
|
|
56
|
+
if (!binding) return operation();
|
|
57
|
+
binding.activeWork = (binding.activeWork ?? 0) + 1;
|
|
58
|
+
try { return await operation(); }
|
|
59
|
+
finally { binding.activeWork--; }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Validate a destination before changing the process default or its services. */
|
|
63
|
+
export function prepareProject(newRoot) {
|
|
64
|
+
const targetRoot = path.resolve(newRoot);
|
|
65
|
+
if (!fs.statSync(targetRoot).isDirectory()) throw new Error(`Project path is not a directory: ${targetRoot}`);
|
|
66
|
+
const dataDir = path.join(targetRoot, '.compose', 'data');
|
|
67
|
+
fs.mkdirSync(dataDir, { recursive: true });
|
|
68
|
+
const config = readProjectConfig(targetRoot, true);
|
|
69
|
+
return { targetRoot, dataDir, config };
|
|
70
|
+
}
|
|
45
71
|
|
|
46
72
|
/** The target project being developed. */
|
|
47
|
-
export function getTargetRoot() { return _targetRoot; }
|
|
73
|
+
export function getTargetRoot() { return projectContext.getStore()?.targetRoot ?? _targetRoot; }
|
|
48
74
|
|
|
49
75
|
/** Data directory for Compose state. Lives in the target project. */
|
|
50
|
-
export function getDataDir() { return _dataDir; }
|
|
76
|
+
export function getDataDir() { return projectContext.getStore()?.dataDir ?? _dataDir; }
|
|
51
77
|
|
|
52
78
|
let _currentWorkspaceId = null;
|
|
53
|
-
export function getCurrentWorkspaceId() { return _currentWorkspaceId; }
|
|
79
|
+
export function getCurrentWorkspaceId() { return projectContext.getStore()?.workspaceId ?? _currentWorkspaceId; }
|
|
54
80
|
export function setCurrentWorkspaceId(id) { _currentWorkspaceId = id; }
|
|
55
81
|
|
|
56
82
|
// ---------------------------------------------------------------------------
|
|
@@ -73,15 +99,12 @@ export function onProjectSwitch(fn) {
|
|
|
73
99
|
* @returns {{ targetRoot: string, dataDir: string }}
|
|
74
100
|
*/
|
|
75
101
|
export function switchProject(newRoot) {
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
_configCache = null;
|
|
83
|
-
fs.mkdirSync(_dataDir, { recursive: true });
|
|
84
|
-
console.log(`[project-root] Switched to: ${resolved}`);
|
|
102
|
+
const prepared = prepareProject(newRoot);
|
|
103
|
+
_targetRoot = prepared.targetRoot;
|
|
104
|
+
_dataDir = prepared.dataDir;
|
|
105
|
+
_configCache = prepared.config;
|
|
106
|
+
_currentWorkspaceId = null;
|
|
107
|
+
console.error(`[project-root] Switched to: ${_targetRoot}`);
|
|
85
108
|
for (const fn of _switchListeners) {
|
|
86
109
|
try { fn(_targetRoot, _dataDir); } catch (e) { console.error('[project-root] Switch listener error:', e.message); }
|
|
87
110
|
}
|
|
@@ -107,18 +130,34 @@ function cloneConfig(obj) {
|
|
|
107
130
|
return JSON.parse(JSON.stringify(obj));
|
|
108
131
|
}
|
|
109
132
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const configPath = path.join(getTargetRoot(), '.compose', 'compose.json');
|
|
133
|
+
function readProjectConfig(root, strict = false) {
|
|
134
|
+
const file = path.join(root, '.compose', 'compose.json');
|
|
113
135
|
try {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
136
|
+
const config = JSON.parse(fs.readFileSync(file, 'utf-8'));
|
|
137
|
+
if (!config || typeof config !== 'object' || Array.isArray(config)) throw new Error('project config must be a JSON object');
|
|
138
|
+
return { ...config, capabilities: { ...DEFAULT_CONFIG.capabilities, ...config.capabilities } };
|
|
139
|
+
} catch (err) {
|
|
140
|
+
if (strict && err.code !== 'ENOENT') {
|
|
141
|
+
// C10: a malformed config is fatal on purpose — silently falling back to
|
|
142
|
+
// defaults switched the workspace into a configuration nobody wrote. Say
|
|
143
|
+
// which file is broken and what to do about it; a bare SyntaxError does
|
|
144
|
+
// neither. A MISSING file is still fine (defaults apply).
|
|
145
|
+
throw Object.assign(
|
|
146
|
+
new Error(`Invalid Compose config at ${file}: ${err.message}. Fix the file or delete it to fall back to defaults.`),
|
|
147
|
+
{ code: 'InvalidProjectConfig', file, cause: err },
|
|
148
|
+
);
|
|
149
|
+
}
|
|
118
150
|
return cloneConfig(DEFAULT_CONFIG);
|
|
119
151
|
}
|
|
120
152
|
}
|
|
121
153
|
|
|
154
|
+
export function loadProjectConfig() {
|
|
155
|
+
const context = projectContext.getStore();
|
|
156
|
+
if (context) return cloneConfig(context.config ?? readProjectConfig(context.targetRoot));
|
|
157
|
+
_configCache ??= readProjectConfig(_targetRoot);
|
|
158
|
+
return cloneConfig(_configCache);
|
|
159
|
+
}
|
|
160
|
+
|
|
122
161
|
export function resolveProjectPath(key) {
|
|
123
162
|
const config = loadProjectConfig();
|
|
124
163
|
// resolvePathValue handles in-root / ../-escaping / absolute overrides.
|
package/server/remote-utils.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import http from 'node:http';
|
|
18
|
-
import { loadProjectConfig } from './project-root.js';
|
|
18
|
+
import { loadProjectConfig, getTargetRoot } from './project-root.js';
|
|
19
19
|
|
|
20
20
|
// ---------------------------------------------------------------------------
|
|
21
21
|
// resolveComposeHost
|
|
@@ -83,6 +83,8 @@ export function attachAgentProxy(app, { agentPort }) {
|
|
|
83
83
|
// Inject the server-side sensitive token; strip any client-sent credential
|
|
84
84
|
delete headers['x-compose-token'];
|
|
85
85
|
delete headers['authorization'];
|
|
86
|
+
// Only the resolved server workspace may choose the SDK cwd.
|
|
87
|
+
headers['x-compose-project-root'] = getTargetRoot();
|
|
86
88
|
const apiToken = process.env.COMPOSE_API_TOKEN;
|
|
87
89
|
if (apiToken) headers['x-compose-token'] = apiToken;
|
|
88
90
|
|
|
@@ -13,7 +13,13 @@ const cache = new Map();
|
|
|
13
13
|
function load(schemaPath = DEFAULT_SCHEMA_PATH) {
|
|
14
14
|
if (cache.has(schemaPath)) return cache.get(schemaPath);
|
|
15
15
|
const schema = JSON.parse(readFileSync(schemaPath, 'utf8'));
|
|
16
|
-
|
|
16
|
+
// `$data: true` lets a schema pin one field to another's value at validation
|
|
17
|
+
// time (`{"const": {"$data": "2/policy_checksum"}}` in
|
|
18
|
+
// contracts/lifecycle-backfill.schema.json). Without the option Ajv treats the
|
|
19
|
+
// `$data` object as a LITERAL to compare against, so such a schema rejects
|
|
20
|
+
// every document — including the valid ones. Additive: no other schema here
|
|
21
|
+
// uses `$data`, and enabling it changes nothing for them.
|
|
22
|
+
const ajv = new Ajv({ strict: false, allErrors: true, $data: true });
|
|
17
23
|
addFormats(ajv);
|
|
18
24
|
ajv.addSchema(schema);
|
|
19
25
|
const entry = { schema, ajv };
|
|
@@ -16,8 +16,6 @@ import { serializeSession, persistSession, readLastSession, readSessionsByFeatur
|
|
|
16
16
|
|
|
17
17
|
import { getTargetRoot, getDataDir } from './project-root.js';
|
|
18
18
|
|
|
19
|
-
const PROJECT_ROOT = getTargetRoot();
|
|
20
|
-
const SESSIONS_FILE = path.join(getDataDir(), 'sessions.json');
|
|
21
19
|
|
|
22
20
|
/** Tools whose events count toward the Haiku summary batch threshold */
|
|
23
21
|
const SIGNIFICANT_TOOLS = new Set(['Write', 'Edit', 'Bash', 'NotebookEdit']);
|
|
@@ -26,7 +24,8 @@ const SIGNIFICANT_TOOLS = new Set(['Write', 'Edit', 'Bash', 'NotebookEdit']);
|
|
|
26
24
|
const BATCH_SIZE = 4;
|
|
27
25
|
|
|
28
26
|
export class SessionManager {
|
|
29
|
-
constructor({ getFeaturePhase, featureRoot, sessionsFile } = {}) {
|
|
27
|
+
constructor({ getFeaturePhase, featureRoot, sessionsFile, projectRoot = getTargetRoot() } = {}) {
|
|
28
|
+
this._projectRoot = projectRoot;
|
|
30
29
|
/** @type {object|null} Current active session */
|
|
31
30
|
this.currentSession = null;
|
|
32
31
|
|
|
@@ -37,7 +36,7 @@ export class SessionManager {
|
|
|
37
36
|
this._featureRoot = featureRoot || 'docs/features';
|
|
38
37
|
|
|
39
38
|
/** @type {string} Path to sessions.json — injectable for tests */
|
|
40
|
-
this._sessionsFile = sessionsFile ||
|
|
39
|
+
this._sessionsFile = sessionsFile || path.join(getDataDir(), 'sessions.json');
|
|
41
40
|
|
|
42
41
|
/** @type {Array<{tool,filePath,input,itemIds,timestamp}>} Buffered significant events */
|
|
43
42
|
this._pendingBatch = [];
|
|
@@ -332,8 +331,8 @@ export class SessionManager {
|
|
|
332
331
|
const batch = this._pendingBatch.splice(0);
|
|
333
332
|
|
|
334
333
|
try {
|
|
335
|
-
const prompt = buildSummaryPrompt(batch,
|
|
336
|
-
const result = await summarize(prompt, { projectRoot:
|
|
334
|
+
const prompt = buildSummaryPrompt(batch, this._projectRoot);
|
|
335
|
+
const result = await summarize(prompt, { projectRoot: this._projectRoot });
|
|
337
336
|
if (result) {
|
|
338
337
|
this._distributeSummary(result, batch);
|
|
339
338
|
for (const fn of this._summaryListeners) {
|
package/server/session-routes.js
CHANGED
|
@@ -245,7 +245,9 @@ export function attachSessionRoutes(app, { sessionManager, scheduleBroadcast, br
|
|
|
245
245
|
session: sessionData || recentSessions[0] || null,
|
|
246
246
|
lifecycle: item?.lifecycle ? {
|
|
247
247
|
currentPhase: item.lifecycle.currentPhase,
|
|
248
|
-
phaseHistory: (item.lifecycle.phaseHistory || []).map(h => ({
|
|
248
|
+
phaseHistory: (item.lifecycle.phaseHistory || []).map(h => ({
|
|
249
|
+
phase: h.phase, enteredAt: h.enteredAt, exitedAt: h.exitedAt, origin: h.origin,
|
|
250
|
+
})),
|
|
249
251
|
artifacts: item.lifecycle.artifacts || {},
|
|
250
252
|
pendingGate: item.lifecycle.pendingGate || null,
|
|
251
253
|
} : null,
|
package/server/stratum-client.js
CHANGED
|
@@ -191,9 +191,12 @@ async function runMutation(args) {
|
|
|
191
191
|
* @param {number} timeoutMs
|
|
192
192
|
* @returns {Promise<{ stdout: string, stderr: string, code: number }>}
|
|
193
193
|
*/
|
|
194
|
-
function spawnStratumStdin(args, inputJson, timeoutMs, bin) {
|
|
194
|
+
function spawnStratumStdin(args, inputJson, timeoutMs, bin, extraEnv) {
|
|
195
195
|
return new Promise((resolve) => {
|
|
196
|
-
const proc = _execFile(bin, args, {
|
|
196
|
+
const proc = _execFile(bin, args, {
|
|
197
|
+
timeout: timeoutMs,
|
|
198
|
+
...(extraEnv ? { env: { ...process.env, ...extraEnv } } : {}),
|
|
199
|
+
}, (err, out, err2) => {
|
|
197
200
|
resolve(_spawnResult(bin, err, out, err2));
|
|
198
201
|
});
|
|
199
202
|
// Same both-paths-settle-identically contract as spawnStratum.
|
|
@@ -219,8 +222,8 @@ function spawnStratumStdin(args, inputJson, timeoutMs, bin) {
|
|
|
219
222
|
*
|
|
220
223
|
* @returns {Promise<any>} parsed JSON result or { error }
|
|
221
224
|
*/
|
|
222
|
-
async function runGuard(action, kwargs, timeoutMs = MUTATION_TIMEOUT_MS) {
|
|
223
|
-
const result = await spawnStratumStdin(['guard', action], JSON.stringify(kwargs), timeoutMs, flowGateBin());
|
|
225
|
+
async function runGuard(action, kwargs, timeoutMs = MUTATION_TIMEOUT_MS, extraEnv) {
|
|
226
|
+
const result = await spawnStratumStdin(['guard', action], JSON.stringify(kwargs), timeoutMs, flowGateBin(), extraEnv);
|
|
224
227
|
|
|
225
228
|
if (result.code === -1) {
|
|
226
229
|
return { error: { code: 'TIMEOUT', message: 'Stratum guard timed out', detail: '' } };
|
|
@@ -230,12 +233,16 @@ async function runGuard(action, kwargs, timeoutMs = MUTATION_TIMEOUT_MS) {
|
|
|
230
233
|
return { error: { code: 'SPAWN', message: result.stderr, detail: '' } };
|
|
231
234
|
}
|
|
232
235
|
if (result.code !== 0) {
|
|
233
|
-
|
|
234
|
-
try {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
236
|
+
let canonical = null;
|
|
237
|
+
try { canonical = JSON.parse(result.stdout); } catch { /* not canonical */ }
|
|
238
|
+
// guard_not_found is a normal answer to a policy/history query (every
|
|
239
|
+
// never-registered feature returns it); logging it as an error spammed
|
|
240
|
+
// 363 lines from one `compose guard status` on 2026-09-06.
|
|
241
|
+
if (canonical?.error_type !== 'guard_not_found') {
|
|
242
|
+
console.error('[stratum-client] guard error stderr:', result.stderr);
|
|
238
243
|
}
|
|
244
|
+
if (canonical) return canonical; // canonical { status:"error", ... }
|
|
245
|
+
return { error: { code: 'UNKNOWN', message: 'Stratum guard failed', detail: '' } };
|
|
239
246
|
}
|
|
240
247
|
try {
|
|
241
248
|
return JSON.parse(result.stdout);
|
|
@@ -346,7 +353,7 @@ export async function guardRegister({ resourceId, graph, edgePredicates, initial
|
|
|
346
353
|
* predicates verify server-side. A refusal is a normal result (status:"refused").
|
|
347
354
|
* @returns {Promise<{status:string,verdict:object,ledger_ref:string,current_state:string}|ErrorResult>}
|
|
348
355
|
*/
|
|
349
|
-
export async function guardTransition({ resourceId, fromState, toState, artifacts, modifiedFiles, idempotencyKey, resolvedBy }) {
|
|
356
|
+
export async function guardTransition({ resourceId, fromState, toState, artifacts, modifiedFiles, idempotencyKey, expectedPolicyChecksum, resolvedBy }) {
|
|
350
357
|
return runGuard('transition', _compact({
|
|
351
358
|
resource_id: resourceId,
|
|
352
359
|
from_state: fromState,
|
|
@@ -354,6 +361,7 @@ export async function guardTransition({ resourceId, fromState, toState, artifact
|
|
|
354
361
|
artifacts,
|
|
355
362
|
modified_files: modifiedFiles,
|
|
356
363
|
idempotency_key: idempotencyKey,
|
|
364
|
+
expected_policy_checksum: expectedPolicyChecksum,
|
|
357
365
|
resolved_by: resolvedBy,
|
|
358
366
|
}));
|
|
359
367
|
}
|
|
@@ -382,3 +390,42 @@ export async function guardOverride({ resourceId, fromState, toState, overrideTo
|
|
|
382
390
|
export async function guardHistory(resourceId) {
|
|
383
391
|
return runGuard('history', { resource_id: resourceId }, QUERY_TIMEOUT_MS);
|
|
384
392
|
}
|
|
393
|
+
|
|
394
|
+
/** Read an immutable guard policy for compatibility and upgrade decisions. */
|
|
395
|
+
export async function guardPolicy(resourceId) {
|
|
396
|
+
return runGuard('policy', { resource_id: resourceId }, QUERY_TIMEOUT_MS);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/** List registered guard resources, optionally filtered by resource_id prefix. */
|
|
400
|
+
export async function guardList({ prefix } = {}) {
|
|
401
|
+
return runGuard('list', prefix ? { prefix } : {}, QUERY_TIMEOUT_MS);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/** Apply a signed, server-owned guard upgrade descriptor. */
|
|
405
|
+
export async function guardApplyUpgrade({ resourceId, descriptorId, descriptorsPath }) {
|
|
406
|
+
return runGuard('apply-upgrade', {
|
|
407
|
+
resource_id: resourceId,
|
|
408
|
+
descriptor_id: descriptorId,
|
|
409
|
+
}, MUTATION_TIMEOUT_MS, {
|
|
410
|
+
STRATUM_GUARD_UPGRADE_DESCRIPTORS: descriptorsPath,
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/** Inspect + verify a descriptor file through stratum's verifier (read-only). */
|
|
415
|
+
export async function guardDescriptors(descriptorsPath) {
|
|
416
|
+
return runGuard('descriptors', {}, MUTATION_TIMEOUT_MS, {
|
|
417
|
+
STRATUM_GUARD_UPGRADE_DESCRIPTORS: descriptorsPath,
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/** Compute stratum's canonical transition payload digest without changing state. */
|
|
422
|
+
export async function guardDigest({ fromState, toState, artifacts, modifiedFiles, resolvedBy, policyChecksum }) {
|
|
423
|
+
return runGuard('digest', {
|
|
424
|
+
from_state: fromState,
|
|
425
|
+
to_state: toState,
|
|
426
|
+
artifacts,
|
|
427
|
+
modified_files: modifiedFiles,
|
|
428
|
+
resolved_by: resolvedBy,
|
|
429
|
+
policy_checksum: policyChecksum,
|
|
430
|
+
}, QUERY_TIMEOUT_MS);
|
|
431
|
+
}
|
package/server/stratum-sync.js
CHANGED
|
@@ -77,6 +77,8 @@ export class StratumSync {
|
|
|
77
77
|
start() {
|
|
78
78
|
this.#stopped = false;
|
|
79
79
|
this.#lastTickAt = Date.now();
|
|
80
|
+
// Idempotent: a second start() must not orphan the armed timer.
|
|
81
|
+
if (this.#pollTimer) return;
|
|
80
82
|
this.#scheduleNext();
|
|
81
83
|
}
|
|
82
84
|
|
|
@@ -241,10 +243,11 @@ export function attachStratumRoutes(app, { store, scheduleBroadcast, broadcastMe
|
|
|
241
243
|
// eslint-disable-next-line no-unused-vars
|
|
242
244
|
const { stratumViolations: _v, violatedAt: _va, ...existingEvidence } = item.evidence || {};
|
|
243
245
|
const evidence = { ...existingEvidence, stratumTrace: trace, tracedAt: new Date().toISOString() };
|
|
246
|
+
// COMP-COMPLETION-GATE slice 3 (AC-11, path 8): an audit trace is NOT
|
|
247
|
+
// completion evidence. A flow reporting `complete` used to flip the item
|
|
248
|
+
// to complete here with no commit, no test attestation and no ledger
|
|
249
|
+
// entry. The trace is still stored; the status is left to the gate.
|
|
244
250
|
const updates = { evidence };
|
|
245
|
-
if (trace.status === 'complete' && item.status !== 'complete') {
|
|
246
|
-
updates.status = 'complete';
|
|
247
|
-
}
|
|
248
251
|
const updatedItem = store.updateItem(req.params.itemId, updates);
|
|
249
252
|
scheduleBroadcast();
|
|
250
253
|
|
package/server/summarizer.js
CHANGED
|
@@ -10,7 +10,6 @@ import path from 'node:path';
|
|
|
10
10
|
|
|
11
11
|
import { getTargetRoot } from './project-root.js';
|
|
12
12
|
|
|
13
|
-
const PROJECT_ROOT = getTargetRoot();
|
|
14
13
|
|
|
15
14
|
const DEFAULT_MODEL = process.env.SUMMARIZER_MODEL || 'haiku';
|
|
16
15
|
|
|
@@ -25,7 +24,7 @@ const DEFAULT_MODEL = process.env.SUMMARIZER_MODEL || 'haiku';
|
|
|
25
24
|
* @param {string} [projectRoot]
|
|
26
25
|
* @returns {string}
|
|
27
26
|
*/
|
|
28
|
-
export function buildSummaryPrompt(batch, projectRoot =
|
|
27
|
+
export function buildSummaryPrompt(batch, projectRoot = getTargetRoot()) {
|
|
29
28
|
const eventLines = batch.map(evt => {
|
|
30
29
|
const itemLabel = evt.itemTitles.length > 0
|
|
31
30
|
? ` [${evt.itemTitles.join(', ')}]`
|
|
@@ -66,9 +65,9 @@ JSON schema:
|
|
|
66
65
|
* @param {string} [opts.projectRoot]
|
|
67
66
|
* @returns {Promise<object|null>}
|
|
68
67
|
*/
|
|
69
|
-
export function summarize(prompt, { model = DEFAULT_MODEL, projectRoot =
|
|
68
|
+
export function summarize(prompt, { model = DEFAULT_MODEL, projectRoot = getTargetRoot() } = {}) {
|
|
70
69
|
return new Promise((resolve) => {
|
|
71
|
-
const cleanEnv = { ...process.env, NO_COLOR: '1' };
|
|
70
|
+
const cleanEnv = { ...process.env, COMPOSE_TARGET: projectRoot, NO_COLOR: '1' };
|
|
72
71
|
delete cleanEnv.CLAUDECODE;
|
|
73
72
|
|
|
74
73
|
const proc = spawn('claude', [
|
package/server/supervisor.js
CHANGED
|
@@ -59,7 +59,6 @@ function ensureComposeApiToken() {
|
|
|
59
59
|
// Expose the same token to Vite client code.
|
|
60
60
|
process.env.VITE_COMPOSE_API_TOKEN = process.env.COMPOSE_API_TOKEN;
|
|
61
61
|
// Expose AGENT_PORT so AgentStream.jsx can reach the right port
|
|
62
|
-
process.env.VITE_AGENT_PORT = process.env.AGENT_PORT || '4002';
|
|
63
62
|
}
|
|
64
63
|
|
|
65
64
|
// --- Singleton enforcement ---
|