@structured-vibe-coding/core 0.5.2 → 0.5.4
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/README.md +1 -1
- package/defaults/memory/index.md +36 -0
- package/dist/{agents-MIMBQVHE.js → agents-4Z3YZRUK.js} +7 -1
- package/dist/{chunk-IO6AS6JI.js → chunk-I45MAHM6.js} +126 -432
- package/dist/index.d.ts +76 -54
- package/dist/index.js +389 -34
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
AGENT_INTEGRATIONS,
|
|
2
3
|
AGENT_TARGET_OPTIONS,
|
|
3
4
|
ALL_AGENT_TARGETS,
|
|
4
5
|
ArtifactDefSchema,
|
|
@@ -14,29 +15,26 @@ import {
|
|
|
14
15
|
WorkflowSchema,
|
|
15
16
|
activePointerPath,
|
|
16
17
|
activeStages,
|
|
17
|
-
advanceStage,
|
|
18
|
-
approveGate,
|
|
19
18
|
archiveDir,
|
|
20
19
|
buildAgentPrompt,
|
|
21
20
|
buildContext,
|
|
22
|
-
canLeaveStage,
|
|
23
21
|
changeDirName,
|
|
24
22
|
changeMetaPath,
|
|
25
23
|
changePath,
|
|
26
24
|
changesDir,
|
|
27
25
|
clearActiveIf,
|
|
28
|
-
completeChange,
|
|
29
26
|
configPath,
|
|
30
27
|
copyDir,
|
|
31
|
-
createChange,
|
|
32
28
|
defaultConfig,
|
|
33
29
|
domainsDir,
|
|
34
30
|
ensureDir,
|
|
35
31
|
firstActiveStageId,
|
|
36
32
|
formatStatus,
|
|
37
33
|
getActiveChangeId,
|
|
34
|
+
getIntegration,
|
|
38
35
|
getStage,
|
|
39
36
|
inferComplexity,
|
|
37
|
+
installAgentIntegration,
|
|
40
38
|
installAgentIntegrations,
|
|
41
39
|
integrationKeyFor,
|
|
42
40
|
isInitialized,
|
|
@@ -68,32 +66,367 @@ import {
|
|
|
68
66
|
sddRoot,
|
|
69
67
|
setActiveChange,
|
|
70
68
|
shouldAutoSkip,
|
|
71
|
-
skipStage,
|
|
72
69
|
slugify,
|
|
73
|
-
switchWorkflow,
|
|
74
70
|
templatesDir,
|
|
75
71
|
todayIsoDate,
|
|
76
72
|
uniqueChangeDirName,
|
|
77
73
|
workflowsDir,
|
|
78
74
|
writeText,
|
|
79
75
|
writeYaml
|
|
80
|
-
} from "./chunk-
|
|
76
|
+
} from "./chunk-I45MAHM6.js";
|
|
81
77
|
|
|
82
|
-
// src/
|
|
78
|
+
// src/change.ts
|
|
83
79
|
import { join as join2 } from "pathe";
|
|
84
80
|
|
|
81
|
+
// src/stage-gates.ts
|
|
82
|
+
import { join } from "pathe";
|
|
83
|
+
async function canLeaveStage(ctx, config) {
|
|
84
|
+
const stage = getStage(ctx.workflow, ctx.meta, ctx.meta.stage);
|
|
85
|
+
const errors = [];
|
|
86
|
+
const warnings = [];
|
|
87
|
+
if (!stage) {
|
|
88
|
+
return { ok: false, errors: [`Unknown stage: ${ctx.meta.stage}`], warnings };
|
|
89
|
+
}
|
|
90
|
+
for (const artifact of stage.artifacts) {
|
|
91
|
+
if (!artifact.required) continue;
|
|
92
|
+
const target = join(ctx.path, artifact.path);
|
|
93
|
+
if (!await pathExists(target)) {
|
|
94
|
+
errors.push(`Missing required artifact: ${artifact.path}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const declared = stage.gate?.type ?? "soft";
|
|
98
|
+
let effectiveGate = declared;
|
|
99
|
+
if (config.policy.gates === "hard" && declared === "soft") {
|
|
100
|
+
effectiveGate = "hard";
|
|
101
|
+
}
|
|
102
|
+
const gateState = ctx.meta.gates?.[stage.id];
|
|
103
|
+
if (effectiveGate === "hard") {
|
|
104
|
+
if (!gateState || gateState.status !== "approved" && gateState.status !== "waived") {
|
|
105
|
+
errors.push(
|
|
106
|
+
`Hard gate on stage "${stage.id}" is not approved. Run \`sdd gate approve\` or complete the checklist.`
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
} else if (effectiveGate === "soft" && stage.gate?.checklist?.length) {
|
|
110
|
+
if (!gateState || gateState.status === "pending") {
|
|
111
|
+
warnings.push(
|
|
112
|
+
`Soft gate checklist not signed off on "${stage.id}" (proceeding allowed).`
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const requiredCmds = stage.verify?.commands?.filter((c) => c.required) ?? [];
|
|
117
|
+
if (requiredCmds.length) {
|
|
118
|
+
const verifyOk = ctx.meta.verify_results?.[stage.id]?.ok === true;
|
|
119
|
+
const humanOverride = gateState?.status === "waived" || gateState?.status === "approved";
|
|
120
|
+
if (!verifyOk && !humanOverride) {
|
|
121
|
+
errors.push(
|
|
122
|
+
`Required local verify commands have not passed for "${stage.id}". Run \`sdd verify\` (or \`sdd gate approve/waive\` with a note).`
|
|
123
|
+
);
|
|
124
|
+
} else if (!verifyOk && humanOverride) {
|
|
125
|
+
warnings.push(
|
|
126
|
+
`Stage "${stage.id}" gate signed off without a successful required verify run.`
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return { ok: errors.length === 0, errors, warnings };
|
|
131
|
+
}
|
|
132
|
+
async function approveGate(projectRoot, config, changeId, stageId, note, status = "approved") {
|
|
133
|
+
const ctx = await buildContext(projectRoot, config, changeId);
|
|
134
|
+
const id = stageId ?? ctx.meta.stage;
|
|
135
|
+
ctx.meta.gates = {
|
|
136
|
+
...ctx.meta.gates,
|
|
137
|
+
[id]: { status, note, at: nowIso() }
|
|
138
|
+
};
|
|
139
|
+
await saveChangeMeta(projectRoot, config, ctx.meta);
|
|
140
|
+
return buildContext(projectRoot, config, changeId);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// src/change.ts
|
|
144
|
+
async function existingChangeIds(projectRoot, config) {
|
|
145
|
+
const open = await listDirs(changesDir(projectRoot, config));
|
|
146
|
+
const archived = await listDirs(archiveDir(projectRoot, config));
|
|
147
|
+
return /* @__PURE__ */ new Set([...open, ...archived]);
|
|
148
|
+
}
|
|
149
|
+
async function createChange(input) {
|
|
150
|
+
const { projectRoot, config, title, workflowName } = input;
|
|
151
|
+
const workflow = await loadWorkflow(projectRoot, workflowName);
|
|
152
|
+
if (!workflow.stages.length) {
|
|
153
|
+
throw new Error(`Workflow ${workflowName} has no stages`);
|
|
154
|
+
}
|
|
155
|
+
const baseId = changeDirName(title);
|
|
156
|
+
const id = uniqueChangeDirName(baseId, await existingChangeIds(projectRoot, config));
|
|
157
|
+
const dir = changePath(projectRoot, config, id);
|
|
158
|
+
await ensureDir(dir);
|
|
159
|
+
let meta = ChangeMetaSchema.parse({
|
|
160
|
+
id,
|
|
161
|
+
title,
|
|
162
|
+
workflow: workflowName,
|
|
163
|
+
created: nowIso(),
|
|
164
|
+
updated: nowIso(),
|
|
165
|
+
status: "in_progress",
|
|
166
|
+
stage: workflow.stages[0].id,
|
|
167
|
+
domain: input.domain,
|
|
168
|
+
branch: input.branch,
|
|
169
|
+
flags: input.flags ?? {},
|
|
170
|
+
overrides: {
|
|
171
|
+
skip_stages: [],
|
|
172
|
+
gates: {},
|
|
173
|
+
extra_stages: []
|
|
174
|
+
},
|
|
175
|
+
skipped: [],
|
|
176
|
+
gates: {}
|
|
177
|
+
});
|
|
178
|
+
const startId = firstActiveStageId(workflow, meta) ?? workflow.stages[0].id;
|
|
179
|
+
meta = { ...meta, stage: startId };
|
|
180
|
+
const firstStage = getStage(workflow, meta, startId);
|
|
181
|
+
if (!firstStage) {
|
|
182
|
+
throw new Error(`Workflow ${workflowName} has no active stages`);
|
|
183
|
+
}
|
|
184
|
+
await writeYaml(changeMetaPath(projectRoot, config, id), meta);
|
|
185
|
+
await materializeStageArtifacts(projectRoot, config, id, workflow, meta, firstStage);
|
|
186
|
+
await setActiveChange(projectRoot, config, id);
|
|
187
|
+
try {
|
|
188
|
+
const { refreshActiveAgentContext: refreshActiveAgentContext2 } = await import("./agents-4Z3YZRUK.js");
|
|
189
|
+
await refreshActiveAgentContext2(projectRoot);
|
|
190
|
+
} catch {
|
|
191
|
+
}
|
|
192
|
+
return buildContext(projectRoot, config, id);
|
|
193
|
+
}
|
|
194
|
+
async function materializeStageArtifacts(projectRoot, config, changeId, workflow, meta, stage) {
|
|
195
|
+
const created = [];
|
|
196
|
+
const dir = changePath(projectRoot, config, changeId);
|
|
197
|
+
const tdir = templatesDir(projectRoot);
|
|
198
|
+
for (const artifact of stage.artifacts) {
|
|
199
|
+
const target = join2(dir, artifact.path);
|
|
200
|
+
if (await pathExists(target)) continue;
|
|
201
|
+
if (artifact.path.endsWith("/")) {
|
|
202
|
+
await ensureDir(target);
|
|
203
|
+
created.push(artifact.path);
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
let content = defaultArtifactContent(artifact.id, meta, stage, workflow);
|
|
207
|
+
if (artifact.template) {
|
|
208
|
+
const templatePath = join2(tdir, artifact.template.replace(/^templates\//, ""));
|
|
209
|
+
const candidates = [
|
|
210
|
+
join2(tdir, artifact.template),
|
|
211
|
+
templatePath,
|
|
212
|
+
join2(tdir, `${artifact.id}.md`)
|
|
213
|
+
];
|
|
214
|
+
for (const c of candidates) {
|
|
215
|
+
if (await pathExists(c)) {
|
|
216
|
+
content = interpolate(await readText(c), meta, stage, workflow);
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
} else {
|
|
221
|
+
const fallback = join2(tdir, `${artifact.id}.md`);
|
|
222
|
+
if (await pathExists(fallback)) {
|
|
223
|
+
content = interpolate(await readText(fallback), meta, stage, workflow);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
await writeText(target, content);
|
|
227
|
+
created.push(artifact.path);
|
|
228
|
+
}
|
|
229
|
+
return created;
|
|
230
|
+
}
|
|
231
|
+
function interpolate(template, meta, stage, workflow) {
|
|
232
|
+
return template.replaceAll("{{title}}", meta.title).replaceAll("{{id}}", meta.id).replaceAll("{{workflow}}", workflow.name).replaceAll("{{stage}}", stage.id).replaceAll("{{stage_title}}", stage.title ?? stage.id).replaceAll("{{date}}", meta.created.slice(0, 10));
|
|
233
|
+
}
|
|
234
|
+
function defaultArtifactContent(artifactId, meta, stage, workflow) {
|
|
235
|
+
return `# ${artifactId}
|
|
236
|
+
|
|
237
|
+
> Change: **${meta.title}** (\`${meta.id}\`)
|
|
238
|
+
> Workflow: \`${workflow.name}\` \xB7 Stage: \`${stage.id}\`
|
|
239
|
+
|
|
240
|
+
## Summary
|
|
241
|
+
|
|
242
|
+
<!-- Write the intent / content for this artifact -->
|
|
243
|
+
|
|
244
|
+
## Notes
|
|
245
|
+
|
|
246
|
+
-
|
|
247
|
+
`;
|
|
248
|
+
}
|
|
249
|
+
async function advanceStage(projectRoot, config, changeId, opts) {
|
|
250
|
+
const ctx = await buildContext(projectRoot, config, changeId);
|
|
251
|
+
const warnings = [];
|
|
252
|
+
if (ctx.meta.status === "completed") {
|
|
253
|
+
throw new Error("Change is already completed.");
|
|
254
|
+
}
|
|
255
|
+
const check = await canLeaveStage(ctx, config);
|
|
256
|
+
warnings.push(...check.warnings);
|
|
257
|
+
if (!check.ok && !opts?.force) {
|
|
258
|
+
throw new Error(check.errors.join("\n"));
|
|
259
|
+
}
|
|
260
|
+
const from = ctx.meta.stage;
|
|
261
|
+
const next = nextStageId(ctx.workflow, ctx.meta);
|
|
262
|
+
if (!next) {
|
|
263
|
+
return {
|
|
264
|
+
ctx,
|
|
265
|
+
from,
|
|
266
|
+
to: null,
|
|
267
|
+
completed: false,
|
|
268
|
+
artifactsCreated: [],
|
|
269
|
+
warnings: [...warnings, "No further stages. Run `sdd complete` when ready."]
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
ctx.meta.stage = next;
|
|
273
|
+
await saveChangeMeta(projectRoot, config, ctx.meta);
|
|
274
|
+
const stage = getStage(ctx.workflow, ctx.meta, next);
|
|
275
|
+
const artifactsCreated = await materializeStageArtifacts(
|
|
276
|
+
projectRoot,
|
|
277
|
+
config,
|
|
278
|
+
changeId,
|
|
279
|
+
ctx.workflow,
|
|
280
|
+
ctx.meta,
|
|
281
|
+
stage
|
|
282
|
+
);
|
|
283
|
+
try {
|
|
284
|
+
const { refreshActiveAgentContext: refreshActiveAgentContext2 } = await import("./agents-4Z3YZRUK.js");
|
|
285
|
+
await refreshActiveAgentContext2(projectRoot);
|
|
286
|
+
} catch {
|
|
287
|
+
}
|
|
288
|
+
const updated = await buildContext(projectRoot, config, changeId);
|
|
289
|
+
return {
|
|
290
|
+
ctx: updated,
|
|
291
|
+
from,
|
|
292
|
+
to: next,
|
|
293
|
+
completed: false,
|
|
294
|
+
artifactsCreated,
|
|
295
|
+
warnings
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
async function skipStage(projectRoot, config, changeId, stageId, reason) {
|
|
299
|
+
if (!config.per_change.allow_skip) {
|
|
300
|
+
throw new Error("Skipping stages is disabled in .sdd/config.yaml (per_change.allow_skip).");
|
|
301
|
+
}
|
|
302
|
+
if (config.per_change.require_reason_on_skip && !reason.trim()) {
|
|
303
|
+
throw new Error("A reason is required to skip a stage.");
|
|
304
|
+
}
|
|
305
|
+
const ctx = await buildContext(projectRoot, config, changeId);
|
|
306
|
+
const stage = getStage(ctx.workflow, ctx.meta, stageId);
|
|
307
|
+
if (!stage) {
|
|
308
|
+
throw new Error(`Stage "${stageId}" not found in workflow ${ctx.meta.workflow}`);
|
|
309
|
+
}
|
|
310
|
+
if (!stage.skippable) {
|
|
311
|
+
throw new Error(`Stage "${stageId}" is not skippable.`);
|
|
312
|
+
}
|
|
313
|
+
const skip_stages = new Set(ctx.meta.overrides.skip_stages ?? []);
|
|
314
|
+
skip_stages.add(stageId);
|
|
315
|
+
ctx.meta.overrides.skip_stages = [...skip_stages];
|
|
316
|
+
ctx.meta.skipped = [
|
|
317
|
+
...ctx.meta.skipped ?? [],
|
|
318
|
+
{ stage: stageId, reason, at: nowIso() }
|
|
319
|
+
];
|
|
320
|
+
if (ctx.meta.stage === stageId) {
|
|
321
|
+
const next = nextStageId(ctx.workflow, ctx.meta);
|
|
322
|
+
if (next) {
|
|
323
|
+
ctx.meta.stage = next;
|
|
324
|
+
const nextStage = getStage(ctx.workflow, ctx.meta, next);
|
|
325
|
+
if (nextStage) {
|
|
326
|
+
await materializeStageArtifacts(
|
|
327
|
+
projectRoot,
|
|
328
|
+
config,
|
|
329
|
+
changeId,
|
|
330
|
+
ctx.workflow,
|
|
331
|
+
ctx.meta,
|
|
332
|
+
nextStage
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
await saveChangeMeta(projectRoot, config, ctx.meta);
|
|
338
|
+
return buildContext(projectRoot, config, changeId);
|
|
339
|
+
}
|
|
340
|
+
async function switchWorkflow(projectRoot, config, changeId, workflowName, reason) {
|
|
341
|
+
const ctx = await buildContext(projectRoot, config, changeId);
|
|
342
|
+
const workflow = await loadWorkflow(projectRoot, workflowName);
|
|
343
|
+
if (config.allowed_workflows?.length && !config.allowed_workflows.includes(workflowName)) {
|
|
344
|
+
throw new Error(
|
|
345
|
+
`Workflow "${workflowName}" is not in allowed_workflows: ${config.allowed_workflows.join(", ")}`
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
ctx.meta.workflow = workflowName;
|
|
349
|
+
const hasStage = workflow.stages.some((s) => s.id === ctx.meta.stage);
|
|
350
|
+
if (!hasStage) {
|
|
351
|
+
ctx.meta.stage = workflow.stages[0].id;
|
|
352
|
+
}
|
|
353
|
+
if (reason) {
|
|
354
|
+
ctx.meta.flags = {
|
|
355
|
+
...ctx.meta.flags,
|
|
356
|
+
workflow_switch_reason: reason
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
await saveChangeMeta(projectRoot, config, ctx.meta);
|
|
360
|
+
const stage = getStage(workflow, ctx.meta, ctx.meta.stage);
|
|
361
|
+
if (stage) {
|
|
362
|
+
await materializeStageArtifacts(projectRoot, config, changeId, workflow, ctx.meta, stage);
|
|
363
|
+
}
|
|
364
|
+
return buildContext(projectRoot, config, changeId);
|
|
365
|
+
}
|
|
366
|
+
async function completeChange(projectRoot, config, changeId) {
|
|
367
|
+
const ctx = await buildContext(projectRoot, config, changeId);
|
|
368
|
+
const next = nextStageId(ctx.workflow, ctx.meta);
|
|
369
|
+
if (next) {
|
|
370
|
+
const active = activeStages(ctx.workflow, ctx.meta);
|
|
371
|
+
const last = active[active.length - 1];
|
|
372
|
+
if (last && ctx.meta.stage !== last.id) {
|
|
373
|
+
throw new Error(
|
|
374
|
+
`Cannot complete: still on stage "${ctx.meta.stage}". Finish remaining stages or skip them. Next: ${next}`
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
const check = await canLeaveStage(ctx, config);
|
|
379
|
+
if (!check.ok) {
|
|
380
|
+
throw new Error(check.errors.join("\n"));
|
|
381
|
+
}
|
|
382
|
+
ctx.meta.status = "completed";
|
|
383
|
+
ctx.meta.completed_at = nowIso();
|
|
384
|
+
await saveChangeMeta(projectRoot, config, ctx.meta);
|
|
385
|
+
await clearActiveIf(projectRoot, config, changeId);
|
|
386
|
+
let archivedTo = null;
|
|
387
|
+
const shouldArchive = config.persistence.archive_on_complete && ctx.workflow.on_complete?.archive !== false;
|
|
388
|
+
if (shouldArchive) {
|
|
389
|
+
const dest = join2(archiveDir(projectRoot, config), changeId);
|
|
390
|
+
if (await pathExists(dest)) {
|
|
391
|
+
throw new Error(`Archive already exists for change: ${changeId}`);
|
|
392
|
+
}
|
|
393
|
+
await ensureDir(archiveDir(projectRoot, config));
|
|
394
|
+
await copyDir(ctx.path, dest);
|
|
395
|
+
await removePath(ctx.path);
|
|
396
|
+
archivedTo = dest;
|
|
397
|
+
}
|
|
398
|
+
if (archivedTo) {
|
|
399
|
+
const meta = ChangeMetaSchema.parse(await readYaml(join2(archivedTo, "meta.yaml")));
|
|
400
|
+
return {
|
|
401
|
+
archivedTo,
|
|
402
|
+
ctx: {
|
|
403
|
+
id: changeId,
|
|
404
|
+
path: archivedTo,
|
|
405
|
+
meta,
|
|
406
|
+
workflow: ctx.workflow,
|
|
407
|
+
stages: ctx.stages,
|
|
408
|
+
active: ctx.active
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
return { archivedTo, ctx: await buildContext(projectRoot, config, changeId) };
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// src/init.ts
|
|
416
|
+
import { join as join4 } from "pathe";
|
|
417
|
+
|
|
85
418
|
// src/defaults.ts
|
|
86
419
|
import { existsSync } from "fs";
|
|
87
420
|
import { fileURLToPath } from "url";
|
|
88
|
-
import { dirname, join } from "pathe";
|
|
421
|
+
import { dirname, join as join3 } from "pathe";
|
|
89
422
|
function defaultsRoot() {
|
|
90
423
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
91
424
|
const candidates = [
|
|
92
|
-
|
|
425
|
+
join3(here, "..", "defaults"),
|
|
93
426
|
// npm package: dist/../defaults
|
|
94
|
-
|
|
427
|
+
join3(here, "defaults"),
|
|
95
428
|
// extension bundle: dist/defaults
|
|
96
|
-
|
|
429
|
+
join3(here, "..", "..", "defaults")
|
|
97
430
|
// src/ during some test runners
|
|
98
431
|
];
|
|
99
432
|
for (const c of candidates) {
|
|
@@ -102,13 +435,13 @@ function defaultsRoot() {
|
|
|
102
435
|
return candidates[0];
|
|
103
436
|
}
|
|
104
437
|
function defaultWorkflowsDir() {
|
|
105
|
-
return
|
|
438
|
+
return join3(defaultsRoot(), "workflows");
|
|
106
439
|
}
|
|
107
440
|
function defaultTemplatesDir() {
|
|
108
|
-
return
|
|
441
|
+
return join3(defaultsRoot(), "templates");
|
|
109
442
|
}
|
|
110
443
|
function defaultMemoryDir() {
|
|
111
|
-
return
|
|
444
|
+
return join3(defaultsRoot(), "memory");
|
|
112
445
|
}
|
|
113
446
|
|
|
114
447
|
// src/init.ts
|
|
@@ -116,15 +449,15 @@ async function initProject(opts) {
|
|
|
116
449
|
const { projectRoot, force = false } = opts;
|
|
117
450
|
const created = [];
|
|
118
451
|
const root = sddRoot(projectRoot);
|
|
119
|
-
if (await pathExists(
|
|
452
|
+
if (await pathExists(join4(root, "config.yaml")) && !force) {
|
|
120
453
|
throw new Error(
|
|
121
|
-
`Already initialized (${
|
|
454
|
+
`Already initialized (${join4(root, "config.yaml")}). Use --force to re-copy defaults.`
|
|
122
455
|
);
|
|
123
456
|
}
|
|
124
457
|
const config = defaultConfig();
|
|
125
458
|
await ensureDir(root);
|
|
126
459
|
await saveConfig(projectRoot, config);
|
|
127
|
-
created.push(
|
|
460
|
+
created.push(join4(root, "config.yaml"));
|
|
128
461
|
const wfDest = workflowsDir(projectRoot);
|
|
129
462
|
await ensureDir(wfDest);
|
|
130
463
|
const wfSrc = defaultWorkflowsDir();
|
|
@@ -146,15 +479,30 @@ async function initProject(opts) {
|
|
|
146
479
|
await copyDir(mSrc, mDest);
|
|
147
480
|
} else {
|
|
148
481
|
await writeText(
|
|
149
|
-
|
|
482
|
+
join4(mDest, "index.md"),
|
|
483
|
+
`# Documentation map
|
|
484
|
+
|
|
485
|
+
Parent page for **stable** project docs. Start here, then open only what you need.
|
|
486
|
+
|
|
487
|
+
| Topic | File |
|
|
488
|
+
|-------|------|
|
|
489
|
+
| Product | [product.md](product.md) |
|
|
490
|
+
| Architecture | [architecture.md](architecture.md) |
|
|
491
|
+
| Conventions | [conventions.md](conventions.md) |
|
|
492
|
+
|
|
493
|
+
Active work: \`.sdd/active-context.md\` and \`changes/<id>/\`.
|
|
494
|
+
`
|
|
495
|
+
);
|
|
496
|
+
await writeText(
|
|
497
|
+
join4(mDest, "product.md"),
|
|
150
498
|
"# Product\n\n<!-- What are you building? -->\n"
|
|
151
499
|
);
|
|
152
500
|
await writeText(
|
|
153
|
-
|
|
501
|
+
join4(mDest, "architecture.md"),
|
|
154
502
|
"# Architecture\n\n<!-- High-level system shape -->\n"
|
|
155
503
|
);
|
|
156
504
|
await writeText(
|
|
157
|
-
|
|
505
|
+
join4(mDest, "conventions.md"),
|
|
158
506
|
"# Conventions\n\n<!-- Coding and process conventions -->\n"
|
|
159
507
|
);
|
|
160
508
|
}
|
|
@@ -165,10 +513,10 @@ async function initProject(opts) {
|
|
|
165
513
|
created.push(changesDir(projectRoot, config));
|
|
166
514
|
created.push(archiveDir(projectRoot, config));
|
|
167
515
|
created.push(domainsDir(projectRoot, config));
|
|
168
|
-
await writeText(
|
|
169
|
-
await writeText(
|
|
170
|
-
await writeText(
|
|
171
|
-
const sddReadme =
|
|
516
|
+
await writeText(join4(changesDir(projectRoot, config), ".gitkeep"), "");
|
|
517
|
+
await writeText(join4(archiveDir(projectRoot, config), ".gitkeep"), "");
|
|
518
|
+
await writeText(join4(domainsDir(projectRoot, config), ".gitkeep"), "");
|
|
519
|
+
const sddReadme = join4(root, "README.md");
|
|
172
520
|
if (!await pathExists(sddReadme) || force) {
|
|
173
521
|
await writeText(
|
|
174
522
|
sddReadme,
|
|
@@ -179,9 +527,13 @@ This directory configures local Spec-Driven Development for this repo.
|
|
|
179
527
|
- \`config.yaml\` \u2014 project settings and per-change policy
|
|
180
528
|
- \`workflows/\` \u2014 YAML workflow packs (customize freely)
|
|
181
529
|
- \`templates/\` \u2014 markdown templates for stage artifacts
|
|
530
|
+
- \`protocol.md\` / \`active-context.md\` \u2014 agent playbook + live task (after agent install)
|
|
182
531
|
|
|
183
532
|
Work lives in \`../changes/\` and completed work in \`../archive/\`.
|
|
184
533
|
|
|
534
|
+
**Documentation map (stable):** \`../memory/index.md\`
|
|
535
|
+
**Change-scoped docs:** \`../changes/<id>/\`
|
|
536
|
+
|
|
185
537
|
## Quick commands
|
|
186
538
|
|
|
187
539
|
\`\`\`bash
|
|
@@ -196,10 +548,10 @@ sdd complete
|
|
|
196
548
|
created.push(sddReadme);
|
|
197
549
|
}
|
|
198
550
|
let agentsResult;
|
|
199
|
-
if (
|
|
200
|
-
const ag = await
|
|
551
|
+
if (opts.agents) {
|
|
552
|
+
const ag = await installAgentIntegration({
|
|
201
553
|
projectRoot,
|
|
202
|
-
|
|
554
|
+
target: opts.agents,
|
|
203
555
|
force
|
|
204
556
|
});
|
|
205
557
|
created.push(...ag.created);
|
|
@@ -210,7 +562,7 @@ sdd complete
|
|
|
210
562
|
|
|
211
563
|
// src/verify.ts
|
|
212
564
|
import { spawn } from "child_process";
|
|
213
|
-
import { join as
|
|
565
|
+
import { join as join5 } from "pathe";
|
|
214
566
|
function runCommand(command, cwd) {
|
|
215
567
|
return new Promise((resolve) => {
|
|
216
568
|
const child = spawn(command, {
|
|
@@ -239,7 +591,7 @@ async function runLocalVerify(projectRoot, config, changeId, opts) {
|
|
|
239
591
|
if (!stage) {
|
|
240
592
|
throw new Error(`Unknown stage: ${ctx.meta.stage}`);
|
|
241
593
|
}
|
|
242
|
-
const evidenceDir = stage.verify?.evidence_dir != null ?
|
|
594
|
+
const evidenceDir = stage.verify?.evidence_dir != null ? join5(ctx.path, stage.verify.evidence_dir) : join5(ctx.path, "evidence", "local");
|
|
243
595
|
await ensureDir(evidenceDir);
|
|
244
596
|
const results = [];
|
|
245
597
|
const runCommands = opts?.runCommands !== false;
|
|
@@ -253,7 +605,7 @@ async function runLocalVerify(projectRoot, config, changeId, opts) {
|
|
|
253
605
|
exitCode,
|
|
254
606
|
output: output.slice(0, 5e4)
|
|
255
607
|
});
|
|
256
|
-
const logPath =
|
|
608
|
+
const logPath = join5(evidenceDir, `${cmd.name.replace(/\s+/g, "-")}.log`);
|
|
257
609
|
await writeText(
|
|
258
610
|
logPath,
|
|
259
611
|
`# ${cmd.name}
|
|
@@ -279,7 +631,7 @@ ${output}`
|
|
|
279
631
|
ok = results.every((r) => r.exitCode === 0);
|
|
280
632
|
}
|
|
281
633
|
const checklist = stage.gate?.checklist ?? [];
|
|
282
|
-
const summaryPath =
|
|
634
|
+
const summaryPath = join5(ctx.path, "local-test-results.md");
|
|
283
635
|
const lines = [
|
|
284
636
|
`# Local verify results`,
|
|
285
637
|
``,
|
|
@@ -315,7 +667,7 @@ ${output}`
|
|
|
315
667
|
if (await pathExists(summaryPath)) {
|
|
316
668
|
const stamp = nowIso();
|
|
317
669
|
await writeText(
|
|
318
|
-
|
|
670
|
+
join5(evidenceDir, `run-${stamp.replace(/[:.]/g, "-")}.md`),
|
|
319
671
|
lines.join("\n")
|
|
320
672
|
);
|
|
321
673
|
} else {
|
|
@@ -339,6 +691,7 @@ ${output}`
|
|
|
339
691
|
};
|
|
340
692
|
}
|
|
341
693
|
export {
|
|
694
|
+
AGENT_INTEGRATIONS,
|
|
342
695
|
AGENT_TARGET_OPTIONS,
|
|
343
696
|
ALL_AGENT_TARGETS,
|
|
344
697
|
ArtifactDefSchema,
|
|
@@ -379,9 +732,11 @@ export {
|
|
|
379
732
|
firstActiveStageId,
|
|
380
733
|
formatStatus,
|
|
381
734
|
getActiveChangeId,
|
|
735
|
+
getIntegration,
|
|
382
736
|
getStage,
|
|
383
737
|
inferComplexity,
|
|
384
738
|
initProject,
|
|
739
|
+
installAgentIntegration,
|
|
385
740
|
installAgentIntegrations,
|
|
386
741
|
integrationKeyFor,
|
|
387
742
|
isInitialized,
|