@smartmemory/compose 0.1.28-beta → 0.1.29-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.
Files changed (65) hide show
  1. package/bin/compose.js +27 -0
  2. package/contracts/task-result.json +60 -0
  3. package/contracts/taskgraph-gsd.json +94 -0
  4. package/dist/assets/{App-Dj7XWWxC.js → App-DyRUFvbx.js} +67 -67
  5. package/dist/assets/{_baseUniq-tNOA7dYy.js → _baseUniq-6fxo8lI4.js} +1 -1
  6. package/dist/assets/{arc-BAmAJ19S.js → arc-ElMd2B94.js} +1 -1
  7. package/dist/assets/{architectureDiagram-Q4EWVU46-BPWGVKHW.js → architectureDiagram-Q4EWVU46-CYkxf4MU.js} +1 -1
  8. package/dist/assets/{blockDiagram-DXYQGD6D-CVlFbWKF.js → blockDiagram-DXYQGD6D-BEbnyY6z.js} +1 -1
  9. package/dist/assets/{c4Diagram-AHTNJAMY-CqzLpnSp.js → c4Diagram-AHTNJAMY-Bo5yMBT9.js} +1 -1
  10. package/dist/assets/channel-BZuHuJYj.js +1 -0
  11. package/dist/assets/{chunk-4BX2VUAB-D27n9FGy.js → chunk-4BX2VUAB-BETV09jq.js} +1 -1
  12. package/dist/assets/{chunk-4TB4RGXK-BNXf8s1x.js → chunk-4TB4RGXK-Dbuohq0Y.js} +1 -1
  13. package/dist/assets/{chunk-55IACEB6-kGd4Gwx6.js → chunk-55IACEB6-CDVuU3Ew.js} +1 -1
  14. package/dist/assets/{chunk-EDXVE4YY-Ci9gWeIv.js → chunk-EDXVE4YY-DjkbltSG.js} +1 -1
  15. package/dist/assets/{chunk-FMBD7UC4-B-C0Qn-h.js → chunk-FMBD7UC4-Dz2qczuu.js} +1 -1
  16. package/dist/assets/{chunk-OYMX7WX6-CosYsxuv.js → chunk-OYMX7WX6-XJH4cbpv.js} +1 -1
  17. package/dist/assets/{chunk-QZHKN3VN-DV2v0Qii.js → chunk-QZHKN3VN-DPhBd22Q.js} +1 -1
  18. package/dist/assets/{chunk-YZCP3GAM-BlgKQRCn.js → chunk-YZCP3GAM-C-DPngtC.js} +1 -1
  19. package/dist/assets/classDiagram-6PBFFD2Q-DTItsUL2.js +1 -0
  20. package/dist/assets/classDiagram-v2-HSJHXN6E-DTItsUL2.js +1 -0
  21. package/dist/assets/clone-BM9Hd7mq.js +1 -0
  22. package/dist/assets/{cose-bilkent-S5V4N54A-CSkzhGHO.js → cose-bilkent-S5V4N54A-CN-sdzRq.js} +1 -1
  23. package/dist/assets/{dagre-KV5264BT-zp76534d.js → dagre-KV5264BT-HcBmaeC1.js} +1 -1
  24. package/dist/assets/{diagram-5BDNPKRD-CAsORZBT.js → diagram-5BDNPKRD-DeMZN1_c.js} +1 -1
  25. package/dist/assets/{diagram-G4DWMVQ6-Da2z6fvR.js → diagram-G4DWMVQ6-DAVSapDS.js} +1 -1
  26. package/dist/assets/{diagram-MMDJMWI5-R9NZEWPF.js → diagram-MMDJMWI5-WiLMNWTz.js} +1 -1
  27. package/dist/assets/{diagram-TYMM5635-DXabRna8.js → diagram-TYMM5635-CjfaWKT0.js} +1 -1
  28. package/dist/assets/{erDiagram-SMLLAGMA-B1zsRPqn.js → erDiagram-SMLLAGMA-DUT_laNT.js} +1 -1
  29. package/dist/assets/{flowDiagram-DWJPFMVM-AvlZ6pTE.js → flowDiagram-DWJPFMVM-J9uyMWkp.js} +1 -1
  30. package/dist/assets/{ganttDiagram-T4ZO3ILL-Bnj-jTcM.js → ganttDiagram-T4ZO3ILL-Cee_YjtS.js} +1 -1
  31. package/dist/assets/{gitGraphDiagram-UUTBAWPF-82ysfuqG.js → gitGraphDiagram-UUTBAWPF-B1jNNoiW.js} +1 -1
  32. package/dist/assets/{graph-6nRhlKgL.js → graph-DV5DY72d.js} +1 -1
  33. package/dist/assets/{index-CF7jc-By.js → index-D5Mh04yh.js} +2 -2
  34. package/dist/assets/{infoDiagram-42DDH7IO-DSGEEGYr.js → infoDiagram-42DDH7IO-m1jIMAlx.js} +1 -1
  35. package/dist/assets/{ishikawaDiagram-UXIWVN3A-COnZHJuM.js → ishikawaDiagram-UXIWVN3A-BC7DwQNb.js} +1 -1
  36. package/dist/assets/{journeyDiagram-VCZTEJTY-Bsssj2jr.js → journeyDiagram-VCZTEJTY-BOCgv7m4.js} +1 -1
  37. package/dist/assets/{kanban-definition-6JOO6SKY-1o5Em0Ia.js → kanban-definition-6JOO6SKY-Fu0eFxr1.js} +1 -1
  38. package/dist/assets/{layout-C6Mitjz_.js → layout-C-R3-tDf.js} +1 -1
  39. package/dist/assets/{linear-DoGSpDWQ.js → linear-NOMW_E2I.js} +1 -1
  40. package/dist/assets/{min-BqH4I4oK.js → min-B7SuZW29.js} +1 -1
  41. package/dist/assets/{mindmap-definition-QFDTVHPH-79V6zmXV.js → mindmap-definition-QFDTVHPH-BPwx_SxA.js} +1 -1
  42. package/dist/assets/{pieDiagram-DEJITSTG-nXz4HiT6.js → pieDiagram-DEJITSTG-DdBjaXTu.js} +1 -1
  43. package/dist/assets/{quadrantDiagram-34T5L4WZ-BuwKLcii.js → quadrantDiagram-34T5L4WZ-D_K_ZMnx.js} +1 -1
  44. package/dist/assets/{requirementDiagram-MS252O5E-9YEiDjlT.js → requirementDiagram-MS252O5E-XlROD_VH.js} +1 -1
  45. package/dist/assets/{sankeyDiagram-XADWPNL6-gX8lhWn5.js → sankeyDiagram-XADWPNL6-Sl0FllYQ.js} +1 -1
  46. package/dist/assets/{sequenceDiagram-FGHM5R23-CkssrD67.js → sequenceDiagram-FGHM5R23-D22asQ-_.js} +1 -1
  47. package/dist/assets/{stateDiagram-FHFEXIEX-DPmEy2eV.js → stateDiagram-FHFEXIEX-BnCepkBg.js} +1 -1
  48. package/dist/assets/stateDiagram-v2-QKLJ7IA2-DD_YjrTQ.js +1 -0
  49. package/dist/assets/{timeline-definition-GMOUNBTQ-DGpUOjs3.js → timeline-definition-GMOUNBTQ-gdlx6TEB.js} +1 -1
  50. package/dist/assets/{vennDiagram-DHZGUBPP-CIvkd661.js → vennDiagram-DHZGUBPP-wy4YzhrG.js} +1 -1
  51. package/dist/assets/{wardley-RL74JXVD-BqRTpa3K.js → wardley-RL74JXVD-D0T_uQ79.js} +1 -1
  52. package/dist/assets/{wardleyDiagram-NUSXRM2D-B93hOd7R.js → wardleyDiagram-NUSXRM2D-Ds2dLjs8.js} +1 -1
  53. package/dist/assets/{xychartDiagram-5P7HB3ND-C6hwimqo.js → xychartDiagram-5P7HB3ND-CLgTTXkj.js} +1 -1
  54. package/dist/index.html +1 -1
  55. package/lib/gsd-blackboard.js +135 -0
  56. package/lib/gsd-decompose-enrich.js +171 -0
  57. package/lib/gsd-prompt.js +82 -0
  58. package/lib/gsd.js +364 -0
  59. package/package.json +1 -1
  60. package/pipelines/gsd.stratum.yaml +141 -0
  61. package/dist/assets/channel-Ddcaj0fR.js +0 -1
  62. package/dist/assets/classDiagram-6PBFFD2Q-DfGxrNIN.js +0 -1
  63. package/dist/assets/classDiagram-v2-HSJHXN6E-DfGxrNIN.js +0 -1
  64. package/dist/assets/clone-DVujR_lO.js +0 -1
  65. package/dist/assets/stateDiagram-v2-QKLJ7IA2-BGZzYkLq.js +0 -1
