@smartmemory/compose 0.2.52-beta → 0.2.54-beta
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 +0 -8
- package/README.md +39 -0
- package/bin/compose.js +378 -3
- package/dist/assets/App-D_tvTs_n.js +896 -0
- package/dist/assets/abnfDiagram-VRR7QNED-Dhb7O5rp.js +1 -0
- package/dist/assets/arc-C4oVdnk2.js +1 -0
- package/dist/assets/architectureDiagram-ZJ3FMSHR-DqHrb5_F.js +36 -0
- package/dist/assets/blockDiagram-677ZJIJ3-DUyBayYK.js +132 -0
- package/dist/assets/{browser-CnKiSnlr.js → browser-Cr0recrN.js} +6 -6
- package/dist/assets/{c4Diagram-AHTNJAMY-DtDSNlOX.js → c4Diagram-LMCZKHZV-CxlkWMRK.js} +1 -1
- package/dist/assets/channel-xb4mQfuL.js +1 -0
- package/dist/assets/{chunk-QZHKN3VN-BMWFLZ8t.js → chunk-2Q5K7J3B-C6QJsyOQ.js} +1 -1
- package/dist/assets/{chunk-YZCP3GAM-CqjNXooj.js → chunk-32BRIVSS-D9NgYu9W.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-BAa7Edp6.js → chunk-5VM5RSS4-DbD86rhK.js} +1 -1
- package/dist/assets/chunk-EX3LRPZG-E4m4xNMw.js +231 -0
- package/dist/assets/{chunk-4BX2VUAB-oXU2npoL.js → chunk-JWPE2WC7-DHGlRWbw.js} +1 -1
- package/dist/assets/chunk-MOJQB5TN-BblYnFUq.js +88 -0
- package/dist/assets/chunk-RYQCIY6F-CGT-88vL.js +1 -0
- package/dist/assets/chunk-V7JOEXUC-BJJmX0mc.js +206 -0
- package/dist/assets/{chunk-EDXVE4YY-Bse2kdRh.js → chunk-VR4S4FIN-B4ygduCD.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-BEEk2FdH.js → chunk-XXDRQBXY-7APXuI2s.js} +1 -1
- package/dist/assets/classDiagram-OUVF2IWQ-BIvgPTgh.js +1 -0
- package/dist/assets/classDiagram-v2-EOCWNBFH-BIvgPTgh.js +1 -0
- package/dist/assets/{cose-bilkent-S5V4N54A-NYnH3mVW.js → cose-bilkent-JH36ORCC-DnVcT6R6.js} +1 -1
- package/dist/assets/cynefin-VYW2F7L2-C9qiUSfX.js +178 -0
- package/dist/assets/cynefinDiagram-TSTJHNR4-BowkQhEu.js +62 -0
- package/dist/assets/dagre-VKFMJZFB-Y5V-7U3p.js +4 -0
- package/dist/assets/diagram-FQU43EPY-I7wDE-Xp.js +3 -0
- package/dist/assets/diagram-G47NLZAW-DapGa1HI.js +24 -0
- package/dist/assets/diagram-NH7WQ7WH-CHO1JntO.js +24 -0
- package/dist/assets/diagram-OA4YK3LP-mBWP2D1G.js +30 -0
- package/dist/assets/diagram-WEI45ONY-C8KN8YnF.js +41 -0
- package/dist/assets/ebnfDiagram-CCIWWBDH-ZkJG9NOL.js +1 -0
- package/dist/assets/erDiagram-Q63AITRT-DGnL3kW1.js +85 -0
- package/dist/assets/flowDiagram-23GEKE2U-DHYupfOu.js +156 -0
- package/dist/assets/ganttDiagram-NO4QXBWP-B1B2zpwR.js +292 -0
- package/dist/assets/gitGraphDiagram-IHSO6WYX-B9_OP4Fs.js +106 -0
- package/dist/assets/graph-C9eacEi8.js +1 -0
- package/dist/assets/graph-xkel59g2.js +331 -0
- package/dist/assets/index-0Hq9436Q.js +119 -0
- package/dist/assets/index-LIwREYgH.css +1 -0
- package/dist/assets/infoDiagram-FWYZ7A6U-D-a34TqE.js +2 -0
- package/dist/assets/{ishikawaDiagram-UXIWVN3A-CK2IFFAP.js → ishikawaDiagram-FXEZZL3T-h0bUvXGV.js} +5 -5
- package/dist/assets/{journeyDiagram-VCZTEJTY-DAH5Stkf.js → journeyDiagram-5HDEW3XC-B4lm0RBa.js} +1 -1
- package/dist/assets/{kanban-definition-6JOO6SKY-Bi0aCqkv.js → kanban-definition-HUTT4EX6-msvfouRX.js} +7 -7
- package/dist/assets/katex-C5jXJg4s.js +257 -0
- package/dist/assets/layout-DEXfKzaS.js +1 -0
- package/dist/assets/{linear-ClGEGlS2.js → linear-B0trv2xN.js} +1 -1
- package/dist/assets/map-Czzmt4hB.js +1 -0
- package/dist/assets/{mindmap-definition-QFDTVHPH-Wjr-PgC6.js → mindmap-definition-LN4V7U3C-CvswbRgd.js} +7 -7
- package/dist/assets/{mobile-Chw8RWyH.js → mobile-CNLMhdFP.js} +2 -2
- package/dist/assets/pegDiagram-2B236MQR-DZEmIYC7.js +1 -0
- package/dist/assets/pieDiagram-ENE6RG2P-1ZfqxHkL.js +39 -0
- package/dist/assets/quadrantDiagram-ABIIQ3AL-Owx49uiJ.js +7 -0
- package/dist/assets/railroadDiagram-RFXS5EU6-B7RiaIGf.js +1 -0
- package/dist/assets/{requirementDiagram-MS252O5E-CU9Rlyq5.js → requirementDiagram-TGXJPOKE-fqmHs_5o.js} +3 -3
- package/dist/assets/sankeyDiagram-HTMAVEWB-D0Q3FeKV.js +40 -0
- package/dist/assets/sequenceDiagram-DBY2YBRQ-CnPuB3fy.js +162 -0
- package/dist/assets/sizeCapture-X5ZJPWSS-CsHoR37V.js +1 -0
- package/dist/assets/stateDiagram-2N3HPSRC-GVwpflU5.js +1 -0
- package/dist/assets/stateDiagram-v2-6OUMAXLB-DCGIHFu9.js +1 -0
- package/dist/assets/swimlanes-5IMT3BWC-DRR41dsQ.js +2 -0
- package/dist/assets/swimlanesDiagram-G3AALYLV-C4RhLkZo.js +8 -0
- package/dist/assets/{timeline-definition-GMOUNBTQ-C8SZmaLp.js → timeline-definition-FHXFAJF6-ePaEemL7.js} +3 -3
- package/dist/assets/vennDiagram-L72KCM5P-BAYP8dNF.js +34 -0
- package/dist/assets/wardleyDiagram-EHGQE667-jNVH5Adm.js +78 -0
- package/dist/assets/xychartDiagram-FW5EYKEG-BYDWGXbZ.js +7 -0
- package/dist/index.html +3 -3
- package/lib/agent-string.js +34 -0
- package/lib/build.js +542 -148
- package/lib/experiment-judge.js +145 -0
- package/lib/experiment-metrics.js +265 -0
- package/lib/experiment-pricing.js +60 -0
- package/lib/experiment-report.js +306 -0
- package/lib/experiment-sandbox.js +147 -0
- package/lib/experiment.js +539 -0
- package/lib/feature-events.js +10 -0
- package/lib/feature-json.js +1 -1
- package/lib/feature-writer.js +30 -0
- package/lib/flow-state.js +36 -0
- package/lib/gate-prompt.js +66 -6
- package/lib/lifecycle-modes.js +213 -0
- package/lib/new.js +6 -2
- package/lib/roadmap-graph/index.js +26 -28
- package/lib/roadmap-graph/vision-adapter.js +126 -0
- package/lib/smartmemory-client.js +139 -0
- package/lib/smartmemory-config.js +50 -0
- package/lib/smartmemory-ingest.js +115 -0
- package/lib/smartmemory-sync.js +275 -0
- package/lib/stratum-mcp-client.js +9 -0
- package/lib/triage.js +7 -1
- package/lib/vision-writer.js +30 -6
- package/package.json +1 -1
- package/pipelines/plan.stratum.yaml +161 -0
- package/server/artifact-manager.js +30 -3
- package/server/compose-mcp-tools.js +11 -9
- package/server/compose-mcp.js +4 -0
- package/server/feature-scan.js +87 -173
- package/server/gate-log-store.js +11 -1
- package/server/graph-export.js +0 -0
- package/server/index.js +3 -5
- package/server/lifecycle-guard.js +62 -25
- package/server/roadmap-graph-vision.js +138 -0
- package/server/smartmemory-routes.js +113 -0
- package/server/status-snapshot.js +8 -7
- package/server/vision-routes.js +64 -33
- package/server/vision-server.js +6 -0
- package/server/vision-store.js +13 -4
- package/.claude/skills/compose/references/hermes-tools.md +0 -80
- package/dist/assets/App-Ddhfx18X.js +0 -889
- package/dist/assets/_baseUniq-CoEVVYyC.js +0 -1
- package/dist/assets/arc-DFnGxuCB.js +0 -1
- package/dist/assets/architectureDiagram-Q4EWVU46-66rx01Tn.js +0 -36
- package/dist/assets/blockDiagram-DXYQGD6D-AE5PKSak.js +0 -132
- package/dist/assets/channel-CScyWprq.js +0 -1
- package/dist/assets/chunk-4TB4RGXK-5w2lZAjW.js +0 -206
- package/dist/assets/chunk-OYMX7WX6-CGr_YiAc.js +0 -231
- package/dist/assets/classDiagram-6PBFFD2Q-DibVYYdr.js +0 -1
- package/dist/assets/classDiagram-v2-HSJHXN6E-DibVYYdr.js +0 -1
- package/dist/assets/clone-ZMALIFmw.js +0 -1
- package/dist/assets/dagre-KV5264BT--mrfaTHR.js +0 -4
- package/dist/assets/diagram-5BDNPKRD-DjXvyFzh.js +0 -10
- package/dist/assets/diagram-G4DWMVQ6-BrrexEvm.js +0 -24
- package/dist/assets/diagram-MMDJMWI5-D3Q0OT2M.js +0 -43
- package/dist/assets/diagram-TYMM5635-DKTntmxE.js +0 -24
- package/dist/assets/erDiagram-SMLLAGMA-BWbR0dPj.js +0 -85
- package/dist/assets/flowDiagram-DWJPFMVM-Dj5M5XaY.js +0 -162
- package/dist/assets/ganttDiagram-T4ZO3ILL-DKppFh_5.js +0 -292
- package/dist/assets/gitGraphDiagram-UUTBAWPF-9xQ9O25V.js +0 -106
- package/dist/assets/graph-Cgmhvu1T.js +0 -1
- package/dist/assets/graph-DZe55uk8.js +0 -331
- package/dist/assets/index-BxRamj_i.css +0 -1
- package/dist/assets/index-D8uDfn8y.js +0 -123
- package/dist/assets/infoDiagram-42DDH7IO-DQYWVzVN.js +0 -2
- package/dist/assets/katex-DkKDou_j.js +0 -257
- package/dist/assets/layout-DN13RkA1.js +0 -1
- package/dist/assets/min-CXr-fUFS.js +0 -1
- package/dist/assets/pieDiagram-DEJITSTG-CVipbIlr.js +0 -30
- package/dist/assets/quadrantDiagram-34T5L4WZ-CHJFyhm-.js +0 -7
- package/dist/assets/sankeyDiagram-XADWPNL6-BSULeqST.js +0 -10
- package/dist/assets/sequenceDiagram-FGHM5R23-CCOKXvjj.js +0 -157
- package/dist/assets/stateDiagram-FHFEXIEX-B91gKA7Q.js +0 -1
- package/dist/assets/stateDiagram-v2-QKLJ7IA2-Dc8ur9ta.js +0 -1
- package/dist/assets/vennDiagram-DHZGUBPP-YvEPDlsM.js +0 -34
- package/dist/assets/wardley-RL74JXVD-TMBUq_w1.js +0 -162
- package/dist/assets/wardleyDiagram-NUSXRM2D-DzEkPrri.js +0 -20
- package/dist/assets/xychartDiagram-5P7HB3ND-BIiCeiYk.js +0 -7
- package/lib/roadmap-graph/collect.js +0 -178
|
@@ -16,14 +16,6 @@ Both modes share Phase 6 (plan), Phase 7 (TDD execute + E2E + review loop + cove
|
|
|
16
16
|
|
|
17
17
|
Before any phase begins, read project state via the `compose` MCP server (get_vision_items, get_phase_summary, get_blocked_items). Every feature OR bug maps to a vision item — check it exists, create it if not.
|
|
18
18
|
|
|
19
|
-
## Platform Adaptation
|
|
20
|
-
|
|
21
|
-
This SKILL.md uses **Claude Code** tool names (Skill, Agent/Task, EnterPlanMode, TodoWrite). On other harnesses, resolve each action to that harness's equivalent:
|
|
22
|
-
|
|
23
|
-
- **Nous Hermes Agent** → `references/hermes-tools.md` (skills, `delegate_task` for subagents, MCP servers, AGENTS.md; Phase-6 plan mode degrades to a flat gate).
|
|
24
|
-
|
|
25
|
-
The headless `compose build`/`compose fix` CLI is harness-agnostic — it runs from any shell and delegates agent execution to Stratum (Claude/Codex) regardless of the launching harness. The mapping files only matter for running the *interactive* lifecycle under a non-Claude-Code harness.
|
|
26
|
-
|
|
27
19
|
## Mode Selection
|
|
28
20
|
|
|
29
21
|
`/compose` with no verb defaults to `build` for back-compat. Explicit verbs:
|
package/README.md
CHANGED
|
@@ -30,6 +30,17 @@ compose build TODO-1
|
|
|
30
30
|
-> done: feature implemented, reviewed, tested, documented
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
+
Starting from a fuzzy goal instead of a known feature? Use the planning lifecycle:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
compose plan "a tool that summarizes my team's standups"
|
|
37
|
+
-> frame + research + ideate (ideas go to the ideabox) -> [gate]
|
|
38
|
+
-> converge + estimate -> writes build-ready feature.json + design.md -> [gate]
|
|
39
|
+
-> handoff: each feature ready for `compose build <CODE>`
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`compose build` then picks up a plan-authored feature and ratifies its design rather than rewriting it.
|
|
43
|
+
|
|
33
44
|
## Quick install
|
|
34
45
|
|
|
35
46
|
Prerequisites: Node.js 18+ and `stratum-mcp` on PATH (`pip install stratum-mcp`, requires Python 3.11+). Codex steps additionally need the OpenAI `codex` CLI. Full prereqs in [docs/install.md](docs/install.md).
|
|
@@ -150,6 +161,34 @@ Tunnel layer is yours: Tailscale (serve/funnel), Cloudflare Tunnel, or a reverse
|
|
|
150
161
|
|
|
151
162
|
`compose remote rotate-secret --yes` invalidates every paired device (post-leak hammer).
|
|
152
163
|
|
|
164
|
+
## SmartMemory coupling (opt-in)
|
|
165
|
+
|
|
166
|
+
Compose can feed its own lifecycle history (feature events, gate decisions, journal entries,
|
|
167
|
+
artifacts) into [SmartMemory](https://github.com/smartmemory) for relevance-ranked recall,
|
|
168
|
+
via a `smartmemory` block in `.compose/compose.json`:
|
|
169
|
+
|
|
170
|
+
```json
|
|
171
|
+
{
|
|
172
|
+
"smartmemory": {
|
|
173
|
+
"enabled": true,
|
|
174
|
+
"baseUrl": "http://localhost:9001",
|
|
175
|
+
"apiKeyEnv": "SMARTMEMORY_API_KEY",
|
|
176
|
+
"timeoutMs": 3000
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
`smartmemory` is absent by default, which means the coupling is fully off: no probes, no
|
|
182
|
+
network calls, no new log lines. With `enabled: true` and a reachable SmartMemory service,
|
|
183
|
+
compose ingests lifecycle events live (fail-open) and the cockpit gains a Recall tab on each
|
|
184
|
+
feature's detail panel.
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
compose smartmemory sync # idempotent backfill of events, journal, artifacts
|
|
188
|
+
compose smartmemory sync --dry-run # preview counts without ingesting
|
|
189
|
+
compose smartmemory sync --feature CODE # scope the sync to one feature
|
|
190
|
+
```
|
|
191
|
+
|
|
153
192
|
## Documentation
|
|
154
193
|
|
|
155
194
|
Topic-scoped reference:
|
package/bin/compose.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* compose start — start the compose app (supervisor.js)
|
|
9
9
|
* compose build — headless feature lifecycle runner
|
|
10
10
|
* compose fix — headless bug-fix lifecycle runner (pipelines/bug-fix.stratum.yaml)
|
|
11
|
+
* compose plan — product-planning lifecycle runner (pipelines/plan.stratum.yaml)
|
|
11
12
|
*/
|
|
12
13
|
import { readFileSync, writeFileSync, existsSync, mkdirSync, copyFileSync, rmSync, readdirSync } from 'fs'
|
|
13
14
|
import { resolve, join, basename, dirname, sep } from 'path'
|
|
@@ -19,6 +20,7 @@ import { resolveWorkspace, getWorkspaceFlag } from '../lib/resolve-workspace.js'
|
|
|
19
20
|
import { resolvePort } from '../lib/resolve-port.js'
|
|
20
21
|
import { resolveRoadmapPath, resolveFeaturesPath, resolveContextPathFromConfig, resolveFeaturesPathFromConfig, resolveRoadmapPathFromConfig } from '../lib/project-paths.js'
|
|
21
22
|
import { installAgentDefs } from '../lib/install-agent-defs.js'
|
|
23
|
+
import { validateAgentString } from '../lib/agent-string.js'
|
|
22
24
|
|
|
23
25
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
24
26
|
const PACKAGE_ROOT = resolve(__dirname, '..')
|
|
@@ -565,7 +567,7 @@ async function runInit(flags, cwdOverride) {
|
|
|
565
567
|
// 8. Copy default pipeline specs if absent
|
|
566
568
|
const pipelinesDir = join(cwd, 'pipelines')
|
|
567
569
|
mkdirSync(pipelinesDir, { recursive: true })
|
|
568
|
-
for (const specName of ['build.stratum.yaml', 'build-quick.stratum.yaml', 'new.stratum.yaml']) {
|
|
570
|
+
for (const specName of ['build.stratum.yaml', 'build-quick.stratum.yaml', 'new.stratum.yaml', 'plan.stratum.yaml']) {
|
|
569
571
|
const dest = join(pipelinesDir, specName)
|
|
570
572
|
if (!existsSync(dest)) {
|
|
571
573
|
const src = join(PACKAGE_ROOT, 'pipelines', specName)
|
|
@@ -1180,6 +1182,125 @@ if (cmd === 'roadmap') {
|
|
|
1180
1182
|
process.exit(0)
|
|
1181
1183
|
}
|
|
1182
1184
|
|
|
1185
|
+
// compose roadmap add <CODE> — write a (build-ready) feature via the canonical
|
|
1186
|
+
// typed writer (lib/feature-writer.js addRoadmapEntry). This is the CLI mirror of
|
|
1187
|
+
// the add_roadmap_entry MCP tool, so an agent of ANY runtime (claude, codex, ...)
|
|
1188
|
+
// can emit a build-ready feature.json without the compose MCP. The plan
|
|
1189
|
+
// lifecycle's spec step uses it to write build-ready features (COMP-ROADMAP-PLAN).
|
|
1190
|
+
if (subcmd === 'add') {
|
|
1191
|
+
const { addRoadmapEntry } = await import('../lib/feature-writer.js')
|
|
1192
|
+
const { root: cwd } = resolveCwdWithWorkspace(args)
|
|
1193
|
+
const VALUE_FLAGS = ['--description', '--phase', '--status', '--complexity', '--profile', '--triage-timestamp', '--planned-by', '--impact', '--cwd', '--workspace', '--team']
|
|
1194
|
+
// flagVal returns the value only when it exists and is not itself another flag,
|
|
1195
|
+
// so `--description --phase X` does not silently capture "--phase" as the value.
|
|
1196
|
+
const flagVal = (name) => {
|
|
1197
|
+
const i = args.indexOf(name)
|
|
1198
|
+
if (i === -1) return undefined
|
|
1199
|
+
const v = args[i + 1]
|
|
1200
|
+
return (v === undefined || v.startsWith('-')) ? undefined : v
|
|
1201
|
+
}
|
|
1202
|
+
// The positional <CODE> is the first non-flag token that is NOT a value-taking
|
|
1203
|
+
// flag's argument (so `--planned-by PLAN-X REAL-1` resolves the code to REAL-1).
|
|
1204
|
+
const valueFlagSet = new Set(VALUE_FLAGS)
|
|
1205
|
+
const valueIdx = new Set()
|
|
1206
|
+
for (let i = 1; i < args.length; i++) { if (valueFlagSet.has(args[i])) valueIdx.add(i + 1) }
|
|
1207
|
+
let code
|
|
1208
|
+
for (let i = 1; i < args.length; i++) {
|
|
1209
|
+
if (!args[i].startsWith('-') && !valueIdx.has(i)) { code = args[i]; break }
|
|
1210
|
+
}
|
|
1211
|
+
const description = flagVal('--description')
|
|
1212
|
+
const phase = flagVal('--phase')
|
|
1213
|
+
if (!code || !description || !phase) {
|
|
1214
|
+
console.error('Usage: compose roadmap add <CODE> --description "..." --phase "..." \\')
|
|
1215
|
+
console.error(' [--status PLANNED] [--complexity S|M|L|XL] [--profile <JSON>] \\')
|
|
1216
|
+
console.error(' [--triage-timestamp <ISO>] [--planned-by <CODE>] [--impact low|medium|high]')
|
|
1217
|
+
console.error('')
|
|
1218
|
+
console.error('Writes feature.json via the canonical writer and regenerates ROADMAP.md.')
|
|
1219
|
+
process.exit(1)
|
|
1220
|
+
}
|
|
1221
|
+
const entry = { code, description, phase, status: flagVal('--status') ?? 'PLANNED' }
|
|
1222
|
+
const complexity = flagVal('--complexity'); if (complexity) entry.complexity = complexity
|
|
1223
|
+
const plannedBy = flagVal('--planned-by'); if (plannedBy) entry.plannedBy = plannedBy
|
|
1224
|
+
// --planned-by present but valueless must NOT silently fall through to the
|
|
1225
|
+
// non-handshake path (it would write a non-build-ready feature).
|
|
1226
|
+
if (args.includes('--planned-by') && !plannedBy) {
|
|
1227
|
+
console.error('--planned-by requires a value (the originating plan session code)')
|
|
1228
|
+
process.exit(1)
|
|
1229
|
+
}
|
|
1230
|
+
const impact = flagVal('--impact'); if (impact) entry.impact = impact
|
|
1231
|
+
const profileRaw = flagVal('--profile')
|
|
1232
|
+
if (profileRaw) {
|
|
1233
|
+
let parsed
|
|
1234
|
+
try { parsed = JSON.parse(profileRaw) }
|
|
1235
|
+
catch (e) { console.error(`--profile must be valid JSON: ${e.message}`); process.exit(1) }
|
|
1236
|
+
// A build-ready triage profile must carry the four needs_* booleans build uses
|
|
1237
|
+
// to gate skippable phases — otherwise build skips fresh triage on a cache with
|
|
1238
|
+
// no gating fields (COMP-ROADMAP-PLAN; profile contract in lib/triage.js).
|
|
1239
|
+
const NEEDS = ['needs_prd', 'needs_architecture', 'needs_verification', 'needs_report']
|
|
1240
|
+
const missing = NEEDS.filter((k) => typeof parsed?.[k] !== 'boolean')
|
|
1241
|
+
if (missing.length) {
|
|
1242
|
+
console.error(`--profile must include boolean fields: ${missing.join(', ')} (build triage gating)`)
|
|
1243
|
+
process.exit(1)
|
|
1244
|
+
}
|
|
1245
|
+
entry.profile = parsed
|
|
1246
|
+
}
|
|
1247
|
+
// A plan handshake (--planned-by) must be FULLY build-ready or build won't consume
|
|
1248
|
+
// it as one: profile + complexity + PLANNED status + an existing design.md (build
|
|
1249
|
+
// ratifies it). Enforce the whole contract here (build reuses cached triage only
|
|
1250
|
+
// when feature.profile exists and the cache is fresh — lib/build.js, lib/triage.js).
|
|
1251
|
+
let designMtime = 0
|
|
1252
|
+
if (plannedBy) {
|
|
1253
|
+
if (!entry.profile) {
|
|
1254
|
+
console.error('--planned-by requires --profile (a build-ready plan handshake must carry the triage profile)')
|
|
1255
|
+
process.exit(1)
|
|
1256
|
+
}
|
|
1257
|
+
if (!entry.complexity) {
|
|
1258
|
+
console.error('--planned-by requires --complexity (S|M|L|XL) for a build-ready handshake')
|
|
1259
|
+
process.exit(1)
|
|
1260
|
+
}
|
|
1261
|
+
if (entry.status !== 'PLANNED') {
|
|
1262
|
+
console.error(`--planned-by must be PLANNED (got ${entry.status}) — a plan handshake produces a build-ready PLANNED feature`)
|
|
1263
|
+
process.exit(1)
|
|
1264
|
+
}
|
|
1265
|
+
const { resolveFeaturesPath } = await import('../lib/project-paths.js')
|
|
1266
|
+
// alias the fs import — statSync is re-bound later in this roadmap block
|
|
1267
|
+
const { statSync: statFile } = await import('fs')
|
|
1268
|
+
const designPath = join(resolveFeaturesPath(cwd), code, 'design.md')
|
|
1269
|
+
if (!existsSync(designPath)) {
|
|
1270
|
+
console.error(`--planned-by: ${code}/design.md not found (${designPath}). Write the plan-approved design.md BEFORE creating the feature — build ratifies it.`)
|
|
1271
|
+
process.exit(1)
|
|
1272
|
+
}
|
|
1273
|
+
designMtime = statFile(designPath).mtime.getTime()
|
|
1274
|
+
}
|
|
1275
|
+
// triageTimestamp: explicit (validated, and for a handshake not older than design.md
|
|
1276
|
+
// or build re-triages), else default to now for a plan handshake.
|
|
1277
|
+
const ts = flagVal('--triage-timestamp')
|
|
1278
|
+
if (ts) {
|
|
1279
|
+
const tsMs = new Date(ts).getTime()
|
|
1280
|
+
if (Number.isNaN(tsMs)) {
|
|
1281
|
+
console.error(`--triage-timestamp must be a valid ISO 8601 date (got "${ts}")`)
|
|
1282
|
+
process.exit(1)
|
|
1283
|
+
}
|
|
1284
|
+
if (plannedBy && tsMs < designMtime) {
|
|
1285
|
+
console.error('--triage-timestamp is older than design.md — build would re-triage. Omit it to stamp now, or pass a time at/after the design write.')
|
|
1286
|
+
process.exit(1)
|
|
1287
|
+
}
|
|
1288
|
+
entry.triageTimestamp = ts
|
|
1289
|
+
} else if (plannedBy) {
|
|
1290
|
+
// Stamp now, but never older than design.md (guards a future-dated design.md /
|
|
1291
|
+
// clock skew, which would otherwise read as stale and force a re-triage).
|
|
1292
|
+
entry.triageTimestamp = new Date(Math.max(Date.now(), designMtime)).toISOString()
|
|
1293
|
+
}
|
|
1294
|
+
try {
|
|
1295
|
+
const r = await addRoadmapEntry(cwd, entry)
|
|
1296
|
+
console.log(`Wrote ${r.code} (${entry.status}) and regenerated ROADMAP.md`)
|
|
1297
|
+
} catch (e) {
|
|
1298
|
+
console.error(`roadmap add failed: ${e.message}`)
|
|
1299
|
+
process.exit(1)
|
|
1300
|
+
}
|
|
1301
|
+
process.exit(0)
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1183
1304
|
// compose roadmap migrate — extract ROADMAP.md entries into feature.json files
|
|
1184
1305
|
if (subcmd === 'migrate') {
|
|
1185
1306
|
const { migrateRoadmap } = await import('../lib/migrate-roadmap.js')
|
|
@@ -1286,9 +1407,11 @@ if (cmd === 'roadmap') {
|
|
|
1286
1407
|
}
|
|
1287
1408
|
|
|
1288
1409
|
// compose roadmap graph — generate a self-contained dependency-graph HTML
|
|
1289
|
-
// from
|
|
1410
|
+
// from the canonical vision projection (COMP-ROADMAP-GRAPH-2: the vision
|
|
1411
|
+
// model is the single source, deterministically seeded from feature.json +
|
|
1412
|
+
// deps.yaml; renders through the one shared renderer).
|
|
1290
1413
|
if (subcmd === 'graph') {
|
|
1291
|
-
const { generateRoadmapGraph, checkRoadmapGraph } = await import('../
|
|
1414
|
+
const { generateRoadmapGraph, checkRoadmapGraph } = await import('../server/roadmap-graph-vision.js')
|
|
1292
1415
|
let cwd
|
|
1293
1416
|
const projIdx = args.indexOf('--project')
|
|
1294
1417
|
if (projIdx !== -1 && args[projIdx + 1]) {
|
|
@@ -1952,6 +2075,34 @@ if (cmd === 'pipeline') {
|
|
|
1952
2075
|
process.exit(0)
|
|
1953
2076
|
}
|
|
1954
2077
|
|
|
2078
|
+
// COMP-MODEL-AB: compose experiment <spec.json> [--prune-workspaces]
|
|
2079
|
+
if (cmd === 'experiment') {
|
|
2080
|
+
const specArg = args[0]
|
|
2081
|
+
if (!specArg || specArg.startsWith('-')) {
|
|
2082
|
+
console.error('Usage: compose experiment <spec.json> [--prune-workspaces]')
|
|
2083
|
+
console.error('')
|
|
2084
|
+
console.error('Runs a sandboxed A/B experiment across model configs.')
|
|
2085
|
+
console.error('Writes results.json + report.md next to the spec file.')
|
|
2086
|
+
process.exit(1)
|
|
2087
|
+
}
|
|
2088
|
+
const pruneWorkspaces = args.includes('--prune-workspaces')
|
|
2089
|
+
const specPath = resolve(specArg)
|
|
2090
|
+
if (!existsSync(specPath)) {
|
|
2091
|
+
console.error(`Error: spec file not found: ${specPath}`)
|
|
2092
|
+
process.exit(1)
|
|
2093
|
+
}
|
|
2094
|
+
const { runExperiment } = await import('../lib/experiment.js')
|
|
2095
|
+
try {
|
|
2096
|
+
const result = await runExperiment(specPath, { pruneWorkspaces })
|
|
2097
|
+
console.log(`Results: ${result.resultsPath}`)
|
|
2098
|
+
console.log(`Report: ${result.reportPath}`)
|
|
2099
|
+
} catch (err) {
|
|
2100
|
+
console.error(`Error: ${err.message}`)
|
|
2101
|
+
process.exit(1)
|
|
2102
|
+
}
|
|
2103
|
+
process.exit(0)
|
|
2104
|
+
}
|
|
2105
|
+
|
|
1955
2106
|
if (cmd === 'build') {
|
|
1956
2107
|
// Parse --cwd <path> for cross-repo builds
|
|
1957
2108
|
let agentWorkDir = null
|
|
@@ -1999,6 +2150,8 @@ if (cmd === 'build') {
|
|
|
1999
2150
|
const all = filteredArgs2.includes('--all')
|
|
2000
2151
|
const dryRun = filteredArgs2.includes('--dry-run')
|
|
2001
2152
|
const skipTriage = filteredArgs2.includes('--skip-triage')
|
|
2153
|
+
const resume = filteredArgs2.includes('--resume')
|
|
2154
|
+
const fresh = filteredArgs2.includes('--fresh')
|
|
2002
2155
|
// COMP-BUILD-QUICK: --quick selects the trimmed build-quick pipeline
|
|
2003
2156
|
// (design → implement → ship). Symmetric to fix mode's Quick path; the real
|
|
2004
2157
|
// mirror is the existing --template mechanism (fix dispatches template:'bug-fix'),
|
|
@@ -2007,6 +2160,29 @@ if (cmd === 'build') {
|
|
|
2007
2160
|
// COMP-CODEX-IMPL: --codex flips the implementer to Codex (Claude reviews). v1 is
|
|
2008
2161
|
// full-build, single-feature only — mutually exclusive with --quick and batch.
|
|
2009
2162
|
const codex = filteredArgs2.includes('--codex')
|
|
2163
|
+
const nonInteractiveBuild = filteredArgs2.includes('--non-interactive')
|
|
2164
|
+
|
|
2165
|
+
// COMP-MODEL-AB: --implementer=<agentStr> / --reviewer=<agentStr> override the
|
|
2166
|
+
// --codex-derived defaults. Format: --implementer=claude::critical (= operator).
|
|
2167
|
+
// Mutual exclusion applies when the explicit value conflicts with --codex's implicit
|
|
2168
|
+
// assignment; identical values (e.g. --codex --implementer=codex) are allowed.
|
|
2169
|
+
const implementerFlagMatch = filteredArgs2.find(a => a.startsWith('--implementer='))
|
|
2170
|
+
const implementerArg = implementerFlagMatch ? implementerFlagMatch.slice('--implementer='.length) : null
|
|
2171
|
+
const reviewerFlagMatch = filteredArgs2.find(a => a.startsWith('--reviewer='))
|
|
2172
|
+
const reviewerArg = reviewerFlagMatch ? reviewerFlagMatch.slice('--reviewer='.length) : null
|
|
2173
|
+
|
|
2174
|
+
if (implementerArg) {
|
|
2175
|
+
try { validateAgentString(implementerArg) } catch (err) {
|
|
2176
|
+
console.error(`Error: --implementer: ${err.message}`)
|
|
2177
|
+
process.exit(1)
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
if (reviewerArg) {
|
|
2181
|
+
try { validateAgentString(reviewerArg) } catch (err) {
|
|
2182
|
+
console.error(`Error: --reviewer: ${err.message}`)
|
|
2183
|
+
process.exit(1)
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2010
2186
|
|
|
2011
2187
|
// Multiple codes: compose build FEAT-1 FEAT-2 FEAT-3
|
|
2012
2188
|
const isMulti = featureCodes.length > 1
|
|
@@ -2018,6 +2194,14 @@ if (cmd === 'build') {
|
|
|
2018
2194
|
console.error('Error: --abort and --all/prefix/multi are mutually exclusive')
|
|
2019
2195
|
process.exit(1)
|
|
2020
2196
|
}
|
|
2197
|
+
if (resume && fresh) {
|
|
2198
|
+
console.error('--resume and --fresh are mutually exclusive')
|
|
2199
|
+
process.exit(1)
|
|
2200
|
+
}
|
|
2201
|
+
if ((resume || fresh) && isBatch) {
|
|
2202
|
+
console.error('Error: --resume/--fresh cannot be combined with --all/prefix/multi (single feature only)')
|
|
2203
|
+
process.exit(1)
|
|
2204
|
+
}
|
|
2021
2205
|
|
|
2022
2206
|
// COMP-BUILD-QUICK: --quick is a single-feature shortcut for a specific
|
|
2023
2207
|
// template, so it conflicts with an explicit --template and with batch builds.
|
|
@@ -2044,6 +2228,17 @@ if (cmd === 'build') {
|
|
|
2044
2228
|
process.exit(1)
|
|
2045
2229
|
}
|
|
2046
2230
|
|
|
2231
|
+
// COMP-MODEL-AB: mutual exclusion — conflict only when the explicit value differs
|
|
2232
|
+
// from what --codex would set (--codex sets implementer=codex, reviewer=claude).
|
|
2233
|
+
if (codex && implementerArg && implementerArg !== 'codex') {
|
|
2234
|
+
console.error(`Error: --implementer=${implementerArg} conflicts with --codex (--codex sets implementer=codex); omit --codex or use --implementer=codex`)
|
|
2235
|
+
process.exit(1)
|
|
2236
|
+
}
|
|
2237
|
+
if (codex && reviewerArg && reviewerArg !== 'claude') {
|
|
2238
|
+
console.error(`Error: --reviewer=${reviewerArg} conflicts with --codex (--codex sets reviewer=claude); omit --codex or use --reviewer=claude`)
|
|
2239
|
+
process.exit(1)
|
|
2240
|
+
}
|
|
2241
|
+
|
|
2047
2242
|
if (!featureCode && !abort && !all) {
|
|
2048
2243
|
console.error('Usage: compose build <feature-code> [feature-code...]')
|
|
2049
2244
|
console.error(' compose build STRAT-COMP (prefix — builds all matching)')
|
|
@@ -2052,6 +2247,8 @@ if (cmd === 'build') {
|
|
|
2052
2247
|
console.error('Options:')
|
|
2053
2248
|
console.error(' --quick Trimmed lifecycle (design → implement → ship) for small additive work')
|
|
2054
2249
|
console.error(' --abort Abort the active build')
|
|
2250
|
+
console.error(' --resume Resume the active build for <feature-code>')
|
|
2251
|
+
console.error(' --fresh Start a fresh build, discarding stale failed/resumable state')
|
|
2055
2252
|
console.error(' --all Build all PLANNED features in dependency order')
|
|
2056
2253
|
console.error(' --dry-run Print build order without executing')
|
|
2057
2254
|
console.error(' --cwd <path> Agent working directory (for cross-repo features)')
|
|
@@ -2100,6 +2297,25 @@ if (cmd === 'build') {
|
|
|
2100
2297
|
})
|
|
2101
2298
|
})
|
|
2102
2299
|
} else {
|
|
2300
|
+
let resumeFlowId = null
|
|
2301
|
+
if (resume && !abort && featureCode) {
|
|
2302
|
+
const activeBuildPath = join(buildCwd, '.compose', 'data', 'active-build.json')
|
|
2303
|
+
let active = null
|
|
2304
|
+
if (existsSync(activeBuildPath)) {
|
|
2305
|
+
try { active = JSON.parse(readFileSync(activeBuildPath, 'utf-8')) } catch { active = null }
|
|
2306
|
+
}
|
|
2307
|
+
if (
|
|
2308
|
+
!active ||
|
|
2309
|
+
active.featureCode !== featureCode ||
|
|
2310
|
+
!active.flowId ||
|
|
2311
|
+
['complete', 'aborted', 'killed'].includes(active.status)
|
|
2312
|
+
) {
|
|
2313
|
+
console.error(`Nothing to resume for ${featureCode} (no in-progress or failed build found)`)
|
|
2314
|
+
process.exit(1)
|
|
2315
|
+
}
|
|
2316
|
+
resumeFlowId = active.flowId
|
|
2317
|
+
}
|
|
2318
|
+
|
|
2103
2319
|
import('../lib/build.js').then(({ runBuild }) => {
|
|
2104
2320
|
const singleOpts = { abort }
|
|
2105
2321
|
if (agentWorkDir) singleOpts.workingDirectory = agentWorkDir
|
|
@@ -2107,6 +2323,12 @@ if (cmd === 'build') {
|
|
|
2107
2323
|
if (templateName) singleOpts.template = templateName
|
|
2108
2324
|
if (quick) singleOpts.template = 'build-quick' // COMP-BUILD-QUICK
|
|
2109
2325
|
if (codex) singleOpts.codex = true // COMP-CODEX-IMPL
|
|
2326
|
+
if (implementerArg) singleOpts.implementer = implementerArg // COMP-MODEL-AB
|
|
2327
|
+
if (reviewerArg) singleOpts.reviewer = reviewerArg // COMP-MODEL-AB
|
|
2328
|
+
if (resume) singleOpts.resume = true
|
|
2329
|
+
if (fresh) singleOpts.fresh = true
|
|
2330
|
+
if (nonInteractiveBuild) singleOpts.gateOpts = { nonInteractive: true }
|
|
2331
|
+
if (resumeFlowId) singleOpts.resumeFlowId = resumeFlowId
|
|
2110
2332
|
runBuild(featureCode, singleOpts).then(() => {
|
|
2111
2333
|
process.exit(0)
|
|
2112
2334
|
}).catch((err) => {
|
|
@@ -2227,6 +2449,116 @@ if (cmd === 'build') {
|
|
|
2227
2449
|
process.exit(1)
|
|
2228
2450
|
})
|
|
2229
2451
|
})
|
|
2452
|
+
} else if (cmd === 'plan') {
|
|
2453
|
+
// compose plan "<intent>" — runs the plan.stratum.yaml product-planning
|
|
2454
|
+
// lifecycle (explore_design → plan → ship). Thin delegation to runBuild() with
|
|
2455
|
+
// template='plan', mode='plan'. Derives a PLAN-<slug> code from the intent
|
|
2456
|
+
// (override with --code). Always passes opts.description so the dead
|
|
2457
|
+
// descriptionLoader:'plan' branch never runs (it would read a stray design.md).
|
|
2458
|
+
// Emits build-ready docs/features/<code>/{feature.json, design.md} that
|
|
2459
|
+
// `compose build <code>` consumes.
|
|
2460
|
+
let agentWorkDir = null
|
|
2461
|
+
const cwdIdx = args.indexOf('--cwd')
|
|
2462
|
+
if (cwdIdx !== -1) {
|
|
2463
|
+
const cwdValue = args[cwdIdx + 1]
|
|
2464
|
+
if (!cwdValue || cwdValue.startsWith('-')) {
|
|
2465
|
+
console.error('Error: --cwd requires a path argument')
|
|
2466
|
+
process.exit(1)
|
|
2467
|
+
}
|
|
2468
|
+
agentWorkDir = resolve(cwdValue)
|
|
2469
|
+
}
|
|
2470
|
+
const codeIdx = args.indexOf('--code')
|
|
2471
|
+
const explicitCode = codeIdx !== -1 ? args[codeIdx + 1] : null
|
|
2472
|
+
const abort = args.includes('--abort')
|
|
2473
|
+
const resume = args.includes('--resume')
|
|
2474
|
+
// Everything that isn't a flag or a value-taking flag's argument is the intent.
|
|
2475
|
+
// Skip the values of every value-taking flag so e.g. `--workspace foo` or
|
|
2476
|
+
// `--team t` never pollute the derived intent/slug.
|
|
2477
|
+
const skip = new Set()
|
|
2478
|
+
for (const flag of ['--cwd', '--code', '--workspace', '--team']) {
|
|
2479
|
+
const fi = args.indexOf(flag)
|
|
2480
|
+
if (fi !== -1) { skip.add(fi); skip.add(fi + 1) }
|
|
2481
|
+
}
|
|
2482
|
+
const intent = args.filter((a, i) => !a.startsWith('-') && !skip.has(i)).join(' ')
|
|
2483
|
+
|
|
2484
|
+
if (!intent && !abort && !resume) {
|
|
2485
|
+
console.error('Usage: compose plan "<what you want to build>" [--code PLAN-X] [--resume] [--abort]')
|
|
2486
|
+
console.error('')
|
|
2487
|
+
console.error('Runs the product-planning lifecycle (frame → research → ideate → converge → handoff).')
|
|
2488
|
+
console.error('Emits build-ready docs/features/<code>/{feature.json, design.md} for `compose build`.')
|
|
2489
|
+
console.error('')
|
|
2490
|
+
console.error('Options:')
|
|
2491
|
+
console.error(' --code <CODE> Use an explicit plan code instead of a derived PLAN-<slug>')
|
|
2492
|
+
console.error(' --resume Resume the active plan run')
|
|
2493
|
+
console.error(' --abort Abort the active plan run')
|
|
2494
|
+
console.error(' --cwd <path> Agent working directory')
|
|
2495
|
+
process.exit(1)
|
|
2496
|
+
}
|
|
2497
|
+
|
|
2498
|
+
const { root: planCwd } = resolveCwdWithWorkspace(args)
|
|
2499
|
+
if (!existsSync(join(planCwd, '.compose', 'compose.json')) || !existsSync(join(planCwd, 'pipelines', 'plan.stratum.yaml'))) {
|
|
2500
|
+
console.log('Running compose init...\n')
|
|
2501
|
+
await runInit(args.filter(a => a.startsWith('--')))
|
|
2502
|
+
console.log('')
|
|
2503
|
+
}
|
|
2504
|
+
|
|
2505
|
+
// Resolve the plan code. --resume/--abort operate on the ACTIVE build, so they
|
|
2506
|
+
// take the code from active-build.json (never derive a fresh one — that would
|
|
2507
|
+
// mint PLAN-<slug>-2 and miss the live flow). A fresh run derives a
|
|
2508
|
+
// deterministic PLAN-<slug> from the intent, dedup'd against existing workspaces.
|
|
2509
|
+
let planCode = explicitCode
|
|
2510
|
+
let resumeFlowId = null
|
|
2511
|
+
if (resume || abort) {
|
|
2512
|
+
const activeBuildPath = join(planCwd, '.compose', 'data', 'active-build.json')
|
|
2513
|
+
let active = null
|
|
2514
|
+
if (existsSync(activeBuildPath)) {
|
|
2515
|
+
try { active = JSON.parse(readFileSync(activeBuildPath, 'utf-8')) } catch { active = null }
|
|
2516
|
+
}
|
|
2517
|
+
if (!planCode) planCode = active?.featureCode ?? null
|
|
2518
|
+
if (resume) {
|
|
2519
|
+
if (!active || !active.flowId || (planCode && active.featureCode !== planCode)) {
|
|
2520
|
+
console.error(`No active plan build to resume${planCode ? ` for ${planCode}` : ''}.`)
|
|
2521
|
+
process.exit(1)
|
|
2522
|
+
}
|
|
2523
|
+
if (active.mode && active.mode !== 'plan') {
|
|
2524
|
+
console.error(`Cannot --resume: active build for ${active.featureCode} is in ${active.mode} mode, not plan mode.`)
|
|
2525
|
+
process.exit(1)
|
|
2526
|
+
}
|
|
2527
|
+
planCode = active.featureCode
|
|
2528
|
+
resumeFlowId = active.flowId
|
|
2529
|
+
}
|
|
2530
|
+
// --abort: only abort an active PLAN build — abortBuild does not check mode,
|
|
2531
|
+
// so guard here or `compose plan --abort` could kill a feature/fix run.
|
|
2532
|
+
if (abort && active && active.mode && active.mode !== 'plan') {
|
|
2533
|
+
console.error(`Cannot --abort as plan: the active build for ${active.featureCode} is in ${active.mode} mode, not plan mode.`)
|
|
2534
|
+
process.exit(1)
|
|
2535
|
+
}
|
|
2536
|
+
// planCode is now the active build's code (or an explicit --code);
|
|
2537
|
+
// runBuild(planCode, {abort}) matches it against active-build.json.
|
|
2538
|
+
} else if (!planCode) {
|
|
2539
|
+
const rawSlug = intent.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 24).replace(/-+$/, '')
|
|
2540
|
+
const slug = rawSlug || 'session'
|
|
2541
|
+
const base = `PLAN-${slug}`.toUpperCase()
|
|
2542
|
+
let candidate = base
|
|
2543
|
+
let n = 2
|
|
2544
|
+
while (existsSync(join(planCwd, 'docs', 'plans', candidate))) {
|
|
2545
|
+
candidate = `${base}-${n}`
|
|
2546
|
+
n++
|
|
2547
|
+
}
|
|
2548
|
+
planCode = candidate
|
|
2549
|
+
}
|
|
2550
|
+
|
|
2551
|
+
import('../lib/build.js').then(({ runBuild }) => {
|
|
2552
|
+
const opts = { abort, template: 'plan', mode: 'plan', description: intent }
|
|
2553
|
+
if (agentWorkDir) opts.workingDirectory = agentWorkDir
|
|
2554
|
+
if (resumeFlowId) opts.resumeFlowId = resumeFlowId
|
|
2555
|
+
runBuild(planCode, opts).then(() => {
|
|
2556
|
+
process.exit(0)
|
|
2557
|
+
}).catch((err) => {
|
|
2558
|
+
console.error(`Plan failed: ${err.message}`)
|
|
2559
|
+
process.exit(1)
|
|
2560
|
+
})
|
|
2561
|
+
})
|
|
2230
2562
|
} else if (cmd === 'gsd') {
|
|
2231
2563
|
// compose gsd <feature-code> — runs the per-task fresh-context dispatch
|
|
2232
2564
|
// pipeline (pipelines/gsd.stratum.yaml). Hard-requires existing
|
|
@@ -3446,6 +3778,49 @@ if (cmd === 'build') {
|
|
|
3446
3778
|
process.exit(1)
|
|
3447
3779
|
}
|
|
3448
3780
|
|
|
3781
|
+
} else if (cmd === 'smartmemory') {
|
|
3782
|
+
// ---------------------------------------------------------------------------
|
|
3783
|
+
// compose smartmemory sync [--dry-run] [--feature <CODE>]
|
|
3784
|
+
// COMP-SMARTMEMORY-INGEST: idempotent backfill/re-sync of feature-events,
|
|
3785
|
+
// gate-log, journal entries, and per-feature artifacts into SmartMemory.
|
|
3786
|
+
// ---------------------------------------------------------------------------
|
|
3787
|
+
const sub = args[0]
|
|
3788
|
+
|
|
3789
|
+
if (sub === 'sync') {
|
|
3790
|
+
const { runSync } = await import('../lib/smartmemory-sync.js')
|
|
3791
|
+
const { flushPending } = await import('../lib/smartmemory-ingest.js')
|
|
3792
|
+
const flagIdx = (flag) => args.indexOf(flag)
|
|
3793
|
+
const flagVal = (flag) => {
|
|
3794
|
+
const i = flagIdx(flag)
|
|
3795
|
+
return i !== -1 && args[i + 1] ? args[i + 1] : null
|
|
3796
|
+
}
|
|
3797
|
+
const dryRun = args.includes('--dry-run')
|
|
3798
|
+
const feature = flagVal('--feature')
|
|
3799
|
+
|
|
3800
|
+
const { root: cwd } = resolveCwdWithWorkspace(args)
|
|
3801
|
+
|
|
3802
|
+
let result
|
|
3803
|
+
try {
|
|
3804
|
+
result = await runSync({ cwd, dryRun, feature })
|
|
3805
|
+
} catch (err) {
|
|
3806
|
+
console.error(`smartmemory sync failed: ${err.message}`)
|
|
3807
|
+
process.exit(1)
|
|
3808
|
+
}
|
|
3809
|
+
|
|
3810
|
+
console.log(
|
|
3811
|
+
`smartmemory sync: ingested=${result.ingested} unchanged=${result.unchanged} ` +
|
|
3812
|
+
`skipped=${result.skipped} failed=${result.failed}` +
|
|
3813
|
+
(result.stoppedOnQuota ? ' (stopped: workspace quota reached)' : '')
|
|
3814
|
+
)
|
|
3815
|
+
await flushPending()
|
|
3816
|
+
process.exit(0)
|
|
3817
|
+
}
|
|
3818
|
+
|
|
3819
|
+
console.error(`Unknown smartmemory subcommand: ${sub}`)
|
|
3820
|
+
console.error('Usage:')
|
|
3821
|
+
console.error(' compose smartmemory sync [--dry-run] [--feature <CODE>]')
|
|
3822
|
+
process.exit(1)
|
|
3823
|
+
|
|
3449
3824
|
} else {
|
|
3450
3825
|
console.error(`Unknown command: ${cmd}`)
|
|
3451
3826
|
process.exit(1)
|