@smartmemory/compose 0.4.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/.claude/agents/compose-architect.md +40 -0
  2. package/.claude/agents/compose-explorer.md +35 -0
  3. package/.claude/hooks/canon-guard.mjs +52 -0
  4. package/README.md +1 -1
  5. package/bin/compose.js +33 -14
  6. package/bin/git-hooks/pre-push.template +26 -1
  7. package/bin/receipts-gate.js +39 -0
  8. package/contracts/fluid-record.schema.json +5 -0
  9. package/dist/assets/{App-Z4MU-H_F.js → App-DC7paCZv.js} +190 -190
  10. package/dist/assets/{_baseUniq-ClWoCPFl.js → _baseUniq-Czad7yiy.js} +1 -1
  11. package/dist/assets/{arc-DY26UIVo.js → arc-EquvLk8y.js} +1 -1
  12. package/dist/assets/{architectureDiagram-Q4EWVU46-6Ggq4DqJ.js → architectureDiagram-Q4EWVU46-Dr_qinWi.js} +1 -1
  13. package/dist/assets/{blockDiagram-DXYQGD6D-CH3Ked0l.js → blockDiagram-DXYQGD6D-D2z46ED_.js} +1 -1
  14. package/dist/assets/{c4Diagram-AHTNJAMY-Bk8dYilu.js → c4Diagram-AHTNJAMY-BHob1Yt0.js} +1 -1
  15. package/dist/assets/channel-B-7ZRCKC.js +1 -0
  16. package/dist/assets/{chunk-4BX2VUAB-BMR0XaAQ.js → chunk-4BX2VUAB-DomWBRa_.js} +1 -1
  17. package/dist/assets/{chunk-4TB4RGXK-JytR14a9.js → chunk-4TB4RGXK-WyC_x_DH.js} +1 -1
  18. package/dist/assets/{chunk-55IACEB6-B4Q97BCP.js → chunk-55IACEB6-BajRv3zx.js} +1 -1
  19. package/dist/assets/{chunk-EDXVE4YY-R_qarkSf.js → chunk-EDXVE4YY-rMnedK_r.js} +1 -1
  20. package/dist/assets/{chunk-FMBD7UC4-C9s7KR9m.js → chunk-FMBD7UC4-BPi03Hcb.js} +1 -1
  21. package/dist/assets/{chunk-OYMX7WX6-BySQzVxc.js → chunk-OYMX7WX6-B7J_mKX0.js} +1 -1
  22. package/dist/assets/{chunk-QZHKN3VN-DdpSYZsW.js → chunk-QZHKN3VN-BLXTVr8N.js} +1 -1
  23. package/dist/assets/{chunk-YZCP3GAM-iE_tzriw.js → chunk-YZCP3GAM-BYWjo2OJ.js} +1 -1
  24. package/dist/assets/classDiagram-6PBFFD2Q-Balz1OEB.js +1 -0
  25. package/dist/assets/classDiagram-v2-HSJHXN6E-Balz1OEB.js +1 -0
  26. package/dist/assets/clone-CfNV0lUO.js +1 -0
  27. package/dist/assets/{cose-bilkent-S5V4N54A-BdlU6ZX_.js → cose-bilkent-S5V4N54A-Coaq0xaU.js} +1 -1
  28. package/dist/assets/{dagre-KV5264BT-Cp3F5KTn.js → dagre-KV5264BT-DvUvAxlj.js} +1 -1
  29. package/dist/assets/{diagram-5BDNPKRD-DiR6_2q_.js → diagram-5BDNPKRD-70bXRUXV.js} +1 -1
  30. package/dist/assets/{diagram-G4DWMVQ6-w0i-p5HX.js → diagram-G4DWMVQ6-hMA8wgzx.js} +1 -1
  31. package/dist/assets/{diagram-MMDJMWI5-tIHhwUv3.js → diagram-MMDJMWI5-BNir7C6i.js} +1 -1
  32. package/dist/assets/{diagram-TYMM5635-BAeY3B19.js → diagram-TYMM5635-BCYl1xrE.js} +1 -1
  33. package/dist/assets/{erDiagram-SMLLAGMA-Ckx_Knko.js → erDiagram-SMLLAGMA-bjxP0_bt.js} +1 -1
  34. package/dist/assets/{flowDiagram-DWJPFMVM-DeoNka6J.js → flowDiagram-DWJPFMVM-CBn9fhEp.js} +1 -1
  35. package/dist/assets/{ganttDiagram-T4ZO3ILL-BmGnFbEg.js → ganttDiagram-T4ZO3ILL-y1O7mWzn.js} +1 -1
  36. package/dist/assets/{gitGraphDiagram-UUTBAWPF-Dk48IHsx.js → gitGraphDiagram-UUTBAWPF-DIxwDXHB.js} +1 -1
  37. package/dist/assets/{graph-BNzKGvoy.js → graph-9D1ZumWp.js} +1 -1
  38. package/dist/assets/{index-BEfrNBp8.js → index-Ds_IXQo3.js} +2 -2
  39. package/dist/assets/{infoDiagram-42DDH7IO-BRf827i0.js → infoDiagram-42DDH7IO-DsWLGhaY.js} +1 -1
  40. package/dist/assets/{ishikawaDiagram-UXIWVN3A-0kCZaeCM.js → ishikawaDiagram-UXIWVN3A-CipZIE90.js} +1 -1
  41. package/dist/assets/{journeyDiagram-VCZTEJTY-rvU7ayRt.js → journeyDiagram-VCZTEJTY-Vr5xqcQm.js} +1 -1
  42. package/dist/assets/{kanban-definition-6JOO6SKY-DpQwX1C5.js → kanban-definition-6JOO6SKY-EqUYneyh.js} +1 -1
  43. package/dist/assets/{layout-BI8cXFPI.js → layout-hfWIIs0-.js} +1 -1
  44. package/dist/assets/{linear-a0glcDiw.js → linear-BdDWoN0t.js} +1 -1
  45. package/dist/assets/{min-vPHfnXcC.js → min-Bn_xAS7n.js} +1 -1
  46. package/dist/assets/{mindmap-definition-QFDTVHPH-D14eF-7C.js → mindmap-definition-QFDTVHPH-qsgubzCF.js} +1 -1
  47. package/dist/assets/{pieDiagram-DEJITSTG-Cno-gETh.js → pieDiagram-DEJITSTG-Bv1xq_58.js} +1 -1
  48. package/dist/assets/{quadrantDiagram-34T5L4WZ-BUQM1Hfm.js → quadrantDiagram-34T5L4WZ-DwMbAegF.js} +1 -1
  49. package/dist/assets/{requirementDiagram-MS252O5E-pOXlN2-q.js → requirementDiagram-MS252O5E-BJVmLNcp.js} +1 -1
  50. package/dist/assets/{sankeyDiagram-XADWPNL6-Crynd3_b.js → sankeyDiagram-XADWPNL6-o5GZb8Y1.js} +1 -1
  51. package/dist/assets/{sequenceDiagram-FGHM5R23-D9fZdCM8.js → sequenceDiagram-FGHM5R23-ocqJp2qk.js} +1 -1
  52. package/dist/assets/{stateDiagram-FHFEXIEX-CW9qVec8.js → stateDiagram-FHFEXIEX-DGaDUFxP.js} +1 -1
  53. package/dist/assets/stateDiagram-v2-QKLJ7IA2-Dz-15i-r.js +1 -0
  54. package/dist/assets/{timeline-definition-GMOUNBTQ-BcHzhm_8.js → timeline-definition-GMOUNBTQ-C4YwFvAn.js} +1 -1
  55. package/dist/assets/{vennDiagram-DHZGUBPP-BfytJcWk.js → vennDiagram-DHZGUBPP-uOKn9j-y.js} +1 -1
  56. package/dist/assets/{wardley-RL74JXVD-DLj-IjyB.js → wardley-RL74JXVD-DIQSmQde.js} +1 -1
  57. package/dist/assets/{wardleyDiagram-NUSXRM2D-Ds0Ue68c.js → wardleyDiagram-NUSXRM2D-CdamsEDC.js} +1 -1
  58. package/dist/assets/{xychartDiagram-5P7HB3ND-vjWDXFL6.js → xychartDiagram-5P7HB3ND-DhLs41yk.js} +1 -1
  59. package/dist/index.html +1 -1
  60. package/lib/build-cancel.js +205 -0
  61. package/lib/build.js +552 -87
  62. package/lib/canon-guard.js +3 -24
  63. package/lib/canon-registry.js +2 -71
  64. package/lib/codex-preflight.js +8 -0
  65. package/lib/colleague/context.js +123 -0
  66. package/lib/consumer-fanout.js +24 -1
  67. package/lib/decision-blocks.js +38 -0
  68. package/lib/dispatch-ledger.js +7 -0
  69. package/lib/fluid/factory.js +112 -1
  70. package/lib/fluid/ideabox-manifest.js +203 -0
  71. package/lib/fluid/ideabox-migrate.js +177 -29
  72. package/lib/fluid/ideabox-preamble.js +155 -0
  73. package/lib/fluid/ideabox-readable.js +83 -0
  74. package/lib/fluid/ideabox-recover.js +393 -0
  75. package/lib/fluid/import-ideabox.js +188 -45
  76. package/lib/fluid/local-provider.js +6 -0
  77. package/lib/fluid/portfolio.js +255 -0
  78. package/lib/fluid/record-shape.js +7 -0
  79. package/lib/fluid/render-ideabox.js +153 -7
  80. package/lib/fluid/smartmemory-provider.js +6 -0
  81. package/lib/gate-prompt.js +14 -7
  82. package/lib/ideabox-cli.js +68 -0
  83. package/lib/ideabox.js +209 -9
  84. package/lib/maya-identity.js +16 -2
  85. package/lib/process-termination.js +121 -3
  86. package/lib/receipts-gate.js +268 -0
  87. package/lib/result-normalizer.js +28 -1
  88. package/lib/smartmemory-client.js +68 -1
  89. package/lib/stratum-mcp-client.js +104 -5
  90. package/lib/tool-inventory.js +0 -1
  91. package/lib/version-check.js +9 -3
  92. package/package.json +7 -5
  93. package/server/build-stream-bridge.js +43 -1
  94. package/server/cc-session-watcher.js +54 -5
  95. package/server/compose-mcp-tools.js +48 -50
  96. package/server/compose-mcp.js +0 -2
  97. package/server/design-routes.js +1 -1
  98. package/server/file-watcher.js +14 -0
  99. package/server/ideabox-routes.js +10 -0
  100. package/server/index.js +5 -1
  101. package/server/lifecycle-guard.js +13 -0
  102. package/server/maya-routes.js +111 -7
  103. package/server/mcp-tool-defs.js +0 -25
  104. package/server/mcp-tool-policy.js +6 -13
  105. package/server/stratum-client.js +61 -15
  106. package/server/supervisor.js +18 -4
  107. package/server/vision-routes.js +9 -3
  108. package/dist/assets/channel-SnZzzh7k.js +0 -1
  109. package/dist/assets/classDiagram-6PBFFD2Q-CBu92dSH.js +0 -1
  110. package/dist/assets/classDiagram-v2-HSJHXN6E-CBu92dSH.js +0 -1
  111. package/dist/assets/clone-DgklGjHm.js +0 -1
  112. package/dist/assets/stateDiagram-v2-QKLJ7IA2-DkVLzHbY.js +0 -1
  113. package/lib/append-integrity.js +0 -81
  114. package/lib/canon-override.js +0 -196
