@structured-vibe-coding/core 0.12.1 → 0.13.0
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 -0
- package/defaults/memory/index.md +3 -0
- package/defaults/templates/features.md +42 -0
- package/defaults/templates/greenfield-architecture.md +39 -0
- package/defaults/templates/requirements.md +29 -0
- package/defaults/templates/vision.md +34 -0
- package/defaults/workflows/greenfield.yaml +92 -0
- package/dist/{agents-R4RMNXOS.js → agents-WL3PNTJL.js} +5 -2
- package/dist/change-context-W4HDO2IK.js +23 -0
- package/dist/{chunk-KZHNYGD6.js → chunk-67AUAGNV.js} +1 -38
- package/dist/chunk-A752UVPF.js +317 -0
- package/dist/chunk-C725DWSB.js +122 -0
- package/dist/chunk-OJFMJHNL.js +42 -0
- package/dist/{chunk-24S2YWWL.js → chunk-S7SHVKG4.js} +22 -435
- package/dist/chunk-SM734TFW.js +784 -0
- package/dist/{config-4QZMRSL3.js → config-ZYEEIRXJ.js} +2 -1
- package/dist/greenfield-NTLW6XBL.js +23 -0
- package/dist/index.d.ts +66 -1
- package/dist/index.js +103 -537
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5,37 +5,67 @@ import {
|
|
|
5
5
|
DEFAULT_INIT_INTEGRATION,
|
|
6
6
|
PROTOCOL_MD,
|
|
7
7
|
SDD_AGENT_ROLES,
|
|
8
|
-
activeStages,
|
|
9
8
|
agentHostPaths,
|
|
10
9
|
agentKickoffMessage,
|
|
10
|
+
getIntegration,
|
|
11
|
+
installAgentIntegration,
|
|
12
|
+
installAgentIntegrations,
|
|
13
|
+
integrationKeyFor,
|
|
14
|
+
loadInstalledAgent,
|
|
15
|
+
optionForTarget,
|
|
16
|
+
parseAgentTargets,
|
|
17
|
+
parseIntegration,
|
|
18
|
+
refreshActiveAgentContext,
|
|
19
|
+
removeOtherAgentHosts,
|
|
20
|
+
renderThinAgent,
|
|
21
|
+
writeAgentHandoff
|
|
22
|
+
} from "./chunk-S7SHVKG4.js";
|
|
23
|
+
import {
|
|
24
|
+
defaultConfig,
|
|
25
|
+
isInitialized,
|
|
26
|
+
loadConfig,
|
|
27
|
+
saveConfig
|
|
28
|
+
} from "./chunk-OJFMJHNL.js";
|
|
29
|
+
import {
|
|
30
|
+
advanceStage,
|
|
31
|
+
approveGate,
|
|
32
|
+
canLeaveStage,
|
|
33
|
+
completeChange,
|
|
34
|
+
createChange,
|
|
35
|
+
fallbackIncompleteStageId,
|
|
36
|
+
findFeaturesBacklogPath,
|
|
37
|
+
incompletePriorStageErrors,
|
|
38
|
+
isSubstantiveArtifactContent,
|
|
39
|
+
listBacklogFeatures,
|
|
40
|
+
parseFeaturesBacklog,
|
|
41
|
+
promoteGreenfieldToMemory,
|
|
42
|
+
skipStage,
|
|
43
|
+
startFeatureFromBacklog,
|
|
44
|
+
startGreenfield,
|
|
45
|
+
switchWorkflow,
|
|
46
|
+
updateFeatureStatus
|
|
47
|
+
} from "./chunk-SM734TFW.js";
|
|
48
|
+
import {
|
|
11
49
|
buildAgentPrompt,
|
|
50
|
+
formatStatus
|
|
51
|
+
} from "./chunk-C725DWSB.js";
|
|
52
|
+
import {
|
|
53
|
+
activeStages,
|
|
12
54
|
buildContext,
|
|
13
55
|
changeDirName,
|
|
14
56
|
clearActiveIf,
|
|
15
57
|
firstActiveStageId,
|
|
16
|
-
formatStatus,
|
|
17
58
|
getActiveChangeId,
|
|
18
|
-
getIntegration,
|
|
19
59
|
getStage,
|
|
20
60
|
inferComplexity,
|
|
21
|
-
installAgentIntegration,
|
|
22
|
-
installAgentIntegrations,
|
|
23
|
-
integrationKeyFor,
|
|
24
61
|
isStageSkipped,
|
|
25
62
|
listChanges,
|
|
26
63
|
listWorkflowNames,
|
|
27
64
|
loadChangeMeta,
|
|
28
|
-
loadInstalledAgent,
|
|
29
65
|
loadWorkflow,
|
|
30
66
|
nextStageId,
|
|
31
67
|
nowIso,
|
|
32
|
-
optionForTarget,
|
|
33
|
-
parseAgentTargets,
|
|
34
|
-
parseIntegration,
|
|
35
68
|
recommendWorkflow,
|
|
36
|
-
refreshActiveAgentContext,
|
|
37
|
-
removeOtherAgentHosts,
|
|
38
|
-
renderThinAgent,
|
|
39
69
|
resolveChangeId,
|
|
40
70
|
resolveStages,
|
|
41
71
|
saveChangeMeta,
|
|
@@ -43,9 +73,8 @@ import {
|
|
|
43
73
|
shouldAutoSkip,
|
|
44
74
|
slugify,
|
|
45
75
|
todayIsoDate,
|
|
46
|
-
uniqueChangeDirName
|
|
47
|
-
|
|
48
|
-
} from "./chunk-24S2YWWL.js";
|
|
76
|
+
uniqueChangeDirName
|
|
77
|
+
} from "./chunk-A752UVPF.js";
|
|
49
78
|
import {
|
|
50
79
|
ArtifactDefSchema,
|
|
51
80
|
ChangeMetaSchema,
|
|
@@ -53,12 +82,8 @@ import {
|
|
|
53
82
|
GateSchema,
|
|
54
83
|
StageSchema,
|
|
55
84
|
VerifyCommandSchema,
|
|
56
|
-
WorkflowSchema
|
|
57
|
-
|
|
58
|
-
isInitialized,
|
|
59
|
-
loadConfig,
|
|
60
|
-
saveConfig
|
|
61
|
-
} from "./chunk-KZHNYGD6.js";
|
|
85
|
+
WorkflowSchema
|
|
86
|
+
} from "./chunk-67AUAGNV.js";
|
|
62
87
|
import {
|
|
63
88
|
SDD_DIR,
|
|
64
89
|
activePointerPath,
|
|
@@ -88,487 +113,21 @@ import {
|
|
|
88
113
|
writeYaml
|
|
89
114
|
} from "./chunk-EPFURATC.js";
|
|
90
115
|
|
|
91
|
-
// src/change.ts
|
|
92
|
-
import { join as join2 } from "pathe";
|
|
93
|
-
|
|
94
|
-
// src/stage-gates.ts
|
|
95
|
-
import { join } from "pathe";
|
|
96
|
-
|
|
97
|
-
// src/artifacts.ts
|
|
98
|
-
function isSubstantiveArtifactContent(content) {
|
|
99
|
-
const stripped = content.replace(/<!--[\s\S]*?-->/g, "").replace(/^#{1,6}\s+.*$/gm, "").replace(/^>\s.*$/gm, "").replace(/^\s*[-*+]\s*$/gm, "").replace(/^\s*[-*+]\s*\.\.\.\s*$/gm, "").replace(/^\s*[-*+]\s*TBD\s*$/gim, "").replace(/^\s*[-*+]\s*TODO\s*$/gim, "").replace(/^\s*\|.*\|\s*$/gm, "").replace(/`+/g, "").trim();
|
|
100
|
-
if (stripped.length < 48) return false;
|
|
101
|
-
const meaningful = stripped.replace(/[-–—_.\s]/g, "").replace(/\b(TBD|TODO|N\/A|none|n\/a)\b/gi, "");
|
|
102
|
-
return meaningful.length >= 32;
|
|
103
|
-
}
|
|
104
|
-
function incompleteArtifactReason(path, content) {
|
|
105
|
-
if (content == null) {
|
|
106
|
-
return `Missing required artifact: ${path}`;
|
|
107
|
-
}
|
|
108
|
-
if (!isSubstantiveArtifactContent(content)) {
|
|
109
|
-
return [
|
|
110
|
-
`Required artifact still incomplete (looks like empty template/stub): ${path}`,
|
|
111
|
-
``,
|
|
112
|
-
`Open that file and replace the template with a few real sentences, for example:`,
|
|
113
|
-
` - What is broken or missing?`,
|
|
114
|
-
` - What will you change?`,
|
|
115
|
-
` - How will you know it worked?`,
|
|
116
|
-
``,
|
|
117
|
-
`Then run: sdd next`,
|
|
118
|
-
`Optional stages only: sdd skip <stage> -r "not needed"`
|
|
119
|
-
].join("\n");
|
|
120
|
-
}
|
|
121
|
-
return null;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// src/stage-gates.ts
|
|
125
|
-
async function checkRequiredArtifacts(changePath2, stageId, artifacts) {
|
|
126
|
-
const errors = [];
|
|
127
|
-
for (const artifact of artifacts) {
|
|
128
|
-
if (!artifact.required) continue;
|
|
129
|
-
if (artifact.path.endsWith("/")) {
|
|
130
|
-
const target2 = join(changePath2, artifact.path);
|
|
131
|
-
if (!await pathExists(target2)) {
|
|
132
|
-
errors.push(`Missing required directory: ${artifact.path} (stage ${stageId})`);
|
|
133
|
-
}
|
|
134
|
-
continue;
|
|
135
|
-
}
|
|
136
|
-
const target = join(changePath2, artifact.path);
|
|
137
|
-
if (!await pathExists(target)) {
|
|
138
|
-
errors.push(incompleteArtifactReason(artifact.path, null));
|
|
139
|
-
continue;
|
|
140
|
-
}
|
|
141
|
-
const content = await readText(target);
|
|
142
|
-
const reason = incompleteArtifactReason(artifact.path, content);
|
|
143
|
-
if (reason) {
|
|
144
|
-
errors.push(`${reason} (stage ${stageId})`);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
return errors;
|
|
148
|
-
}
|
|
149
|
-
async function incompletePriorStageErrors(ctx) {
|
|
150
|
-
const errors = [];
|
|
151
|
-
const stages = activeStages(ctx.workflow, ctx.meta);
|
|
152
|
-
const currentIdx = stages.findIndex((s) => s.id === ctx.meta.stage);
|
|
153
|
-
if (currentIdx <= 0) return errors;
|
|
154
|
-
for (let i = 0; i < currentIdx; i++) {
|
|
155
|
-
const stage = stages[i];
|
|
156
|
-
if (isStageSkipped(ctx.meta, stage.id) || shouldAutoSkip(stage, ctx.meta)) {
|
|
157
|
-
continue;
|
|
158
|
-
}
|
|
159
|
-
const required = stage.artifacts.filter((a) => a.required);
|
|
160
|
-
if (!required.length && stage.optional) continue;
|
|
161
|
-
const stageErrors = await checkRequiredArtifacts(
|
|
162
|
-
ctx.path,
|
|
163
|
-
stage.id,
|
|
164
|
-
stage.artifacts
|
|
165
|
-
);
|
|
166
|
-
if (stageErrors.length) {
|
|
167
|
-
errors.push(
|
|
168
|
-
`Prior stage "${stage.id}" is not complete \u2014 finish it or \`sdd skip ${stage.id} -r "\u2026"\` if optional.`
|
|
169
|
-
);
|
|
170
|
-
errors.push(...stageErrors);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
return errors;
|
|
174
|
-
}
|
|
175
|
-
async function canLeaveStage(ctx, config) {
|
|
176
|
-
const stage = getStage(ctx.workflow, ctx.meta, ctx.meta.stage);
|
|
177
|
-
const errors = [];
|
|
178
|
-
const warnings = [];
|
|
179
|
-
if (!stage) {
|
|
180
|
-
return { ok: false, errors: [`Unknown stage: ${ctx.meta.stage}`], warnings };
|
|
181
|
-
}
|
|
182
|
-
errors.push(...await incompletePriorStageErrors(ctx));
|
|
183
|
-
errors.push(
|
|
184
|
-
...await checkRequiredArtifacts(ctx.path, stage.id, stage.artifacts)
|
|
185
|
-
);
|
|
186
|
-
const declared = stage.gate?.type ?? "soft";
|
|
187
|
-
let effectiveGate = declared;
|
|
188
|
-
if (config.policy.gates === "hard" && declared === "soft") {
|
|
189
|
-
effectiveGate = "hard";
|
|
190
|
-
}
|
|
191
|
-
const gateState = ctx.meta.gates?.[stage.id];
|
|
192
|
-
if (effectiveGate === "hard") {
|
|
193
|
-
if (!gateState || gateState.status !== "approved" && gateState.status !== "waived") {
|
|
194
|
-
errors.push(
|
|
195
|
-
`Hard gate on stage "${stage.id}" is not approved. Run \`sdd gate approve\` or complete the checklist.`
|
|
196
|
-
);
|
|
197
|
-
}
|
|
198
|
-
} else if (effectiveGate === "soft" && stage.gate?.checklist?.length) {
|
|
199
|
-
if (!gateState || gateState.status === "pending") {
|
|
200
|
-
warnings.push(
|
|
201
|
-
`Soft gate checklist not signed off on "${stage.id}" (proceeding allowed).`
|
|
202
|
-
);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
const requiredCmds = stage.verify?.commands?.filter((c) => c.required) ?? [];
|
|
206
|
-
if (requiredCmds.length) {
|
|
207
|
-
const verifyOk = ctx.meta.verify_results?.[stage.id]?.ok === true;
|
|
208
|
-
const humanOverride = gateState?.status === "waived" || gateState?.status === "approved";
|
|
209
|
-
if (!verifyOk && !humanOverride) {
|
|
210
|
-
errors.push(
|
|
211
|
-
`Required local verify commands have not passed for "${stage.id}". Run \`sdd verify\` (or \`sdd gate approve/waive\` with a note).`
|
|
212
|
-
);
|
|
213
|
-
} else if (!verifyOk && humanOverride) {
|
|
214
|
-
warnings.push(
|
|
215
|
-
`Stage "${stage.id}" gate signed off without a successful required verify run.`
|
|
216
|
-
);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
if (stage.optional || stage.kind === "clarify" || stage.kind === "brainstorm") {
|
|
220
|
-
warnings.push(
|
|
221
|
-
`Stage "${stage.id}" is optional (${stage.kind ?? "optional"}). Skip with: sdd skip ${stage.id} -r "not needed"`
|
|
222
|
-
);
|
|
223
|
-
}
|
|
224
|
-
return { ok: errors.length === 0, errors, warnings };
|
|
225
|
-
}
|
|
226
|
-
async function fallbackIncompleteStageId(ctx) {
|
|
227
|
-
const stages = activeStages(ctx.workflow, ctx.meta);
|
|
228
|
-
for (const stage of stages) {
|
|
229
|
-
if (stage.id === ctx.meta.stage) break;
|
|
230
|
-
if (isStageSkipped(ctx.meta, stage.id) || shouldAutoSkip(stage, ctx.meta)) {
|
|
231
|
-
continue;
|
|
232
|
-
}
|
|
233
|
-
const required = stage.artifacts.filter((a) => a.required);
|
|
234
|
-
if (!required.length && stage.optional) continue;
|
|
235
|
-
const errs = await checkRequiredArtifacts(ctx.path, stage.id, stage.artifacts);
|
|
236
|
-
if (errs.length) return stage.id;
|
|
237
|
-
}
|
|
238
|
-
return null;
|
|
239
|
-
}
|
|
240
|
-
async function approveGate(projectRoot, config, changeId, stageId, note, status = "approved") {
|
|
241
|
-
const ctx = await buildContext(projectRoot, config, changeId);
|
|
242
|
-
const id = stageId ?? ctx.meta.stage;
|
|
243
|
-
ctx.meta.gates = {
|
|
244
|
-
...ctx.meta.gates,
|
|
245
|
-
[id]: { status, note, at: nowIso() }
|
|
246
|
-
};
|
|
247
|
-
await saveChangeMeta(projectRoot, config, ctx.meta);
|
|
248
|
-
return buildContext(projectRoot, config, changeId);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// src/change.ts
|
|
252
|
-
async function existingChangeIds(projectRoot, config) {
|
|
253
|
-
const open = await listDirs(changesDir(projectRoot, config));
|
|
254
|
-
const archived = await listDirs(archiveDir(projectRoot, config));
|
|
255
|
-
return /* @__PURE__ */ new Set([...open, ...archived]);
|
|
256
|
-
}
|
|
257
|
-
async function createChange(input) {
|
|
258
|
-
const { projectRoot, config, title, workflowName } = input;
|
|
259
|
-
const workflow = await loadWorkflow(projectRoot, workflowName);
|
|
260
|
-
if (!workflow.stages.length) {
|
|
261
|
-
throw new Error(`Workflow ${workflowName} has no stages`);
|
|
262
|
-
}
|
|
263
|
-
const baseId = changeDirName(title);
|
|
264
|
-
const id = uniqueChangeDirName(baseId, await existingChangeIds(projectRoot, config));
|
|
265
|
-
const dir = changePath(projectRoot, config, id);
|
|
266
|
-
await ensureDir(dir);
|
|
267
|
-
let meta = ChangeMetaSchema.parse({
|
|
268
|
-
id,
|
|
269
|
-
title,
|
|
270
|
-
workflow: workflowName,
|
|
271
|
-
created: nowIso(),
|
|
272
|
-
updated: nowIso(),
|
|
273
|
-
status: "in_progress",
|
|
274
|
-
stage: workflow.stages[0].id,
|
|
275
|
-
domain: input.domain,
|
|
276
|
-
branch: input.branch,
|
|
277
|
-
flags: input.flags ?? {},
|
|
278
|
-
overrides: {
|
|
279
|
-
skip_stages: [],
|
|
280
|
-
gates: {},
|
|
281
|
-
extra_stages: []
|
|
282
|
-
},
|
|
283
|
-
skipped: [],
|
|
284
|
-
gates: {}
|
|
285
|
-
});
|
|
286
|
-
const startId = firstActiveStageId(workflow, meta) ?? workflow.stages[0].id;
|
|
287
|
-
meta = { ...meta, stage: startId };
|
|
288
|
-
const firstStage = getStage(workflow, meta, startId);
|
|
289
|
-
if (!firstStage) {
|
|
290
|
-
throw new Error(`Workflow ${workflowName} has no active stages`);
|
|
291
|
-
}
|
|
292
|
-
await writeYaml(changeMetaPath(projectRoot, config, id), meta);
|
|
293
|
-
await materializeStageArtifacts(projectRoot, config, id, workflow, meta, firstStage);
|
|
294
|
-
await setActiveChange(projectRoot, config, id);
|
|
295
|
-
try {
|
|
296
|
-
const { refreshActiveAgentContext: refreshActiveAgentContext2 } = await import("./agents-R4RMNXOS.js");
|
|
297
|
-
await refreshActiveAgentContext2(projectRoot);
|
|
298
|
-
} catch {
|
|
299
|
-
}
|
|
300
|
-
return buildContext(projectRoot, config, id);
|
|
301
|
-
}
|
|
302
|
-
async function materializeStageArtifacts(projectRoot, config, changeId, workflow, meta, stage) {
|
|
303
|
-
const created = [];
|
|
304
|
-
const dir = changePath(projectRoot, config, changeId);
|
|
305
|
-
const tdir = templatesDir(projectRoot);
|
|
306
|
-
for (const artifact of stage.artifacts) {
|
|
307
|
-
const target = join2(dir, artifact.path);
|
|
308
|
-
if (await pathExists(target)) continue;
|
|
309
|
-
if (artifact.path.endsWith("/")) {
|
|
310
|
-
await ensureDir(target);
|
|
311
|
-
created.push(artifact.path);
|
|
312
|
-
continue;
|
|
313
|
-
}
|
|
314
|
-
let content = defaultArtifactContent(artifact.id, meta, stage, workflow);
|
|
315
|
-
if (artifact.template) {
|
|
316
|
-
const templatePath = join2(tdir, artifact.template.replace(/^templates\//, ""));
|
|
317
|
-
const candidates = [
|
|
318
|
-
join2(tdir, artifact.template),
|
|
319
|
-
templatePath,
|
|
320
|
-
join2(tdir, `${artifact.id}.md`)
|
|
321
|
-
];
|
|
322
|
-
for (const c of candidates) {
|
|
323
|
-
if (await pathExists(c)) {
|
|
324
|
-
content = interpolate(await readText(c), meta, stage, workflow);
|
|
325
|
-
break;
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
} else {
|
|
329
|
-
const fallback = join2(tdir, `${artifact.id}.md`);
|
|
330
|
-
if (await pathExists(fallback)) {
|
|
331
|
-
content = interpolate(await readText(fallback), meta, stage, workflow);
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
await writeText(target, content);
|
|
335
|
-
created.push(artifact.path);
|
|
336
|
-
}
|
|
337
|
-
return created;
|
|
338
|
-
}
|
|
339
|
-
function interpolate(template, meta, stage, workflow) {
|
|
340
|
-
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));
|
|
341
|
-
}
|
|
342
|
-
function defaultArtifactContent(artifactId, meta, stage, workflow) {
|
|
343
|
-
return `# ${artifactId}
|
|
344
|
-
|
|
345
|
-
> Change: **${meta.title}** (\`${meta.id}\`)
|
|
346
|
-
> Workflow: \`${workflow.name}\` \xB7 Stage: \`${stage.id}\`
|
|
347
|
-
|
|
348
|
-
## Summary
|
|
349
|
-
|
|
350
|
-
<!-- Write the intent / content for this artifact -->
|
|
351
|
-
|
|
352
|
-
## Notes
|
|
353
|
-
|
|
354
|
-
-
|
|
355
|
-
`;
|
|
356
|
-
}
|
|
357
|
-
async function advanceStage(projectRoot, config, changeId, opts) {
|
|
358
|
-
let ctx = await buildContext(projectRoot, config, changeId);
|
|
359
|
-
const warnings = [];
|
|
360
|
-
if (ctx.meta.status === "completed") {
|
|
361
|
-
throw new Error("Change is already completed.");
|
|
362
|
-
}
|
|
363
|
-
if (!opts?.force) {
|
|
364
|
-
const fallback = await fallbackIncompleteStageId(ctx);
|
|
365
|
-
if (fallback && fallback !== ctx.meta.stage) {
|
|
366
|
-
const from2 = ctx.meta.stage;
|
|
367
|
-
ctx.meta.stage = fallback;
|
|
368
|
-
await saveChangeMeta(projectRoot, config, ctx.meta);
|
|
369
|
-
const stage2 = getStage(ctx.workflow, ctx.meta, fallback);
|
|
370
|
-
const artifactsCreated2 = await materializeStageArtifacts(
|
|
371
|
-
projectRoot,
|
|
372
|
-
config,
|
|
373
|
-
changeId,
|
|
374
|
-
ctx.workflow,
|
|
375
|
-
ctx.meta,
|
|
376
|
-
stage2
|
|
377
|
-
);
|
|
378
|
-
try {
|
|
379
|
-
const { refreshActiveAgentContext: refreshActiveAgentContext2 } = await import("./agents-R4RMNXOS.js");
|
|
380
|
-
await refreshActiveAgentContext2(projectRoot);
|
|
381
|
-
} catch {
|
|
382
|
-
}
|
|
383
|
-
const updated2 = await buildContext(projectRoot, config, changeId);
|
|
384
|
-
return {
|
|
385
|
-
ctx: updated2,
|
|
386
|
-
from: from2,
|
|
387
|
-
to: fallback,
|
|
388
|
-
completed: false,
|
|
389
|
-
artifactsCreated: artifactsCreated2,
|
|
390
|
-
warnings: [
|
|
391
|
-
...warnings,
|
|
392
|
-
`Fell back to incomplete stage "${fallback}" (required work not done). Finish it before advancing.`
|
|
393
|
-
]
|
|
394
|
-
};
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
const check = await canLeaveStage(ctx, config);
|
|
398
|
-
warnings.push(...check.warnings);
|
|
399
|
-
if (!check.ok && !opts?.force) {
|
|
400
|
-
throw new Error(check.errors.join("\n"));
|
|
401
|
-
}
|
|
402
|
-
const from = ctx.meta.stage;
|
|
403
|
-
const next = nextStageId(ctx.workflow, ctx.meta);
|
|
404
|
-
if (!next) {
|
|
405
|
-
return {
|
|
406
|
-
ctx,
|
|
407
|
-
from,
|
|
408
|
-
to: null,
|
|
409
|
-
completed: false,
|
|
410
|
-
artifactsCreated: [],
|
|
411
|
-
warnings: [...warnings, "No further stages. Run `sdd complete` when ready."]
|
|
412
|
-
};
|
|
413
|
-
}
|
|
414
|
-
ctx.meta.stage = next;
|
|
415
|
-
await saveChangeMeta(projectRoot, config, ctx.meta);
|
|
416
|
-
const stage = getStage(ctx.workflow, ctx.meta, next);
|
|
417
|
-
const artifactsCreated = await materializeStageArtifacts(
|
|
418
|
-
projectRoot,
|
|
419
|
-
config,
|
|
420
|
-
changeId,
|
|
421
|
-
ctx.workflow,
|
|
422
|
-
ctx.meta,
|
|
423
|
-
stage
|
|
424
|
-
);
|
|
425
|
-
try {
|
|
426
|
-
const { refreshActiveAgentContext: refreshActiveAgentContext2 } = await import("./agents-R4RMNXOS.js");
|
|
427
|
-
await refreshActiveAgentContext2(projectRoot);
|
|
428
|
-
} catch {
|
|
429
|
-
}
|
|
430
|
-
const updated = await buildContext(projectRoot, config, changeId);
|
|
431
|
-
return {
|
|
432
|
-
ctx: updated,
|
|
433
|
-
from,
|
|
434
|
-
to: next,
|
|
435
|
-
completed: false,
|
|
436
|
-
artifactsCreated,
|
|
437
|
-
warnings
|
|
438
|
-
};
|
|
439
|
-
}
|
|
440
|
-
async function skipStage(projectRoot, config, changeId, stageId, reason) {
|
|
441
|
-
if (!config.per_change.allow_skip) {
|
|
442
|
-
throw new Error("Skipping stages is disabled in .sdd/config.yaml (per_change.allow_skip).");
|
|
443
|
-
}
|
|
444
|
-
if (config.per_change.require_reason_on_skip && !reason.trim()) {
|
|
445
|
-
throw new Error("A reason is required to skip a stage.");
|
|
446
|
-
}
|
|
447
|
-
const ctx = await buildContext(projectRoot, config, changeId);
|
|
448
|
-
const stage = getStage(ctx.workflow, ctx.meta, stageId);
|
|
449
|
-
if (!stage) {
|
|
450
|
-
throw new Error(`Stage "${stageId}" not found in workflow ${ctx.meta.workflow}`);
|
|
451
|
-
}
|
|
452
|
-
if (!stage.skippable) {
|
|
453
|
-
throw new Error(`Stage "${stageId}" is not skippable.`);
|
|
454
|
-
}
|
|
455
|
-
const skip_stages = new Set(ctx.meta.overrides.skip_stages ?? []);
|
|
456
|
-
skip_stages.add(stageId);
|
|
457
|
-
ctx.meta.overrides.skip_stages = [...skip_stages];
|
|
458
|
-
ctx.meta.skipped = [
|
|
459
|
-
...ctx.meta.skipped ?? [],
|
|
460
|
-
{ stage: stageId, reason, at: nowIso() }
|
|
461
|
-
];
|
|
462
|
-
if (ctx.meta.stage === stageId) {
|
|
463
|
-
const next = nextStageId(ctx.workflow, ctx.meta);
|
|
464
|
-
if (next) {
|
|
465
|
-
ctx.meta.stage = next;
|
|
466
|
-
const nextStage = getStage(ctx.workflow, ctx.meta, next);
|
|
467
|
-
if (nextStage) {
|
|
468
|
-
await materializeStageArtifacts(
|
|
469
|
-
projectRoot,
|
|
470
|
-
config,
|
|
471
|
-
changeId,
|
|
472
|
-
ctx.workflow,
|
|
473
|
-
ctx.meta,
|
|
474
|
-
nextStage
|
|
475
|
-
);
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
await saveChangeMeta(projectRoot, config, ctx.meta);
|
|
480
|
-
return buildContext(projectRoot, config, changeId);
|
|
481
|
-
}
|
|
482
|
-
async function switchWorkflow(projectRoot, config, changeId, workflowName, reason) {
|
|
483
|
-
const ctx = await buildContext(projectRoot, config, changeId);
|
|
484
|
-
const workflow = await loadWorkflow(projectRoot, workflowName);
|
|
485
|
-
if (config.allowed_workflows?.length && !config.allowed_workflows.includes(workflowName)) {
|
|
486
|
-
throw new Error(
|
|
487
|
-
`Workflow "${workflowName}" is not in allowed_workflows: ${config.allowed_workflows.join(", ")}`
|
|
488
|
-
);
|
|
489
|
-
}
|
|
490
|
-
ctx.meta.workflow = workflowName;
|
|
491
|
-
const hasStage = workflow.stages.some((s) => s.id === ctx.meta.stage);
|
|
492
|
-
if (!hasStage) {
|
|
493
|
-
ctx.meta.stage = workflow.stages[0].id;
|
|
494
|
-
}
|
|
495
|
-
if (reason) {
|
|
496
|
-
ctx.meta.flags = {
|
|
497
|
-
...ctx.meta.flags,
|
|
498
|
-
workflow_switch_reason: reason
|
|
499
|
-
};
|
|
500
|
-
}
|
|
501
|
-
await saveChangeMeta(projectRoot, config, ctx.meta);
|
|
502
|
-
const stage = getStage(workflow, ctx.meta, ctx.meta.stage);
|
|
503
|
-
if (stage) {
|
|
504
|
-
await materializeStageArtifacts(projectRoot, config, changeId, workflow, ctx.meta, stage);
|
|
505
|
-
}
|
|
506
|
-
return buildContext(projectRoot, config, changeId);
|
|
507
|
-
}
|
|
508
|
-
async function completeChange(projectRoot, config, changeId) {
|
|
509
|
-
const ctx = await buildContext(projectRoot, config, changeId);
|
|
510
|
-
const next = nextStageId(ctx.workflow, ctx.meta);
|
|
511
|
-
if (next) {
|
|
512
|
-
const active = activeStages(ctx.workflow, ctx.meta);
|
|
513
|
-
const last = active[active.length - 1];
|
|
514
|
-
if (last && ctx.meta.stage !== last.id) {
|
|
515
|
-
throw new Error(
|
|
516
|
-
`Cannot complete: still on stage "${ctx.meta.stage}". Finish remaining stages or skip them. Next: ${next}`
|
|
517
|
-
);
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
const check = await canLeaveStage(ctx, config);
|
|
521
|
-
if (!check.ok) {
|
|
522
|
-
throw new Error(check.errors.join("\n"));
|
|
523
|
-
}
|
|
524
|
-
ctx.meta.status = "completed";
|
|
525
|
-
ctx.meta.completed_at = nowIso();
|
|
526
|
-
await saveChangeMeta(projectRoot, config, ctx.meta);
|
|
527
|
-
await clearActiveIf(projectRoot, config, changeId);
|
|
528
|
-
let archivedTo = null;
|
|
529
|
-
const shouldArchive = config.persistence.archive_on_complete && ctx.workflow.on_complete?.archive !== false;
|
|
530
|
-
if (shouldArchive) {
|
|
531
|
-
const dest = join2(archiveDir(projectRoot, config), changeId);
|
|
532
|
-
if (await pathExists(dest)) {
|
|
533
|
-
throw new Error(`Archive already exists for change: ${changeId}`);
|
|
534
|
-
}
|
|
535
|
-
await ensureDir(archiveDir(projectRoot, config));
|
|
536
|
-
await copyDir(ctx.path, dest);
|
|
537
|
-
await removePath(ctx.path);
|
|
538
|
-
archivedTo = dest;
|
|
539
|
-
}
|
|
540
|
-
if (archivedTo) {
|
|
541
|
-
const meta = ChangeMetaSchema.parse(await readYaml(join2(archivedTo, "meta.yaml")));
|
|
542
|
-
return {
|
|
543
|
-
archivedTo,
|
|
544
|
-
ctx: {
|
|
545
|
-
id: changeId,
|
|
546
|
-
path: archivedTo,
|
|
547
|
-
meta,
|
|
548
|
-
workflow: ctx.workflow,
|
|
549
|
-
stages: ctx.stages,
|
|
550
|
-
active: ctx.active
|
|
551
|
-
}
|
|
552
|
-
};
|
|
553
|
-
}
|
|
554
|
-
return { archivedTo, ctx: await buildContext(projectRoot, config, changeId) };
|
|
555
|
-
}
|
|
556
|
-
|
|
557
116
|
// src/init.ts
|
|
558
|
-
import { join as
|
|
117
|
+
import { join as join2 } from "pathe";
|
|
559
118
|
|
|
560
119
|
// src/defaults.ts
|
|
561
120
|
import { existsSync } from "fs";
|
|
562
121
|
import { fileURLToPath } from "url";
|
|
563
|
-
import { dirname, join
|
|
122
|
+
import { dirname, join } from "pathe";
|
|
564
123
|
function defaultsRoot() {
|
|
565
124
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
566
125
|
const candidates = [
|
|
567
|
-
|
|
126
|
+
join(here, "..", "defaults"),
|
|
568
127
|
// npm package: dist/../defaults
|
|
569
|
-
|
|
128
|
+
join(here, "defaults"),
|
|
570
129
|
// same-dir defaults if present
|
|
571
|
-
|
|
130
|
+
join(here, "..", "..", "defaults")
|
|
572
131
|
// src/ during some test runners
|
|
573
132
|
];
|
|
574
133
|
for (const c of candidates) {
|
|
@@ -577,13 +136,13 @@ function defaultsRoot() {
|
|
|
577
136
|
return candidates[0];
|
|
578
137
|
}
|
|
579
138
|
function defaultWorkflowsDir() {
|
|
580
|
-
return
|
|
139
|
+
return join(defaultsRoot(), "workflows");
|
|
581
140
|
}
|
|
582
141
|
function defaultTemplatesDir() {
|
|
583
|
-
return
|
|
142
|
+
return join(defaultsRoot(), "templates");
|
|
584
143
|
}
|
|
585
144
|
function defaultMemoryDir() {
|
|
586
|
-
return
|
|
145
|
+
return join(defaultsRoot(), "memory");
|
|
587
146
|
}
|
|
588
147
|
|
|
589
148
|
// src/init.ts
|
|
@@ -591,15 +150,15 @@ async function initProject(opts) {
|
|
|
591
150
|
const { projectRoot, force = false } = opts;
|
|
592
151
|
const created = [];
|
|
593
152
|
const root = sddRoot(projectRoot);
|
|
594
|
-
if (await pathExists(
|
|
153
|
+
if (await pathExists(join2(root, "config.yaml")) && !force) {
|
|
595
154
|
throw new Error(
|
|
596
|
-
`Already initialized (${
|
|
155
|
+
`Already initialized (${join2(root, "config.yaml")}). Use --force to re-copy defaults.`
|
|
597
156
|
);
|
|
598
157
|
}
|
|
599
158
|
const config = defaultConfig();
|
|
600
159
|
await ensureDir(root);
|
|
601
160
|
await saveConfig(projectRoot, config);
|
|
602
|
-
created.push(
|
|
161
|
+
created.push(join2(root, "config.yaml"));
|
|
603
162
|
const wfDest = workflowsDir(projectRoot);
|
|
604
163
|
await ensureDir(wfDest);
|
|
605
164
|
const wfSrc = defaultWorkflowsDir();
|
|
@@ -621,7 +180,7 @@ async function initProject(opts) {
|
|
|
621
180
|
await copyDirSkipExisting(mSrc, mDest);
|
|
622
181
|
} else {
|
|
623
182
|
await writeTextIfMissing(
|
|
624
|
-
|
|
183
|
+
join2(mDest, "index.md"),
|
|
625
184
|
`# Documentation map
|
|
626
185
|
|
|
627
186
|
Parent page for **stable** project docs. Start here, then open only what you need.
|
|
@@ -637,11 +196,11 @@ Active work: \`.sdd/active-context.md\` and \`changes/<id>/\`.
|
|
|
637
196
|
`
|
|
638
197
|
);
|
|
639
198
|
await writeTextIfMissing(
|
|
640
|
-
|
|
199
|
+
join2(mDest, "product.md"),
|
|
641
200
|
"# Product\n\n<!-- What are you building? -->\n"
|
|
642
201
|
);
|
|
643
202
|
await writeTextIfMissing(
|
|
644
|
-
|
|
203
|
+
join2(mDest, "constitution.md"),
|
|
645
204
|
`# Constitution
|
|
646
205
|
|
|
647
206
|
<!-- Non-negotiables agents must not violate. Broader style lives in conventions.md. -->
|
|
@@ -668,21 +227,21 @@ Active work: \`.sdd/active-context.md\` and \`changes/<id>/\`.
|
|
|
668
227
|
`
|
|
669
228
|
);
|
|
670
229
|
await writeTextIfMissing(
|
|
671
|
-
|
|
230
|
+
join2(mDest, "architecture.md"),
|
|
672
231
|
"# Architecture\n\n<!-- High-level system shape -->\n"
|
|
673
232
|
);
|
|
674
233
|
await writeTextIfMissing(
|
|
675
|
-
|
|
234
|
+
join2(mDest, "conventions.md"),
|
|
676
235
|
"# Conventions\n\n<!-- Coding and process conventions -->\n"
|
|
677
236
|
);
|
|
678
237
|
}
|
|
679
|
-
if (!await pathExists(
|
|
680
|
-
const fromDefault =
|
|
238
|
+
if (!await pathExists(join2(mDest, "constitution.md"))) {
|
|
239
|
+
const fromDefault = join2(mSrc, "constitution.md");
|
|
681
240
|
if (await pathExists(fromDefault)) {
|
|
682
241
|
await copyDirSkipExisting(mSrc, mDest);
|
|
683
242
|
} else {
|
|
684
243
|
await writeTextIfMissing(
|
|
685
|
-
|
|
244
|
+
join2(mDest, "constitution.md"),
|
|
686
245
|
`# Constitution
|
|
687
246
|
|
|
688
247
|
<!-- Non-negotiables agents must not violate. Broader style lives in conventions.md. -->
|
|
@@ -715,9 +274,9 @@ Active work: \`.sdd/active-context.md\` and \`changes/<id>/\`.
|
|
|
715
274
|
await ensureDir(domainsDir(projectRoot, config));
|
|
716
275
|
created.push(changesDir(projectRoot, config));
|
|
717
276
|
created.push(domainsDir(projectRoot, config));
|
|
718
|
-
await writeText(
|
|
719
|
-
await writeText(
|
|
720
|
-
const sddReadme =
|
|
277
|
+
await writeText(join2(changesDir(projectRoot, config), ".gitkeep"), "");
|
|
278
|
+
await writeText(join2(domainsDir(projectRoot, config), ".gitkeep"), "");
|
|
279
|
+
const sddReadme = join2(root, "README.md");
|
|
721
280
|
if (!await pathExists(sddReadme) || force) {
|
|
722
281
|
await writeText(
|
|
723
282
|
sddReadme,
|
|
@@ -764,7 +323,7 @@ sdd complete
|
|
|
764
323
|
|
|
765
324
|
// src/verify.ts
|
|
766
325
|
import { spawn } from "child_process";
|
|
767
|
-
import { join as
|
|
326
|
+
import { join as join3 } from "pathe";
|
|
768
327
|
function runCommand(command, cwd) {
|
|
769
328
|
return new Promise((resolve) => {
|
|
770
329
|
const child = spawn(command, {
|
|
@@ -836,7 +395,7 @@ async function runLocalVerify(projectRoot, config, changeId, opts) {
|
|
|
836
395
|
throw new Error(`Unknown stage: ${ctx.meta.stage}`);
|
|
837
396
|
}
|
|
838
397
|
const evidenceDirRaw = stage.verify?.evidence_dir?.trim();
|
|
839
|
-
const evidenceDir = evidenceDirRaw && evidenceDirRaw.length > 0 ?
|
|
398
|
+
const evidenceDir = evidenceDirRaw && evidenceDirRaw.length > 0 ? join3(ctx.path, evidenceDirRaw) : null;
|
|
840
399
|
if (evidenceDir) {
|
|
841
400
|
await ensureDir(evidenceDir);
|
|
842
401
|
}
|
|
@@ -853,7 +412,7 @@ async function runLocalVerify(projectRoot, config, changeId, opts) {
|
|
|
853
412
|
output: output.slice(0, 5e4)
|
|
854
413
|
});
|
|
855
414
|
if (evidenceDir) {
|
|
856
|
-
const logPath =
|
|
415
|
+
const logPath = join3(evidenceDir, `${cmd.name.replace(/\s+/g, "-")}.log`);
|
|
857
416
|
await writeText(
|
|
858
417
|
logPath,
|
|
859
418
|
`# ${cmd.name}
|
|
@@ -880,7 +439,7 @@ ${output}`
|
|
|
880
439
|
ok = results.every((r) => r.exitCode === 0);
|
|
881
440
|
}
|
|
882
441
|
const checklist = stage.gate?.checklist ?? [];
|
|
883
|
-
const summaryPath =
|
|
442
|
+
const summaryPath = join3(ctx.path, "local-test-results.md");
|
|
884
443
|
const lines = buildSummaryLines(ctx.meta.title, stage.id, ok, results, checklist);
|
|
885
444
|
const summaryBody = lines.join("\n");
|
|
886
445
|
if (await pathExists(summaryPath)) {
|
|
@@ -913,7 +472,7 @@ ${output}`
|
|
|
913
472
|
if (evidenceDir) {
|
|
914
473
|
const stamp = nowIso();
|
|
915
474
|
await writeText(
|
|
916
|
-
|
|
475
|
+
join3(evidenceDir, `run-${stamp.replace(/[:.]/g, "-")}.md`),
|
|
917
476
|
summaryBody
|
|
918
477
|
);
|
|
919
478
|
}
|
|
@@ -939,7 +498,7 @@ ${output}`
|
|
|
939
498
|
}
|
|
940
499
|
|
|
941
500
|
// src/code-context/index.ts
|
|
942
|
-
import { extname as extname4, join as
|
|
501
|
+
import { extname as extname4, join as join6, normalize as normalize5 } from "pathe";
|
|
943
502
|
|
|
944
503
|
// src/code-context/adapters/registry.ts
|
|
945
504
|
import { extname as extname2 } from "pathe";
|
|
@@ -1262,7 +821,7 @@ function formatJsonSummary(summary, gaps, slices, ok) {
|
|
|
1262
821
|
|
|
1263
822
|
// src/code-context/focus.ts
|
|
1264
823
|
import { promises as fs } from "fs";
|
|
1265
|
-
import { isAbsolute, join as
|
|
824
|
+
import { isAbsolute, join as join4, normalize as normalize3, relative } from "pathe";
|
|
1266
825
|
|
|
1267
826
|
// src/code-context/ignore.ts
|
|
1268
827
|
import { basename as basename2, extname as extname3, normalize as normalize2 } from "pathe";
|
|
@@ -1440,12 +999,12 @@ function extractPathLikeTokens(text) {
|
|
|
1440
999
|
return found;
|
|
1441
1000
|
}
|
|
1442
1001
|
function toRepoRelative(projectRoot, p) {
|
|
1443
|
-
const abs = isAbsolute(p) ? p :
|
|
1002
|
+
const abs = isAbsolute(p) ? p : join4(projectRoot, p);
|
|
1444
1003
|
const rel = relative(projectRoot, abs);
|
|
1445
1004
|
return normalize3(rel).replace(/\\/g, "/");
|
|
1446
1005
|
}
|
|
1447
1006
|
function toAbsolute(projectRoot, p) {
|
|
1448
|
-
return isAbsolute(p) ? p :
|
|
1007
|
+
return isAbsolute(p) ? p : join4(projectRoot, p);
|
|
1449
1008
|
}
|
|
1450
1009
|
async function walkFiles(absDir, projectRoot, maxFiles, out, gaps) {
|
|
1451
1010
|
if (out.length >= maxFiles) return;
|
|
@@ -1458,7 +1017,7 @@ async function walkFiles(absDir, projectRoot, maxFiles, out, gaps) {
|
|
|
1458
1017
|
}
|
|
1459
1018
|
for (const entry of entries) {
|
|
1460
1019
|
if (out.length >= maxFiles) return;
|
|
1461
|
-
const abs =
|
|
1020
|
+
const abs = join4(absDir, entry.name);
|
|
1462
1021
|
const rel = toRepoRelative(projectRoot, abs);
|
|
1463
1022
|
const kind = classifyPath(rel);
|
|
1464
1023
|
if (kind === "secret" || kind === "ignored") continue;
|
|
@@ -1552,7 +1111,7 @@ async function resolveChangeMeta(projectRoot, changeId, gaps) {
|
|
|
1552
1111
|
return null;
|
|
1553
1112
|
}
|
|
1554
1113
|
const cpath = changePath(projectRoot, config, id);
|
|
1555
|
-
const metaPath =
|
|
1114
|
+
const metaPath = join4(cpath, "meta.yaml");
|
|
1556
1115
|
if (!await pathExists(metaPath)) {
|
|
1557
1116
|
if (!await pathExists(cpath)) {
|
|
1558
1117
|
gaps.push({
|
|
@@ -1585,7 +1144,7 @@ async function searchByKeywords(projectRoot, keywords, symbols, maxFiles) {
|
|
|
1585
1144
|
const terms = [...keywords, ...symbols.map((s) => s.toLowerCase())];
|
|
1586
1145
|
if (!terms.length) return out;
|
|
1587
1146
|
for (const root of SOURCE_ROOTS) {
|
|
1588
|
-
const abs =
|
|
1147
|
+
const abs = join4(projectRoot, root);
|
|
1589
1148
|
if (!await pathExists(abs)) continue;
|
|
1590
1149
|
const files = [];
|
|
1591
1150
|
await walkFiles(abs, projectRoot, maxFiles * 4, files, []);
|
|
@@ -1599,7 +1158,7 @@ async function searchByKeywords(projectRoot, keywords, symbols, maxFiles) {
|
|
|
1599
1158
|
}
|
|
1600
1159
|
if (symbols.length) {
|
|
1601
1160
|
try {
|
|
1602
|
-
const text = await readText(
|
|
1161
|
+
const text = await readText(join4(projectRoot, rel));
|
|
1603
1162
|
if (symbols.some((s) => text.includes(s))) {
|
|
1604
1163
|
out.push(rel);
|
|
1605
1164
|
}
|
|
@@ -1635,7 +1194,7 @@ async function resolveFocus(request, options) {
|
|
|
1635
1194
|
}
|
|
1636
1195
|
notes.push(`Change signals from: ${change.id}`);
|
|
1637
1196
|
for (const artifact of ["tasks.md", "feature.md", "stories.md", "lld.md"]) {
|
|
1638
|
-
const body = await readBounded(
|
|
1197
|
+
const body = await readBounded(join4(change.path, artifact), 8e3);
|
|
1639
1198
|
if (!body) continue;
|
|
1640
1199
|
if (!request.paths?.length) {
|
|
1641
1200
|
for (const p of extractPathLikeTokens(body)) {
|
|
@@ -1718,7 +1277,7 @@ async function resolveFocus(request, options) {
|
|
|
1718
1277
|
}
|
|
1719
1278
|
|
|
1720
1279
|
// src/code-context/graph.ts
|
|
1721
|
-
import { dirname as dirname2, join as
|
|
1280
|
+
import { dirname as dirname2, join as join5, normalize as normalize4 } from "pathe";
|
|
1722
1281
|
var GraphLite = class {
|
|
1723
1282
|
files = /* @__PURE__ */ new Map();
|
|
1724
1283
|
edges = [];
|
|
@@ -1749,7 +1308,7 @@ var GraphLite = class {
|
|
|
1749
1308
|
resolveImportTarget(fromFile, specifier) {
|
|
1750
1309
|
if (!specifier.startsWith(".")) return void 0;
|
|
1751
1310
|
const base = dirname2(fromFile);
|
|
1752
|
-
const joined = normalize4(
|
|
1311
|
+
const joined = normalize4(join5(base, specifier)).replace(/\\/g, "/");
|
|
1753
1312
|
const candidates = [
|
|
1754
1313
|
joined,
|
|
1755
1314
|
`${joined}.ts`,
|
|
@@ -2175,7 +1734,7 @@ async function generateCodeContext(request, deps) {
|
|
|
2175
1734
|
});
|
|
2176
1735
|
continue;
|
|
2177
1736
|
}
|
|
2178
|
-
const abs =
|
|
1737
|
+
const abs = join6(projectRoot, rel);
|
|
2179
1738
|
if (!await pathExists(abs)) {
|
|
2180
1739
|
gaps.push({
|
|
2181
1740
|
code: "path_missing",
|
|
@@ -2264,7 +1823,7 @@ async function generateCodeContext(request, deps) {
|
|
|
2264
1823
|
if (plan.changeId) {
|
|
2265
1824
|
try {
|
|
2266
1825
|
const { readYaml: readYaml2 } = await import("./fs-J6DIV6CX.js");
|
|
2267
|
-
const metaPath =
|
|
1826
|
+
const metaPath = join6(
|
|
2268
1827
|
projectRoot,
|
|
2269
1828
|
// best-effort: changes/<id>/meta.yaml via relative walk
|
|
2270
1829
|
// Prefer using change path from resolve — re-read lightly
|
|
@@ -2272,10 +1831,10 @@ async function generateCodeContext(request, deps) {
|
|
|
2272
1831
|
plan.changeId,
|
|
2273
1832
|
"meta.yaml"
|
|
2274
1833
|
);
|
|
2275
|
-
const { loadConfig: loadConfig2 } = await import("./config-
|
|
1834
|
+
const { loadConfig: loadConfig2 } = await import("./config-ZYEEIRXJ.js");
|
|
2276
1835
|
const { changePath: changePath2 } = await import("./paths-64GA32IZ.js");
|
|
2277
1836
|
const config = await loadConfig2(projectRoot);
|
|
2278
|
-
const mp =
|
|
1837
|
+
const mp = join6(changePath2(projectRoot, config, plan.changeId), "meta.yaml");
|
|
2279
1838
|
if (await pathExists(mp)) {
|
|
2280
1839
|
const meta = await readYaml2(mp);
|
|
2281
1840
|
changeTitle = meta.title ?? plan.changeId;
|
|
@@ -2329,7 +1888,7 @@ async function generateCodeContext(request, deps) {
|
|
|
2329
1888
|
}
|
|
2330
1889
|
|
|
2331
1890
|
// src/refine.ts
|
|
2332
|
-
import { join as
|
|
1891
|
+
import { join as join7 } from "pathe";
|
|
2333
1892
|
function stageArtifactRefs(ctx, stageId) {
|
|
2334
1893
|
const stage = getStage(ctx.workflow, ctx.meta, stageId);
|
|
2335
1894
|
if (!stage) return [];
|
|
@@ -2337,7 +1896,7 @@ function stageArtifactRefs(ctx, stageId) {
|
|
|
2337
1896
|
stageId,
|
|
2338
1897
|
path: a.path,
|
|
2339
1898
|
required: Boolean(a.required),
|
|
2340
|
-
absPath:
|
|
1899
|
+
absPath: join7(ctx.path, a.path)
|
|
2341
1900
|
}));
|
|
2342
1901
|
}
|
|
2343
1902
|
function listMarkdown(refs) {
|
|
@@ -2382,11 +1941,11 @@ async function buildRefinePlan(opts) {
|
|
|
2382
1941
|
laterArtifacts.push(...stageArtifactRefs(ctx, s.id));
|
|
2383
1942
|
}
|
|
2384
1943
|
const focusArtifacts = stageArtifactRefs(ctx, focusStageId);
|
|
2385
|
-
const constitutionPath =
|
|
1944
|
+
const constitutionPath = join7(memoryDir(opts.projectRoot, opts.config), "constitution.md");
|
|
2386
1945
|
const constitutionExists = await pathExists(constitutionPath);
|
|
2387
|
-
const briefPath =
|
|
2388
|
-
const reportPath =
|
|
2389
|
-
const openItemsPath =
|
|
1946
|
+
const briefPath = join7(ctx.path, "refine-brief.md");
|
|
1947
|
+
const reportPath = join7(ctx.path, "quality-report.md");
|
|
1948
|
+
const openItemsPath = join7(ctx.path, "open-items.md");
|
|
2390
1949
|
const briefMarkdown = buildRefineBriefMarkdown({
|
|
2391
1950
|
ctx,
|
|
2392
1951
|
focusStageId,
|
|
@@ -2515,7 +2074,7 @@ Do **not** invent content for later stages that were not materialized yet. If a
|
|
|
2515
2074
|
}
|
|
2516
2075
|
async function writeRefineBrief(plan, projectRoot) {
|
|
2517
2076
|
await writeText(plan.briefPath, plan.briefMarkdown);
|
|
2518
|
-
const pointer =
|
|
2077
|
+
const pointer = join7(sddRoot(projectRoot), "refine-brief.md");
|
|
2519
2078
|
await writeText(
|
|
2520
2079
|
pointer,
|
|
2521
2080
|
`# Refine brief pointer
|
|
@@ -2576,6 +2135,7 @@ export {
|
|
|
2576
2135
|
ensureDir,
|
|
2577
2136
|
fallbackAdapter,
|
|
2578
2137
|
fallbackIncompleteStageId,
|
|
2138
|
+
findFeaturesBacklogPath,
|
|
2579
2139
|
firstActiveStageId,
|
|
2580
2140
|
formatJsonSummary,
|
|
2581
2141
|
formatStatus,
|
|
@@ -2594,6 +2154,7 @@ export {
|
|
|
2594
2154
|
isSecretPath,
|
|
2595
2155
|
isStageSkipped,
|
|
2596
2156
|
isSubstantiveArtifactContent,
|
|
2157
|
+
listBacklogFeatures,
|
|
2597
2158
|
listChanges,
|
|
2598
2159
|
listDirs,
|
|
2599
2160
|
listFiles,
|
|
@@ -2608,8 +2169,10 @@ export {
|
|
|
2608
2169
|
nowIso,
|
|
2609
2170
|
optionForTarget,
|
|
2610
2171
|
parseAgentTargets,
|
|
2172
|
+
parseFeaturesBacklog,
|
|
2611
2173
|
parseIntegration,
|
|
2612
2174
|
pathExists,
|
|
2175
|
+
promoteGreenfieldToMemory,
|
|
2613
2176
|
readText,
|
|
2614
2177
|
readYaml,
|
|
2615
2178
|
recommendWorkflow,
|
|
@@ -2628,12 +2191,15 @@ export {
|
|
|
2628
2191
|
shouldAutoSkip,
|
|
2629
2192
|
skipStage,
|
|
2630
2193
|
slugify,
|
|
2194
|
+
startFeatureFromBacklog,
|
|
2195
|
+
startGreenfield,
|
|
2631
2196
|
switchWorkflow,
|
|
2632
2197
|
templatesDir,
|
|
2633
2198
|
todayIsoDate,
|
|
2634
2199
|
tokenizeKeywords,
|
|
2635
2200
|
typescriptAdapter,
|
|
2636
2201
|
uniqueChangeDirName,
|
|
2202
|
+
updateFeatureStatus,
|
|
2637
2203
|
workflowsDir,
|
|
2638
2204
|
writeAgentHandoff,
|
|
2639
2205
|
writeRefineBrief,
|