@structured-vibe-coding/core 0.5.1 → 0.5.3
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/dist/{agents-MIMBQVHE.js → agents-5YI2CWAB.js} +7 -1
- package/dist/{chunk-IO6AS6JI.js → chunk-CHDYZIOH.js} +122 -430
- package/dist/index.d.ts +75 -53
- package/dist/index.js +370 -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-CHDYZIOH.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-5YI2CWAB.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-5YI2CWAB.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,15 @@ async function initProject(opts) {
|
|
|
146
479
|
await copyDir(mSrc, mDest);
|
|
147
480
|
} else {
|
|
148
481
|
await writeText(
|
|
149
|
-
|
|
482
|
+
join4(mDest, "product.md"),
|
|
150
483
|
"# Product\n\n<!-- What are you building? -->\n"
|
|
151
484
|
);
|
|
152
485
|
await writeText(
|
|
153
|
-
|
|
486
|
+
join4(mDest, "architecture.md"),
|
|
154
487
|
"# Architecture\n\n<!-- High-level system shape -->\n"
|
|
155
488
|
);
|
|
156
489
|
await writeText(
|
|
157
|
-
|
|
490
|
+
join4(mDest, "conventions.md"),
|
|
158
491
|
"# Conventions\n\n<!-- Coding and process conventions -->\n"
|
|
159
492
|
);
|
|
160
493
|
}
|
|
@@ -165,10 +498,10 @@ async function initProject(opts) {
|
|
|
165
498
|
created.push(changesDir(projectRoot, config));
|
|
166
499
|
created.push(archiveDir(projectRoot, config));
|
|
167
500
|
created.push(domainsDir(projectRoot, config));
|
|
168
|
-
await writeText(
|
|
169
|
-
await writeText(
|
|
170
|
-
await writeText(
|
|
171
|
-
const sddReadme =
|
|
501
|
+
await writeText(join4(changesDir(projectRoot, config), ".gitkeep"), "");
|
|
502
|
+
await writeText(join4(archiveDir(projectRoot, config), ".gitkeep"), "");
|
|
503
|
+
await writeText(join4(domainsDir(projectRoot, config), ".gitkeep"), "");
|
|
504
|
+
const sddReadme = join4(root, "README.md");
|
|
172
505
|
if (!await pathExists(sddReadme) || force) {
|
|
173
506
|
await writeText(
|
|
174
507
|
sddReadme,
|
|
@@ -196,10 +529,10 @@ sdd complete
|
|
|
196
529
|
created.push(sddReadme);
|
|
197
530
|
}
|
|
198
531
|
let agentsResult;
|
|
199
|
-
if (
|
|
200
|
-
const ag = await
|
|
532
|
+
if (opts.agents) {
|
|
533
|
+
const ag = await installAgentIntegration({
|
|
201
534
|
projectRoot,
|
|
202
|
-
|
|
535
|
+
target: opts.agents,
|
|
203
536
|
force
|
|
204
537
|
});
|
|
205
538
|
created.push(...ag.created);
|
|
@@ -210,7 +543,7 @@ sdd complete
|
|
|
210
543
|
|
|
211
544
|
// src/verify.ts
|
|
212
545
|
import { spawn } from "child_process";
|
|
213
|
-
import { join as
|
|
546
|
+
import { join as join5 } from "pathe";
|
|
214
547
|
function runCommand(command, cwd) {
|
|
215
548
|
return new Promise((resolve) => {
|
|
216
549
|
const child = spawn(command, {
|
|
@@ -239,7 +572,7 @@ async function runLocalVerify(projectRoot, config, changeId, opts) {
|
|
|
239
572
|
if (!stage) {
|
|
240
573
|
throw new Error(`Unknown stage: ${ctx.meta.stage}`);
|
|
241
574
|
}
|
|
242
|
-
const evidenceDir = stage.verify?.evidence_dir != null ?
|
|
575
|
+
const evidenceDir = stage.verify?.evidence_dir != null ? join5(ctx.path, stage.verify.evidence_dir) : join5(ctx.path, "evidence", "local");
|
|
243
576
|
await ensureDir(evidenceDir);
|
|
244
577
|
const results = [];
|
|
245
578
|
const runCommands = opts?.runCommands !== false;
|
|
@@ -253,7 +586,7 @@ async function runLocalVerify(projectRoot, config, changeId, opts) {
|
|
|
253
586
|
exitCode,
|
|
254
587
|
output: output.slice(0, 5e4)
|
|
255
588
|
});
|
|
256
|
-
const logPath =
|
|
589
|
+
const logPath = join5(evidenceDir, `${cmd.name.replace(/\s+/g, "-")}.log`);
|
|
257
590
|
await writeText(
|
|
258
591
|
logPath,
|
|
259
592
|
`# ${cmd.name}
|
|
@@ -279,7 +612,7 @@ ${output}`
|
|
|
279
612
|
ok = results.every((r) => r.exitCode === 0);
|
|
280
613
|
}
|
|
281
614
|
const checklist = stage.gate?.checklist ?? [];
|
|
282
|
-
const summaryPath =
|
|
615
|
+
const summaryPath = join5(ctx.path, "local-test-results.md");
|
|
283
616
|
const lines = [
|
|
284
617
|
`# Local verify results`,
|
|
285
618
|
``,
|
|
@@ -315,7 +648,7 @@ ${output}`
|
|
|
315
648
|
if (await pathExists(summaryPath)) {
|
|
316
649
|
const stamp = nowIso();
|
|
317
650
|
await writeText(
|
|
318
|
-
|
|
651
|
+
join5(evidenceDir, `run-${stamp.replace(/[:.]/g, "-")}.md`),
|
|
319
652
|
lines.join("\n")
|
|
320
653
|
);
|
|
321
654
|
} else {
|
|
@@ -339,6 +672,7 @@ ${output}`
|
|
|
339
672
|
};
|
|
340
673
|
}
|
|
341
674
|
export {
|
|
675
|
+
AGENT_INTEGRATIONS,
|
|
342
676
|
AGENT_TARGET_OPTIONS,
|
|
343
677
|
ALL_AGENT_TARGETS,
|
|
344
678
|
ArtifactDefSchema,
|
|
@@ -379,9 +713,11 @@ export {
|
|
|
379
713
|
firstActiveStageId,
|
|
380
714
|
formatStatus,
|
|
381
715
|
getActiveChangeId,
|
|
716
|
+
getIntegration,
|
|
382
717
|
getStage,
|
|
383
718
|
inferComplexity,
|
|
384
719
|
initProject,
|
|
720
|
+
installAgentIntegration,
|
|
385
721
|
installAgentIntegrations,
|
|
386
722
|
integrationKeyFor,
|
|
387
723
|
isInitialized,
|