@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.
- package/.claude/agents/compose-architect.md +40 -0
- package/.claude/agents/compose-explorer.md +35 -0
- package/.claude/hooks/canon-guard.mjs +52 -0
- package/README.md +1 -1
- package/bin/compose.js +33 -14
- package/bin/git-hooks/pre-push.template +26 -1
- package/bin/receipts-gate.js +39 -0
- package/contracts/fluid-record.schema.json +5 -0
- package/dist/assets/{App-Z4MU-H_F.js → App-DC7paCZv.js} +190 -190
- package/dist/assets/{_baseUniq-ClWoCPFl.js → _baseUniq-Czad7yiy.js} +1 -1
- package/dist/assets/{arc-DY26UIVo.js → arc-EquvLk8y.js} +1 -1
- package/dist/assets/{architectureDiagram-Q4EWVU46-6Ggq4DqJ.js → architectureDiagram-Q4EWVU46-Dr_qinWi.js} +1 -1
- package/dist/assets/{blockDiagram-DXYQGD6D-CH3Ked0l.js → blockDiagram-DXYQGD6D-D2z46ED_.js} +1 -1
- package/dist/assets/{c4Diagram-AHTNJAMY-Bk8dYilu.js → c4Diagram-AHTNJAMY-BHob1Yt0.js} +1 -1
- package/dist/assets/channel-B-7ZRCKC.js +1 -0
- package/dist/assets/{chunk-4BX2VUAB-BMR0XaAQ.js → chunk-4BX2VUAB-DomWBRa_.js} +1 -1
- package/dist/assets/{chunk-4TB4RGXK-JytR14a9.js → chunk-4TB4RGXK-WyC_x_DH.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-B4Q97BCP.js → chunk-55IACEB6-BajRv3zx.js} +1 -1
- package/dist/assets/{chunk-EDXVE4YY-R_qarkSf.js → chunk-EDXVE4YY-rMnedK_r.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-C9s7KR9m.js → chunk-FMBD7UC4-BPi03Hcb.js} +1 -1
- package/dist/assets/{chunk-OYMX7WX6-BySQzVxc.js → chunk-OYMX7WX6-B7J_mKX0.js} +1 -1
- package/dist/assets/{chunk-QZHKN3VN-DdpSYZsW.js → chunk-QZHKN3VN-BLXTVr8N.js} +1 -1
- package/dist/assets/{chunk-YZCP3GAM-iE_tzriw.js → chunk-YZCP3GAM-BYWjo2OJ.js} +1 -1
- package/dist/assets/classDiagram-6PBFFD2Q-Balz1OEB.js +1 -0
- package/dist/assets/classDiagram-v2-HSJHXN6E-Balz1OEB.js +1 -0
- package/dist/assets/clone-CfNV0lUO.js +1 -0
- package/dist/assets/{cose-bilkent-S5V4N54A-BdlU6ZX_.js → cose-bilkent-S5V4N54A-Coaq0xaU.js} +1 -1
- package/dist/assets/{dagre-KV5264BT-Cp3F5KTn.js → dagre-KV5264BT-DvUvAxlj.js} +1 -1
- package/dist/assets/{diagram-5BDNPKRD-DiR6_2q_.js → diagram-5BDNPKRD-70bXRUXV.js} +1 -1
- package/dist/assets/{diagram-G4DWMVQ6-w0i-p5HX.js → diagram-G4DWMVQ6-hMA8wgzx.js} +1 -1
- package/dist/assets/{diagram-MMDJMWI5-tIHhwUv3.js → diagram-MMDJMWI5-BNir7C6i.js} +1 -1
- package/dist/assets/{diagram-TYMM5635-BAeY3B19.js → diagram-TYMM5635-BCYl1xrE.js} +1 -1
- package/dist/assets/{erDiagram-SMLLAGMA-Ckx_Knko.js → erDiagram-SMLLAGMA-bjxP0_bt.js} +1 -1
- package/dist/assets/{flowDiagram-DWJPFMVM-DeoNka6J.js → flowDiagram-DWJPFMVM-CBn9fhEp.js} +1 -1
- package/dist/assets/{ganttDiagram-T4ZO3ILL-BmGnFbEg.js → ganttDiagram-T4ZO3ILL-y1O7mWzn.js} +1 -1
- package/dist/assets/{gitGraphDiagram-UUTBAWPF-Dk48IHsx.js → gitGraphDiagram-UUTBAWPF-DIxwDXHB.js} +1 -1
- package/dist/assets/{graph-BNzKGvoy.js → graph-9D1ZumWp.js} +1 -1
- package/dist/assets/{index-BEfrNBp8.js → index-Ds_IXQo3.js} +2 -2
- package/dist/assets/{infoDiagram-42DDH7IO-BRf827i0.js → infoDiagram-42DDH7IO-DsWLGhaY.js} +1 -1
- package/dist/assets/{ishikawaDiagram-UXIWVN3A-0kCZaeCM.js → ishikawaDiagram-UXIWVN3A-CipZIE90.js} +1 -1
- package/dist/assets/{journeyDiagram-VCZTEJTY-rvU7ayRt.js → journeyDiagram-VCZTEJTY-Vr5xqcQm.js} +1 -1
- package/dist/assets/{kanban-definition-6JOO6SKY-DpQwX1C5.js → kanban-definition-6JOO6SKY-EqUYneyh.js} +1 -1
- package/dist/assets/{layout-BI8cXFPI.js → layout-hfWIIs0-.js} +1 -1
- package/dist/assets/{linear-a0glcDiw.js → linear-BdDWoN0t.js} +1 -1
- package/dist/assets/{min-vPHfnXcC.js → min-Bn_xAS7n.js} +1 -1
- package/dist/assets/{mindmap-definition-QFDTVHPH-D14eF-7C.js → mindmap-definition-QFDTVHPH-qsgubzCF.js} +1 -1
- package/dist/assets/{pieDiagram-DEJITSTG-Cno-gETh.js → pieDiagram-DEJITSTG-Bv1xq_58.js} +1 -1
- package/dist/assets/{quadrantDiagram-34T5L4WZ-BUQM1Hfm.js → quadrantDiagram-34T5L4WZ-DwMbAegF.js} +1 -1
- package/dist/assets/{requirementDiagram-MS252O5E-pOXlN2-q.js → requirementDiagram-MS252O5E-BJVmLNcp.js} +1 -1
- package/dist/assets/{sankeyDiagram-XADWPNL6-Crynd3_b.js → sankeyDiagram-XADWPNL6-o5GZb8Y1.js} +1 -1
- package/dist/assets/{sequenceDiagram-FGHM5R23-D9fZdCM8.js → sequenceDiagram-FGHM5R23-ocqJp2qk.js} +1 -1
- package/dist/assets/{stateDiagram-FHFEXIEX-CW9qVec8.js → stateDiagram-FHFEXIEX-DGaDUFxP.js} +1 -1
- package/dist/assets/stateDiagram-v2-QKLJ7IA2-Dz-15i-r.js +1 -0
- package/dist/assets/{timeline-definition-GMOUNBTQ-BcHzhm_8.js → timeline-definition-GMOUNBTQ-C4YwFvAn.js} +1 -1
- package/dist/assets/{vennDiagram-DHZGUBPP-BfytJcWk.js → vennDiagram-DHZGUBPP-uOKn9j-y.js} +1 -1
- package/dist/assets/{wardley-RL74JXVD-DLj-IjyB.js → wardley-RL74JXVD-DIQSmQde.js} +1 -1
- package/dist/assets/{wardleyDiagram-NUSXRM2D-Ds0Ue68c.js → wardleyDiagram-NUSXRM2D-CdamsEDC.js} +1 -1
- package/dist/assets/{xychartDiagram-5P7HB3ND-vjWDXFL6.js → xychartDiagram-5P7HB3ND-DhLs41yk.js} +1 -1
- package/dist/index.html +1 -1
- package/lib/build-cancel.js +205 -0
- package/lib/build.js +552 -87
- package/lib/canon-guard.js +3 -24
- package/lib/canon-registry.js +2 -71
- package/lib/codex-preflight.js +8 -0
- package/lib/colleague/context.js +123 -0
- package/lib/consumer-fanout.js +24 -1
- package/lib/decision-blocks.js +38 -0
- package/lib/dispatch-ledger.js +7 -0
- package/lib/fluid/factory.js +112 -1
- package/lib/fluid/ideabox-manifest.js +203 -0
- package/lib/fluid/ideabox-migrate.js +177 -29
- package/lib/fluid/ideabox-preamble.js +155 -0
- package/lib/fluid/ideabox-readable.js +83 -0
- package/lib/fluid/ideabox-recover.js +393 -0
- package/lib/fluid/import-ideabox.js +188 -45
- package/lib/fluid/local-provider.js +6 -0
- package/lib/fluid/portfolio.js +255 -0
- package/lib/fluid/record-shape.js +7 -0
- package/lib/fluid/render-ideabox.js +153 -7
- package/lib/fluid/smartmemory-provider.js +6 -0
- package/lib/gate-prompt.js +14 -7
- package/lib/ideabox-cli.js +68 -0
- package/lib/ideabox.js +209 -9
- package/lib/maya-identity.js +16 -2
- package/lib/process-termination.js +121 -3
- package/lib/receipts-gate.js +268 -0
- package/lib/result-normalizer.js +28 -1
- package/lib/smartmemory-client.js +68 -1
- package/lib/stratum-mcp-client.js +104 -5
- package/lib/tool-inventory.js +0 -1
- package/lib/version-check.js +9 -3
- package/package.json +7 -5
- package/server/build-stream-bridge.js +43 -1
- package/server/cc-session-watcher.js +54 -5
- package/server/compose-mcp-tools.js +48 -50
- package/server/compose-mcp.js +0 -2
- package/server/design-routes.js +1 -1
- package/server/file-watcher.js +14 -0
- package/server/ideabox-routes.js +10 -0
- package/server/index.js +5 -1
- package/server/lifecycle-guard.js +13 -0
- package/server/maya-routes.js +111 -7
- package/server/mcp-tool-defs.js +0 -25
- package/server/mcp-tool-policy.js +6 -13
- package/server/stratum-client.js +61 -15
- package/server/supervisor.js +18 -4
- package/server/vision-routes.js +9 -3
- package/dist/assets/channel-SnZzzh7k.js +0 -1
- package/dist/assets/classDiagram-6PBFFD2Q-CBu92dSH.js +0 -1
- package/dist/assets/classDiagram-v2-HSJHXN6E-CBu92dSH.js +0 -1
- package/dist/assets/clone-DgklGjHm.js +0 -1
- package/dist/assets/stateDiagram-v2-QKLJ7IA2-DkVLzHbY.js +0 -1
- package/lib/append-integrity.js +0 -81
- package/lib/canon-override.js +0 -196
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: compose-architect
|
|
3
|
+
description: Architecture-proposal agent for Compose's Phase 3. Dispatched 2-3 in parallel, each under a different competing mandate (minimal-changes / clean-architecture / pragmatic-balance), to produce a candidate architecture for a feature. Returns a self-contained proposal with component boundaries, interfaces, and honest trade-offs, grounded in the real codebase. Proposes only — does not implement.
|
|
4
|
+
tools: Read, Grep, Glob, Bash
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You are compose-architect. You are dispatched with ONE explicit mandate and must produce the best
|
|
8
|
+
architecture for the feature **under that mandate** — so the main agent can compare competing
|
|
9
|
+
proposals and synthesize. You propose; you do not write production code.
|
|
10
|
+
|
|
11
|
+
## Mandates (you will be given exactly one)
|
|
12
|
+
- **minimal-changes** — smallest possible diff, maximum reuse of what exists. Bias to extending
|
|
13
|
+
current modules over adding new ones.
|
|
14
|
+
- **clean-architecture** — maintainability first; elegant boundaries and abstractions even if it
|
|
15
|
+
means more moving parts.
|
|
16
|
+
- **pragmatic-balance** — 80/20: good boundaries without over-engineering.
|
|
17
|
+
|
|
18
|
+
## Your job
|
|
19
|
+
1. Read the relevant existing code (Grep/Glob/Read; Bash read-only) so the proposal is grounded in
|
|
20
|
+
real modules, signatures, and patterns — cite `path:line`.
|
|
21
|
+
2. Design the architecture under your mandate: components and their responsibilities, the
|
|
22
|
+
interfaces/contracts between them, data flow, and error/edge handling.
|
|
23
|
+
3. Be honest about trade-offs — what your mandate buys and what it costs versus the alternatives.
|
|
24
|
+
|
|
25
|
+
## Hard constraints
|
|
26
|
+
- READ-ONLY. Do not Edit/Write or run mutating commands. The main agent writes `architecture.md`
|
|
27
|
+
and implements.
|
|
28
|
+
- Ground every structural claim in the real codebase (file:line). Flag where the feature would
|
|
29
|
+
cut against existing patterns.
|
|
30
|
+
- Honor your mandate even where you'd personally choose differently — the value is in the contrast.
|
|
31
|
+
|
|
32
|
+
## Output
|
|
33
|
+
Return a self-contained proposal:
|
|
34
|
+
- **Approach (mandate)** — one line naming your mandate and the core idea.
|
|
35
|
+
- **Components & boundaries** — each component, its responsibility, and its interface/contract.
|
|
36
|
+
- **Data flow** — how a request moves through the components.
|
|
37
|
+
- **Trade-offs** — strengths, weaknesses, and what this costs vs the other mandates.
|
|
38
|
+
- **Reuse / new** — what existing code is reused (file:line) vs what's new.
|
|
39
|
+
|
|
40
|
+
Your final message IS the deliverable — return the proposal itself.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: compose-explorer
|
|
3
|
+
description: Read-only codebase exploration for Compose's design (Phase 1) and blueprint (Phase 4) phases. Use to find features similar to a target, map the architecture and abstractions of an area, or trace how an existing feature is implemented — dispatched 2-3 in parallel with distinct mandates. Returns findings with concrete file:line references; never edits code.
|
|
4
|
+
tools: Read, Grep, Glob, Bash
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You are compose-explorer, a focused read-only research agent for the Compose lifecycle. You are
|
|
8
|
+
dispatched to investigate ONE specific question about the codebase and return grounded findings —
|
|
9
|
+
not to design, plan, or change anything.
|
|
10
|
+
|
|
11
|
+
## Your job
|
|
12
|
+
Given a research mandate (e.g. "find features similar to X and trace their implementation", "map
|
|
13
|
+
the architecture and abstractions for area Y", "analyze how feature Z works today"):
|
|
14
|
+
|
|
15
|
+
1. Locate the relevant code with Grep/Glob; read the specific files and lines that matter.
|
|
16
|
+
2. Identify the patterns, abstractions, and conventions actually in use — with **file:line**
|
|
17
|
+
references for every claim. Prefer reading exact regions over whole files.
|
|
18
|
+
3. Note anything that contradicts the assumption behind the mandate (a "spec vs reality" gap).
|
|
19
|
+
4. Use Bash only for read-only inspection (`git log`, `git grep`, `ls`, `rg`). Never write.
|
|
20
|
+
|
|
21
|
+
## Hard constraints
|
|
22
|
+
- READ-ONLY. Do not use Edit/Write. Do not run mutating commands. You investigate; the main agent
|
|
23
|
+
decides and implements.
|
|
24
|
+
- Every load-bearing claim cites `path:line`. If you can't verify it, say so explicitly.
|
|
25
|
+
- Stay within your mandate — don't expand into adjacent areas unless they're load-bearing for it.
|
|
26
|
+
|
|
27
|
+
## Output
|
|
28
|
+
Return a concise structured summary:
|
|
29
|
+
- **Findings** — bulleted, each with file:line evidence.
|
|
30
|
+
- **Relevant files** — the files the main agent should read to act on this.
|
|
31
|
+
- **Patterns to follow** — conventions/idioms observed (with refs).
|
|
32
|
+
- **Mismatches / risks** — where reality diverges from the mandate's assumption, if any.
|
|
33
|
+
|
|
34
|
+
Your final message IS the deliverable — return the structured findings, not a narrative of what
|
|
35
|
+
you did.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* canon-guard.mjs — COMP-CANON-GUARD S4 PreToolUse hook (runtime wrapper).
|
|
4
|
+
*
|
|
5
|
+
* Registered in .claude/settings.json by `compose guard install`. Claude Code
|
|
6
|
+
* runs it before every Write/Edit/NotebookEdit and pipes the tool call as JSON
|
|
7
|
+
* on stdin. All logic lives in lib/canon-guard.js (pure, tested); this wrapper
|
|
8
|
+
* only does I/O: read stdin → decide → emit the deny envelope.
|
|
9
|
+
*
|
|
10
|
+
* Deny protocol (verified against code.claude.com/docs/en/hooks 2026-07-24):
|
|
11
|
+
* exit 0 + stdout {"hookSpecificOutput":{"hookEventName":"PreToolUse",
|
|
12
|
+
* "permissionDecision":"deny","permissionDecisionReason":"..."}}. No output =
|
|
13
|
+
* allow. FAIL OPEN on any error — a guard that wedges the session is worse than
|
|
14
|
+
* one that misses.
|
|
15
|
+
*/
|
|
16
|
+
import { readFileSync } from 'node:fs';
|
|
17
|
+
import { fileURLToPath } from 'node:url';
|
|
18
|
+
import { dirname, resolve } from 'node:path';
|
|
19
|
+
import { decideCanonGuard, realpathCanonicalize } from '../../lib/canon-guard.js';
|
|
20
|
+
|
|
21
|
+
function allow() { process.exit(0); }
|
|
22
|
+
|
|
23
|
+
let payload;
|
|
24
|
+
try {
|
|
25
|
+
payload = JSON.parse(readFileSync(0, 'utf-8'));
|
|
26
|
+
} catch {
|
|
27
|
+
allow(); // unreadable/invalid stdin → don't block
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
// The hook lives at <repo>/.claude/hooks/canon-guard.mjs → repo root is two up.
|
|
32
|
+
const projectRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
33
|
+
const decision = decideCanonGuard({
|
|
34
|
+
toolName: payload?.tool_name,
|
|
35
|
+
toolInput: payload?.tool_input,
|
|
36
|
+
cwd: payload?.cwd,
|
|
37
|
+
projectRoot,
|
|
38
|
+
canonicalize: realpathCanonicalize,
|
|
39
|
+
});
|
|
40
|
+
if (decision.deny) {
|
|
41
|
+
process.stdout.write(JSON.stringify({
|
|
42
|
+
hookSpecificOutput: {
|
|
43
|
+
hookEventName: 'PreToolUse',
|
|
44
|
+
permissionDecision: 'deny',
|
|
45
|
+
permissionDecisionReason: decision.reason,
|
|
46
|
+
},
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
} catch {
|
|
50
|
+
// fall through to allow
|
|
51
|
+
}
|
|
52
|
+
process.exit(0);
|
package/README.md
CHANGED
|
@@ -198,7 +198,7 @@ Signed descriptors live in `.compose/guard-upgrades/<sha256>/` (immutable) with
|
|
|
198
198
|
The mobile cockpit at `/m` can be reached from outside localhost — bring your own tunnel, compose handles auth and pairing:
|
|
199
199
|
|
|
200
200
|
```bash
|
|
201
|
-
npm run build #
|
|
201
|
+
npm run build # source checkouts only; the npm package already ships dist/
|
|
202
202
|
COMPOSE_REMOTE_AUTH=enabled compose start --host=0.0.0.0
|
|
203
203
|
compose remote pair --public-host=https://your-tunnel-host # prints a QR — scan it with your phone
|
|
204
204
|
compose remote status # bind, devices, tunnel reachability
|
package/bin/compose.js
CHANGED
|
@@ -98,6 +98,8 @@ import {
|
|
|
98
98
|
formatDriftNudge,
|
|
99
99
|
} from '../lib/version-check.js';
|
|
100
100
|
import { computeHooksStatus, formatHookStatusLines, HOOK_MARKERS } from '../lib/hooks-status.js';
|
|
101
|
+
// COMP-BUILD-CANCEL S06-3: the CLI must not exit out from under a live teardown (C46).
|
|
102
|
+
import { pendingTeardown } from '../lib/build-cancel.js';
|
|
101
103
|
|
|
102
104
|
const [,, cmd, ...args] = process.argv
|
|
103
105
|
|
|
@@ -968,6 +970,16 @@ if (cmd === 'new') {
|
|
|
968
970
|
console.warn(`--from-idea: idea not found: ${fromIdeaId}`)
|
|
969
971
|
}
|
|
970
972
|
} catch (err) {
|
|
973
|
+
// An unreadable ideabox is not a missing idea, and it must not be
|
|
974
|
+
// downgraded into a warning that the build then walks straight past.
|
|
975
|
+
// `--from-idea` is an explicit request for THAT idea's content; building
|
|
976
|
+
// the feature without it produces a plausible-looking design derived from
|
|
977
|
+
// nothing the user asked for, while the ideas sit unread on disk
|
|
978
|
+
// (COMP-IDEABOX-MIGRATE-DIALECT FU-2).
|
|
979
|
+
if (err?.code === 'IDEABOX_UNREADABLE') {
|
|
980
|
+
console.error(`--from-idea: ${err.message}`)
|
|
981
|
+
process.exit(1)
|
|
982
|
+
}
|
|
971
983
|
console.warn(`--from-idea: could not load idea: ${err.message}`)
|
|
972
984
|
}
|
|
973
985
|
}
|
|
@@ -2313,7 +2325,8 @@ if (cmd === 'guard') {
|
|
|
2313
2325
|
if (sub === 'install') {
|
|
2314
2326
|
if (!exSync(hookScript)) {
|
|
2315
2327
|
console.error(`Error: hook script missing at ${hookScript}`)
|
|
2316
|
-
console.error('
|
|
2328
|
+
console.error('npm install: reinstall with `npm install @smartmemory/compose@latest` (add `--global` for a global install).')
|
|
2329
|
+
console.error('source checkout: update the checkout, or restore the tracked .claude/hooks/canon-guard.mjs file.')
|
|
2317
2330
|
process.exit(1)
|
|
2318
2331
|
}
|
|
2319
2332
|
const { settings, changed } = installGuardHook(readSettings())
|
|
@@ -2346,10 +2359,10 @@ if (cmd === 'guard') {
|
|
|
2346
2359
|
else if (st.state === 'stale') console.log('canon-guard: installed (stale — re-run `compose guard install`)')
|
|
2347
2360
|
else console.log('canon-guard: absent — run `compose guard install`')
|
|
2348
2361
|
console.log(` hook script: ${scriptState} (${hookScript})`)
|
|
2349
|
-
// Derived from the registry, never hardcoded
|
|
2350
|
-
// silently
|
|
2351
|
-
//
|
|
2352
|
-
//
|
|
2362
|
+
// Derived from the registry, never hardcoded, so registry changes cannot
|
|
2363
|
+
// silently desynchronize status output. "Claude-runtime only" is not
|
|
2364
|
+
// modesty — Bash and Codex writes never reach this hook (drift detection,
|
|
2365
|
+
// not enforcement).
|
|
2353
2366
|
const { guardedDisplaysFor } = await import('../lib/canon-registry.js')
|
|
2354
2367
|
for (const pattern of guardedDisplaysFor('hook')) {
|
|
2355
2368
|
console.log(` guards: ${pattern} (Write|Edit|NotebookEdit) — Claude-runtime only`)
|
|
@@ -2846,9 +2859,11 @@ if (cmd === 'build') {
|
|
|
2846
2859
|
if (fresh) singleOpts.fresh = true
|
|
2847
2860
|
if (nonInteractiveBuild) singleOpts.gateOpts = { nonInteractive: true }
|
|
2848
2861
|
if (resumeFlowId) singleOpts.resumeFlowId = resumeFlowId
|
|
2849
|
-
runBuild(featureCode, singleOpts).then(() => {
|
|
2850
|
-
|
|
2851
|
-
|
|
2862
|
+
runBuild(featureCode, singleOpts).then(async (result) => {
|
|
2863
|
+
await pendingTeardown()
|
|
2864
|
+
process.exit(abort && result?.ok === false ? 1 : 0)
|
|
2865
|
+
}).catch(async (err) => {
|
|
2866
|
+
await pendingTeardown()
|
|
2852
2867
|
console.error(`Build failed: ${err.message}`)
|
|
2853
2868
|
process.exit(1)
|
|
2854
2869
|
})
|
|
@@ -2963,9 +2978,11 @@ if (cmd === 'build') {
|
|
|
2963
2978
|
if (agentWorkDir) opts.workingDirectory = agentWorkDir
|
|
2964
2979
|
if (bugDescription) opts.description = bugDescription
|
|
2965
2980
|
if (resumeFlowId) opts.resumeFlowId = resumeFlowId
|
|
2966
|
-
runBuild(bugCode, opts).then(() => {
|
|
2967
|
-
|
|
2968
|
-
|
|
2981
|
+
runBuild(bugCode, opts).then(async (result) => {
|
|
2982
|
+
await pendingTeardown()
|
|
2983
|
+
process.exit(abort && result?.ok === false ? 1 : 0)
|
|
2984
|
+
}).catch(async (err) => {
|
|
2985
|
+
await pendingTeardown()
|
|
2969
2986
|
console.error(`Fix failed: ${err.message}`)
|
|
2970
2987
|
process.exit(1)
|
|
2971
2988
|
})
|
|
@@ -3074,9 +3091,11 @@ if (cmd === 'build') {
|
|
|
3074
3091
|
const opts = { abort, template: 'plan', mode: 'plan', description: intent }
|
|
3075
3092
|
if (agentWorkDir) opts.workingDirectory = agentWorkDir
|
|
3076
3093
|
if (resumeFlowId) opts.resumeFlowId = resumeFlowId
|
|
3077
|
-
runBuild(planCode, opts).then(() => {
|
|
3078
|
-
|
|
3079
|
-
|
|
3094
|
+
runBuild(planCode, opts).then(async (result) => {
|
|
3095
|
+
await pendingTeardown()
|
|
3096
|
+
process.exit(abort && result?.ok === false ? 1 : 0)
|
|
3097
|
+
}).catch(async (err) => {
|
|
3098
|
+
await pendingTeardown()
|
|
3080
3099
|
console.error(`Plan failed: ${err.message}`)
|
|
3081
3100
|
process.exit(1)
|
|
3082
3101
|
})
|
|
@@ -42,12 +42,16 @@ command -v perl >/dev/null 2>&1 && perl -MFcntl -e '
|
|
|
42
42
|
ZERO_SHA="0000000000000000000000000000000000000000"
|
|
43
43
|
DOCS_ONLY=1
|
|
44
44
|
SAW_RANGE=0
|
|
45
|
+
RANGES=" "
|
|
45
46
|
while read -r _local_ref local_sha _remote_ref remote_sha; do
|
|
46
47
|
[ "$local_sha" = "$ZERO_SHA" ] && continue # ref delete — no content pushed
|
|
47
48
|
if [ "$remote_sha" = "$ZERO_SHA" ]; then
|
|
48
|
-
DOCS_ONLY=0; SAW_RANGE=1
|
|
49
|
+
DOCS_ONLY=0; SAW_RANGE=1
|
|
50
|
+
RANGES="$RANGES $remote_sha:$local_sha" # receipts gate bounds a new ref itself
|
|
51
|
+
continue # new ref — range unbounded, fail closed
|
|
49
52
|
fi
|
|
50
53
|
SAW_RANGE=1
|
|
54
|
+
RANGES="$RANGES $remote_sha:$local_sha"
|
|
51
55
|
CHANGED=$(git diff --name-only "$remote_sha".."$local_sha" 2>/dev/null) || { DOCS_ONLY=0; continue; }
|
|
52
56
|
while IFS= read -r f; do
|
|
53
57
|
[ -z "$f" ] && continue
|
|
@@ -90,6 +94,27 @@ if [ "$GUARD_EXIT" -ne 0 ]; then
|
|
|
90
94
|
fi
|
|
91
95
|
echo "pre-push: judgment canon drift check green." >&2
|
|
92
96
|
|
|
97
|
+
# ── Receipts gate ────────────────────────────────────────────────────────────
|
|
98
|
+
# A claim written as a fact must carry its receipt: a CHECKED box saying a test
|
|
99
|
+
# pins something needs the test path; "flake"/"flaky" needs a measurement or a
|
|
100
|
+
# RESOLVED @sha; a commit saying the suite is green needs the counts. Scans only
|
|
101
|
+
# the ADDED lines of the pushed range, so it never blocks on history. Runs on
|
|
102
|
+
# docs-only pushes too — that is where "known flake" notes get written, and one
|
|
103
|
+
# of those sat on a live product defect for two months (12a357a). Quote a phrase
|
|
104
|
+
# in backticks to talk about it without asserting it. Bypass: --no-verify.
|
|
105
|
+
RECEIPTS_GATE="$(dirname "$COMPOSE_BIN")/receipts-gate.js"
|
|
106
|
+
if [ -f "$RECEIPTS_GATE" ]; then
|
|
107
|
+
for pair in $RANGES; do
|
|
108
|
+
"$COMPOSE_NODE" "$RECEIPTS_GATE" "${pair%%:*}" "${pair##*:}" 2>&1 | tee -a "$LOG" >&2
|
|
109
|
+
RECEIPTS_EXIT=${PIPESTATUS[0]}
|
|
110
|
+
if [ "$RECEIPTS_EXIT" -ne 0 ]; then
|
|
111
|
+
echo "" >&2
|
|
112
|
+
echo "pre-push: a claim has no receipt beside it — push aborted (full log: $LOG)." >&2
|
|
113
|
+
exit "$RECEIPTS_EXIT"
|
|
114
|
+
fi
|
|
115
|
+
done
|
|
116
|
+
fi
|
|
117
|
+
|
|
93
118
|
# ── Test gate (COMP-RESUME follow-up) ───────────────────────────────────────
|
|
94
119
|
# Block the push if the suite is red. This is the gate whose absence let a broken
|
|
95
120
|
# integration test (and a stale-schema consumer bug) reach main — nothing ran the
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* receipts-gate — pre-push runner. `node bin/receipts-gate.js <base> <head>`.
|
|
4
|
+
*
|
|
5
|
+
* `base` may be git's all-zeros sha (a new ref); then the range is bounded by
|
|
6
|
+
* the merge-base with origin/main, or head~1 when there is no origin/main —
|
|
7
|
+
* fail CLOSED (scan something) rather than open (scan nothing).
|
|
8
|
+
* Exit 1 on any violation. See lib/receipts-gate.js for what counts.
|
|
9
|
+
*/
|
|
10
|
+
import { execFileSync } from 'node:child_process';
|
|
11
|
+
import { scanRange, formatViolations } from '../lib/receipts-gate.js';
|
|
12
|
+
|
|
13
|
+
const ZERO = /^0{40}$/;
|
|
14
|
+
const [, , baseArg, headArg = 'HEAD'] = process.argv;
|
|
15
|
+
|
|
16
|
+
function git(args) {
|
|
17
|
+
return execFileSync('git', args, { encoding: 'utf8' }).trim();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let base = baseArg;
|
|
21
|
+
if (!base || ZERO.test(base)) {
|
|
22
|
+
try { base = git(['merge-base', 'origin/main', headArg]); }
|
|
23
|
+
catch { base = `${headArg}~1`; }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let found;
|
|
27
|
+
try {
|
|
28
|
+
found = scanRange(base, headArg);
|
|
29
|
+
} catch (err) {
|
|
30
|
+
// A gate that cannot read its input must not pass silently.
|
|
31
|
+
console.error(`receipts-gate: could not scan ${base}..${headArg}: ${err.message}`);
|
|
32
|
+
process.exit(2);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (found.length) {
|
|
36
|
+
console.error(formatViolations(found));
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
console.error(`receipts-gate: ${base.slice(0, 7)}..${headArg} carries no unreceipted claims.`);
|
|
@@ -122,6 +122,11 @@
|
|
|
122
122
|
"type": ["string", "null"],
|
|
123
123
|
"description": "Where the idea came from, as free text."
|
|
124
124
|
},
|
|
125
|
+
"extra_fields": {
|
|
126
|
+
"type": "array",
|
|
127
|
+
"items": { "type": "string" },
|
|
128
|
+
"description": "Hand-authored markdown field lines the ideabox parser did not recognise, kept verbatim in serializer order. Exists so migrating a markdown ideabox into records is lossless: this contract is closed and the record shape is an allowlist, so a custom field with nowhere to live is deleted from the user's file by the first render after the upgrade, silently. See COMP-IDEABOX-MIGRATE-DIALECT."
|
|
129
|
+
},
|
|
125
130
|
"links": {
|
|
126
131
|
"type": "array",
|
|
127
132
|
"items": { "$ref": "#/definitions/link" }
|