@smartmemory/compose 0.3.6-beta → 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/.claude/skills/compose/SKILL.md +42 -88
- package/.compose-deps.json +1 -13
- package/README.md +72 -5
- package/bin/compose.js +754 -347
- package/bin/git-hooks/pre-push.template +29 -0
- package/bin/judgment-import.js +7 -0
- package/bin/judgment-migrate.js +387 -0
- package/contracts/comp-obs-contract.schema.json +9 -3
- package/contracts/feature-json.schema.json +5 -0
- package/contracts/fluid-record.schema.json +209 -0
- package/contracts/judgment-record.schema.json +425 -4
- package/contracts/lifecycle-backfill.schema.json +322 -0
- package/dist/assets/App-Z4MU-H_F.js +916 -0
- package/dist/assets/_baseUniq-ClWoCPFl.js +1 -0
- package/dist/assets/arc-DY26UIVo.js +1 -0
- package/dist/assets/architectureDiagram-Q4EWVU46-6Ggq4DqJ.js +36 -0
- package/dist/assets/blockDiagram-DXYQGD6D-CH3Ked0l.js +132 -0
- package/dist/assets/{browser-BSM23If2.js → browser-BWkrenen.js} +6 -6
- package/dist/assets/{c4Diagram-LMCZKHZV-DZf45Fbz.js → c4Diagram-AHTNJAMY-Bk8dYilu.js} +1 -1
- package/dist/assets/channel-SnZzzh7k.js +1 -0
- package/dist/assets/{chunk-JWPE2WC7-_7ujgd_Q.js → chunk-4BX2VUAB-BMR0XaAQ.js} +1 -1
- package/dist/assets/chunk-4TB4RGXK-JytR14a9.js +206 -0
- package/dist/assets/{chunk-XXDRQBXY-DfdVhbmA.js → chunk-55IACEB6-B4Q97BCP.js} +1 -1
- package/dist/assets/{chunk-VR4S4FIN-Dt9NZ67m.js → chunk-EDXVE4YY-R_qarkSf.js} +1 -1
- package/dist/assets/{chunk-5VM5RSS4-BY4_PV5H.js → chunk-FMBD7UC4-C9s7KR9m.js} +1 -1
- package/dist/assets/chunk-OYMX7WX6-BySQzVxc.js +231 -0
- package/dist/assets/{chunk-2Q5K7J3B-Dn1spZYu.js → chunk-QZHKN3VN-DdpSYZsW.js} +1 -1
- package/dist/assets/{chunk-32BRIVSS-pURGrJDk.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-JH36ORCC-BieYif4o.js → cose-bilkent-S5V4N54A-BdlU6ZX_.js} +1 -1
- package/dist/assets/dagre-KV5264BT-Cp3F5KTn.js +4 -0
- package/dist/assets/diagram-5BDNPKRD-DiR6_2q_.js +10 -0
- package/dist/assets/diagram-G4DWMVQ6-w0i-p5HX.js +24 -0
- package/dist/assets/diagram-MMDJMWI5-tIHhwUv3.js +43 -0
- package/dist/assets/diagram-TYMM5635-BAeY3B19.js +24 -0
- package/dist/assets/erDiagram-SMLLAGMA-Ckx_Knko.js +85 -0
- package/dist/assets/flowDiagram-DWJPFMVM-DeoNka6J.js +162 -0
- package/dist/assets/ganttDiagram-T4ZO3ILL-BmGnFbEg.js +292 -0
- package/dist/assets/gitGraphDiagram-UUTBAWPF-Dk48IHsx.js +106 -0
- package/dist/assets/graph-BNzKGvoy.js +1 -0
- package/dist/assets/graph-CI_1htl0.js +331 -0
- package/dist/assets/index-BEfrNBp8.js +123 -0
- package/dist/assets/index-yyrA5OZd.css +1 -0
- package/dist/assets/infoDiagram-42DDH7IO-BRf827i0.js +2 -0
- package/dist/assets/{ishikawaDiagram-FXEZZL3T-CzEB9fQS.js → ishikawaDiagram-UXIWVN3A-0kCZaeCM.js} +5 -5
- package/dist/assets/{journeyDiagram-5HDEW3XC-Bz8TCdz2.js → journeyDiagram-VCZTEJTY-rvU7ayRt.js} +1 -1
- package/dist/assets/{kanban-definition-HUTT4EX6-tozrMoV_.js → kanban-definition-6JOO6SKY-DpQwX1C5.js} +7 -7
- package/dist/assets/katex-DkKDou_j.js +257 -0
- package/dist/assets/layout-BI8cXFPI.js +1 -0
- package/dist/assets/{linear-Ck7gpa5N.js → linear-a0glcDiw.js} +1 -1
- package/dist/assets/min-vPHfnXcC.js +1 -0
- package/dist/assets/{mindmap-definition-LN4V7U3C-DTcHO0DJ.js → mindmap-definition-QFDTVHPH-D14eF-7C.js} +7 -7
- package/dist/assets/mobile-B7m9EO9D.js +17 -0
- package/dist/assets/pieDiagram-DEJITSTG-Cno-gETh.js +30 -0
- package/dist/assets/quadrantDiagram-34T5L4WZ-BUQM1Hfm.js +7 -0
- package/dist/assets/{requirementDiagram-TGXJPOKE-bnI2zJeT.js → requirementDiagram-MS252O5E-pOXlN2-q.js} +3 -3
- package/dist/assets/sankeyDiagram-XADWPNL6-Crynd3_b.js +10 -0
- package/dist/assets/sequenceDiagram-FGHM5R23-D9fZdCM8.js +157 -0
- package/dist/assets/stateDiagram-FHFEXIEX-CW9qVec8.js +1 -0
- package/dist/assets/stateDiagram-v2-QKLJ7IA2-DkVLzHbY.js +1 -0
- package/dist/assets/{timeline-definition-FHXFAJF6-D267GQFF.js → timeline-definition-GMOUNBTQ-BcHzhm_8.js} +3 -3
- package/dist/assets/vennDiagram-DHZGUBPP-BfytJcWk.js +34 -0
- package/dist/assets/wardley-RL74JXVD-DLj-IjyB.js +162 -0
- package/dist/assets/wardleyDiagram-NUSXRM2D-Ds0Ue68c.js +20 -0
- package/dist/assets/xychartDiagram-5P7HB3ND-vjWDXFL6.js +7 -0
- 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 +39 -4
- package/lib/build-stream-schema.js +3 -1
- package/lib/build-stream-writer.js +25 -0
- package/lib/build.js +1624 -195
- package/lib/canon-guard.js +245 -0
- package/lib/canon-override.js +196 -0
- package/lib/canon-registry.js +291 -0
- package/lib/cli-commands.js +144 -0
- package/lib/codex-preflight.js +50 -15
- 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 +301 -0
- package/lib/dispatch-metrics.js +236 -0
- package/lib/experiment-judge.js +6 -1
- 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 +66 -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 -14
- 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/hooks-status.js +32 -3
- package/lib/ideabox-cli.js +315 -0
- package/lib/ideabox.js +121 -21
- package/lib/judgment/store/index.js +166 -0
- package/lib/judgment/store/records.js +184 -25
- package/lib/judgment/trace.js +380 -0
- package/lib/judgment-attest.js +259 -0
- package/lib/judgment-decision-write.js +277 -0
- package/lib/judgment-decisions.js +466 -0
- package/lib/judgment-gen.js +375 -22
- package/lib/judgment-verify.js +153 -0
- package/lib/judgment-writer.js +2842 -262
- package/lib/lane-gate.js +2 -0
- package/lib/lifecycle-modes.js +4 -4
- package/lib/lineage.js +400 -0
- package/lib/local-claude-connector.js +250 -54
- package/lib/maya-client.js +302 -0
- package/lib/maya-config.js +53 -0
- package/lib/maya-identity.js +283 -0
- package/lib/mcp-enforcement.js +21 -35
- 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 +428 -153
- package/lib/review-normalize.js +4 -0
- 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 +391 -31
- 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 +124 -24
- package/server/compose-mcp.js +91 -790
- 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 +35 -3
- 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-BG3ngu8H.js +0 -896
- package/dist/assets/abnfDiagram-VRR7QNED-CjB_sD3D.js +0 -1
- package/dist/assets/arc-_v4hR_uD.js +0 -1
- package/dist/assets/architectureDiagram-ZJ3FMSHR-DreJmzXQ.js +0 -36
- package/dist/assets/blockDiagram-677ZJIJ3-BG9-c0O1.js +0 -132
- package/dist/assets/channel-B3U5wFAT.js +0 -1
- package/dist/assets/chunk-EX3LRPZG-DdELs1qP.js +0 -231
- package/dist/assets/chunk-MOJQB5TN-D-ky35G-.js +0 -88
- package/dist/assets/chunk-RYQCIY6F-Dag_kVlO.js +0 -1
- package/dist/assets/chunk-V7JOEXUC-BtewURat.js +0 -206
- package/dist/assets/classDiagram-OUVF2IWQ-B6fCN-ht.js +0 -1
- package/dist/assets/classDiagram-v2-EOCWNBFH-B6fCN-ht.js +0 -1
- package/dist/assets/cynefin-VYW2F7L2-CT2BA6KE.js +0 -178
- package/dist/assets/cynefinDiagram-TSTJHNR4-Bh6exbyg.js +0 -62
- package/dist/assets/dagre-VKFMJZFB-aXMLSmQL.js +0 -4
- package/dist/assets/diagram-FQU43EPY-Dr7JAOuQ.js +0 -3
- package/dist/assets/diagram-G47NLZAW-DUvA3FQK.js +0 -24
- package/dist/assets/diagram-NH7WQ7WH-BQUARqcu.js +0 -24
- package/dist/assets/diagram-OA4YK3LP-dDUc1zHi.js +0 -30
- package/dist/assets/diagram-WEI45ONY-B2h5Qlb1.js +0 -41
- package/dist/assets/ebnfDiagram-CCIWWBDH-DThRGupB.js +0 -1
- package/dist/assets/erDiagram-Q63AITRT-BUCsprO2.js +0 -85
- package/dist/assets/flowDiagram-23GEKE2U-DXtNNi6r.js +0 -156
- package/dist/assets/ganttDiagram-NO4QXBWP-D4zbBHh_.js +0 -292
- package/dist/assets/gitGraphDiagram-IHSO6WYX-DpoQws0W.js +0 -106
- package/dist/assets/graph-BXPQrYYB.js +0 -331
- package/dist/assets/graph-C9eacEi8.js +0 -1
- package/dist/assets/index-3ZH5eMcZ.js +0 -119
- package/dist/assets/index-LIwREYgH.css +0 -1
- package/dist/assets/infoDiagram-FWYZ7A6U-Bbas2GAo.js +0 -2
- package/dist/assets/katex-C5jXJg4s.js +0 -257
- package/dist/assets/layout-DEXfKzaS.js +0 -1
- package/dist/assets/map-Czzmt4hB.js +0 -1
- package/dist/assets/mobile-CaoXUwAr.js +0 -17
- package/dist/assets/pegDiagram-2B236MQR-CHiINrNy.js +0 -1
- package/dist/assets/pieDiagram-ENE6RG2P-CfS4YFlR.js +0 -39
- package/dist/assets/quadrantDiagram-ABIIQ3AL-CadesS9w.js +0 -7
- package/dist/assets/railroadDiagram-RFXS5EU6-CgWEspBN.js +0 -1
- package/dist/assets/sankeyDiagram-HTMAVEWB-YWKFgOGw.js +0 -40
- package/dist/assets/sequenceDiagram-DBY2YBRQ-BvkNOyF9.js +0 -162
- package/dist/assets/sizeCapture-X5ZJPWSS-DlFPA2yO.js +0 -1
- package/dist/assets/stateDiagram-2N3HPSRC-h8NIx0kQ.js +0 -1
- package/dist/assets/stateDiagram-v2-6OUMAXLB-DjPgZtJ9.js +0 -1
- package/dist/assets/swimlanes-5IMT3BWC-CT5n22kG.js +0 -2
- package/dist/assets/swimlanesDiagram-G3AALYLV-Dn318Bhq.js +0 -8
- package/dist/assets/vennDiagram-L72KCM5P-Dj-wWLYG.js +0 -34
- package/dist/assets/wardleyDiagram-EHGQE667-BxCeYxkG.js +0 -78
- package/dist/assets/xychartDiagram-FW5EYKEG-DMFqWn7z.js +0 -7
- package/lib/staleness.js +0 -87
- package/server/ideabox-cache.js +0 -77
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* canon-registry.js — COMP-CANON-GUARD S1.
|
|
3
|
+
*
|
|
4
|
+
* The single declaration of what is canon: path pattern → writer → typed tools
|
|
5
|
+
* → enforcement points. Pure, no I/O (shape template: server/mcp-tool-policy.js).
|
|
6
|
+
*
|
|
7
|
+
* Consumed by BOTH:
|
|
8
|
+
* - lib/mcp-enforcement.js (the ship-time scan — the 'ship' point)
|
|
9
|
+
* - .claude/hooks/canon-guard.mjs (the write-time PreToolUse hook — 'hook')
|
|
10
|
+
*
|
|
11
|
+
* The load-bearing invariant (design Decision 1, corrected in blueprint-s1-s4):
|
|
12
|
+
* ONE registry does NOT imply ONE coverage. Each entry declares `enforcedBy`,
|
|
13
|
+
* and every enforcement point consumes only the subset that names it. This is
|
|
14
|
+
* what keeps the shared registry from locking out paths a point cannot yet
|
|
15
|
+
* legally guard:
|
|
16
|
+
* - docs/judgment/** is 100% tool-covered (S3 shipped the 8 judgment_* tools
|
|
17
|
+
* + regen), so the write-time hook can guard it with no lockout → ['hook'].
|
|
18
|
+
* - ROADMAP.md / feature.json have legal mutations NO tool covers yet
|
|
19
|
+
* (open a preserved section; edit a feature description — Decision 2), so
|
|
20
|
+
* the always-deny hook would lock them out. They stay ['ship'] (their
|
|
21
|
+
* existing build-event correlation) until update_feature_fields /
|
|
22
|
+
* open_preserved_section + the override land.
|
|
23
|
+
*
|
|
24
|
+
* Adding a path here turns on real enforcement — register a path for 'hook'
|
|
25
|
+
* ONLY once every legal mutation of it has a tool or an override.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
// ── Tool sets ────────────────────────────────────────────────────────────────
|
|
29
|
+
// Moved verbatim from the pre-refactor mcp-enforcement.js literal sets. The
|
|
30
|
+
// contract test pins these against the legacy values.
|
|
31
|
+
const TOOLS_FOR_ROADMAP = ['add_roadmap_entry', 'set_feature_status', 'propose_followup'];
|
|
32
|
+
const TOOLS_FOR_CHANGELOG = ['add_changelog_entry'];
|
|
33
|
+
/** The override's governance state is written only by the grant tool itself. */
|
|
34
|
+
const TOOLS_FOR_OVERRIDE = ['canon_override_grant'];
|
|
35
|
+
const TOOLS_FOR_FEATURE_JSON = [
|
|
36
|
+
'add_roadmap_entry',
|
|
37
|
+
'set_feature_status',
|
|
38
|
+
'link_artifact',
|
|
39
|
+
'link_features',
|
|
40
|
+
'record_completion',
|
|
41
|
+
// COMP-LIFECYCLE-BACKFILL S3-2: writes feature.json through the completion gate via HTTP.
|
|
42
|
+
'backfill_completion',
|
|
43
|
+
'propose_followup',
|
|
44
|
+
// COMP-COVERAGE-GATE C2 (2026-08-24): both write feature.json server-side via
|
|
45
|
+
// _postLifecycle (server/vision-routes.js:366 status write-back, :451
|
|
46
|
+
// kill→KILLED) and were missing here. `entry.tools` feeds only the
|
|
47
|
+
// canon-guard deny message ("use one of these tools instead") — no allow/deny
|
|
48
|
+
// decision reads it — so this widens no enforcement; it stops the rejection
|
|
49
|
+
// message from omitting two legitimate alternatives.
|
|
50
|
+
'complete_feature',
|
|
51
|
+
'kill_feature',
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The eight judgment WRITE tools (COMP-JUDGMENT-WRITER, shipped @751cc96a).
|
|
56
|
+
* get_judgment_state is read-only and is deliberately NOT here. Every mutation
|
|
57
|
+
* of docs/judgment/** — records under records/ and the generated projections
|
|
58
|
+
* (REGISTER/LEDGER/OBJECTIVE/SITUATION/index.md, people/*.md, positions/*.md) —
|
|
59
|
+
* goes through one of these, which regenerate the projections atomically.
|
|
60
|
+
*/
|
|
61
|
+
export const JUDGMENT_WRITE_TOOLS = [
|
|
62
|
+
'judgment_position_create',
|
|
63
|
+
'judgment_position_amend',
|
|
64
|
+
'judgment_joint_add',
|
|
65
|
+
'judgment_transition',
|
|
66
|
+
'judgment_ledger_append',
|
|
67
|
+
'judgment_person_write',
|
|
68
|
+
'judgment_situation_write',
|
|
69
|
+
'judgment_goal_write',
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
// ── Matchers ─────────────────────────────────────────────────────────────────
|
|
73
|
+
// A matcher is (path, { featuresDir }) => boolean. featuresDir is relative and
|
|
74
|
+
// project-configurable (loadFeaturesDir), so the feature.json matcher is
|
|
75
|
+
// parameterized; fixed-name and docs/judgment matchers ignore it.
|
|
76
|
+
|
|
77
|
+
function matchExact(name) {
|
|
78
|
+
return (path) => path === name;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** startsWith(<featuresDir>/) && endsWith(/feature.json) — mirrors the legacy
|
|
82
|
+
* isGuardedPath exactly (does NOT require a single-segment middle; that
|
|
83
|
+
* constraint lives only in code-correlation, below). */
|
|
84
|
+
function matchFeatureJson(path, featuresDir) {
|
|
85
|
+
if (typeof path !== 'string' || !featuresDir) return false;
|
|
86
|
+
const prefix = featuresDir.replace(/\/$/, '') + '/';
|
|
87
|
+
if (!path.startsWith(prefix)) return false;
|
|
88
|
+
return path.endsWith('/feature.json');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Anything under docs/judgment/ (records or projections). Fixed root — matches
|
|
92
|
+
* lib/judgment-gen.js, which hardcodes docs/judgment/. */
|
|
93
|
+
function matchJudgment(path) {
|
|
94
|
+
return typeof path === 'string' && path.startsWith('docs/judgment/');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Prefix match on a directory, requiring the separator so that a sibling with
|
|
99
|
+
* a longer name (`canon-grants-backup/`) cannot masquerade as a child.
|
|
100
|
+
*/
|
|
101
|
+
function matchUnder(dir) {
|
|
102
|
+
return (path) => typeof path === 'string' && path.startsWith(`${dir}/`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ── The registry ─────────────────────────────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* @typedef {object} CanonEntry
|
|
109
|
+
* @property {string} id — stable identifier
|
|
110
|
+
* @property {string} display — human-readable path pattern, for status/help output
|
|
111
|
+
* @property {string} writer — the module that legitimately produces this path
|
|
112
|
+
* @property {string[]} tools — typed tools authorised to write it
|
|
113
|
+
* @property {Array<'ship'|'hook'|'pre-commit'>} enforcedBy — points that guard it
|
|
114
|
+
* @property {boolean} [overrideEligible] — may a canon override be granted FOR
|
|
115
|
+
* this path? Absent means yes. Set false for the override's own governance
|
|
116
|
+
* state, which must be guarded without being grantable.
|
|
117
|
+
* @property {(path:string, featuresDir:string)=>boolean} matches
|
|
118
|
+
*/
|
|
119
|
+
|
|
120
|
+
/** @type {CanonEntry[]} */
|
|
121
|
+
const REGISTRY = [
|
|
122
|
+
{
|
|
123
|
+
id: 'roadmap',
|
|
124
|
+
display: 'ROADMAP.md',
|
|
125
|
+
writer: 'lib/roadmap-gen.js',
|
|
126
|
+
tools: TOOLS_FOR_ROADMAP,
|
|
127
|
+
enforcedBy: ['ship'],
|
|
128
|
+
matches: matchExact('ROADMAP.md'),
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
id: 'changelog',
|
|
132
|
+
display: 'CHANGELOG.md',
|
|
133
|
+
writer: 'lib/changelog-writer.js',
|
|
134
|
+
tools: TOOLS_FOR_CHANGELOG,
|
|
135
|
+
enforcedBy: ['ship'],
|
|
136
|
+
matches: matchExact('CHANGELOG.md'),
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
id: 'feature-json',
|
|
140
|
+
display: '<features>/*/feature.json',
|
|
141
|
+
writer: 'lib/feature-writer.js',
|
|
142
|
+
tools: TOOLS_FOR_FEATURE_JSON,
|
|
143
|
+
enforcedBy: ['ship'],
|
|
144
|
+
matches: (path, featuresDir) => matchFeatureJson(path, featuresDir),
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
id: 'judgment',
|
|
148
|
+
display: 'docs/judgment/**',
|
|
149
|
+
writer: 'lib/judgment-writer.js',
|
|
150
|
+
tools: JUDGMENT_WRITE_TOOLS,
|
|
151
|
+
enforcedBy: ['hook'],
|
|
152
|
+
matches: (path) => matchJudgment(path),
|
|
153
|
+
},
|
|
154
|
+
|
|
155
|
+
// ── Governance class (COMP-CANON-OVERRIDE S1) ──────────────────────────────
|
|
156
|
+
// The override's own state. Guarded like any canon, and additionally
|
|
157
|
+
// `overrideEligible: false` so the override cannot be turned on itself:
|
|
158
|
+
// without this, "hook-registered" and "grantable" are the same set, and an
|
|
159
|
+
// agent could grant a bypass FOR the bypass ledger and then rewrite it
|
|
160
|
+
// (gate round 2, finding 2). The grant directory is governance state for the
|
|
161
|
+
// same reason — unregistered, a raw-written token would be consumable with
|
|
162
|
+
// no ledger row at all (finding 1).
|
|
163
|
+
//
|
|
164
|
+
// Runtime-scoped, like every hook guarantee: `Bash` never reaches this.
|
|
165
|
+
{
|
|
166
|
+
id: 'override-ledger',
|
|
167
|
+
display: '.compose/canon-overrides.jsonl',
|
|
168
|
+
writer: 'lib/canon-override.js',
|
|
169
|
+
tools: TOOLS_FOR_OVERRIDE,
|
|
170
|
+
enforcedBy: ['hook'],
|
|
171
|
+
overrideEligible: false,
|
|
172
|
+
matches: matchExact('.compose/canon-overrides.jsonl'),
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
id: 'override-attest',
|
|
176
|
+
display: '.compose/canon-overrides-attest.json',
|
|
177
|
+
writer: 'lib/canon-override.js',
|
|
178
|
+
tools: TOOLS_FOR_OVERRIDE,
|
|
179
|
+
enforcedBy: ['hook'],
|
|
180
|
+
overrideEligible: false,
|
|
181
|
+
matches: matchExact('.compose/canon-overrides-attest.json'),
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
id: 'override-grants',
|
|
185
|
+
display: '.compose/data/canon-grants/**',
|
|
186
|
+
writer: 'lib/canon-override.js',
|
|
187
|
+
tools: TOOLS_FOR_OVERRIDE,
|
|
188
|
+
enforcedBy: ['hook'],
|
|
189
|
+
overrideEligible: false,
|
|
190
|
+
matches: matchUnder('.compose/data/canon-grants'),
|
|
191
|
+
},
|
|
192
|
+
];
|
|
193
|
+
|
|
194
|
+
// ── Public API ───────────────────────────────────────────────────────────────
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Resolve the registry entry guarding `path` at enforcement `point`, or null.
|
|
198
|
+
* Only entries whose `enforcedBy` includes the point are considered — this is
|
|
199
|
+
* the per-point-subset invariant.
|
|
200
|
+
*
|
|
201
|
+
* @param {string} path
|
|
202
|
+
* @param {{ featuresDir?: string, point: 'ship'|'hook'|'pre-commit' }} opts
|
|
203
|
+
* @returns {CanonEntry|null}
|
|
204
|
+
*/
|
|
205
|
+
export function matchEntry(path, { featuresDir, point }) {
|
|
206
|
+
for (const entry of REGISTRY) {
|
|
207
|
+
if (!entry.enforcedBy.includes(point)) continue;
|
|
208
|
+
if (entry.matches(path, featuresDir)) return entry;
|
|
209
|
+
}
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** True if `path` is guarded at `point`. */
|
|
214
|
+
export function isGuarded(path, opts) {
|
|
215
|
+
return matchEntry(path, opts) !== null;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* True if a canon override may be granted for `path` at `point`.
|
|
220
|
+
*
|
|
221
|
+
* Deliberately NOT the same predicate as `isGuarded`. Two paths are guarded
|
|
222
|
+
* but ungrantable:
|
|
223
|
+
* - governance state (`overrideEligible: false`) — else the override could
|
|
224
|
+
* authorise rewriting its own audit trail;
|
|
225
|
+
* - anything not guarded at THIS point — a grant for a ship-only path is
|
|
226
|
+
* meaningless, because the hook already allows it, and would write a
|
|
227
|
+
* misleading bypass row.
|
|
228
|
+
* An unguarded path is likewise ineligible: nothing is blocking it, so there
|
|
229
|
+
* is nothing to override (the lockout invariant).
|
|
230
|
+
*/
|
|
231
|
+
/**
|
|
232
|
+
* Human-readable path patterns guarded at `point`, for status and help output.
|
|
233
|
+
* Derived rather than hand-written: a hardcoded string in `compose guard
|
|
234
|
+
* status` silently under-reported the guarded set the moment S1 widened it.
|
|
235
|
+
*/
|
|
236
|
+
export function guardedDisplaysFor(point) {
|
|
237
|
+
return REGISTRY.filter((e) => e.enforcedBy.includes(point)).map((e) => e.display);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export function isOverrideEligible(path, opts) {
|
|
241
|
+
const entry = matchEntry(path, opts);
|
|
242
|
+
return entry !== null && entry.overrideEligible !== false;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** The typed tools authorised to write `path` at `point`, or [] if unguarded. */
|
|
246
|
+
export function toolsForPath(path, opts) {
|
|
247
|
+
const entry = matchEntry(path, opts);
|
|
248
|
+
return entry ? [...entry.tools] : [];
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* The feature code for a feature.json path (single-segment middle), else null.
|
|
253
|
+
* Used for ship-scan code correlation so an event for feature A cannot bless a
|
|
254
|
+
* dirty edit to feature B's feature.json.
|
|
255
|
+
*
|
|
256
|
+
* @param {string} path
|
|
257
|
+
* @param {{ featuresDir: string }} opts
|
|
258
|
+
* @returns {string|null}
|
|
259
|
+
*/
|
|
260
|
+
export function featureCodeForPath(path, { featuresDir }) {
|
|
261
|
+
if (typeof path !== 'string' || !featuresDir) return null;
|
|
262
|
+
const prefix = featuresDir.replace(/\/$/, '') + '/';
|
|
263
|
+
if (!path.startsWith(prefix) || !path.endsWith('/feature.json')) return null;
|
|
264
|
+
const middle = path.slice(prefix.length, -'/feature.json'.length);
|
|
265
|
+
if (!middle || middle.includes('/')) return null;
|
|
266
|
+
return middle;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* The registry as plain data — `{ id, display, tools }` per entry, deep-copied.
|
|
271
|
+
*
|
|
272
|
+
* Exists so COMP-COVERAGE-GATE can cross-check `entry.tools` against the tool
|
|
273
|
+
* inventory without reaching into `_internals` (which is test-only) and without
|
|
274
|
+
* being able to mutate the live registry.
|
|
275
|
+
*/
|
|
276
|
+
export function canonEntries() {
|
|
277
|
+
return REGISTRY.map((e) => ({ id: e.id, display: e.display, tools: [...e.tools] }));
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/** The entry ids guarded at `point` (for the contract test + introspection). */
|
|
281
|
+
export function guardedPatternIdsFor(point) {
|
|
282
|
+
return REGISTRY.filter((e) => e.enforcedBy.includes(point)).map((e) => e.id);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export const _internals = {
|
|
286
|
+
REGISTRY,
|
|
287
|
+
TOOLS_FOR_ROADMAP,
|
|
288
|
+
TOOLS_FOR_CHANGELOG,
|
|
289
|
+
TOOLS_FOR_FEATURE_JSON,
|
|
290
|
+
JUDGMENT_WRITE_TOOLS,
|
|
291
|
+
};
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cli-commands.js — COMP-AUDIT-13: the single source of truth for compose's
|
|
3
|
+
* top-level command surface.
|
|
4
|
+
*
|
|
5
|
+
* `compose --help` (bin/compose.js) and the Command Index in docs/cli.md are
|
|
6
|
+
* both rendered from COMMANDS here, so installed capability can never again be
|
|
7
|
+
* invisible unless a command is missing from this table — and a drift-guard test
|
|
8
|
+
* (test/cli-commands.test.js) asserts this table matches the real dispatch
|
|
9
|
+
* branches in bin/compose.js in both directions.
|
|
10
|
+
*
|
|
11
|
+
* To add a command: add its `cmd === '<name>'` branch in bin/compose.js AND a
|
|
12
|
+
* row here. The test fails until both exist.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {Object} Command
|
|
17
|
+
* @property {string} name Canonical command token (matches `cmd === '<name>'`)
|
|
18
|
+
* @property {string[]} aliases Alternate tokens that dispatch the same command
|
|
19
|
+
* @property {string} group Display group (must be one of COMMAND_GROUPS)
|
|
20
|
+
* @property {string} summary One-line description (help + docs index)
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/** Ordered display groups. */
|
|
24
|
+
export const COMMAND_GROUPS = [
|
|
25
|
+
'Getting started',
|
|
26
|
+
'Features & roadmap',
|
|
27
|
+
'Build & implement',
|
|
28
|
+
'Lifecycle, gates & review',
|
|
29
|
+
'Vision, ideas & tracking',
|
|
30
|
+
'App, integrations & runtime',
|
|
31
|
+
'Maintenance & info',
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
/** @type {Command[]} */
|
|
35
|
+
export const COMMANDS = [
|
|
36
|
+
// Getting started
|
|
37
|
+
{ name: 'init', aliases: [], group: 'Getting started', summary: 'Initialize Compose in the current project' },
|
|
38
|
+
{ name: 'setup', aliases: ['sync'], group: 'Getting started', summary: 'Install/sync global Compose skills' },
|
|
39
|
+
{ name: 'install', aliases: [], group: 'Getting started', summary: 'Legacy bootstrap — runs init + setup' },
|
|
40
|
+
{ name: 'import', aliases: [], group: 'Getting started', summary: 'Scan an existing project and generate a structured analysis' },
|
|
41
|
+
{ name: 'doctor', aliases: [], group: 'Getting started', summary: 'Check external skill dependencies' },
|
|
42
|
+
{ name: 'update', aliases: ['upgrade'], group: 'Getting started', summary: 'Pull latest compose, reinstall deps, refresh global skill' },
|
|
43
|
+
|
|
44
|
+
// Features & roadmap
|
|
45
|
+
{ name: 'new', aliases: [], group: 'Features & roadmap', summary: 'Kickoff a product (research, brainstorm, roadmap, scaffold)' },
|
|
46
|
+
{ name: 'feature', aliases: [], group: 'Features & roadmap', summary: 'Add a single feature (folder, design seed, ROADMAP entry)' },
|
|
47
|
+
{ name: 'roadmap', aliases: [], group: 'Features & roadmap', summary: 'Show roadmap status; generate/migrate/check ROADMAP.md' },
|
|
48
|
+
{ name: 'triage', aliases: [], group: 'Features & roadmap', summary: 'Analyze a feature and recommend a build profile' },
|
|
49
|
+
{ name: 'qa-scope', aliases: [], group: 'Features & roadmap', summary: "Show affected routes from a feature's changed files" },
|
|
50
|
+
|
|
51
|
+
// Build & implement
|
|
52
|
+
{ name: 'build', aliases: [], group: 'Build & implement', summary: 'Run a feature through the headless lifecycle' },
|
|
53
|
+
{ name: 'fix', aliases: [], group: 'Build & implement', summary: 'Run a bug through the headless bug-fix lifecycle' },
|
|
54
|
+
{ name: 'plan', aliases: [], group: 'Build & implement', summary: 'Plan work into a structured roadmap from a prompt' },
|
|
55
|
+
{ name: 'gsd', aliases: [], group: 'Build & implement', summary: 'Per-task fresh-context dispatch from a blueprint + Boundary Map' },
|
|
56
|
+
{ name: 'pipeline', aliases: [], group: 'Build & implement', summary: 'View and edit the build pipeline' },
|
|
57
|
+
{ name: 'experiment', aliases: [], group: 'Build & implement', summary: 'Run an A/B model experiment from a spec' },
|
|
58
|
+
|
|
59
|
+
// Lifecycle, gates & review
|
|
60
|
+
{ name: 'gates', aliases: ['gate'], group: 'Lifecycle, gates & review', summary: 'List and resolve pending gates' },
|
|
61
|
+
{ name: 'loops', aliases: [], group: 'Lifecycle, gates & review', summary: 'Manage iteration loops for a feature' },
|
|
62
|
+
{ name: 'guard', aliases: [], group: 'Lifecycle, gates & review', summary: 'Manage the canon guard and drift detection' },
|
|
63
|
+
{ name: 'validate', aliases: [], group: 'Lifecycle, gates & review', summary: 'Validate feature/project artifacts against contracts' },
|
|
64
|
+
{ name: 'record-completion', aliases: [], group: 'Lifecycle, gates & review', summary: 'Record a completion bound to a commit SHA (flips status to COMPLETE)' },
|
|
65
|
+
{ name: 'lineage', aliases: [], group: 'Lifecycle, gates & review', summary: 'PROV-O artifact lineage: stamp | stale | show' },
|
|
66
|
+
{ name: 'context', aliases: [], group: 'Lifecycle, gates & review', summary: 'Show the build decision log' },
|
|
67
|
+
|
|
68
|
+
// Vision, ideas & tracking
|
|
69
|
+
{ name: 'items', aliases: [], group: 'Vision, ideas & tracking', summary: 'List vision items from local state (no server)' },
|
|
70
|
+
{ name: 'ideabox', aliases: [], group: 'Vision, ideas & tracking', summary: 'Capture, review, and promote product ideas' },
|
|
71
|
+
{ name: 'judgment', aliases: [], group: 'Vision, ideas & tracking', summary: "Judgment records: trace a position's causal ancestry" },
|
|
72
|
+
{ name: 'metrics', aliases: [], group: 'Vision, ideas & tracking', summary: 'Report dispatch, settlement, and triage metrics' },
|
|
73
|
+
{ name: 'tracker', aliases: [], group: 'Vision, ideas & tracking', summary: 'Tracker provider status and op-log sync' },
|
|
74
|
+
|
|
75
|
+
// App, integrations & runtime
|
|
76
|
+
{ name: 'start', aliases: [], group: 'App, integrations & runtime', summary: 'Start the compose app (UI + API) for this project' },
|
|
77
|
+
{ name: 'remote', aliases: [], group: 'App, integrations & runtime', summary: 'Manage remote access: pair, list, revoke, status' },
|
|
78
|
+
{ name: 'smartmemory', aliases: [], group: 'App, integrations & runtime', summary: 'Sync feature-events/journal/artifacts into SmartMemory' },
|
|
79
|
+
|
|
80
|
+
// Maintenance & info
|
|
81
|
+
{ name: 'migrate-state', aliases: [], group: 'Maintenance & info', summary: 'Run pending feature.json state migrations' },
|
|
82
|
+
{ name: 'migrate-anon', aliases: [], group: 'Maintenance & info', summary: 'Promote anonymous ROADMAP rows to typed features (interactive)' },
|
|
83
|
+
{ name: 'hooks', aliases: [], group: 'Maintenance & info', summary: 'Manage Claude Code hooks (install | uninstall | status)' },
|
|
84
|
+
{ name: 'version', aliases: ['--version', '-V'], group: 'Maintenance & info', summary: 'Print compose version, git SHA, and install root' },
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
/** Every token (names + aliases) that should dispatch to a command. */
|
|
88
|
+
export function allCommandTokens() {
|
|
89
|
+
return COMMANDS.flatMap((c) => [c.name, ...c.aliases]);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** A command's display label: `name` plus any aliases, e.g. `setup, sync`. */
|
|
93
|
+
function labelOf(cmd) {
|
|
94
|
+
return cmd.aliases.length ? `${cmd.name}, ${cmd.aliases.join(', ')}` : cmd.name;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Render the grouped `compose --help` body from COMMANDS.
|
|
99
|
+
* @returns {string}
|
|
100
|
+
*/
|
|
101
|
+
export function renderHelp() {
|
|
102
|
+
const lines = ['Usage: compose <command>', ''];
|
|
103
|
+
const width = Math.max(...COMMANDS.map((c) => labelOf(c).length));
|
|
104
|
+
for (const group of COMMAND_GROUPS) {
|
|
105
|
+
const inGroup = COMMANDS.filter((c) => c.group === group);
|
|
106
|
+
if (!inGroup.length) continue;
|
|
107
|
+
lines.push(`${group}:`);
|
|
108
|
+
for (const cmd of inGroup) {
|
|
109
|
+
lines.push(` ${labelOf(cmd).padEnd(width)} ${cmd.summary}`);
|
|
110
|
+
}
|
|
111
|
+
lines.push('');
|
|
112
|
+
}
|
|
113
|
+
lines.push('Run `compose <command> --help` (or see docs/cli.md) for command details.');
|
|
114
|
+
return lines.join('\n');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Render the docs/cli.md Command Index — a complete, grouped table generated
|
|
119
|
+
* from COMMANDS. The completeness test in test/cli-commands.test.js asserts
|
|
120
|
+
* every command appears in cli.md, so this table cannot silently omit one.
|
|
121
|
+
* @returns {string}
|
|
122
|
+
*/
|
|
123
|
+
export function renderCommandIndex() {
|
|
124
|
+
const out = [
|
|
125
|
+
'## Command Index',
|
|
126
|
+
'',
|
|
127
|
+
'<!-- Generated from lib/cli-commands.js (COMP-AUDIT-13). Every shipped command appears here. -->',
|
|
128
|
+
'',
|
|
129
|
+
];
|
|
130
|
+
for (const group of COMMAND_GROUPS) {
|
|
131
|
+
const inGroup = COMMANDS.filter((c) => c.group === group);
|
|
132
|
+
if (!inGroup.length) continue;
|
|
133
|
+
out.push(`### ${group}`, '', '| Command | Summary |', '|---|---|');
|
|
134
|
+
for (const cmd of inGroup) {
|
|
135
|
+
const label = cmd.aliases.length
|
|
136
|
+
? `\`compose ${cmd.name}\` (alias: ${cmd.aliases.map((a) => `\`${a}\``).join(', ')})`
|
|
137
|
+
: `\`compose ${cmd.name}\``;
|
|
138
|
+
// Escape pipes so a summary like "stamp | stale | show" can't break the table.
|
|
139
|
+
out.push(`| ${label} | ${cmd.summary.replace(/\|/g, '\\|')} |`);
|
|
140
|
+
}
|
|
141
|
+
out.push('');
|
|
142
|
+
}
|
|
143
|
+
return out.join('\n');
|
|
144
|
+
}
|
package/lib/codex-preflight.js
CHANGED
|
@@ -21,10 +21,12 @@
|
|
|
21
21
|
import { execSync } from 'node:child_process';
|
|
22
22
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
23
23
|
import { join } from 'node:path';
|
|
24
|
-
import { homedir } from 'node:os';
|
|
24
|
+
import { homedir, tmpdir } from 'node:os';
|
|
25
25
|
|
|
26
26
|
export const PROBE_SENTINEL = 'COMPOSE_CODEX_PROBE_OK';
|
|
27
|
-
const WORKTREE_BASE =
|
|
27
|
+
const WORKTREE_BASE = process.env.NODE_TEST_CONTEXT
|
|
28
|
+
? join(tmpdir(), 'compose-test-worktrees', 'codex-preflight')
|
|
29
|
+
: join(homedir(), '.stratum', 'worktrees');
|
|
28
30
|
const PROBE_CACHE = 'codex-worktree-probe.json';
|
|
29
31
|
// Bound the probe's agent run so a wedged Codex CLI can't hang the whole build
|
|
30
32
|
// before the flow even starts. On timeout we reject → the finally cleanup runs.
|
|
@@ -63,13 +65,25 @@ function isGitRepo(cwd) {
|
|
|
63
65
|
*
|
|
64
66
|
* @param {object} args
|
|
65
67
|
* @param {string} args.cwd repo working directory
|
|
68
|
+
* @param {string} args.projectCwd project root used for dispatch telemetry
|
|
69
|
+
* @param {string} args.buildId current Compose build identifier
|
|
70
|
+
* @param {string} args.featureCode current feature code
|
|
66
71
|
* @param {object} args.stratum stratum client (uses runAgentText('codex', ...))
|
|
67
72
|
* @param {string} args.dataDir .compose/data dir for the per-repo cache
|
|
68
73
|
* @param {string} args.ts caller-supplied timestamp string (unique worktree name)
|
|
69
74
|
* @param {boolean} [args.force] ignore the cache and re-probe
|
|
70
75
|
* @returns {Promise<{ok: boolean, reason: string, cached?: boolean, skipped?: boolean}>}
|
|
71
76
|
*/
|
|
72
|
-
export async function preflightCodexWorktreeProbe({
|
|
77
|
+
export async function preflightCodexWorktreeProbe({
|
|
78
|
+
cwd,
|
|
79
|
+
projectCwd,
|
|
80
|
+
buildId,
|
|
81
|
+
featureCode,
|
|
82
|
+
stratum,
|
|
83
|
+
dataDir,
|
|
84
|
+
ts,
|
|
85
|
+
force = false,
|
|
86
|
+
}) {
|
|
73
87
|
if (process.env.COMPOSE_SKIP_CODEX_PROBE) {
|
|
74
88
|
return { ok: true, skipped: true, reason: 'COMPOSE_SKIP_CODEX_PROBE set — probe skipped' };
|
|
75
89
|
}
|
|
@@ -101,21 +115,40 @@ export async function preflightCodexWorktreeProbe({ cwd, stratum, dataDir, ts, f
|
|
|
101
115
|
// Unique per-run filename so a file committed in the repo (carried into the
|
|
102
116
|
// detached worktree from HEAD) can never make the probe false-pass.
|
|
103
117
|
const probeName = `codex-probe-${ts}.txt`;
|
|
118
|
+
let terminationUnconfirmed = false;
|
|
104
119
|
try {
|
|
105
120
|
const prompt =
|
|
106
121
|
`Write a file named ${probeName} in the current working directory whose exact ` +
|
|
107
122
|
`contents are the single line: ${PROBE_SENTINEL}\n` +
|
|
108
123
|
`Do nothing else. This is an environment write-probe.`;
|
|
109
124
|
try {
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
125
|
+
// Wait for acknowledged termination before removing the worktree. A
|
|
126
|
+
// Promise.race timeout could delete it while the agent was still writing.
|
|
127
|
+
const controller = new AbortController();
|
|
128
|
+
const timer = setTimeout(() => controller.abort(), PROBE_AGENT_TIMEOUT_MS);
|
|
129
|
+
timer.unref?.();
|
|
130
|
+
try {
|
|
131
|
+
await stratum.runAgentText('codex', prompt, {
|
|
132
|
+
cwd: wtPath,
|
|
133
|
+
sandboxMode: 'workspace-write',
|
|
134
|
+
signal: controller.signal,
|
|
135
|
+
telemetry: {
|
|
136
|
+
site: 'preflight',
|
|
137
|
+
project_cwd: projectCwd,
|
|
138
|
+
build_id: buildId,
|
|
139
|
+
feature_code: featureCode,
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
} finally {
|
|
143
|
+
clearTimeout(timer);
|
|
144
|
+
}
|
|
118
145
|
} catch (err) {
|
|
146
|
+
if (['CANCELLATION_UNCONFIRMED', 'CANCELLATION_TEARDOWN_TIMEOUT'].includes(err?.code)) {
|
|
147
|
+
terminationUnconfirmed = true;
|
|
148
|
+
err.worktreePath = wtPath;
|
|
149
|
+
err.message += `; probe worktree retained at ${wtPath}`;
|
|
150
|
+
throw err;
|
|
151
|
+
}
|
|
119
152
|
const result = { ok: false, reason: `Codex agent run failed/timed out inside the worktree: ${err?.message ?? err}` };
|
|
120
153
|
writeProbeCache(dataDir, result);
|
|
121
154
|
return result;
|
|
@@ -131,10 +164,12 @@ export async function preflightCodexWorktreeProbe({ cwd, stratum, dataDir, ts, f
|
|
|
131
164
|
writeProbeCache(dataDir, result);
|
|
132
165
|
return result;
|
|
133
166
|
} finally {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
167
|
+
if (!terminationUnconfirmed) {
|
|
168
|
+
try {
|
|
169
|
+
execSync(`git worktree remove "${wtPath}" --force`, { cwd, encoding: 'utf-8', timeout: 30_000, stdio: 'pipe' });
|
|
170
|
+
} catch {
|
|
171
|
+
try { execSync(`rm -rf "${wtPath}"`, { encoding: 'utf-8', timeout: 10_000 }); } catch { /* give up */ }
|
|
172
|
+
}
|
|
138
173
|
}
|
|
139
174
|
}
|
|
140
175
|
}
|