@smartmemory/compose 0.1.1-beta → 0.1.2-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/bug-fix/SKILL.md +143 -0
- package/.claude/skills/compose/SKILL.md +604 -0
- package/.compose-deps.json +89 -0
- package/README.md +14 -3
- package/bin/compose.js +473 -0
- package/contracts/comp-obs-contract.schema.json +362 -0
- package/contracts/cross-model-review-result.json +78 -0
- package/contracts/review-result.json +126 -0
- package/dist/assets/{_baseUniq-CQwX6VLz.js → _baseUniq-D-avYfn5.js} +1 -1
- package/dist/assets/{arc-SxJ2J1sh.js → arc-BC4dfQ-X.js} +1 -1
- package/dist/assets/{architectureDiagram-Q4EWVU46-BykunY1F.js → architectureDiagram-Q4EWVU46-BZmFXnGI.js} +1 -1
- package/dist/assets/{blockDiagram-DXYQGD6D-ohAKBOUw.js → blockDiagram-DXYQGD6D-DlfWSuux.js} +1 -1
- package/dist/assets/{c4Diagram-AHTNJAMY-DBDC3ENB.js → c4Diagram-AHTNJAMY-Y__uJrRx.js} +1 -1
- package/dist/assets/channel-LRG9kHqJ.js +1 -0
- package/dist/assets/{chunk-4BX2VUAB-Cv93Z7uM.js → chunk-4BX2VUAB-BfMePfTp.js} +1 -1
- package/dist/assets/{chunk-4TB4RGXK-DE0WBDkj.js → chunk-4TB4RGXK-BdlMSdEA.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-CE1EXenG.js → chunk-55IACEB6-vrQHZTdv.js} +1 -1
- package/dist/assets/{chunk-EDXVE4YY-DA7Ana6H.js → chunk-EDXVE4YY-B8wioVlW.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-CTDIPA3p.js → chunk-FMBD7UC4-Cd6Hrux2.js} +1 -1
- package/dist/assets/{chunk-OYMX7WX6-uGBaPaTX.js → chunk-OYMX7WX6-CfrhdQXY.js} +1 -1
- package/dist/assets/{chunk-QZHKN3VN-CYlnXuUO.js → chunk-QZHKN3VN-B9JQerOU.js} +1 -1
- package/dist/assets/{chunk-YZCP3GAM-ojGkzcZK.js → chunk-YZCP3GAM-DFN9X99H.js} +1 -1
- package/dist/assets/classDiagram-6PBFFD2Q-BC9a6pDE.js +1 -0
- package/dist/assets/classDiagram-v2-HSJHXN6E-BC9a6pDE.js +1 -0
- package/dist/assets/clone-dRxgFrBv.js +1 -0
- package/dist/assets/{cose-bilkent-S5V4N54A-Bktn9hL-.js → cose-bilkent-S5V4N54A-BAn0ap_E.js} +1 -1
- package/dist/assets/{dagre-KV5264BT-DFaSzuRF.js → dagre-KV5264BT-DyxnVq1g.js} +1 -1
- package/dist/assets/{diagram-5BDNPKRD-DnfmDzEm.js → diagram-5BDNPKRD-XCrzqski.js} +1 -1
- package/dist/assets/{diagram-G4DWMVQ6-Bm8W9YnG.js → diagram-G4DWMVQ6-MBCAXft_.js} +1 -1
- package/dist/assets/{diagram-MMDJMWI5-B5-TSKvp.js → diagram-MMDJMWI5-DbtB2yS6.js} +1 -1
- package/dist/assets/{diagram-TYMM5635-ls4rqlky.js → diagram-TYMM5635-Bb5NzX61.js} +1 -1
- package/dist/assets/{erDiagram-SMLLAGMA-giG6WO-r.js → erDiagram-SMLLAGMA-CpIeCOh2.js} +1 -1
- package/dist/assets/{flowDiagram-DWJPFMVM-XvlUuz-7.js → flowDiagram-DWJPFMVM-CHyoKnhW.js} +1 -1
- package/dist/assets/{ganttDiagram-T4ZO3ILL-hLBV57oV.js → ganttDiagram-T4ZO3ILL-DErKteO_.js} +1 -1
- package/dist/assets/{gitGraphDiagram-UUTBAWPF-BHu3s_Gn.js → gitGraphDiagram-UUTBAWPF-KFVAtj2F.js} +1 -1
- package/dist/assets/{graph-D0Cfv00Y.js → graph-CRnO_ifT.js} +1 -1
- package/dist/assets/index-DKBsEUJ-.css +1 -0
- package/dist/assets/index-DkRKLuNr.js +1144 -0
- package/dist/assets/{infoDiagram-42DDH7IO-DbqRsOo3.js → infoDiagram-42DDH7IO-BZFnuSp5.js} +1 -1
- package/dist/assets/{ishikawaDiagram-UXIWVN3A-DnCdx7zb.js → ishikawaDiagram-UXIWVN3A-4Xe2Szde.js} +1 -1
- package/dist/assets/{journeyDiagram-VCZTEJTY-CfD7eNcP.js → journeyDiagram-VCZTEJTY-CZRByfS-.js} +1 -1
- package/dist/assets/{kanban-definition-6JOO6SKY-BYaO9-mK.js → kanban-definition-6JOO6SKY-B95sk6Fk.js} +1 -1
- package/dist/assets/{layout-Bj72wOEB.js → layout-BqNQzxWT.js} +1 -1
- package/dist/assets/{linear-BRFo114D.js → linear-CUh7qb64.js} +1 -1
- package/dist/assets/{min-GCHnKlJS.js → min-wXgOS3ig.js} +1 -1
- package/dist/assets/{mindmap-definition-QFDTVHPH-n0PMebY4.js → mindmap-definition-QFDTVHPH-DB6iaAbO.js} +1 -1
- package/dist/assets/{pieDiagram-DEJITSTG-pN4CljHF.js → pieDiagram-DEJITSTG-CHkZHrTW.js} +1 -1
- package/dist/assets/{quadrantDiagram-34T5L4WZ-DNoAy8-D.js → quadrantDiagram-34T5L4WZ-DoTEO8e3.js} +1 -1
- package/dist/assets/{requirementDiagram-MS252O5E-BhtY05PT.js → requirementDiagram-MS252O5E-Dn8peXYp.js} +1 -1
- package/dist/assets/{sankeyDiagram-XADWPNL6-B6AD-16A.js → sankeyDiagram-XADWPNL6-DRXs6Ipb.js} +1 -1
- package/dist/assets/{sequenceDiagram-FGHM5R23-DShHM-uk.js → sequenceDiagram-FGHM5R23-wBBYZ0aq.js} +1 -1
- package/dist/assets/{stateDiagram-FHFEXIEX-DMxn7HTo.js → stateDiagram-FHFEXIEX-DPlBNGmf.js} +1 -1
- package/dist/assets/stateDiagram-v2-QKLJ7IA2-BW0ezXb4.js +1 -0
- package/dist/assets/{timeline-definition-GMOUNBTQ-Cdu6uq52.js → timeline-definition-GMOUNBTQ-CbbyTlHk.js} +1 -1
- package/dist/assets/{vennDiagram-DHZGUBPP-CpK29iRe.js → vennDiagram-DHZGUBPP-Bj4GaFfj.js} +1 -1
- package/dist/assets/{wardley-RL74JXVD-BQgSkdcO.js → wardley-RL74JXVD-RtNzq8KU.js} +55 -55
- package/dist/assets/{wardleyDiagram-NUSXRM2D-DJHYev6O.js → wardleyDiagram-NUSXRM2D-CDfE3zSj.js} +1 -1
- package/dist/assets/{xychartDiagram-5P7HB3ND-1d75pbaO.js → xychartDiagram-5P7HB3ND-CZXHHYD5.js} +1 -1
- package/dist/index.html +2 -2
- package/lib/budget-ledger.js +45 -0
- package/lib/bug-bisect.js +292 -0
- package/lib/bug-checkpoint.js +191 -0
- package/lib/bug-escalation.js +306 -0
- package/lib/bug-index-gen.js +136 -0
- package/lib/bug-ledger.js +126 -0
- package/lib/build-stream-schema.js +176 -0
- package/lib/build-stream-writer.js +3 -1
- package/lib/build.js +854 -284
- package/lib/connector-factory-shim.js +167 -0
- package/lib/constants.js +18 -0
- package/lib/debug-discipline.js +176 -27
- package/lib/deps.js +205 -0
- package/lib/health-score.js +4 -4
- package/lib/import.js +26 -13
- package/lib/inject-schema.js +21 -0
- package/lib/new.js +27 -53
- package/lib/result-normalizer.js +160 -144
- package/lib/review-lenses.js +5 -5
- package/lib/review-normalize.js +413 -0
- package/lib/review-prompt.js +163 -0
- package/lib/sections.js +325 -0
- package/lib/step-prompt.js +21 -1
- package/lib/step-validator.js +5 -3
- package/lib/stratum-mcp-client.js +172 -7
- package/package.json +14 -3
- package/pipelines/bug-fix.stratum.yaml +39 -1
- package/pipelines/build.stratum.yaml +28 -45
- package/pipelines/review-fix.stratum.yaml +1 -1
- package/presets/team-review.stratum.yaml +21 -14
- package/server/build-stream-bridge.js +28 -0
- package/server/cc-session-feature-resolver.js +111 -0
- package/server/cc-session-reader.js +327 -0
- package/server/cc-session-watcher.js +318 -0
- package/server/compose-mcp-tools.js +0 -125
- package/server/compose-mcp.js +2 -4
- package/server/contract-diff.js +192 -0
- package/server/decision-event-emit.js +175 -0
- package/server/decision-event-id.js +64 -0
- package/server/decision-events-snapshot.js +166 -0
- package/server/design-routes.js +92 -49
- package/server/drift-axes.js +365 -0
- package/server/drift-emit.js +121 -0
- package/server/gate-log-store.js +102 -0
- package/server/lifecycle-phase-history.js +44 -0
- package/server/open-loops-store.js +102 -0
- package/server/schema-validator.js +49 -0
- package/server/status-emit.js +27 -0
- package/server/status-snapshot.js +218 -0
- package/server/vision-routes.js +332 -4
- package/server/vision-server.js +104 -12
- package/server/vision-store.js +21 -0
- package/dist/assets/channel-DGElom1e.js +0 -1
- package/dist/assets/classDiagram-6PBFFD2Q-KqWP9wWZ.js +0 -1
- package/dist/assets/classDiagram-v2-HSJHXN6E-KqWP9wWZ.js +0 -1
- package/dist/assets/clone-DUJKJXd7.js +0 -1
- package/dist/assets/index-CUd6pFGF.css +0 -1
- package/dist/assets/index-DReRlzZI.js +0 -1144
- package/dist/assets/stateDiagram-v2-QKLJ7IA2-o6PnCs4e.js +0 -1
- package/server/connectors/agent-connector.js +0 -78
- package/server/connectors/claude-sdk-connector.js +0 -198
- package/server/connectors/codex-connector.js +0 -240
- package/server/connectors/connector-discovery.js +0 -18
- package/server/connectors/connector-runtime.js +0 -13
- package/server/connectors/opencode-connector.js +0 -200
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* decision-events-snapshot.js — Derive DecisionEvent[] from persisted lifecycle state.
|
|
3
|
+
*
|
|
4
|
+
* COMP-OBS-TIMELINE A6: on WS connect the server re-derives the current
|
|
5
|
+
* feature's DecisionEvents from already-persisted sources so the client can
|
|
6
|
+
* seed its store without waiting for a live replay.
|
|
7
|
+
*
|
|
8
|
+
* Sources:
|
|
9
|
+
* - kind=phase_transition → lifecycle.phaseHistory[] (populated by lifecycle-phase-history.js)
|
|
10
|
+
* - kind=iteration → lifecycle.iterationState (start + complete pairs)
|
|
11
|
+
* - kind=branch → lifecycle.lifecycle_ext.branch_lineage.branches[]
|
|
12
|
+
* - kind=gate → gate-log.jsonl (populated by COMP-OBS-GATELOG)
|
|
13
|
+
* - kind=drift_threshold → lifecycle.lifecycle_ext.drift_axes[] (COMP-OBS-DRIFT)
|
|
14
|
+
*
|
|
15
|
+
* Deterministic ids: re-derive == identity with the live emitters because both
|
|
16
|
+
* use the same id helpers from decision-event-id.js.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import {
|
|
20
|
+
phaseTransitionDecisionEventId,
|
|
21
|
+
iterationDecisionEventId,
|
|
22
|
+
branchDecisionEventId,
|
|
23
|
+
} from './decision-event-id.js';
|
|
24
|
+
import { buildPhaseTransitionEvent, buildIterationEvent, buildGateEvent, buildDriftThresholdEvent } from './decision-event-emit.js';
|
|
25
|
+
import { readGateLog } from './gate-log-store.js';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Derive all DecisionEvents for a given featureCode from persisted lifecycle state.
|
|
29
|
+
*
|
|
30
|
+
* @param {object} state — { items: Map<id, item> } (from VisionStore.getState or internal)
|
|
31
|
+
* @param {string} featureCode — the feature to filter for
|
|
32
|
+
* @returns {DecisionEvent[]} array sorted by timestamp ascending
|
|
33
|
+
*/
|
|
34
|
+
export function deriveDecisionEvents(state, featureCode) {
|
|
35
|
+
const events = [];
|
|
36
|
+
|
|
37
|
+
// state may expose items as Map (internal) or Array (getState). Handle both.
|
|
38
|
+
const itemsIterable = state.items instanceof Map
|
|
39
|
+
? state.items.values()
|
|
40
|
+
: (Array.isArray(state.items) ? state.items : []);
|
|
41
|
+
|
|
42
|
+
for (const item of itemsIterable) {
|
|
43
|
+
const lc = item?.lifecycle;
|
|
44
|
+
if (!lc) continue;
|
|
45
|
+
if (lc.featureCode !== featureCode) continue;
|
|
46
|
+
|
|
47
|
+
// ── phase_transition events ─────────────────────────────────────────────
|
|
48
|
+
for (const entry of lc.phaseHistory || []) {
|
|
49
|
+
events.push(buildPhaseTransitionEvent({
|
|
50
|
+
featureCode,
|
|
51
|
+
from: entry.from,
|
|
52
|
+
to: entry.to,
|
|
53
|
+
outcome: entry.outcome,
|
|
54
|
+
agent_id: entry.agent_id || null,
|
|
55
|
+
timestamp: entry.timestamp,
|
|
56
|
+
}));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ── iteration events ────────────────────────────────────────────────────
|
|
60
|
+
const iter = lc.iterationState;
|
|
61
|
+
if (iter?.loopId) {
|
|
62
|
+
// Always emit start event
|
|
63
|
+
events.push(buildIterationEvent({
|
|
64
|
+
featureCode,
|
|
65
|
+
loopId: iter.loopId,
|
|
66
|
+
loopType: iter.loopType,
|
|
67
|
+
stage: 'start',
|
|
68
|
+
attempt: null,
|
|
69
|
+
outcome: 'retry',
|
|
70
|
+
timestamp: iter.startedAt,
|
|
71
|
+
}));
|
|
72
|
+
|
|
73
|
+
// Emit complete event only when loop has actually completed
|
|
74
|
+
if (iter.status === 'complete' && iter.completedAt) {
|
|
75
|
+
events.push(buildIterationEvent({
|
|
76
|
+
featureCode,
|
|
77
|
+
loopId: iter.loopId,
|
|
78
|
+
loopType: iter.loopType,
|
|
79
|
+
stage: 'complete',
|
|
80
|
+
attempt: iter.count,
|
|
81
|
+
outcome: iter.outcome,
|
|
82
|
+
timestamp: iter.completedAt,
|
|
83
|
+
}));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ── branch events ───────────────────────────────────────────────────────
|
|
88
|
+
// Production lineage is persisted at `item.lifecycle.lifecycle_ext.branch_lineage`
|
|
89
|
+
// by `vision-store.updateLifecycleExt`. Earlier drafts read `item.lifecycle_ext`
|
|
90
|
+
// (top-level), which is why test fixtures occasionally produced the wrong shape.
|
|
91
|
+
const lineage = lc.lifecycle_ext?.branch_lineage;
|
|
92
|
+
if (lineage?.branches) {
|
|
93
|
+
// Compute sibling_branch_ids per branch — all branches sharing the same
|
|
94
|
+
// non-null fork_uuid (the convention BRANCH's live emitter uses).
|
|
95
|
+
const siblingsByFork = new Map();
|
|
96
|
+
for (const b of lineage.branches) {
|
|
97
|
+
if (!b.fork_uuid) continue;
|
|
98
|
+
if (!siblingsByFork.has(b.fork_uuid)) siblingsByFork.set(b.fork_uuid, []);
|
|
99
|
+
siblingsByFork.get(b.fork_uuid).push(b.branch_id);
|
|
100
|
+
}
|
|
101
|
+
for (const branch of lineage.branches) {
|
|
102
|
+
const eventId = branchDecisionEventId(featureCode, branch.branch_id);
|
|
103
|
+
const siblingIds = branch.fork_uuid ? siblingsByFork.get(branch.fork_uuid) ?? [] : [];
|
|
104
|
+
events.push({
|
|
105
|
+
id: eventId,
|
|
106
|
+
feature_code: featureCode,
|
|
107
|
+
timestamp: branch.started_at,
|
|
108
|
+
kind: 'branch',
|
|
109
|
+
title: `New branch ${branch.branch_id.slice(0, 8)}…`,
|
|
110
|
+
metadata: {
|
|
111
|
+
branch_id: branch.branch_id,
|
|
112
|
+
fork_uuid: branch.fork_uuid || null,
|
|
113
|
+
sibling_branch_ids: siblingIds,
|
|
114
|
+
},
|
|
115
|
+
roles: [],
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ── drift_threshold events (kind=drift_threshold) — COMP-OBS-DRIFT ────────
|
|
121
|
+
// Rehydrate from persisted DriftAxis.breach_event_id + breach_started_at.
|
|
122
|
+
// Using persisted fields guarantees byte-for-byte identity with the live emit
|
|
123
|
+
// — we do NOT recompute from current computed_at, which would produce a
|
|
124
|
+
// different id on every reconnect.
|
|
125
|
+
const driftAxes = lc.lifecycle_ext?.drift_axes ?? [];
|
|
126
|
+
for (const axis of driftAxes) {
|
|
127
|
+
if (axis.breached === true && axis.breach_event_id && axis.breach_started_at) {
|
|
128
|
+
events.push(buildDriftThresholdEvent({
|
|
129
|
+
featureCode,
|
|
130
|
+
axisId: axis.axis_id,
|
|
131
|
+
ratio: axis.ratio,
|
|
132
|
+
threshold: axis.threshold,
|
|
133
|
+
breachStartedAt: axis.breach_started_at,
|
|
134
|
+
breachEventId: axis.breach_event_id,
|
|
135
|
+
}));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ── gate events (kind=gate) — rehydrate from project gate-log.jsonl ──────
|
|
141
|
+
// Without this, live gate cards on the timeline disappear after WS reconnect.
|
|
142
|
+
// The gate log is project-scoped (NOT app-global), so cross-feature filter is safe.
|
|
143
|
+
try {
|
|
144
|
+
const entries = readGateLog({ featureCode });
|
|
145
|
+
for (const entry of entries) {
|
|
146
|
+
// Translate route-vocab decision (approve/revise/kill) into schema vocab
|
|
147
|
+
// before composing the event. Entry already stores schema vocab, but
|
|
148
|
+
// buildGateEvent re-maps for safety.
|
|
149
|
+
const event = buildGateEvent({
|
|
150
|
+
featureCode,
|
|
151
|
+
gateLogEntryId: entry.id,
|
|
152
|
+
gateId: entry.gate_id,
|
|
153
|
+
decision: entry.decision, // already schema vocab — buildGateEvent passes through
|
|
154
|
+
timestamp: entry.timestamp,
|
|
155
|
+
});
|
|
156
|
+
events.push(event);
|
|
157
|
+
}
|
|
158
|
+
} catch (err) {
|
|
159
|
+
// Gate log read is best-effort; missing/unreadable files yield no gate events.
|
|
160
|
+
// Existing rehydration of phase/iteration/branch events still proceeds.
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Sort by timestamp ascending (oldest first — strip renders newest-right)
|
|
164
|
+
events.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
|
|
165
|
+
return events;
|
|
166
|
+
}
|
package/server/design-routes.js
CHANGED
|
@@ -12,7 +12,34 @@
|
|
|
12
12
|
|
|
13
13
|
import fs from 'node:fs';
|
|
14
14
|
import path from 'node:path';
|
|
15
|
+
import { randomUUID } from 'node:crypto';
|
|
15
16
|
import { parseDecisionBlocks } from '../src/components/vision/designSessionState.js';
|
|
17
|
+
import { StratumMcpClient } from '../lib/stratum-mcp-client.js';
|
|
18
|
+
|
|
19
|
+
// Lazy singleton — design conversations share one stratum-mcp connection
|
|
20
|
+
// across the server process lifetime. Concurrent runs are correlation-id scoped.
|
|
21
|
+
let _stratumClient = null;
|
|
22
|
+
let _stratumConnectPromise = null;
|
|
23
|
+
async function _getStratum() {
|
|
24
|
+
if (_stratumClient) return _stratumClient;
|
|
25
|
+
if (!_stratumConnectPromise) {
|
|
26
|
+
const c = new StratumMcpClient();
|
|
27
|
+
_stratumConnectPromise = c.connect()
|
|
28
|
+
.then(() => { _stratumClient = c; return c; })
|
|
29
|
+
.catch((err) => { _stratumConnectPromise = null; throw err; });
|
|
30
|
+
}
|
|
31
|
+
return _stratumConnectPromise;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Tear down the cached stratum-mcp connection. Tests should call this in after(). */
|
|
35
|
+
export async function closeDesignStratum() {
|
|
36
|
+
const c = _stratumClient;
|
|
37
|
+
_stratumClient = null;
|
|
38
|
+
_stratumConnectPromise = null;
|
|
39
|
+
if (c) {
|
|
40
|
+
try { await c.close(); } catch { /* ignore */ }
|
|
41
|
+
}
|
|
42
|
+
}
|
|
16
43
|
|
|
17
44
|
/** @type {Map<string, Set<import('node:http').ServerResponse>>} — key is `${scope}:${featureCode || ''}` */
|
|
18
45
|
export const designListeners = new Map();
|
|
@@ -64,6 +91,9 @@ export function broadcastDesignEvent(key, type, data) {
|
|
|
64
91
|
async function dispatchDesignAgent(sessionManager, projectRoot, scope, featureCode) {
|
|
65
92
|
const key = sessionKey(scope, featureCode, projectRoot);
|
|
66
93
|
if (_inFlight.has(key)) return; // already running
|
|
94
|
+
// Tests assert on the HTTP response only — skip the LLM round-trip to avoid
|
|
95
|
+
// keeping a stratum-mcp subprocess alive past the test lifetime.
|
|
96
|
+
if (process.env.NODE_ENV === 'test' || process.env.COMPOSE_DESIGN_DISPATCH === '0') return;
|
|
67
97
|
_inFlight.add(key);
|
|
68
98
|
// Snapshot message count so we can detect new messages arriving during the run
|
|
69
99
|
let promptMessageCount = 0;
|
|
@@ -107,44 +137,60 @@ Conversation history:
|
|
|
107
137
|
${formattedMessages}`;
|
|
108
138
|
|
|
109
139
|
let fullContent = '';
|
|
110
|
-
let lastToolName = null;
|
|
111
140
|
let toolUseCounter = 0;
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
if (
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
141
|
+
|
|
142
|
+
const stratum = await _getStratum();
|
|
143
|
+
const correlationId = randomUUID();
|
|
144
|
+
const subStepId = '_agent_run';
|
|
145
|
+
|
|
146
|
+
const unsub = stratum.onEvent(correlationId, subStepId, (env) => {
|
|
147
|
+
if (!env || env.schema_version !== '0.2.5') return;
|
|
148
|
+
const m = env.metadata ?? {};
|
|
149
|
+
switch (env.kind) {
|
|
150
|
+
case 'agent_relay':
|
|
151
|
+
if (m.role === 'assistant' && typeof m.text === 'string' && m.text.length > 0) {
|
|
152
|
+
fullContent += m.text;
|
|
153
|
+
broadcastDesignEvent(key, 'text', { content: m.text });
|
|
154
|
+
}
|
|
155
|
+
break;
|
|
156
|
+
case 'tool_use_summary': {
|
|
157
|
+
const toolUseId = `tu-${++toolUseCounter}`;
|
|
158
|
+
if (m.tool) {
|
|
159
|
+
broadcastDesignEvent(key, 'research', {
|
|
160
|
+
id: toolUseId,
|
|
161
|
+
tool: m.tool,
|
|
162
|
+
input: m.input ?? {},
|
|
163
|
+
timestamp: new Date().toISOString(),
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
if (m.summary) {
|
|
167
|
+
broadcastDesignEvent(key, 'research_result', {
|
|
168
|
+
id: toolUseId,
|
|
169
|
+
tool: m.tool ?? null,
|
|
170
|
+
summary: String(m.summary).slice(0, 200),
|
|
171
|
+
timestamp: new Date().toISOString(),
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
break;
|
|
126
175
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
return;
|
|
130
|
-
} else if (event.type === 'tool_use') {
|
|
131
|
-
lastToolName = event.tool;
|
|
132
|
-
lastToolUseId = `tu-${++toolUseCounter}`;
|
|
133
|
-
broadcastDesignEvent(key, 'research', {
|
|
134
|
-
id: lastToolUseId,
|
|
135
|
-
tool: event.tool,
|
|
136
|
-
input: event.input,
|
|
137
|
-
timestamp: new Date().toISOString(),
|
|
138
|
-
});
|
|
139
|
-
} else if (event.type === 'tool_use_summary') {
|
|
140
|
-
broadcastDesignEvent(key, 'research_result', {
|
|
141
|
-
id: lastToolUseId,
|
|
142
|
-
tool: lastToolName,
|
|
143
|
-
summary: (event.summary || '').slice(0, 200),
|
|
144
|
-
timestamp: new Date().toISOString(),
|
|
145
|
-
});
|
|
176
|
+
default:
|
|
177
|
+
break;
|
|
146
178
|
}
|
|
147
|
-
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
let runResult;
|
|
182
|
+
try {
|
|
183
|
+
runResult = await stratum.agentRun('claude', systemPrompt, {
|
|
184
|
+
cwd: projectRoot,
|
|
185
|
+
correlationId,
|
|
186
|
+
});
|
|
187
|
+
} finally {
|
|
188
|
+
unsub();
|
|
189
|
+
}
|
|
190
|
+
// Fall back to result.text if no agent_relay events arrived (older producers).
|
|
191
|
+
if (!fullContent && runResult?.text) {
|
|
192
|
+
fullContent = runResult.text;
|
|
193
|
+
broadcastDesignEvent(key, 'text', { content: fullContent });
|
|
148
194
|
}
|
|
149
195
|
|
|
150
196
|
// Parse for decision blocks and broadcast them
|
|
@@ -190,9 +236,9 @@ ${formattedMessages}`;
|
|
|
190
236
|
* surviving project switches via /api/project/switch.
|
|
191
237
|
*
|
|
192
238
|
* @param {object} app — Express app
|
|
193
|
-
* @param {{ getSessionManager: () => import('./design-session.js').DesignSessionManager,
|
|
239
|
+
* @param {{ getSessionManager: () => import('./design-session.js').DesignSessionManager, getProjectRoot: () => string }} deps
|
|
194
240
|
*/
|
|
195
|
-
export function attachDesignRoutes(app, { getSessionManager,
|
|
241
|
+
export function attachDesignRoutes(app, { getSessionManager, getProjectRoot }) {
|
|
196
242
|
// POST /api/design/start
|
|
197
243
|
app.post('/api/design/start', (req, res) => {
|
|
198
244
|
const { scope, featureCode } = req.body || {};
|
|
@@ -328,8 +374,10 @@ export function attachDesignRoutes(app, { getSessionManager, getConnector, getPr
|
|
|
328
374
|
return res.status(404).json({ error: `No session found for ${scope}${featureCode ? `:${featureCode}` : ''}` });
|
|
329
375
|
}
|
|
330
376
|
|
|
331
|
-
//
|
|
332
|
-
|
|
377
|
+
// No projectRoot, or test/dispatch-disabled mode → mark complete and skip doc generation.
|
|
378
|
+
const dispatchDisabled =
|
|
379
|
+
process.env.NODE_ENV === 'test' || process.env.COMPOSE_DESIGN_DISPATCH === '0';
|
|
380
|
+
if (!projectRoot || dispatchDisabled) {
|
|
333
381
|
const completedSession = sessionManager.completeSession(scope, featureCode);
|
|
334
382
|
res.json({ session: completedSession });
|
|
335
383
|
return;
|
|
@@ -379,18 +427,13 @@ Write the design document in Markdown format. Include:
|
|
|
379
427
|
|
|
380
428
|
Output ONLY the Markdown content, no code fences.`;
|
|
381
429
|
|
|
382
|
-
// Generate the design doc via
|
|
383
|
-
|
|
384
|
-
const connector = new ClaudeSDKConnector({ cwd: projectRoot });
|
|
430
|
+
// Generate the design doc via the persistent stratum-mcp client.
|
|
431
|
+
// No streaming needed for the one-shot doc generation.
|
|
385
432
|
let docContent = '';
|
|
386
433
|
try {
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
} else if (event.type === 'result' && event.content && !docContent) {
|
|
391
|
-
docContent = event.content;
|
|
392
|
-
}
|
|
393
|
-
}
|
|
434
|
+
const stratum = await _getStratum();
|
|
435
|
+
const result = await stratum.runAgentText('claude', docPrompt, { cwd: projectRoot });
|
|
436
|
+
docContent = result || '';
|
|
394
437
|
} catch (err) {
|
|
395
438
|
// If generation fails, don't complete — let user retry
|
|
396
439
|
console.error('[design] Doc generation failed:', err.message);
|