package/lib/gsd.js ADDED
@@ -0,0 +1,364 @@
1
+ // lib/gsd.js
2
+ //
3
+ // COMP-GSD-2 T6: runGsd lifecycle entry — `compose gsd <featureCode>`.
4
+ //
5
+ // Self-contained status loop. Does NOT modify lib/build.js. Reuses primitives:
6
+ // - StratumMcpClient (lib/stratum-mcp-client.js) for plan/stepDone/runAgentText
7
+ // - executeParallelDispatchServer (lib/build.js) for the execute step
8
+ // - validateBoundaryMap (lib/boundary-map.js) for precondition check
9
+ // - enrichTaskGraph (lib/gsd-decompose-enrich.js) for decompose validation
10
+ // - buildTaskDescription (lib/gsd-prompt.js) for description repair fallback
11
+ // - gsd-blackboard.writeAll for post-step finalization
12
+ //
13
+ // V1 limitation: runtime task-to-task handoff is not implemented; tasks see
14
+ // only spec-level upstream context (Boundary Map declarations) per blueprint.
15
+
16
+ import { readFileSync, existsSync, readdirSync } from 'node:fs';
17
+ import { join, resolve, dirname } from 'node:path';
18
+ import { fileURLToPath } from 'node:url';
19
+ import { execSync } from 'node:child_process';
20
+
21
+ import { StratumMcpClient } from './stratum-mcp-client.js';
22
+ import { validateBoundaryMap } from './boundary-map.js';
23
+ import { enrichTaskGraph } from './gsd-decompose-enrich.js';
24
+ import { buildTaskDescription } from './gsd-prompt.js';
25
+ import { writeAll, validate as validateTaskResult } from './gsd-blackboard.js';
26
+ import { executeParallelDispatchServer, executeShipStep } from './build.js';
27
+
28
+ const __dirname = dirname(fileURLToPath(import.meta.url));
29
+ const PACKAGE_ROOT = resolve(__dirname, '..');
30
+
31
+ const DEFAULT_GATE_COMMANDS = ['pnpm lint', 'pnpm build', 'pnpm test'];
32
+
33
+ // ---------- Public API ----------
34
+
35
+ export async function runGsd(featureCode, opts = {}) {
36
+ if (!featureCode || typeof featureCode !== 'string') {
37
+ throw new Error('runGsd: featureCode required');
38
+ }
39
+ const cwd = opts.cwd ?? process.cwd();
40
+
41
+ // 1. Validate preconditions: blueprint exists + Boundary Map ok
42
+ const blueprintPath = join(cwd, 'docs', 'features', featureCode, 'blueprint.md');
43
+ if (!existsSync(blueprintPath)) {
44
+ throw new Error(
45
+ `runGsd: blueprint missing at ${blueprintPath}. ` +
46
+ `Run \`compose build ${featureCode}\` to generate it, or author it by hand.`,
47
+ );
48
+ }
49
+ const blueprintText = readFileSync(blueprintPath, 'utf-8');
50
+ const bmResult = validateBoundaryMap({
51
+ blueprintText,
52
+ blueprintPath,
53
+ repoRoot: cwd,
54
+ });
55
+ if (!bmResult.ok) {
56
+ const summary = bmResult.violations
57
+ .slice(0, 5)
58
+ .map((v) => `${v.kind}: ${v.message}`)
59
+ .join('\n - ');
60
+ throw new Error(
61
+ `runGsd: Boundary Map invalid in ${blueprintPath}:\n - ${summary}`,
62
+ );
63
+ }
64
+
65
+ // 2. Refuse to start in a dirty workspace BEFORE any Stratum side effects.
66
+ // v1 rationale: alternatives (baseline subtract + post-execute delta) drop
67
+ // legitimate edits to pre-existing dirty files. Refuse-if-dirty makes
68
+ // post-execute dirty set unambiguous: every entry is GSD-produced.
69
+ if (!opts.allowDirtyWorkspace) {
70
+ const startingDirty = collectChangedFiles(cwd);
71
+ if (startingDirty.length > 0) {
72
+ throw new Error(
73
+ `runGsd: working tree must be clean to ensure ship_gsd stages only GSD-produced changes. ` +
74
+ `Dirty files: ${startingDirty.slice(0, 5).join(', ')}${startingDirty.length > 5 ? `, +${startingDirty.length - 5} more` : ''}. ` +
75
+ `Commit or stash and re-run, or pass {allowDirtyWorkspace: true} (advanced; risks staging unrelated edits).`,
76
+ );
77
+ }
78
+ }
79
+
80
+ // 3. Resolve gateCommands. loadProjectConfig() does not merge defaults, so
81
+ // explicit fallback here.
82
+ const gateCommands = resolveGateCommands(cwd, opts.gateCommands);
83
+
84
+ // 4. Load pipeline spec
85
+ const specPath = join(PACKAGE_ROOT, 'pipelines', 'gsd.stratum.yaml');
86
+ const specYaml = readFileSync(specPath, 'utf-8');
87
+
88
+ // 5. Connect Stratum + plan (only after preconditions pass)
89
+ const stratum = opts.stratum ?? new StratumMcpClient();
90
+ const ownsStratum = !opts.stratum;
91
+ if (ownsStratum) await stratum.connect();
92
+ try {
93
+ let response = await stratum.plan(specYaml, 'gsd', {
94
+ featureCode,
95
+ gateCommands,
96
+ });
97
+ const flowId = response.flow_id;
98
+
99
+ // Track files merged into the base cwd by the execute step so ship_gsd
100
+ // can stage them. executeShipStep's default filter only stages feature
101
+ // docs unless context.filesChanged is provided.
102
+ const stepCtx = {
103
+ stratum, cwd, featureCode, blueprintText, gateCommands,
104
+ filesChanged: [],
105
+ };
106
+
107
+ // 5. Status loop
108
+ while (response.status !== 'complete' && response.status !== 'killed') {
109
+ response = await runOneStep(response, stepCtx);
110
+ }
111
+
112
+ // 6. Post-step blackboard finalization — read each task's TaskResult JSON
113
+ // and write the consolidated blackboard.
114
+ const blackboard = collectBlackboard(cwd, featureCode);
115
+ if (Object.keys(blackboard).length > 0) {
116
+ await writeAll(featureCode, blackboard, { cwd });
117
+ }
118
+
119
+ return {
120
+ status: response.status,
121
+ flowId,
122
+ blackboardEntries: Object.keys(blackboard).length,
123
+ };
124
+ } finally {
125
+ if (ownsStratum) {
126
+ try { await stratum.disconnect?.(); } catch { /* best-effort */ }
127
+ }
128
+ }
129
+ }
130
+
131
+ // ---------- Internals ----------
132
+
133
+ export function resolveGateCommands(cwd, override) {
134
+ if (Array.isArray(override) && override.length > 0) return override;
135
+ // loadProjectConfig() returns raw .compose/compose.json — does NOT merge
136
+ // defaults — so we must do our own fallback.
137
+ const configPath = join(cwd, '.compose', 'compose.json');
138
+ if (existsSync(configPath)) {
139
+ try {
140
+ const cfg = JSON.parse(readFileSync(configPath, 'utf-8'));
141
+ if (Array.isArray(cfg.gateCommands) && cfg.gateCommands.length > 0) {
142
+ return cfg.gateCommands;
143
+ }
144
+ } catch {
145
+ /* fall through to default */
146
+ }
147
+ }
148
+ return [...DEFAULT_GATE_COMMANDS];
149
+ }
150
+
151
+ async function runOneStep(response, ctx) {
152
+ const { stratum, cwd, featureCode, blueprintText, gateCommands } = ctx;
153
+ const flowId = response.flow_id;
154
+ const stepId = response.step_id;
155
+ const stepType = response.type ?? response.step_type;
156
+
157
+ if (response.status === 'execute_step') {
158
+ // parallel_dispatch step (the `execute` step)
159
+ if (stepType === 'parallel_dispatch' || response.tasks) {
160
+ const outcome = await executeParallelDispatchServer(
161
+ response,
162
+ stratum,
163
+ { cwd, featureCode },
164
+ null, // progress
165
+ { write: () => {} }, // streamWriter — no-op for v1
166
+ cwd,
167
+ );
168
+ // After diffs are merged, capture the touched files for ship_gsd
169
+ // staging. The clean-workspace precondition above guarantees every
170
+ // file in the post-execute dirty set is genuinely a GSD-produced change.
171
+ ctx.filesChanged = collectChangedFiles(cwd);
172
+ // executeParallelDispatchServer returns the next-step dispatch envelope
173
+ return outcome;
174
+ }
175
+
176
+ // ship_gsd: delegate to executeShipStep with filesChanged from execute step
177
+ // so source files are staged. Agent sandbox blocks git, so commit must
178
+ // run in-process (mirrors runBuild's special case at lib/build.js:963-981).
179
+ if (stepId === 'ship_gsd') {
180
+ const shipResult = await executeShipStep(
181
+ featureCode,
182
+ cwd,
183
+ cwd,
184
+ { cwd, featureCode, mode: 'feature', filesChanged: ctx.filesChanged ?? [] },
185
+ '',
186
+ null,
187
+ );
188
+ // executeShipStep stages + commits but does NOT push. Push is a
189
+ // user-facing operation deferred to the user in v1; runBuild's ship
190
+ // step doesn't auto-push either. Document via ship intent later.
191
+ return await stratum.stepDone(flowId, stepId, shipResult);
192
+ }
193
+
194
+ // Single-agent step: dispatch via runAgentText. The agent returns text;
195
+ // we expect JSON matching the step's output_contract.
196
+ const prompt = response.intent ?? '';
197
+ const text = await stratum.runAgentText(response.agent ?? 'claude', prompt, { cwd });
198
+ let result;
199
+ try {
200
+ result = parseJsonFromText(text);
201
+ } catch (err) {
202
+ throw new Error(
203
+ `runGsd: step ${stepId} agent did not return parseable JSON: ${err.message}`,
204
+ );
205
+ }
206
+
207
+ // T6 step 7: validate decompose_gsd output and repair missing descriptions.
208
+ if (stepId === 'decompose_gsd') {
209
+ result = validateAndRepairTaskGraph(result, blueprintText, gateCommands);
210
+ }
211
+
212
+ return await stratum.stepDone(flowId, stepId, result);
213
+ }
214
+
215
+ if (response.status === 'await_gate') {
216
+ // GSD has no gates in v1. If we hit one, surface it.
217
+ throw new Error(
218
+ `runGsd: unexpected gate at step ${stepId}. v1 has no gates in the gsd flow.`,
219
+ );
220
+ }
221
+
222
+ throw new Error(`runGsd: unknown response status: ${response.status}`);
223
+ }
224
+
225
+ export function validateAndRepairTaskGraph(taskGraph, blueprintText, gateCommands) {
226
+ // Structural check via enrichTaskGraph. Throws on orphan slice/task —
227
+ // that's a "fail loudly" case (no reliable repair path).
228
+ const enriched = enrichTaskGraph(taskGraph, blueprintText);
229
+
230
+ // Per-task description check. The agent must produce a description with
231
+ // all six required sections (per T4 prompt contract). If ANY section
232
+ // marker is missing, repair via buildTaskDescription. Length-only would
233
+ // miss long-but-malformed strings.
234
+ const enrichedById = new Map(enriched.tasks.map((t) => [t.id, t]));
235
+ const repairedTasks = enriched.tasks.map((task) => {
236
+ if (typeof task.description === 'string' && hasAllRequiredSections(task.description)) {
237
+ return task;
238
+ }
239
+ // Repair: synthesize a fresh description.
240
+ const sliceText = extractSliceTextForTask(blueprintText, task);
241
+ const upstream = (task.depends_on || [])
242
+ .map((dep) => enrichedById.get(dep))
243
+ .filter(Boolean);
244
+ const fresh = buildTaskDescription({
245
+ task,
246
+ slice: sliceText,
247
+ upstreamTasks: upstream,
248
+ gateCommands,
249
+ });
250
+ return { ...task, description: fresh };
251
+ });
252
+
253
+ return { tasks: repairedTasks };
254
+ }
255
+
256
+ const REQUIRED_DESCRIPTION_SECTIONS = [
257
+ 'Symbols you must produce',
258
+ 'Symbols you may consume from upstream tasks',
259
+ 'Boundary Map slice',
260
+ 'Upstream tasks',
261
+ 'GATES',
262
+ ];
263
+
264
+ function hasAllRequiredSections(description) {
265
+ for (const marker of REQUIRED_DESCRIPTION_SECTIONS) {
266
+ if (!description.includes(marker)) return false;
267
+ }
268
+ return true;
269
+ }
270
+
271
+ function extractSliceTextForTask(blueprintText, task) {
272
+ // Find any Boundary Map slice whose File Plan files match the task's
273
+ // files_owned. We don't have a sliceId here, so we scan slice blocks for
274
+ // the first one whose File Plan ⊆ task.files_owned. Best-effort — only
275
+ // used in the description-repair path.
276
+ const lines = blueprintText.split(/\r?\n/);
277
+ const owned = new Set(task.files_owned || []);
278
+ const blocks = [];
279
+ let cur = null;
280
+ for (let i = 0; i < lines.length; i++) {
281
+ const m = lines[i].match(/^### (S\d{2,})/);
282
+ if (m) {
283
+ if (cur) blocks.push(cur);
284
+ cur = { id: m[1], start: i, end: lines.length };
285
+ } else if (cur && /^### S\d/.test(lines[i])) {
286
+ cur.end = i;
287
+ blocks.push(cur);
288
+ cur = null;
289
+ } else if (cur && /^## /.test(lines[i])) {
290
+ cur.end = i;
291
+ blocks.push(cur);
292
+ cur = null;
293
+ }
294
+ }
295
+ if (cur) blocks.push(cur);
296
+ for (const b of blocks) {
297
+ const block = lines.slice(b.start, b.end).join('\n');
298
+ const fpMatch = block.match(/^File Plan\s*:\s*(.+)$/m);
299
+ if (!fpMatch) continue;
300
+ const files = [...fpMatch[1].matchAll(/`([^`]+)`/g)].map((mm) => mm[1].trim());
301
+ if (files.length > 0 && files.every((f) => owned.has(f))) {
302
+ return block;
303
+ }
304
+ }
305
+ return '';
306
+ }
307
+
308
+ function parseJsonFromText(text) {
309
+ // Strip code fences if present.
310
+ const trimmed = text.trim();
311
+ const fenced = trimmed.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
312
+ const body = fenced ? fenced[1] : trimmed;
313
+ return JSON.parse(body);
314
+ }
315
+
316
+ function collectChangedFiles(cwd) {
317
+ try {
318
+ const tracked = execSync('git diff --name-only HEAD', {
319
+ cwd, encoding: 'utf-8', timeout: 5000, stdio: ['ignore', 'pipe', 'ignore'],
320
+ }).trim();
321
+ const untracked = execSync('git ls-files --others --exclude-standard', {
322
+ cwd, encoding: 'utf-8', timeout: 5000, stdio: ['ignore', 'pipe', 'ignore'],
323
+ }).trim();
324
+ const all = [
325
+ ...tracked.split('\n').filter(Boolean),
326
+ ...untracked.split('\n').filter(Boolean),
327
+ ];
328
+ return [...new Set(all)];
329
+ } catch {
330
+ return [];
331
+ }
332
+ }
333
+
334
+ function collectBlackboard(cwd, featureCode) {
335
+ const dir = join(cwd, '.compose', 'gsd', featureCode, 'results');
336
+ if (!existsSync(dir)) return {};
337
+ const files = readdirSync(dir).filter((f) => f.endsWith('.json'));
338
+ const out = {};
339
+ const failures = [];
340
+ for (const f of files) {
341
+ const taskId = f.replace(/\.json$/, '');
342
+ let parsed;
343
+ try {
344
+ parsed = JSON.parse(readFileSync(join(dir, f), 'utf-8'));
345
+ } catch (err) {
346
+ failures.push(`${f}: unreadable JSON (${err.message})`);
347
+ continue;
348
+ }
349
+ const v = validateTaskResult(parsed);
350
+ if (v.ok) {
351
+ out[taskId] = parsed;
352
+ } else {
353
+ failures.push(`${f}: ${v.errors.join('; ')}`);
354
+ }
355
+ }
356
+ if (failures.length > 0) {
357
+ // Plan T6 acceptance: blackboard must contain one VALIDATED entry per task.
358
+ // A partial blackboard is worse than no blackboard — fail loudly.
359
+ throw new Error(
360
+ `runGsd: ${failures.length} TaskResult file(s) failed validation; refusing to write partial blackboard:\n - ${failures.join('\n - ')}`,
361
+ );
362
+ }
363
+ return out;
364
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smartmemory/compose",
3
- "version": "0.1.28-beta",
3
+ "version": "0.1.29-beta",
4
4
  "description": "Structured AI dev pipeline — goal-to-product orchestration with gates, iteration loops, and feature lifecycle management.",
5
5
  "author": "SmartMemory",
6
6
  "license": "MIT",
@@ -0,0 +1,141 @@
1
+ # metadata:
2
+ # id: gsd
3
+ # label: "GSD — Per-Task Fresh-Context Dispatch"
4
+ # description: "Long-run autonomous mode. Decompose blueprint+Boundary Map into tasks, dispatch each as a fresh-context worktree-isolated agent, sequential by default. Reads existing blueprint.md."
5
+ # category: development
6
+ # steps: 3
7
+ # estimated_minutes: 60
8
+
9
+ version: "0.3"
10
+
11
+ workflow:
12
+ name: gsd
13
+ description: "Per-task fresh-context dispatch from existing blueprint+Boundary Map (COMP-GSD-2)"
14
+ input:
15
+ featureCode:
16
+ type: string
17
+ required: true
18
+ gateCommands:
19
+ type: array
20
+ required: true # injected by runGsd from loadProjectConfig() with default fallback
21
+
22
+ contracts:
23
+ PhaseResult:
24
+ phase: {type: string}
25
+ artifact: {type: string}
26
+ outcome: {type: string, values: [complete, skipped, failed]}
27
+ summary: {type: string}
28
+
29
+ # TaskGraph is a v0.3 built-in contract — must NOT be redeclared.
30
+ # The richer TaskGraphGsd shape (per-task produces/consumes) is enforced
31
+ # post-decompose by lib/gsd-decompose-enrich.js + contracts/taskgraph-gsd.json
32
+ # rather than via Stratum's contract layer.
33
+
34
+ flows:
35
+ gsd:
36
+ input:
37
+ featureCode: {type: string}
38
+ gateCommands: {type: array}
39
+ output: PhaseResult
40
+ max_rounds: 10
41
+ steps:
42
+ # Phase: Decompose blueprint + Boundary Map into TaskGraphGsd
43
+ - id: decompose_gsd
44
+ type: decompose
45
+ agent: claude
46
+ intent: >
47
+ Read docs/features/{input.featureCode}/blueprint.md including its
48
+ ## Boundary Map section. Decompose into independent tasks.
49
+
50
+ For each task emit: id, files_owned, files_read, depends_on, AND a
51
+ rich `description` string that bundles all the spec context Stratum's
52
+ parallel_dispatch interpolation cannot carry on its own.
53
+
54
+ Each task's `description` MUST include these labeled sections:
55
+ - "Symbols you must produce:" + the matching Boundary Map slice's
56
+ produces entries (file → symbols (kind))
57
+ - "Symbols you may consume from upstream tasks:" + the slice's
58
+ consumes entries
59
+ - "Boundary Map slice (the contract for this task):" + the verbatim
60
+ slice block from blueprint.md
61
+ - "Upstream tasks (spec-level summary; their code lands at end-of-step
62
+ merge):" + one line per upstream task listing its declared produces
63
+ - "GATES — you MUST run each command and they MUST pass before
64
+ declaring done:" + each command from the gateCommands array
65
+ - "Fix and re-run within this invocation. Do NOT declare done while
66
+ gates are red."
67
+
68
+ Stratum's parallel_dispatch only interpolates {task.id},
69
+ {task.description}, {task.files_owned}, {task.files_read},
70
+ {task.depends_on}, {task.index}, {input.<field>}. Pack everything else
71
+ into description.
72
+
73
+ Each Boundary Map slice maps to exactly one task whose files_owned ⊇
74
+ the slice's File Plan files. The decomposition is invalid if a slice
75
+ has no matching task or its File Plan files are split across
76
+ multiple tasks.
77
+
78
+ gateCommands to embed: {input.gateCommands}
79
+ inputs:
80
+ featureCode: "$.input.featureCode"
81
+ gateCommands: "$.input.gateCommands"
82
+ output_contract: TaskGraph
83
+ ensure:
84
+ - "no_file_conflicts(result.tasks)"
85
+ - "len(result.tasks) >= 1"
86
+ retries: 2
87
+
88
+ # Phase: Execute — sequential per-task fresh-context dispatch
89
+ - id: execute
90
+ type: parallel_dispatch
91
+ source: "$.steps.decompose_gsd.output.tasks"
92
+ agent: claude
93
+ max_concurrent: 1
94
+ isolation: worktree
95
+ capture_diff: true
96
+ require: all
97
+ merge: sequential_apply
98
+ retries: 2
99
+ intent_template: >
100
+ Implement task {task.id} using TDD.
101
+
102
+ Files you own (write): {task.files_owned}
103
+ Files you may read: {task.files_read}
104
+ Depends on: {task.depends_on}
105
+
106
+ {task.description}
107
+
108
+ When done, write TaskResult JSON to
109
+ .compose/gsd/{input.featureCode}/results/{task.id}.json
110
+ (schema: contracts/task-result.json) and INCLUDE that file in your diff.
111
+ depends_on: [decompose_gsd]
112
+
113
+ # Phase: Ship — verify, update docs, commit, push
114
+ - id: ship_gsd
115
+ agent: "claude::critical"
116
+ intent: >
117
+ Final verification and ship for feature {input.featureCode}.
118
+
119
+ You MUST:
120
+ 1. Run the project test suite. If tests fail, fix them.
121
+ 2. Run the project build. Must pass.
122
+ 3. Update ROADMAP.md — find COMP-GSD-2's row and mark it COMPLETE.
123
+ If this completes a parent feature, update the parent header.
124
+ 4. Update CHANGELOG.md — add an entry under today's date with
125
+ COMP-GSD-2 and a bullet list of what was built. Read the
126
+ existing format first.
127
+ 5. Update CLAUDE.md ONLY if a new convention/command/architecture
128
+ surface emerged. Skip otherwise.
129
+ 6. Stage changed files explicitly (no worktrees, pycache, build
130
+ artifacts). Commit with message:
131
+ "feat(COMP-GSD-2): per-task fresh-context dispatch"
132
+ (Note: push is deferred to the user — `compose gsd` does not
133
+ auto-push, mirroring `compose build`'s ship behavior.)
134
+ 7. Report commit hash and files_changed in result JSON.
135
+ inputs:
136
+ featureCode: "$.input.featureCode"
137
+ output_contract: PhaseResult
138
+ ensure:
139
+ - "result.outcome == 'complete'"
140
+ retries: 2
141
+ depends_on: [execute]
@@ -1 +0,0 @@
1
- import{aq as o,ar as n}from"./App-Dj7XWWxC.js";const t=(r,a)=>o.lang.round(n.parse(r)[a]);export{t as c};
@@ -1 +0,0 @@
1
- import{s as a,c as s,a as e,C as t}from"./chunk-4TB4RGXK-BNXf8s1x.js";import{_ as i}from"./App-Dj7XWWxC.js";import"./chunk-FMBD7UC4-B-C0Qn-h.js";import"./chunk-YZCP3GAM-BlgKQRCn.js";import"./chunk-55IACEB6-kGd4Gwx6.js";import"./chunk-EDXVE4YY-Ci9gWeIv.js";import"./mobile-BOyZ87uL.js";import"./index-CF7jc-By.js";import"./graph-CfEl_ohV.js";var b={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{b as diagram};
@@ -1 +0,0 @@
1
- import{s as a,c as s,a as e,C as t}from"./chunk-4TB4RGXK-BNXf8s1x.js";import{_ as i}from"./App-Dj7XWWxC.js";import"./chunk-FMBD7UC4-B-C0Qn-h.js";import"./chunk-YZCP3GAM-BlgKQRCn.js";import"./chunk-55IACEB6-kGd4Gwx6.js";import"./chunk-EDXVE4YY-Ci9gWeIv.js";import"./mobile-BOyZ87uL.js";import"./index-CF7jc-By.js";import"./graph-CfEl_ohV.js";var b={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{b as diagram};
@@ -1 +0,0 @@
1
- import{b as r}from"./graph-6nRhlKgL.js";var e=4;function a(o){return r(o,e)}export{a as c};
@@ -1 +0,0 @@
1
- import{s as r,b as e,a,S as s}from"./chunk-OYMX7WX6-CosYsxuv.js";import{_ as i}from"./App-Dj7XWWxC.js";import"./chunk-55IACEB6-kGd4Gwx6.js";import"./chunk-EDXVE4YY-Ci9gWeIv.js";import"./mobile-BOyZ87uL.js";import"./index-CF7jc-By.js";import"./graph-CfEl_ohV.js";var n={parser:a,get db(){return new s(2)},renderer:e,styles:r,init:i(t=>{t.state||(t.state={}),t.state.arrowMarkerAbsolute=t.arrowMarkerAbsolute},"init")};export{n as diagram};