@@ -0,0 +1,205 @@
1
+ /**
2
+ * build-cancel.js — the build-level cancel handle and its in-process registry.
3
+ *
4
+ * Shared cancellation detection, registry and bounded teardown handshake (§3.6).
5
+ */
6
+
7
+ /**
8
+ * One handle, created once per `runBuild`, threaded through the build as `buildCancel`.
9
+ *
10
+ * TWO independent states (C27). `cancelled` is set by anyone — the signal handler, the
11
+ * cross-process detector, or a same-process `abortBuild`. `teardownStarted` is set ONLY by
12
+ * `runCancelTeardown`. Collapsing them would make the FIRST Ctrl-C after a detected
13
+ * cross-process cancel take the second-signal branch and skip the teardown entirely.
14
+ */
15
+ export function createBuildCancel() {
16
+ const controller = new AbortController();
17
+ const state = { cancelled: false, reason: null, at: null, teardownStarted: false };
18
+ let resolveDrained;
19
+ const drained = new Promise((resolve) => { resolveDrained = resolve; });
20
+ return {
21
+ signal: controller.signal, // -> runAndNormalize opts.buildSignal (C13)
22
+ get cancelled() { return state.cancelled; },
23
+ get reason() { return state.reason; },
24
+ get at() { return state.at; },
25
+ get teardownStarted() { return state.teardownStarted; },
26
+ /** Idempotent. Returns true only for the FIRST caller. */
27
+ cancel(reason) {
28
+ if (state.cancelled) return false;
29
+ state.cancelled = true;
30
+ state.reason = reason;
31
+ state.at = new Date().toISOString();
32
+ controller.abort(new Error(`build cancelled: ${reason}`));
33
+ return true;
34
+ },
35
+ /** Returns true only for the FIRST teardown, which is what forces a second signal to exit. */
36
+ beginTeardown() {
37
+ if (state.teardownStarted) return false;
38
+ state.teardownStarted = true;
39
+ return true;
40
+ },
41
+ // C37/§3.6. `teardown` is set synchronously by runCancelTeardown before its first await, so
42
+ // the outer catch can see it and stand down. `drained` is resolved by the build's inner
43
+ // finally once its resources are closed, so the teardown's writes cannot race
44
+ // finalizeBuildAttempt. Both are plain promises; both waits on them are bounded.
45
+ teardown: null,
46
+ drained,
47
+ resolveDrained,
48
+ };
49
+ }
50
+
51
+ /**
52
+ * ONE place decides whether a dispatch is tagged, so the kill switch has one seam
53
+ * (S03-6). `COMPOSE_FLOW_TAGGING=0` drops the `flow` key — and with it the
54
+ * flow-driven `cancellationId` mint, since the client mints one only when a `flow`
55
+ * or a `signal` is present — making a build byte-identical on the wire to a
56
+ * pre-feature one. It exists so an exec-transport or detachment regression can be
57
+ * bypassed without a release.
58
+ *
59
+ * `itemIndex` is OMITTED rather than sent as null: the server's default-deny shape
60
+ * check rejects a null.
61
+ */
62
+ export function flowTag(runId, stepId, itemIndex) {
63
+ if (process.env.COMPOSE_FLOW_TAGGING === '0' || !runId) return undefined;
64
+ return {
65
+ runId,
66
+ ...(stepId ? { stepId } : {}),
67
+ ...(typeof itemIndex === 'number' ? { itemIndex } : {}),
68
+ };
69
+ }
70
+
71
+ /** flowId -> BuildCancel, for builds running in THIS process. A same-process abort must
72
+ * cancel through the handle, never by signalling a pid: on the HTTP path that pid is the
73
+ * compose server itself (server/build-routes.js:134 and :151). Module-level and therefore
74
+ * per-process: a foreign build is unreachable through it by construction, and that absence
75
+ * is the signal that the pid path applies. */
76
+ const activeBuildCancels = new Map();
77
+
78
+ export function registerBuildCancel(flowId, handle) {
79
+ if (flowId) activeBuildCancels.set(flowId, handle);
80
+ }
81
+
82
+ export function unregisterBuildCancel(flowId) {
83
+ if (flowId) activeBuildCancels.delete(flowId);
84
+ }
85
+
86
+ export function lookupBuildCancel(flowId) {
87
+ return (flowId && activeBuildCancels.get(flowId)) ?? null;
88
+ }
89
+
90
+ /** The teardown in flight for ANY build in this process, or null. The CLI awaits it before
91
+ * exiting; it is an accessor rather than an export of the handle so the CLI never needs to
92
+ * know which build it belongs to. */
93
+ export function pendingTeardown() {
94
+ for (const handle of activeBuildCancels.values()) if (handle.teardown) return handle.teardown;
95
+ return null;
96
+ }
97
+
98
+ /** The AUTHORITY on "was this run cancelled". stratum_audit succeeds on a cancelled run
99
+ * (stratum/ts/src/engine/engine.ts:1057-1061), unlike stepDone/gateResolve/resume, which
100
+ * refuse. Returns false on any audit failure: an unreachable engine is not evidence of a
101
+ * cancel, and treating it as one would abandon a live build. */
102
+ export async function isRunCancelled(stratum, flowId) {
103
+ if (!stratum || !flowId) return false;
104
+ try {
105
+ const audit = await stratum.audit(flowId);
106
+ return audit?.status === 'cancelled';
107
+ } catch {
108
+ return false;
109
+ }
110
+ }
111
+
112
+ /** A cheap trigger for engine writes; only audit can confirm cancellation. */
113
+ export function looksCancelled(error) {
114
+ if (!error) return false;
115
+ if (['PERSIST_ON_CANCELLED_RUN', 'flow_cancelled', 'flow_not_running', 'FLOW_NOT_RUNNING'].includes(error.code)) return true;
116
+ return /is cancelled|flow cancelled/i.test(String(error.message ?? ''));
117
+ }
118
+
119
+ /** C34: any failed tagged dispatch is suspicious, including an uncoded exit 143.
120
+ * Call before normalizing the error or deciding to retry. */
121
+ export async function confirmCancellation(error, { stratum, flowId, buildCancel, tagged = false }) {
122
+ if (!buildCancel || !flowId) return false;
123
+ if (buildCancel.cancelled) return true;
124
+ if (!tagged && !looksCancelled(error)) return false;
125
+ if (!await isRunCancelled(stratum, flowId)) return false;
126
+ buildCancel.cancel('flow_cancelled');
127
+ return true;
128
+ }
129
+
130
+ /**
131
+ * Resolve a promise, or reject at `ms`. The single bounded-wait primitive this file
132
+ * uses; exported because `runBuild`'s outermost finally joins on the teardown with
133
+ * the same bound.
134
+ */
135
+ export function withDeadline(promise, ms) {
136
+ let timer;
137
+ return Promise.race([
138
+ Promise.resolve(promise).finally(() => clearTimeout(timer)),
139
+ new Promise((_resolve, reject) => {
140
+ timer = setTimeout(() => reject(new Error(`deadline exceeded after ${ms}ms`)), ms);
141
+ }),
142
+ ]);
143
+ }
144
+
145
+ /**
146
+ * The two teardown deadlines and the join bound DERIVED from them (C46).
147
+ *
148
+ * `cancelMs` is `COMPOSE_CANCEL_TIMEOUT_MS`, the variable the client already reads
149
+ * (lib/stratum-mcp-client.js:304) — one knob, not two. `joinMs` is never a chosen
150
+ * constant: any smaller bound expires before the teardown's own worst case and hands
151
+ * the exit back to the CLI mid-write.
152
+ */
153
+ export function cancelBudgets(env = process.env) {
154
+ const cancelMs = Number(env.COMPOSE_CANCEL_TIMEOUT_MS ?? 15000);
155
+ const drainMs = Number(env.COMPOSE_TEARDOWN_DRAIN_MS ?? 10000);
156
+ return { cancelMs, drainMs, joinMs: cancelMs + drainMs + 1000 };
157
+ }
158
+
159
+ /**
160
+ * Bounded, idempotent teardown for SIGINT/SIGTERM (§9 S06-1). Every dependency is
161
+ * injected, so the whole sequence runs under test with a fake client, a fake process
162
+ * and injected deadlines. It NEVER awaits the build pump — it races it.
163
+ *
164
+ * Absent by design: `emitActuals` and `closeStream`. Both belong to the build's inner
165
+ * `finally`; `finalizeBuildAttempt` (lib/build.js:2294-2301) is the single actuals
166
+ * emitter and is already idempotent through `attemptFinalized` (C45).
167
+ */
168
+ export async function runCancelTeardown({
169
+ buildCancel, signal, flowId, flowCancel, timeoutMs, drainMs,
170
+ claimOwnership = () => ({ ok: true }), killVision, writeTerminal, removeListeners, exit, log,
171
+ }) {
172
+ // C27: key the force-exit on teardownStarted, NOT on cancelled. By the time a user
173
+ // presses Ctrl-C the handle may ALREADY be cancelled — the cross-process detector
174
+ // sets it — and keying on that would make the FIRST signal behave like a second
175
+ // and skip the teardown entirely.
176
+ if (!buildCancel.beginTeardown()) { exit(signal === 'SIGINT' ? 130 : 143); return; }
177
+ buildCancel.cancel(`signal:${signal}`); // idempotent; records the reason if it is first
178
+ try {
179
+ await withDeadline(flowCancel(flowId), timeoutMs);
180
+ } catch (error) {
181
+ // `already_cancelled` is success: abortBuild got here first. Anything else is
182
+ // reported and does not stop the local teardown — the local record must not be
183
+ // left `running`.
184
+ if (error?.reason !== 'already_cancelled') {
185
+ log(`flow cancel: ${error?.reason ?? error?.code ?? error?.message}`);
186
+ }
187
+ }
188
+ // §3.6: wait for the build's own finally to close its resources and emit actuals, so
189
+ // this teardown's writes cannot race finalizeBuildAttempt. Bounded, because a wedged
190
+ // pump never reaches that finally and D-E forbids awaiting the pump.
191
+ await withDeadline(buildCancel.drained, drainMs).catch(() => undefined);
192
+ // Claims expire across awaits. A replacement owns its vision and record.
193
+ let claim = claimOwnership();
194
+ if (claim.ok) {
195
+ // Spend at most the join's 1000ms local-cleanup allowance on vision. A slow
196
+ // REST update must never prevent the durable terminal record or signal exit.
197
+ try { await withDeadline(killVision(), Math.min(timeoutMs, 1000)); } catch { /* best-effort */ }
198
+ claim = claimOwnership();
199
+ if (claim.ok) {
200
+ try { writeTerminal(claim.record); } catch { /* best-effort */ }
201
+ }
202
+ }
203
+ removeListeners();
204
+ exit(signal === 'SIGINT' ? 130 : 143); // the ONLY exit on this path
205
+ }