@structured-vibe-coding/core 0.20.0 → 0.22.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/dist/{agents-YN3Z3VOH.js → agents-EDCIINXG.js} +4 -3
- package/dist/chunk-47GWMUCR.js +153 -0
- package/dist/{chunk-HUSXETPS.js → chunk-QE6RMJUS.js} +224 -39
- package/dist/{chunk-EDXT3RTN.js → chunk-RXVTVLCJ.js} +3 -129
- package/dist/{chunk-O4OQWVC7.js → chunk-WCYVBZC4.js} +2 -2
- package/dist/{chunk-NY5FFWXG.js → chunk-XHNO536D.js} +62 -5
- package/dist/{gather-ZKVMJW5L.js → gather-ZK722X65.js} +2 -2
- package/dist/{greenfield-O2DI27HY.js → greenfield-E5CHDQPH.js} +4 -3
- package/dist/index.d.ts +82 -6
- package/dist/index.js +600 -124
- package/package.json +1 -1
- package/dist/chunk-MHODNFIL.js +0 -26
package/dist/index.js
CHANGED
|
@@ -1,32 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
|
-
SDD_CORE_VERSION,
|
|
3
|
-
callMcpSourceTool,
|
|
4
2
|
explainMatch,
|
|
5
3
|
formatMcpContextForHandoff,
|
|
6
4
|
gatherMcpContext,
|
|
7
5
|
gatherMcpContextForChange,
|
|
8
6
|
listMatchingSourceIds,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
mcpTimeoutMs,
|
|
12
|
-
toProcessEnv,
|
|
13
|
-
withMcpSource
|
|
14
|
-
} from "./chunk-EDXT3RTN.js";
|
|
15
|
-
import {
|
|
16
|
-
McpConfigSchema,
|
|
17
|
-
McpInvokeSchema,
|
|
18
|
-
McpSourceSchema,
|
|
19
|
-
McpWhenSchema,
|
|
20
|
-
addMcpSource,
|
|
21
|
-
defaultMcpConfig,
|
|
22
|
-
ensureMcpConfig,
|
|
23
|
-
interpolate,
|
|
24
|
-
interpolateArgs,
|
|
25
|
-
loadMcpConfig,
|
|
26
|
-
mcpConfigPath,
|
|
27
|
-
removeMcpSource,
|
|
28
|
-
saveMcpConfig
|
|
29
|
-
} from "./chunk-4BRO7BOJ.js";
|
|
7
|
+
matchMcpSources
|
|
8
|
+
} from "./chunk-RXVTVLCJ.js";
|
|
30
9
|
import {
|
|
31
10
|
AGENT_INTEGRATIONS,
|
|
32
11
|
AGENT_TARGET_OPTIONS,
|
|
@@ -68,7 +47,7 @@ import {
|
|
|
68
47
|
renderThinAgent,
|
|
69
48
|
saveGeneratedManifest,
|
|
70
49
|
writeAgentHandoff
|
|
71
|
-
} from "./chunk-
|
|
50
|
+
} from "./chunk-WCYVBZC4.js";
|
|
72
51
|
import {
|
|
73
52
|
ChangeMetricsSchema,
|
|
74
53
|
DecisionMetricSchema,
|
|
@@ -83,6 +62,8 @@ import {
|
|
|
83
62
|
emptyChangeMetrics,
|
|
84
63
|
fallbackIncompleteStageId,
|
|
85
64
|
findFeaturesBacklogPath,
|
|
65
|
+
h2Title,
|
|
66
|
+
headingsFilled,
|
|
86
67
|
importSessionMetrics,
|
|
87
68
|
incompletePriorStageErrors,
|
|
88
69
|
isSubstantiveArtifactContent,
|
|
@@ -100,7 +81,7 @@ import {
|
|
|
100
81
|
switchWorkflow,
|
|
101
82
|
transitionPhase,
|
|
102
83
|
updateFeatureStatus
|
|
103
|
-
} from "./chunk-
|
|
84
|
+
} from "./chunk-XHNO536D.js";
|
|
104
85
|
import {
|
|
105
86
|
EMBEDDINGS_UNAVAILABLE,
|
|
106
87
|
EMBEDDING_MODEL,
|
|
@@ -164,6 +145,7 @@ import {
|
|
|
164
145
|
memoryDir as memoryDir2,
|
|
165
146
|
mergeConfigDefaults,
|
|
166
147
|
mergeRankedHits,
|
|
148
|
+
openMcpSearchProvider,
|
|
167
149
|
packHits,
|
|
168
150
|
parseRoleDocument,
|
|
169
151
|
pinRole,
|
|
@@ -188,9 +170,10 @@ import {
|
|
|
188
170
|
searchProject,
|
|
189
171
|
showRoles,
|
|
190
172
|
showTeam,
|
|
173
|
+
splitFrontmatter,
|
|
191
174
|
workKind,
|
|
192
175
|
writePackedCache
|
|
193
|
-
} from "./chunk-
|
|
176
|
+
} from "./chunk-QE6RMJUS.js";
|
|
194
177
|
import {
|
|
195
178
|
ArtifactDefSchema,
|
|
196
179
|
ChangeMetaSchema,
|
|
@@ -236,10 +219,31 @@ import {
|
|
|
236
219
|
workflowUnavailableMessage
|
|
237
220
|
} from "./chunk-TNWFMRIA.js";
|
|
238
221
|
import {
|
|
222
|
+
SDD_CORE_VERSION,
|
|
239
223
|
SddError,
|
|
224
|
+
callMcpSourceTool,
|
|
240
225
|
errorMessage,
|
|
241
|
-
isSddError
|
|
242
|
-
|
|
226
|
+
isSddError,
|
|
227
|
+
listMcpSourceTools,
|
|
228
|
+
mcpTimeoutMs,
|
|
229
|
+
toProcessEnv,
|
|
230
|
+
withMcpSource
|
|
231
|
+
} from "./chunk-47GWMUCR.js";
|
|
232
|
+
import {
|
|
233
|
+
McpConfigSchema,
|
|
234
|
+
McpInvokeSchema,
|
|
235
|
+
McpSourceSchema,
|
|
236
|
+
McpWhenSchema,
|
|
237
|
+
addMcpSource,
|
|
238
|
+
defaultMcpConfig,
|
|
239
|
+
ensureMcpConfig,
|
|
240
|
+
interpolate,
|
|
241
|
+
interpolateArgs,
|
|
242
|
+
loadMcpConfig,
|
|
243
|
+
mcpConfigPath,
|
|
244
|
+
removeMcpSource,
|
|
245
|
+
saveMcpConfig
|
|
246
|
+
} from "./chunk-4BRO7BOJ.js";
|
|
243
247
|
import {
|
|
244
248
|
SDD_DIR,
|
|
245
249
|
activePointerPath,
|
|
@@ -1395,7 +1399,7 @@ ${output}`);
|
|
|
1395
1399
|
status = failed.length ? "fail" : "pass";
|
|
1396
1400
|
reason = failed.length ? `failed: ${failed.join(", ")}` : void 0;
|
|
1397
1401
|
}
|
|
1398
|
-
const
|
|
1402
|
+
const ok12 = status === "pass";
|
|
1399
1403
|
const checklist = stage.gate?.checklist ?? [];
|
|
1400
1404
|
const lines = buildSummaryLines(
|
|
1401
1405
|
ctx.meta.title,
|
|
@@ -1446,7 +1450,7 @@ ${output}`);
|
|
|
1446
1450
|
ctx.meta.verify_results = {
|
|
1447
1451
|
...ctx.meta.verify_results,
|
|
1448
1452
|
[stage.id]: {
|
|
1449
|
-
ok:
|
|
1453
|
+
ok: ok12,
|
|
1450
1454
|
status,
|
|
1451
1455
|
...results.length && headSha2 ? { head_sha: headSha2 } : {},
|
|
1452
1456
|
at: nowIso(),
|
|
@@ -1462,7 +1466,7 @@ ${output}`);
|
|
|
1462
1466
|
checklist,
|
|
1463
1467
|
evidencePath: evidenceDir,
|
|
1464
1468
|
resultsPath: summaryPath,
|
|
1465
|
-
ok:
|
|
1469
|
+
ok: ok12
|
|
1466
1470
|
};
|
|
1467
1471
|
}
|
|
1468
1472
|
|
|
@@ -1875,10 +1879,10 @@ function formatMarkdown(input) {
|
|
|
1875
1879
|
`;
|
|
1876
1880
|
return { markdown: packed ? frame + packed : frame, truncated: input.summary.truncated };
|
|
1877
1881
|
}
|
|
1878
|
-
function formatJsonSummary(summary, gaps, slices,
|
|
1882
|
+
function formatJsonSummary(summary, gaps, slices, ok12) {
|
|
1879
1883
|
return JSON.stringify(
|
|
1880
1884
|
{
|
|
1881
|
-
ok:
|
|
1885
|
+
ok: ok12,
|
|
1882
1886
|
summary,
|
|
1883
1887
|
gaps,
|
|
1884
1888
|
slices: slices.map((s) => ({
|
|
@@ -3562,6 +3566,43 @@ function revisionPrompt(role, draft, gaps) {
|
|
|
3562
3566
|
"changed must list every gap cite."
|
|
3563
3567
|
].join("\n");
|
|
3564
3568
|
}
|
|
3569
|
+
function guideDraftPrompt(input) {
|
|
3570
|
+
const rows = [`Job: ${input.role.job}`, `Description: ${input.role.description}`];
|
|
3571
|
+
if (input.role.stages.length > 0) rows.push(`Stages: ${input.role.stages.join(", ")}`);
|
|
3572
|
+
if (input.role.owns.length > 0) rows.push(`Owns: ${input.role.owns.join(", ")}`);
|
|
3573
|
+
if (input.role.skills.length > 0) rows.push(`Skills: ${input.role.skills.join(", ")}`);
|
|
3574
|
+
if (input.role.never.length > 0) rows.push(`Never: ${input.role.never.join(", ")}`);
|
|
3575
|
+
if (input.role.askHumanWhen.length > 0) {
|
|
3576
|
+
rows.push(`Ask when: ${input.role.askHumanWhen.join(", ")}`);
|
|
3577
|
+
}
|
|
3578
|
+
if (rows.length > 2) rows.push("Stay inside this role.");
|
|
3579
|
+
rows.push(`Request: ${input.request}`);
|
|
3580
|
+
if (input.answers.length > 0) {
|
|
3581
|
+
rows.push("Answers:");
|
|
3582
|
+
for (const item of input.answers) rows.push(`- ${item.question}: ${item.answer}`);
|
|
3583
|
+
}
|
|
3584
|
+
rows.push(`Stage: ${input.stageId}`);
|
|
3585
|
+
if (input.summary.trim()) rows.push(`Summary: ${input.summary.trim()}`);
|
|
3586
|
+
if (input.instructions.trim()) rows.push(`Instructions:
|
|
3587
|
+
${input.instructions.trim()}`);
|
|
3588
|
+
rows.push(`Artifact: ${input.artifactPath}`);
|
|
3589
|
+
if (input.headings.length > 0) {
|
|
3590
|
+
rows.push("Headings:");
|
|
3591
|
+
for (const heading of input.headings) rows.push(`- ${heading}`);
|
|
3592
|
+
}
|
|
3593
|
+
if (input.earlier.length > 0) {
|
|
3594
|
+
rows.push("Earlier:");
|
|
3595
|
+
for (const file of input.earlier) rows.push(`## ${file.path}`, file.body.trim());
|
|
3596
|
+
}
|
|
3597
|
+
rows.push('If one fact is missing, reply with JSON {"text":"Question: ..."}');
|
|
3598
|
+
rows.push('Otherwise reply with JSON {"text":"...","parts":[{"heading":"...","text":"..."}]}');
|
|
3599
|
+
rows.push("Use only the headings listed above.");
|
|
3600
|
+
rows.push("Put this artifact's words under those headings.");
|
|
3601
|
+
rows.push("Do not rename a heading.");
|
|
3602
|
+
rows.push("Do not paste an earlier artifact.");
|
|
3603
|
+
rows.push("Do not name a module, a path, or a command result that is not in this prompt.");
|
|
3604
|
+
return rows.join("\n");
|
|
3605
|
+
}
|
|
3565
3606
|
|
|
3566
3607
|
// src/team/tools.ts
|
|
3567
3608
|
import { spawn as spawn4 } from "child_process";
|
|
@@ -3608,8 +3649,8 @@ function asRecord(value) {
|
|
|
3608
3649
|
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
3609
3650
|
return value;
|
|
3610
3651
|
}
|
|
3611
|
-
function result(name,
|
|
3612
|
-
return { ok:
|
|
3652
|
+
function result(name, ok12, text2) {
|
|
3653
|
+
return { ok: ok12, name, text: clip(text2) };
|
|
3613
3654
|
}
|
|
3614
3655
|
async function git(cwd, args) {
|
|
3615
3656
|
return new Promise((resolve) => {
|
|
@@ -6139,6 +6180,176 @@ async function pickLead(root, all, services) {
|
|
|
6139
6180
|
return { kind: "none" };
|
|
6140
6181
|
}
|
|
6141
6182
|
|
|
6183
|
+
// src/role/connector.ts
|
|
6184
|
+
import { spawn as spawn5 } from "child_process";
|
|
6185
|
+
var X_LINE = "A connector cannot read or post on X.";
|
|
6186
|
+
var NOT_IN_SLICE = "That connector is not in this slice.";
|
|
6187
|
+
var DOES_NOT_RUN = "That connector action does not run in this slice.";
|
|
6188
|
+
var BAD_NUMBER = "A GitHub issue number is a positive integer.";
|
|
6189
|
+
var QA_LINE = "A routine cannot write a QA result.";
|
|
6190
|
+
var BAD_ROUTINES = "Routines on this role are invalid.";
|
|
6191
|
+
var NO_ROUTINE = "That routine is not on this role.";
|
|
6192
|
+
var READ_FIELDS = /* @__PURE__ */ new Set(["name", "read", "record"]);
|
|
6193
|
+
function ok9(stdout) {
|
|
6194
|
+
return { exitCode: 0, stdout };
|
|
6195
|
+
}
|
|
6196
|
+
function fail9(stdout) {
|
|
6197
|
+
return { exitCode: 1, stdout };
|
|
6198
|
+
}
|
|
6199
|
+
function positiveInteger(record) {
|
|
6200
|
+
if (!/^[1-9][0-9]*$/.test(record)) return null;
|
|
6201
|
+
const number = Number(record);
|
|
6202
|
+
if (!Number.isSafeInteger(number)) return null;
|
|
6203
|
+
return number;
|
|
6204
|
+
}
|
|
6205
|
+
function missingLine(number) {
|
|
6206
|
+
return `GitHub issue ${number} was not found.`;
|
|
6207
|
+
}
|
|
6208
|
+
function unreadLine(number) {
|
|
6209
|
+
return `GitHub issue ${number} could not be read.`;
|
|
6210
|
+
}
|
|
6211
|
+
function cleanLine(value) {
|
|
6212
|
+
if (typeof value !== "string") return null;
|
|
6213
|
+
const text2 = value.trim();
|
|
6214
|
+
if (!text2 || text2.includes("\n") || text2.includes("\r")) return null;
|
|
6215
|
+
return text2;
|
|
6216
|
+
}
|
|
6217
|
+
function ghIssueSource() {
|
|
6218
|
+
return {
|
|
6219
|
+
async read(number) {
|
|
6220
|
+
const run = await spawnGh(number);
|
|
6221
|
+
if (!run.ok) return { ok: false, missing: run.missing };
|
|
6222
|
+
let parsed;
|
|
6223
|
+
try {
|
|
6224
|
+
parsed = JSON.parse(run.stdout);
|
|
6225
|
+
} catch {
|
|
6226
|
+
return { ok: false, missing: false };
|
|
6227
|
+
}
|
|
6228
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
6229
|
+
return { ok: false, missing: false };
|
|
6230
|
+
}
|
|
6231
|
+
const rec = parsed;
|
|
6232
|
+
const title = cleanLine(rec.title);
|
|
6233
|
+
const state = cleanLine(rec.state);
|
|
6234
|
+
const found = typeof rec.number === "number" ? rec.number : Number(rec.number);
|
|
6235
|
+
if (found !== number || !title || !state) return { ok: false, missing: false };
|
|
6236
|
+
return { ok: true, issue: { number, title, state } };
|
|
6237
|
+
}
|
|
6238
|
+
};
|
|
6239
|
+
}
|
|
6240
|
+
function spawnGh(number) {
|
|
6241
|
+
return new Promise((resolve) => {
|
|
6242
|
+
const child = spawn5("gh", ["issue", "view", String(number), "--json", "number,title,state"], {
|
|
6243
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
6244
|
+
});
|
|
6245
|
+
const out = [];
|
|
6246
|
+
const err = [];
|
|
6247
|
+
child.stdout?.on("data", (chunk) => out.push(chunk));
|
|
6248
|
+
child.stderr?.on("data", (chunk) => err.push(chunk));
|
|
6249
|
+
child.on("error", () => resolve({ ok: false, missing: false }));
|
|
6250
|
+
child.on("close", (code) => {
|
|
6251
|
+
const stdout = Buffer.concat(out).toString("utf8");
|
|
6252
|
+
const stderr = Buffer.concat(err).toString("utf8");
|
|
6253
|
+
if (code === 0) {
|
|
6254
|
+
resolve({ ok: true, stdout });
|
|
6255
|
+
return;
|
|
6256
|
+
}
|
|
6257
|
+
const text2 = `${stdout}
|
|
6258
|
+
${stderr}`.toLowerCase();
|
|
6259
|
+
resolve({
|
|
6260
|
+
ok: false,
|
|
6261
|
+
missing: text2.includes("could not resolve") || text2.includes("not found")
|
|
6262
|
+
});
|
|
6263
|
+
});
|
|
6264
|
+
});
|
|
6265
|
+
}
|
|
6266
|
+
function threeLines(issue) {
|
|
6267
|
+
return `${issue.number}
|
|
6268
|
+
${issue.title}
|
|
6269
|
+
${issue.state}`;
|
|
6270
|
+
}
|
|
6271
|
+
async function readConnector(input) {
|
|
6272
|
+
const verb = input.verb.trim();
|
|
6273
|
+
const connector = input.connector.trim();
|
|
6274
|
+
const record = input.record.trim();
|
|
6275
|
+
if (connector === "x") return fail9(X_LINE);
|
|
6276
|
+
if (verb !== "read") return fail9(DOES_NOT_RUN);
|
|
6277
|
+
if (connector !== "github-issue") return fail9(NOT_IN_SLICE);
|
|
6278
|
+
const number = positiveInteger(record);
|
|
6279
|
+
if (number === null) return fail9(BAD_NUMBER);
|
|
6280
|
+
const source = input.source ?? ghIssueSource();
|
|
6281
|
+
let read;
|
|
6282
|
+
try {
|
|
6283
|
+
read = await source.read(number);
|
|
6284
|
+
} catch {
|
|
6285
|
+
return fail9(unreadLine(number));
|
|
6286
|
+
}
|
|
6287
|
+
if (!read.ok) return fail9(read.missing ? missingLine(number) : unreadLine(number));
|
|
6288
|
+
if (read.issue.number !== number) return fail9(unreadLine(number));
|
|
6289
|
+
const title = cleanLine(read.issue.title);
|
|
6290
|
+
const state = cleanLine(read.issue.state);
|
|
6291
|
+
if (!title || !state) return fail9(unreadLine(number));
|
|
6292
|
+
return ok9(threeLines({ number, title, state }));
|
|
6293
|
+
}
|
|
6294
|
+
function classifyRoutines(value) {
|
|
6295
|
+
if (!Array.isArray(value)) return { ok: false, reason: "invalid" };
|
|
6296
|
+
for (const item of value) {
|
|
6297
|
+
if (item && typeof item === "object" && !Array.isArray(item) && Object.hasOwn(item, "write")) {
|
|
6298
|
+
return { ok: false, reason: "qa" };
|
|
6299
|
+
}
|
|
6300
|
+
}
|
|
6301
|
+
const items = [];
|
|
6302
|
+
const names = /* @__PURE__ */ new Set();
|
|
6303
|
+
for (const item of value) {
|
|
6304
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) {
|
|
6305
|
+
return { ok: false, reason: "invalid" };
|
|
6306
|
+
}
|
|
6307
|
+
const rec = item;
|
|
6308
|
+
const keys = Object.keys(rec);
|
|
6309
|
+
if (keys.length !== 3 || keys.some((key) => !READ_FIELDS.has(key))) {
|
|
6310
|
+
return { ok: false, reason: "invalid" };
|
|
6311
|
+
}
|
|
6312
|
+
const name = rec.name;
|
|
6313
|
+
const read = rec.read;
|
|
6314
|
+
const record = rec.record;
|
|
6315
|
+
if (typeof name !== "string" || typeof read !== "string" || typeof record !== "string") {
|
|
6316
|
+
return { ok: false, reason: "invalid" };
|
|
6317
|
+
}
|
|
6318
|
+
const trimmed = { name: name.trim(), read: read.trim(), record: record.trim() };
|
|
6319
|
+
if (!trimmed.name || !trimmed.read || !trimmed.record || names.has(trimmed.name)) {
|
|
6320
|
+
return { ok: false, reason: "invalid" };
|
|
6321
|
+
}
|
|
6322
|
+
names.add(trimmed.name);
|
|
6323
|
+
items.push(trimmed);
|
|
6324
|
+
}
|
|
6325
|
+
return { ok: true, items };
|
|
6326
|
+
}
|
|
6327
|
+
async function runRoutine(root, roleName, routineName, source) {
|
|
6328
|
+
const name = canonicalRoleName(roleName);
|
|
6329
|
+
if (!name) return fail9(ROLE_TEXT.notFound);
|
|
6330
|
+
const path = roleFilePath(root, name);
|
|
6331
|
+
if (!await pathExists(path)) return fail9(ROLE_TEXT.notFound);
|
|
6332
|
+
let raw = "";
|
|
6333
|
+
try {
|
|
6334
|
+
raw = await readText(path);
|
|
6335
|
+
} catch {
|
|
6336
|
+
return fail9(ROLE_TEXT.notFound);
|
|
6337
|
+
}
|
|
6338
|
+
const split = splitFrontmatter(raw);
|
|
6339
|
+
if (!split || !Object.hasOwn(split.data, "routines")) return fail9(NO_ROUTINE);
|
|
6340
|
+
const classified = classifyRoutines(split.data.routines);
|
|
6341
|
+
if (!classified.ok) return fail9(classified.reason === "qa" ? QA_LINE : BAD_ROUTINES);
|
|
6342
|
+
const routine = classified.items.find((item) => item.name === routineName.trim());
|
|
6343
|
+
if (!routine) return fail9(NO_ROUTINE);
|
|
6344
|
+
if (routine.read === "x") return fail9(X_LINE);
|
|
6345
|
+
return readConnector({
|
|
6346
|
+
verb: "read",
|
|
6347
|
+
connector: routine.read,
|
|
6348
|
+
record: routine.record,
|
|
6349
|
+
source
|
|
6350
|
+
});
|
|
6351
|
+
}
|
|
6352
|
+
|
|
6142
6353
|
// src/role/guide.ts
|
|
6143
6354
|
import { join as join18 } from "pathe";
|
|
6144
6355
|
var PLANNING = /* @__PURE__ */ new Set([
|
|
@@ -6205,10 +6416,10 @@ var DUTIES = [
|
|
|
6205
6416
|
description: "The builder builds the change. The person decides."
|
|
6206
6417
|
}
|
|
6207
6418
|
];
|
|
6208
|
-
function
|
|
6419
|
+
function ok10(stdout) {
|
|
6209
6420
|
return { exitCode: 0, stdout };
|
|
6210
6421
|
}
|
|
6211
|
-
function
|
|
6422
|
+
function fail10(stdout) {
|
|
6212
6423
|
return { exitCode: 1, stdout };
|
|
6213
6424
|
}
|
|
6214
6425
|
function statePath(root) {
|
|
@@ -6293,15 +6504,6 @@ function nextRoleQuestion(role, answers) {
|
|
|
6293
6504
|
);
|
|
6294
6505
|
return role.askHumanWhen.find((item) => !answered.has(item));
|
|
6295
6506
|
}
|
|
6296
|
-
function taskFor(request, role, answers) {
|
|
6297
|
-
const rows = [`Request: ${request}`];
|
|
6298
|
-
const mine = answers.filter((item) => item.role === role);
|
|
6299
|
-
if (mine.length > 0) {
|
|
6300
|
-
rows.push("Answers:");
|
|
6301
|
-
for (const item of mine) rows.push(`- ${item.question}: ${item.answer}`);
|
|
6302
|
-
}
|
|
6303
|
-
return rows.join("\n");
|
|
6304
|
-
}
|
|
6305
6507
|
async function roleFiles(root) {
|
|
6306
6508
|
const names = await listFiles(join18(root, ".sdd", "roles"));
|
|
6307
6509
|
return names.filter((name) => name.endsWith(".md"));
|
|
@@ -6333,35 +6535,218 @@ async function prepareGuide(root, input = {}) {
|
|
|
6333
6535
|
}
|
|
6334
6536
|
}
|
|
6335
6537
|
lines.push('Next: sdd go "what you want to build"');
|
|
6336
|
-
return
|
|
6538
|
+
return ok10(lines.join("\n"));
|
|
6337
6539
|
} catch (error) {
|
|
6338
|
-
return
|
|
6540
|
+
return fail10(error instanceof Error ? error.message : String(error));
|
|
6339
6541
|
}
|
|
6340
6542
|
}
|
|
6341
|
-
|
|
6342
|
-
|
|
6543
|
+
function partText(text2) {
|
|
6544
|
+
return text2.split(/\r?\n/).filter((line) => h2Title(line) === null).join("\n").trim();
|
|
6545
|
+
}
|
|
6546
|
+
function draftHeader(stage, role) {
|
|
6547
|
+
return [
|
|
6343
6548
|
`# ${stage.title ?? stage.id}`,
|
|
6344
6549
|
"",
|
|
6345
6550
|
`${role} drafted this stage. The person has not signed it.`,
|
|
6346
|
-
"",
|
|
6347
|
-
text2.trim(),
|
|
6348
|
-
"",
|
|
6349
|
-
`Request: ${request}`,
|
|
6350
6551
|
""
|
|
6351
|
-
]
|
|
6352
|
-
|
|
6353
|
-
|
|
6354
|
-
|
|
6355
|
-
|
|
6356
|
-
|
|
6357
|
-
|
|
6552
|
+
];
|
|
6553
|
+
}
|
|
6554
|
+
function plainBody(stage, role, request, text2) {
|
|
6555
|
+
return [...draftHeader(stage, role), text2.trim(), "", `Request: ${request}`, ""].join("\n");
|
|
6556
|
+
}
|
|
6557
|
+
function headedBody(stage, role, request, headings, parts) {
|
|
6558
|
+
const matched = /* @__PURE__ */ new Map();
|
|
6559
|
+
for (const part of parts) {
|
|
6560
|
+
const heading = part.heading.trim();
|
|
6561
|
+
const text2 = partText(part.text);
|
|
6562
|
+
if (!headings.includes(heading) || !text2) continue;
|
|
6563
|
+
const list = matched.get(heading) ?? [];
|
|
6564
|
+
list.push(text2);
|
|
6565
|
+
matched.set(heading, list);
|
|
6566
|
+
}
|
|
6567
|
+
const rows = draftHeader(stage, role);
|
|
6568
|
+
for (const heading of headings) {
|
|
6569
|
+
rows.push(`## ${heading}`, "", (matched.get(heading) ?? []).join("\n\n"), "");
|
|
6570
|
+
}
|
|
6571
|
+
rows.push(`Request: ${request}`, "");
|
|
6572
|
+
return rows.join("\n");
|
|
6573
|
+
}
|
|
6574
|
+
var RESULTS_LINES = {
|
|
6575
|
+
Results: "No command output is stored for this change.",
|
|
6576
|
+
"Commands run": "This guide did not run a verify command.",
|
|
6577
|
+
"Sign-off": "The person has not signed it."
|
|
6578
|
+
};
|
|
6579
|
+
function resultsBody(stage, role, request, headings) {
|
|
6580
|
+
if (headings.length === 0) {
|
|
6581
|
+
return plainBody(stage, role, request, Object.values(RESULTS_LINES).join(" "));
|
|
6582
|
+
}
|
|
6583
|
+
return headedBody(
|
|
6584
|
+
stage,
|
|
6585
|
+
role,
|
|
6586
|
+
request,
|
|
6587
|
+
headings,
|
|
6588
|
+
headings.map((heading) => ({
|
|
6589
|
+
heading,
|
|
6590
|
+
text: RESULTS_LINES[heading] ?? RESULTS_LINES.Results
|
|
6591
|
+
}))
|
|
6592
|
+
);
|
|
6593
|
+
}
|
|
6594
|
+
function parseGuideReply(raw) {
|
|
6595
|
+
let value;
|
|
6596
|
+
try {
|
|
6597
|
+
value = JSON.parse(raw);
|
|
6598
|
+
} catch {
|
|
6599
|
+
return null;
|
|
6600
|
+
}
|
|
6601
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
6602
|
+
const rec = value;
|
|
6603
|
+
if (typeof rec.text !== "string") return null;
|
|
6604
|
+
if (rec.parts === void 0) return { text: rec.text, parts: [] };
|
|
6605
|
+
if (!Array.isArray(rec.parts)) return null;
|
|
6606
|
+
const parts = [];
|
|
6607
|
+
for (const part of rec.parts) {
|
|
6608
|
+
if (!part || typeof part !== "object" || Array.isArray(part)) return null;
|
|
6609
|
+
const item = part;
|
|
6610
|
+
if (typeof item.heading !== "string" || typeof item.text !== "string") return null;
|
|
6611
|
+
parts.push({ heading: item.heading, text: item.text });
|
|
6612
|
+
}
|
|
6613
|
+
return { text: rec.text, parts };
|
|
6614
|
+
}
|
|
6615
|
+
function missingHeading(content, headings) {
|
|
6616
|
+
for (const heading of headings) {
|
|
6617
|
+
if (!headingsFilled(content, [heading])) return heading;
|
|
6618
|
+
}
|
|
6619
|
+
return null;
|
|
6620
|
+
}
|
|
6621
|
+
function isResultsFile(path) {
|
|
6622
|
+
const name = path.replace(/\\/g, "/");
|
|
6623
|
+
return name === "local-test-results.md" || name.endsWith("/local-test-results.md");
|
|
6624
|
+
}
|
|
6625
|
+
async function templateHeadings(root, template) {
|
|
6626
|
+
const name = template?.trim();
|
|
6627
|
+
if (!name) return [];
|
|
6628
|
+
const path = join18(templatesDir(root), name.replace(/^templates\//, ""));
|
|
6629
|
+
if (!await pathExists(path)) return [];
|
|
6630
|
+
const headings = [];
|
|
6631
|
+
for (const line of (await readText(path)).split(/\r?\n/)) {
|
|
6632
|
+
const title = h2Title(line);
|
|
6633
|
+
if (title) headings.push(title);
|
|
6634
|
+
}
|
|
6635
|
+
return headings;
|
|
6636
|
+
}
|
|
6637
|
+
async function artifactReady(abs, headings) {
|
|
6638
|
+
if (!await pathExists(abs)) return false;
|
|
6639
|
+
const current = await readText(abs);
|
|
6640
|
+
if (!isSubstantiveArtifactContent(current)) return false;
|
|
6641
|
+
return headings.length === 0 || headingsFilled(current, headings);
|
|
6642
|
+
}
|
|
6643
|
+
async function readyArtifacts(changeDir, stages, currentPath) {
|
|
6644
|
+
const files = [];
|
|
6645
|
+
for (const stage of stages) {
|
|
6646
|
+
for (const artifact of stage.artifacts) {
|
|
6647
|
+
if (artifact.required === false || artifact.path === currentPath) continue;
|
|
6648
|
+
const abs = join18(changeDir, artifact.path);
|
|
6649
|
+
if (!await pathExists(abs)) continue;
|
|
6650
|
+
const body = await readText(abs);
|
|
6651
|
+
if (!isSubstantiveArtifactContent(body)) continue;
|
|
6652
|
+
files.push({ path: artifact.path, body });
|
|
6358
6653
|
}
|
|
6359
|
-
await writeText(abs, body);
|
|
6360
6654
|
}
|
|
6655
|
+
return files;
|
|
6656
|
+
}
|
|
6657
|
+
async function writeOne(abs, body) {
|
|
6658
|
+
await ensureDir(join18(abs, ".."));
|
|
6659
|
+
await writeText(abs, body);
|
|
6361
6660
|
}
|
|
6362
6661
|
function optionalStage(stage) {
|
|
6363
6662
|
return stage.optional || stage.kind === "clarify" || stage.kind === "brainstorm";
|
|
6364
6663
|
}
|
|
6664
|
+
async function leaveStage(root, config, changeId, stageId, roleName, drafted, lines, state) {
|
|
6665
|
+
const note = drafted ? `${roleName} drafted ${stageId}.` : `${roleName} left ${stageId}.`;
|
|
6666
|
+
try {
|
|
6667
|
+
const advanced = await advanceStage(root, config, changeId);
|
|
6668
|
+
lines.push(note);
|
|
6669
|
+
if (!advanced.to) {
|
|
6670
|
+
await writeState(root, state);
|
|
6671
|
+
return ok10(
|
|
6672
|
+
[
|
|
6673
|
+
"Done.",
|
|
6674
|
+
...lines,
|
|
6675
|
+
"No further stages. Run `sdd complete` when ready.",
|
|
6676
|
+
"The person has not signed this change."
|
|
6677
|
+
].join("\n")
|
|
6678
|
+
);
|
|
6679
|
+
}
|
|
6680
|
+
return "next";
|
|
6681
|
+
} catch (error) {
|
|
6682
|
+
await writeState(root, state);
|
|
6683
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6684
|
+
return ok10(["Stopped.", note, message, ...lines].join("\n"));
|
|
6685
|
+
}
|
|
6686
|
+
}
|
|
6687
|
+
async function draftStage(root, config, ctx, state, stage, role, required, input, lines) {
|
|
6688
|
+
if (required.length === 0) {
|
|
6689
|
+
return leaveStage(root, config, ctx.id, stage.id, role.name, false, lines, state);
|
|
6690
|
+
}
|
|
6691
|
+
const answers = state.answers.filter((item) => item.role === role.name);
|
|
6692
|
+
for (const artifact of required) {
|
|
6693
|
+
const headings = await templateHeadings(root, artifact.template);
|
|
6694
|
+
const abs = join18(ctx.path, artifact.path);
|
|
6695
|
+
if (await artifactReady(abs, headings)) continue;
|
|
6696
|
+
if (isResultsFile(artifact.path)) {
|
|
6697
|
+
await writeOne(abs, resultsBody(stage, role.name, state.request, headings));
|
|
6698
|
+
continue;
|
|
6699
|
+
}
|
|
6700
|
+
if (!input.model) return fail10(ROLE_TEXT.noModel);
|
|
6701
|
+
let raw = "";
|
|
6702
|
+
try {
|
|
6703
|
+
raw = await input.model.complete({
|
|
6704
|
+
roleId: role.name,
|
|
6705
|
+
prompt: guideDraftPrompt({
|
|
6706
|
+
role,
|
|
6707
|
+
request: state.request,
|
|
6708
|
+
answers,
|
|
6709
|
+
stageId: stage.id,
|
|
6710
|
+
summary: stage.summary ?? "",
|
|
6711
|
+
instructions: stage.agent_context?.instructions ?? "",
|
|
6712
|
+
artifactPath: artifact.path,
|
|
6713
|
+
headings,
|
|
6714
|
+
earlier: await readyArtifacts(ctx.path, ctx.workflow.stages, artifact.path)
|
|
6715
|
+
})
|
|
6716
|
+
});
|
|
6717
|
+
} catch {
|
|
6718
|
+
return fail10("A draft is not valid.");
|
|
6719
|
+
}
|
|
6720
|
+
const parsed = parseGuideReply(raw);
|
|
6721
|
+
if (!parsed) return fail10("A draft is not valid.");
|
|
6722
|
+
const asked = parsed.text.trim();
|
|
6723
|
+
if (asked.startsWith("Question:")) {
|
|
6724
|
+
state.pending = {
|
|
6725
|
+
role: role.name,
|
|
6726
|
+
kind: "model",
|
|
6727
|
+
question: asked.replace(/^Question:\s*/, "").trim() || asked
|
|
6728
|
+
};
|
|
6729
|
+
await writeState(root, state);
|
|
6730
|
+
return ok10(
|
|
6731
|
+
[
|
|
6732
|
+
"Question:",
|
|
6733
|
+
...lines,
|
|
6734
|
+
`${role.name} asks:`,
|
|
6735
|
+
state.pending.question,
|
|
6736
|
+
'Answer with: sdd go --answer "..."'
|
|
6737
|
+
].join("\n")
|
|
6738
|
+
);
|
|
6739
|
+
}
|
|
6740
|
+
const body = headings.length > 0 ? headedBody(stage, role.name, state.request, headings, parsed.parts) : plainBody(stage, role.name, state.request, parsed.text);
|
|
6741
|
+
const missing = headings.length > 0 ? missingHeading(body, headings) : null;
|
|
6742
|
+
if (missing) {
|
|
6743
|
+
await writeState(root, state);
|
|
6744
|
+
return ok10(["Stopped.", `${missing} is still empty.`, ...lines].join("\n"));
|
|
6745
|
+
}
|
|
6746
|
+
await writeOne(abs, body);
|
|
6747
|
+
}
|
|
6748
|
+
return leaveStage(root, config, ctx.id, stage.id, role.name, true, lines, state);
|
|
6749
|
+
}
|
|
6365
6750
|
async function runGuide(root, input = {}) {
|
|
6366
6751
|
const prepared = await prepareGuide(root, {
|
|
6367
6752
|
workflow: input.workflow,
|
|
@@ -6370,14 +6755,14 @@ async function runGuide(root, input = {}) {
|
|
|
6370
6755
|
if (prepared.exitCode !== 0) return prepared;
|
|
6371
6756
|
const title = input.title?.trim() ?? "";
|
|
6372
6757
|
const answer = input.answer?.trim() ?? "";
|
|
6373
|
-
if (input.answer !== void 0 && !answer) return
|
|
6758
|
+
if (input.answer !== void 0 && !answer) return fail10("An answer needs text.");
|
|
6374
6759
|
let loaded;
|
|
6375
6760
|
try {
|
|
6376
6761
|
loaded = await readState(root);
|
|
6377
6762
|
} catch (error) {
|
|
6378
|
-
return
|
|
6763
|
+
return fail10(error instanceof Error ? error.message : String(error));
|
|
6379
6764
|
}
|
|
6380
|
-
if (loaded === "invalid") return
|
|
6765
|
+
if (loaded === "invalid") return fail10("The guide file is invalid.");
|
|
6381
6766
|
const config = await loadConfig(root);
|
|
6382
6767
|
let state = loaded;
|
|
6383
6768
|
if (title) {
|
|
@@ -6391,7 +6776,7 @@ async function runGuide(root, input = {}) {
|
|
|
6391
6776
|
state = { change: ctx.id, request: title, answers: [] };
|
|
6392
6777
|
await writeState(root, state);
|
|
6393
6778
|
} catch (error) {
|
|
6394
|
-
return
|
|
6779
|
+
return fail10(error instanceof Error ? error.message : String(error));
|
|
6395
6780
|
}
|
|
6396
6781
|
} else if (!state) {
|
|
6397
6782
|
const active = await getActiveChangeId(root, config);
|
|
@@ -6400,10 +6785,10 @@ async function runGuide(root, input = {}) {
|
|
|
6400
6785
|
const ctx = await buildContext(root, config, active);
|
|
6401
6786
|
state = { change: active, request: ctx.meta.title, answers: [] };
|
|
6402
6787
|
} catch (error) {
|
|
6403
|
-
return
|
|
6788
|
+
return fail10(error instanceof Error ? error.message : String(error));
|
|
6404
6789
|
}
|
|
6405
6790
|
}
|
|
6406
|
-
if (answer && !state.pending) return
|
|
6791
|
+
if (answer && !state.pending) return fail10("No question is waiting.");
|
|
6407
6792
|
if (answer && state.pending) {
|
|
6408
6793
|
state.answers.push({
|
|
6409
6794
|
role: state.pending.role,
|
|
@@ -6413,7 +6798,7 @@ async function runGuide(root, input = {}) {
|
|
|
6413
6798
|
state.pending = void 0;
|
|
6414
6799
|
await writeState(root, state);
|
|
6415
6800
|
} else if (state.pending) {
|
|
6416
|
-
return
|
|
6801
|
+
return ok10(questionText(state.pending.role, state.pending.question));
|
|
6417
6802
|
}
|
|
6418
6803
|
const lines = [];
|
|
6419
6804
|
let lastRole = "";
|
|
@@ -6422,35 +6807,35 @@ async function runGuide(root, input = {}) {
|
|
|
6422
6807
|
try {
|
|
6423
6808
|
ctx = await buildContext(root, config, state.change);
|
|
6424
6809
|
} catch (error) {
|
|
6425
|
-
return
|
|
6810
|
+
return fail10(error instanceof Error ? error.message : String(error));
|
|
6426
6811
|
}
|
|
6427
6812
|
const stage = getStage(ctx.workflow, ctx.meta, ctx.meta.stage);
|
|
6428
|
-
if (!stage) return
|
|
6813
|
+
if (!stage) return fail10("No active change.");
|
|
6429
6814
|
if (optionalStage(stage) && stage.skippable) {
|
|
6430
6815
|
const before = ctx.meta.stage;
|
|
6431
6816
|
try {
|
|
6432
6817
|
await skipStage(root, config, ctx.id, stage.id, "No role runs this optional stage.");
|
|
6433
6818
|
} catch (error) {
|
|
6434
|
-
return
|
|
6819
|
+
return fail10(error instanceof Error ? error.message : String(error));
|
|
6435
6820
|
}
|
|
6436
6821
|
const after = await buildContext(root, config, state.change);
|
|
6437
|
-
if (after.meta.stage === before) return
|
|
6822
|
+
if (after.meta.stage === before) return fail10("This stage stayed put.");
|
|
6438
6823
|
lines.push(`Skipped ${stage.id}.`);
|
|
6439
6824
|
continue;
|
|
6440
6825
|
}
|
|
6441
6826
|
const routed = await matchRoute(root, { stage: stage.id });
|
|
6442
|
-
if (!routed.ok) return
|
|
6827
|
+
if (!routed.ok) return fail10(routed.message);
|
|
6443
6828
|
if (routed.match.kind === "ask") {
|
|
6444
|
-
return
|
|
6829
|
+
return ok10(["Stopped.", "Ask the human.", routed.match.reason, ...lines].join("\n"));
|
|
6445
6830
|
}
|
|
6446
6831
|
const role = await readRole(root, routed.match.role);
|
|
6447
|
-
if (!role) return
|
|
6832
|
+
if (!role) return fail10(ROLE_TEXT.notFound);
|
|
6448
6833
|
if (lastRole && lastRole !== role.name) lines.push(`Handoff: ${lastRole} to ${role.name}.`);
|
|
6449
6834
|
const question = nextRoleQuestion(role, state.answers);
|
|
6450
6835
|
if (question) {
|
|
6451
6836
|
state.pending = { role: role.name, kind: "role", question };
|
|
6452
6837
|
await writeState(root, state);
|
|
6453
|
-
return
|
|
6838
|
+
return ok10(
|
|
6454
6839
|
[
|
|
6455
6840
|
"Question:",
|
|
6456
6841
|
...lines,
|
|
@@ -6460,55 +6845,136 @@ async function runGuide(root, input = {}) {
|
|
|
6460
6845
|
].join("\n")
|
|
6461
6846
|
);
|
|
6462
6847
|
}
|
|
6463
|
-
|
|
6464
|
-
const
|
|
6465
|
-
|
|
6466
|
-
|
|
6467
|
-
|
|
6468
|
-
|
|
6469
|
-
|
|
6470
|
-
|
|
6471
|
-
|
|
6472
|
-
|
|
6848
|
+
const required = stage.artifacts.filter((artifact) => artifact.required !== false);
|
|
6849
|
+
const moved = await draftStage(root, config, ctx, state, stage, role, required, input, lines);
|
|
6850
|
+
if (moved !== "next") return moved;
|
|
6851
|
+
lastRole = role.name;
|
|
6852
|
+
}
|
|
6853
|
+
return fail10("The guide stopped after 16 steps.");
|
|
6854
|
+
}
|
|
6855
|
+
|
|
6856
|
+
// src/role/setup-tour.ts
|
|
6857
|
+
import { join as join19 } from "pathe";
|
|
6858
|
+
var SETUP_STEP_IDS = [
|
|
6859
|
+
"folder",
|
|
6860
|
+
"workflow",
|
|
6861
|
+
"host",
|
|
6862
|
+
"roles",
|
|
6863
|
+
"model",
|
|
6864
|
+
"check",
|
|
6865
|
+
"start"
|
|
6866
|
+
];
|
|
6867
|
+
var SETUP_WHY = {
|
|
6868
|
+
folder: "sdd reads .sdd/config.yaml first.",
|
|
6869
|
+
workflow: "Stage lists on new role files come from this workflow.",
|
|
6870
|
+
host: "A host is optional. Drafts use a role model, not the host.",
|
|
6871
|
+
roles: "These files say who drafts each stage. Existing files stay.",
|
|
6872
|
+
model: "A draft needs SDD_ROLE_MODEL. Setup does not write that command into the repo.",
|
|
6873
|
+
check: "This is what is already in place.",
|
|
6874
|
+
start: "The first change is optional. sdd go asks the role for that stage."
|
|
6875
|
+
};
|
|
6876
|
+
function ok11(stdout) {
|
|
6877
|
+
return { exitCode: 0, stdout };
|
|
6878
|
+
}
|
|
6879
|
+
function fail11(stdout) {
|
|
6880
|
+
return { exitCode: 1, stdout };
|
|
6881
|
+
}
|
|
6882
|
+
function parseSetupStep(raw) {
|
|
6883
|
+
const id = raw.trim();
|
|
6884
|
+
return SETUP_STEP_IDS.includes(id) ? id : null;
|
|
6885
|
+
}
|
|
6886
|
+
function formatSetupStatus(status) {
|
|
6887
|
+
const folder = status.folder ? "Folder: ready." : "Folder: not set up.";
|
|
6888
|
+
const host = status.host ? `Host: ${status.host}.` : "Host: none.";
|
|
6889
|
+
const roles = status.roles.length > 0 ? `Roles: ${status.roles.join(", ")}.` : "Roles: none.";
|
|
6890
|
+
const model = status.model === "configured" ? "Model: configured." : status.model === "missing" ? "Model: No role model is configured." : `Model: ${status.model.error}`;
|
|
6891
|
+
return [folder, host, roles, model].join("\n");
|
|
6892
|
+
}
|
|
6893
|
+
async function roleNames(root) {
|
|
6894
|
+
const names = await listFiles(join19(root, ".sdd", "roles"));
|
|
6895
|
+
return names.filter((name) => name.endsWith(".md")).map((name) => name.slice(0, -".md".length)).sort((left, right) => left.localeCompare(right));
|
|
6896
|
+
}
|
|
6897
|
+
async function modelState(root, env) {
|
|
6898
|
+
if (modelFromEnv(env)) return "configured";
|
|
6899
|
+
const loaded = await loadModels(root);
|
|
6900
|
+
if (loaded === null) return "missing";
|
|
6901
|
+
if ("error" in loaded) return { error: loaded.error };
|
|
6902
|
+
return "configured";
|
|
6903
|
+
}
|
|
6904
|
+
async function readSetupStatus(root, env = process.env) {
|
|
6905
|
+
const installed = await loadInstalledAgent(root);
|
|
6906
|
+
return {
|
|
6907
|
+
folder: await isInitialized(root),
|
|
6908
|
+
host: installed?.ai ?? null,
|
|
6909
|
+
roles: await roleNames(root),
|
|
6910
|
+
model: await modelState(root, env)
|
|
6911
|
+
};
|
|
6912
|
+
}
|
|
6913
|
+
function withRoleHints(result2) {
|
|
6914
|
+
if (result2.exitCode !== 0) return result2;
|
|
6915
|
+
const hints = [
|
|
6916
|
+
"Add stages with sdd role edit <name> --stages ...",
|
|
6917
|
+
'Add context with sdd role context <name> --text "..."'
|
|
6918
|
+
];
|
|
6919
|
+
const body = result2.stdout.trim();
|
|
6920
|
+
return ok11(body.length > 0 ? `${body}
|
|
6921
|
+
${hints.join("\n")}` : hints.join("\n"));
|
|
6922
|
+
}
|
|
6923
|
+
function messageOf(error) {
|
|
6924
|
+
return error instanceof Error ? error.message : String(error);
|
|
6925
|
+
}
|
|
6926
|
+
async function runSetupStep(root, input, env = process.env) {
|
|
6927
|
+
const workflow = input.workflow?.trim() || "feature";
|
|
6928
|
+
try {
|
|
6929
|
+
if (input.step === "folder") {
|
|
6930
|
+
if (!await isInitialized(root)) {
|
|
6931
|
+
await initProject({ projectRoot: root, agents: false });
|
|
6932
|
+
}
|
|
6933
|
+
return ok11("Folder: ready.");
|
|
6934
|
+
}
|
|
6935
|
+
if (input.step === "workflow") {
|
|
6936
|
+
const lines = [`Workflow: ${workflow}.`];
|
|
6937
|
+
if ((await roleNames(root)).length > 0) lines.push("Roles are already here.");
|
|
6938
|
+
return ok11(lines.join("\n"));
|
|
6939
|
+
}
|
|
6940
|
+
if (input.step === "host") {
|
|
6941
|
+
const installed = await loadInstalledAgent(root);
|
|
6942
|
+
if (installed) return ok11(`Host: ${installed.ai}.`);
|
|
6943
|
+
if (!input.host) return ok11("Host: none.");
|
|
6944
|
+
if (!await isInitialized(root)) {
|
|
6945
|
+
await initProject({ projectRoot: root, agents: input.host });
|
|
6946
|
+
} else {
|
|
6947
|
+
await installAgentIntegration({ projectRoot: root, target: input.host });
|
|
6948
|
+
}
|
|
6949
|
+
const after = await loadInstalledAgent(root);
|
|
6950
|
+
return ok11(`Host: ${after?.ai ?? input.host}.`);
|
|
6951
|
+
}
|
|
6952
|
+
if (input.step === "roles") {
|
|
6953
|
+
const choice = input.roles ?? "duties";
|
|
6954
|
+
if (choice === "keep") {
|
|
6955
|
+
const names = await roleNames(root);
|
|
6956
|
+
const line = names.length > 0 ? "Roles are already here." : "Roles: none.";
|
|
6957
|
+
return withRoleHints(ok11(line));
|
|
6958
|
+
}
|
|
6959
|
+
if (choice === "software") {
|
|
6960
|
+
return withRoleHints(await initTeam(root, "software", gitUserName(root)));
|
|
6961
|
+
}
|
|
6962
|
+
return withRoleHints(await prepareGuide(root, { workflow, agents: false }));
|
|
6473
6963
|
}
|
|
6474
|
-
|
|
6475
|
-
|
|
6476
|
-
|
|
6477
|
-
|
|
6478
|
-
|
|
6479
|
-
return
|
|
6480
|
-
[
|
|
6481
|
-
"Question:",
|
|
6482
|
-
...lines,
|
|
6483
|
-
`${role.name} asks:`,
|
|
6484
|
-
state.pending.question,
|
|
6485
|
-
'Answer with: sdd go --answer "..."'
|
|
6486
|
-
].join("\n")
|
|
6487
|
-
);
|
|
6964
|
+
if (input.step === "model") {
|
|
6965
|
+
const state = await modelState(root, env);
|
|
6966
|
+
if (state === "configured") return ok11("Model: configured.");
|
|
6967
|
+
if (typeof state === "object") return fail11(state.error);
|
|
6968
|
+
if (input.requireModel) return fail11("No role model is configured.");
|
|
6969
|
+
return ok11("No role model is configured.");
|
|
6488
6970
|
}
|
|
6489
|
-
|
|
6490
|
-
|
|
6491
|
-
const advanced = await advanceStage(root, config, ctx.id);
|
|
6492
|
-
lines.push(`${role.name} drafted ${stage.id}.`);
|
|
6493
|
-
lastRole = role.name;
|
|
6494
|
-
if (!advanced.to) {
|
|
6495
|
-
await writeState(root, state);
|
|
6496
|
-
return ok9(
|
|
6497
|
-
[
|
|
6498
|
-
"Done.",
|
|
6499
|
-
...lines,
|
|
6500
|
-
"No further stages. Run `sdd complete` when ready.",
|
|
6501
|
-
"The person has not signed this change."
|
|
6502
|
-
].join("\n")
|
|
6503
|
-
);
|
|
6504
|
-
}
|
|
6505
|
-
} catch (error) {
|
|
6506
|
-
await writeState(root, state);
|
|
6507
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
6508
|
-
return ok9(["Stopped.", `${role.name} drafted ${stage.id}.`, message, ...lines].join("\n"));
|
|
6971
|
+
if (input.step === "check") {
|
|
6972
|
+
return ok11(formatSetupStatus(await readSetupStatus(root, env)));
|
|
6509
6973
|
}
|
|
6974
|
+
return ok11('Next: sdd go "what you want to build"');
|
|
6975
|
+
} catch (error) {
|
|
6976
|
+
return fail11(messageOf(error));
|
|
6510
6977
|
}
|
|
6511
|
-
return fail9("The guide stopped after 16 steps.");
|
|
6512
6978
|
}
|
|
6513
6979
|
|
|
6514
6980
|
// src/role/models/show.ts
|
|
@@ -6549,7 +7015,7 @@ function explainModel(config, workType, ctx) {
|
|
|
6549
7015
|
}
|
|
6550
7016
|
|
|
6551
7017
|
// src/stories.ts
|
|
6552
|
-
import { join as
|
|
7018
|
+
import { join as join20 } from "pathe";
|
|
6553
7019
|
var STORY_ID = /^S-\d+$/i;
|
|
6554
7020
|
var EPIC_KEY = /^[A-Za-z0-9][A-Za-z0-9-]{0,39}$/;
|
|
6555
7021
|
function validBranchName(name) {
|
|
@@ -6591,7 +7057,7 @@ function parseStoryDraft(input) {
|
|
|
6591
7057
|
async function metasIn(dir) {
|
|
6592
7058
|
const out = [];
|
|
6593
7059
|
for (const id of await listDirs(dir)) {
|
|
6594
|
-
const path =
|
|
7060
|
+
const path = join20(dir, id, "meta.yaml");
|
|
6595
7061
|
if (!await pathExists(path)) continue;
|
|
6596
7062
|
try {
|
|
6597
7063
|
out.push(ChangeMetaSchema.parse(await readYaml(path)));
|
|
@@ -6739,6 +7205,8 @@ export {
|
|
|
6739
7205
|
SDD_AGENT_ROLES,
|
|
6740
7206
|
SDD_CORE_VERSION,
|
|
6741
7207
|
SDD_DIR,
|
|
7208
|
+
SETUP_STEP_IDS,
|
|
7209
|
+
SETUP_WHY,
|
|
6742
7210
|
SUMMARY_TOKEN_CAP,
|
|
6743
7211
|
SddError,
|
|
6744
7212
|
SearchConfigError,
|
|
@@ -6834,6 +7302,7 @@ export {
|
|
|
6834
7302
|
formatJsonSummary,
|
|
6835
7303
|
formatMcpContextForHandoff,
|
|
6836
7304
|
formatModels,
|
|
7305
|
+
formatSetupStatus,
|
|
6837
7306
|
formatStatus,
|
|
6838
7307
|
formatTeam,
|
|
6839
7308
|
formatTokenInventory,
|
|
@@ -6849,6 +7318,7 @@ export {
|
|
|
6849
7318
|
getHeadSha,
|
|
6850
7319
|
getIntegration,
|
|
6851
7320
|
getStage,
|
|
7321
|
+
ghIssueSource,
|
|
6852
7322
|
gitUserName,
|
|
6853
7323
|
guidancePaths,
|
|
6854
7324
|
handoffRole,
|
|
@@ -6913,11 +7383,13 @@ export {
|
|
|
6913
7383
|
modelFromEnv,
|
|
6914
7384
|
nextStageId,
|
|
6915
7385
|
nowIso,
|
|
7386
|
+
openMcpSearchProvider,
|
|
6916
7387
|
optionForTarget,
|
|
6917
7388
|
packHits,
|
|
6918
7389
|
parseAgentTargets,
|
|
6919
7390
|
parseFeaturesBacklog,
|
|
6920
7391
|
parseIntegration,
|
|
7392
|
+
parseSetupStep,
|
|
6921
7393
|
parseStoryDraft,
|
|
6922
7394
|
pathExists,
|
|
6923
7395
|
phaseTiming,
|
|
@@ -6931,7 +7403,9 @@ export {
|
|
|
6931
7403
|
queueRole,
|
|
6932
7404
|
rankScope,
|
|
6933
7405
|
readChangeMetrics,
|
|
7406
|
+
readConnector,
|
|
6934
7407
|
readHandoffCodePoints,
|
|
7408
|
+
readSetupStatus,
|
|
6935
7409
|
readTeamConfig,
|
|
6936
7410
|
readText,
|
|
6937
7411
|
readYaml,
|
|
@@ -6975,6 +7449,8 @@ export {
|
|
|
6975
7449
|
runLoop,
|
|
6976
7450
|
runNetwork,
|
|
6977
7451
|
runRole,
|
|
7452
|
+
runRoutine,
|
|
7453
|
+
runSetupStep,
|
|
6978
7454
|
saveChangeMeta,
|
|
6979
7455
|
saveConfig,
|
|
6980
7456
|
saveGeneratedManifest,
|