@structured-vibe-coding/core 0.17.1 → 0.18.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.
Files changed (31) hide show
  1. package/defaults/prices.yaml +9 -0
  2. package/defaults/search.yaml +18 -0
  3. package/dist/{agents-CLTJJVOD.js → agents-AKSUXQHN.js} +18 -7
  4. package/dist/{change-context-W4HDO2IK.js → change-context-REE7UKU7.js} +2 -4
  5. package/dist/chunk-44LQ5BKZ.js +2628 -0
  6. package/dist/{chunk-KYWETNFU.js → chunk-4BRO7BOJ.js} +2 -4
  7. package/dist/chunk-4PIRWYRA.js +1125 -0
  8. package/dist/{chunk-72QJHR53.js → chunk-LHSR7JG2.js} +569 -39
  9. package/dist/{chunk-EPFURATC.js → chunk-O5SP6X5R.js} +89 -0
  10. package/dist/{chunk-BGM6KS64.js → chunk-QGP43XCN.js} +2 -2
  11. package/dist/{chunk-A752UVPF.js → chunk-QHV5KZKX.js} +193 -28
  12. package/dist/{gather-S4A5YR7X.js → gather-ZNV2NAUN.js} +3 -4
  13. package/dist/{greenfield-QQJH3XL3.js → greenfield-I5S5OS2D.js} +5 -6
  14. package/dist/index.d.ts +1034 -40
  15. package/dist/index.js +2927 -988
  16. package/dist/{sources-PF45EUH2.js → sources-3TXUPU6V.js} +2 -3
  17. package/package.json +11 -3
  18. package/vendor/tree-sitter/NOTICE +29 -0
  19. package/vendor/tree-sitter/tree-sitter-javascript.wasm +0 -0
  20. package/vendor/tree-sitter/tree-sitter-python.wasm +0 -0
  21. package/vendor/tree-sitter/tree-sitter-rust.wasm +0 -0
  22. package/vendor/tree-sitter/tree-sitter-tsx.wasm +0 -0
  23. package/vendor/tree-sitter/tree-sitter-typescript.wasm +0 -0
  24. package/dist/chunk-67AUAGNV.js +0 -150
  25. package/dist/chunk-7MJYPJMR.js +0 -51
  26. package/dist/chunk-BVCTVKBD.js +0 -538
  27. package/dist/chunk-WHDG6OTG.js +0 -46
  28. package/dist/chunk-WPAGUBZ5.js +0 -139
  29. package/dist/config-X5VDBVR4.js +0 -16
  30. package/dist/fs-J6DIV6CX.js +0 -28
  31. package/dist/paths-64GA32IZ.js +0 -28
@@ -0,0 +1,1125 @@
1
+ import {
2
+ buildAgentPrompt,
3
+ defaultConfig,
4
+ formatStatus,
5
+ loadConfig
6
+ } from "./chunk-44LQ5BKZ.js";
7
+ import {
8
+ buildContext,
9
+ getActiveChangeId
10
+ } from "./chunk-QHV5KZKX.js";
11
+ import {
12
+ SddError
13
+ } from "./chunk-MHODNFIL.js";
14
+ import {
15
+ configPath,
16
+ guidancePaths,
17
+ listDirs,
18
+ listFiles,
19
+ memoryDir,
20
+ pathExists,
21
+ readText,
22
+ sddRoot,
23
+ writeText
24
+ } from "./chunk-O5SP6X5R.js";
25
+
26
+ // src/agents.ts
27
+ import { constants } from "fs";
28
+ import { access, lstat as lstat2, readdir, rmdir, unlink } from "fs/promises";
29
+ import { dirname as dirname2, join as join4 } from "pathe";
30
+
31
+ // src/generated.ts
32
+ import { createHash } from "crypto";
33
+ import { lstat } from "fs/promises";
34
+ import { join } from "pathe";
35
+ import { z } from "zod";
36
+ var MARKER_RE = /^<!-- sdd:generated sha256=([0-9a-f]{64})\b[^\n]*-->[ \t]*\r?\n/m;
37
+ function contentHash(body) {
38
+ return createHash("sha256").update(body.replace(/\r\n/g, "\n"), "utf8").digest("hex");
39
+ }
40
+ function markerLine(hash) {
41
+ return `<!-- sdd:generated sha256=${hash} (sdd regenerates this file only while it is unchanged; edit freely) -->
42
+ `;
43
+ }
44
+ function markGenerated(body) {
45
+ const line = markerLine(contentHash(body));
46
+ if (body.startsWith("---\n")) {
47
+ const close = body.indexOf("\n---\n", 3);
48
+ if (close !== -1) {
49
+ const at = close + "\n---\n".length;
50
+ return body.slice(0, at) + line + body.slice(at);
51
+ }
52
+ }
53
+ return line + body;
54
+ }
55
+ function generatedState(content) {
56
+ const m = MARKER_RE.exec(content);
57
+ if (!m) return "not-generated";
58
+ const body = content.slice(0, m.index) + content.slice(m.index + m[0].length);
59
+ return contentHash(body) === m[1] ? "unmodified" : "edited";
60
+ }
61
+ var GeneratedManifestSchema = z.object({
62
+ version: z.literal(1),
63
+ files: z.record(z.string().regex(/^[0-9a-f]{64}$/))
64
+ }).strict();
65
+ async function loadGeneratedManifest(path) {
66
+ let st;
67
+ try {
68
+ st = await lstat(path);
69
+ } catch (err) {
70
+ if (err.code === "ENOENT") return { version: 1, files: {} };
71
+ throw err;
72
+ }
73
+ if (st.isSymbolicLink() || !st.isFile()) {
74
+ throw new Error(`Cannot trust generated file manifest at ${path}; no files were changed.`);
75
+ }
76
+ try {
77
+ return GeneratedManifestSchema.parse(JSON.parse(await readText(path)));
78
+ } catch (err) {
79
+ throw new Error(
80
+ `Invalid generated file manifest at ${path}: ${err instanceof Error ? err.message : String(err)}. No files were changed.`
81
+ );
82
+ }
83
+ }
84
+ async function saveGeneratedManifest(path, manifest) {
85
+ const validated = GeneratedManifestSchema.parse(manifest);
86
+ const files = Object.fromEntries(
87
+ Object.entries(validated.files).sort(([a], [b]) => a.localeCompare(b))
88
+ );
89
+ await writeText(path, `${JSON.stringify({ version: 1, files }, null, 2)}
90
+ `);
91
+ }
92
+ async function defaultFiles(sourceDir, relativeDir = "") {
93
+ const files = (await listFiles(join(sourceDir, relativeDir))).map(
94
+ (file) => [relativeDir, file].filter(Boolean).join("/")
95
+ );
96
+ const nested = await Promise.all(
97
+ (await listDirs(join(sourceDir, relativeDir))).map(
98
+ (dir) => defaultFiles(sourceDir, [relativeDir, dir].filter(Boolean).join("/"))
99
+ )
100
+ );
101
+ return [...files, ...nested.flat()].sort();
102
+ }
103
+ async function symlinkInPath(projectRoot, relativePath) {
104
+ let current = projectRoot;
105
+ const parts = relativePath.split("/");
106
+ const traversed = [];
107
+ for (const part of parts) {
108
+ current = join(current, part);
109
+ traversed.push(part);
110
+ try {
111
+ if ((await lstat(current)).isSymbolicLink()) return traversed.join("/");
112
+ } catch (err) {
113
+ const code = err.code;
114
+ if (code === "ENOENT" || code === "ENOTDIR") return null;
115
+ throw err;
116
+ }
117
+ }
118
+ return null;
119
+ }
120
+ async function reconcileGeneratedTree(projectRoot, sourceDir, destinationDir, destinationRelative, manifest, force) {
121
+ const created = [];
122
+ const refreshed = [];
123
+ const kept = [];
124
+ let changed = false;
125
+ for (const relativeFile of await defaultFiles(sourceDir)) {
126
+ const projectRelative = `${destinationRelative}/${relativeFile}`;
127
+ const sourcePath = join(sourceDir, relativeFile);
128
+ const destPath = join(destinationDir, relativeFile);
129
+ const symlinkPath = await symlinkInPath(projectRoot, projectRelative);
130
+ if (symlinkPath) {
131
+ kept.push({ path: symlinkPath, reason: "symlink" });
132
+ continue;
133
+ }
134
+ const generated = await readText(sourcePath);
135
+ const generatedHash = contentHash(generated);
136
+ if (!await pathExists(destPath)) {
137
+ await writeText(destPath, generated);
138
+ manifest.files[projectRelative] = generatedHash;
139
+ created.push(projectRelative);
140
+ changed = true;
141
+ continue;
142
+ }
143
+ let current;
144
+ try {
145
+ const st = await lstat(destPath);
146
+ if (!st.isFile()) {
147
+ kept.push({ path: projectRelative, reason: "unreadable" });
148
+ continue;
149
+ }
150
+ current = await readText(destPath);
151
+ } catch {
152
+ kept.push({ path: projectRelative, reason: "unreadable" });
153
+ continue;
154
+ }
155
+ const previousHash = manifest.files[projectRelative];
156
+ if (!previousHash) {
157
+ kept.push({ path: projectRelative, reason: "not-generated" });
158
+ continue;
159
+ }
160
+ if (contentHash(current) !== previousHash) {
161
+ kept.push({ path: projectRelative, reason: "edited" });
162
+ continue;
163
+ }
164
+ if (force && current !== generated) {
165
+ await writeText(destPath, generated);
166
+ manifest.files[projectRelative] = generatedHash;
167
+ refreshed.push(projectRelative);
168
+ changed = true;
169
+ }
170
+ }
171
+ return { created, refreshed, kept, changed };
172
+ }
173
+ function describeKept(k) {
174
+ switch (k.reason) {
175
+ case "edited":
176
+ return `${k.path} (generated by sdd, then edited \u2014 kept)`;
177
+ case "symlink":
178
+ return `${k.path} (symbolic link \u2014 not followed, left untouched)`;
179
+ case "unreadable":
180
+ return `${k.path} (could not be read \u2014 left untouched)`;
181
+ default:
182
+ return `${k.path} (not generated by sdd \u2014 left untouched)`;
183
+ }
184
+ }
185
+
186
+ // src/guidance.ts
187
+ import { join as join3 } from "pathe";
188
+
189
+ // src/defaults.ts
190
+ import { existsSync } from "fs";
191
+ import { fileURLToPath } from "url";
192
+ import { dirname, join as join2 } from "pathe";
193
+ function defaultsRoot() {
194
+ const here = dirname(fileURLToPath(import.meta.url));
195
+ const candidates = [
196
+ join2(here, "..", "defaults"),
197
+ // npm package: dist/../defaults
198
+ join2(here, "defaults"),
199
+ // same-dir defaults if present
200
+ join2(here, "..", "..", "defaults")
201
+ // src/ during some test runners
202
+ ];
203
+ for (const c of candidates) {
204
+ if (existsSync(c)) return c;
205
+ }
206
+ return candidates[0];
207
+ }
208
+ function defaultWorkflowsDir() {
209
+ return join2(defaultsRoot(), "workflows");
210
+ }
211
+ function defaultTemplatesDir() {
212
+ return join2(defaultsRoot(), "templates");
213
+ }
214
+ function defaultMemoryDir() {
215
+ return join2(defaultsRoot(), "memory");
216
+ }
217
+ function defaultMcpYamlPath() {
218
+ return join2(defaultsRoot(), "mcp.yaml");
219
+ }
220
+ function defaultPricesYamlPath() {
221
+ return join2(defaultsRoot(), "prices.yaml");
222
+ }
223
+ function defaultSearchYamlPath() {
224
+ return join2(defaultsRoot(), "search.yaml");
225
+ }
226
+
227
+ // src/guidance.ts
228
+ function renderSddReadme(config) {
229
+ const paths = guidancePaths(config);
230
+ return `# Structured Vibe Coding (SDD)
231
+
232
+ This directory configures local Spec-Driven Development for this repo.
233
+
234
+ - \`config.yaml\` \u2014 project settings and per-change policy
235
+ - \`workflows/\` \u2014 YAML workflow packs (customize freely)
236
+ - \`templates/\` \u2014 markdown templates for stage artifacts
237
+ - \`protocol.md\` / \`active-context.md\` \u2014 agent playbook + live task (after agent install)
238
+
239
+ Work lives in \`../${paths.changes}/<id>/\`. Completed packs stay there with \`status: completed\`.
240
+ Promote lasting decisions into \`../${paths.memory}/\`. (Archive-on-complete is opt-in.)
241
+
242
+ **Documentation map (stable):** \`../${paths.memoryIndex}\`
243
+ **Change-scoped docs:** \`../${paths.changes}/<id>/\`
244
+
245
+ ## Quick commands
246
+
247
+ \`\`\`bash
248
+ sdd new "My feature"
249
+ sdd status
250
+ sdd next
251
+ sdd verify
252
+ sdd complete
253
+ \`\`\`
254
+ `;
255
+ }
256
+ function renderMemoryIndex(config) {
257
+ const paths = guidancePaths(config);
258
+ return `# Documentation map
259
+
260
+ Parent page for **stable** project docs. Humans and coding agents: start here, then open only what you need.
261
+
262
+ | Kind | Where | Lifetime |
263
+ |------|--------|----------|
264
+ | **This map + memory** | \`${paths.memory}/*.md\` | Long-lived |
265
+ | **Active change** | \`${paths.changes}/<id>/\` (see \`.sdd/active-context.md\`) | Until complete |
266
+ | **Finished work** | \`${paths.changes}/<id>/\` with \`status: completed\` | History (pack stays in place; archive is opt-in) |
267
+ | **Optional domains** | \`domains/\` | Long-lived by area |
268
+
269
+ ## Stable docs
270
+
271
+ | Topic | File |
272
+ |-------|------|
273
+ | Product / vision | [product.md](product.md) |
274
+ | Requirements | [requirements.md](requirements.md) (after greenfield) |
275
+ | Feature backlog | [features.md](features.md) (after greenfield; \`sdd backlog start F-001\`) |
276
+ | Constitution (non-negotiables) | [constitution.md](constitution.md) |
277
+ | Architecture | [architecture.md](architecture.md) |
278
+ | Coding & process conventions | [conventions.md](conventions.md) |
279
+
280
+ **Greenfield:** \`sdd greenfield "one-line idea"\` then promote into \`${paths.memory}/\` on complete.
281
+ Add rows here when you introduce new durable docs (e.g. \`security.md\`, \`ops.md\`).
282
+
283
+ ## Active work
284
+
285
+ 1. Run \`sdd status\` (or open \`.sdd/active-context.md\`).
286
+ 2. Read stage artifacts under the active change folder only.
287
+ 3. Prefer small updates to change packs; **promote** lasting decisions into \`${paths.memory}/\` after \`sdd complete\`.
288
+
289
+ ## Ownership (suggested)
290
+
291
+ | Area | Owner |
292
+ |------|--------|
293
+ | \`${paths.memory}/*\` | Tech lead / architect |
294
+ | Current \`${paths.changes}/<id>/\` | Author of the change (PR owner) |
295
+ | \`domains/<name>/\` | Domain owner (if used) |
296
+
297
+ Keep this map short. Link out; do not paste large designs here.
298
+ `;
299
+ }
300
+ async function refreshConfiguredGuidance(projectRoot, config) {
301
+ await writeConfiguredMemoryIndex(memoryDir(projectRoot, config), config);
302
+ await writeConfiguredReadme(projectRoot, config);
303
+ }
304
+ async function writeConfiguredMemoryIndex(memoryRoot, config) {
305
+ const indexPath = join3(memoryRoot, "index.md");
306
+ const paths = guidancePaths(config);
307
+ if (paths.memory === "memory" && paths.changes === "changes") return;
308
+ if (!await pathExists(indexPath)) return;
309
+ const existing = await readText(indexPath);
310
+ const state = generatedState(existing);
311
+ if (state === "edited") return;
312
+ const shippedPath = join3(defaultMemoryDir(), "index.md");
313
+ const shipped = await pathExists(shippedPath) ? await readText(shippedPath) : "";
314
+ if (state !== "unmodified" && existing.replace(/\r\n/g, "\n").trim() !== shipped.trim()) return;
315
+ const body = renderMemoryIndex(config);
316
+ if (existing.includes(`\`${paths.memory}/`) && existing.includes(`\`${paths.changes}/`)) return;
317
+ await writeText(indexPath, markGenerated(body));
318
+ }
319
+ async function writeConfiguredReadme(projectRoot, config) {
320
+ const readmePath = join3(sddRoot(projectRoot), "README.md");
321
+ if (!await pathExists(readmePath)) return;
322
+ const existing = await readText(readmePath);
323
+ if (generatedState(existing) !== "unmodified") return;
324
+ const body = renderSddReadme(config);
325
+ const marked = markGenerated(body);
326
+ if (existing === marked) return;
327
+ await writeText(readmePath, marked);
328
+ }
329
+
330
+ // src/agents.ts
331
+ var AGENT_INTEGRATIONS = [
332
+ {
333
+ id: "copilot",
334
+ key: "copilot",
335
+ aliases: ["github-copilot", "gh-copilot"],
336
+ label: "GitHub Copilot",
337
+ hint: ".github/agents/*.agent.md (VS Code, Cursor, JetBrains, \u2026)",
338
+ requiresCli: false,
339
+ installUrl: "https://github.com/features/copilot",
340
+ rolePath: (roleId) => `.github/agents/${roleId}.agent.md`,
341
+ agentsMdRow: "| GitHub Copilot | `.github/agents/*.agent.md` (same roles; any IDE that supports Copilot agents) |"
342
+ },
343
+ {
344
+ id: "claude-code",
345
+ key: "claude",
346
+ aliases: ["claude-code", "claudecode"],
347
+ label: "Claude Code",
348
+ hint: ".claude/agents/*.md (terminal agent)",
349
+ requiresCli: true,
350
+ cliBinary: "claude",
351
+ installUrl: "https://docs.anthropic.com/en/docs/claude-code",
352
+ rolePath: (roleId) => `.claude/agents/${roleId}.md`,
353
+ agentsMdRow: "| Claude Code | `.claude/agents/` (`sdd`, `sdd-planner`, `sdd-implementer`, `sdd-reviewer`) |"
354
+ },
355
+ {
356
+ id: "grok",
357
+ key: "grok",
358
+ aliases: ["grok-build", "grokbuild", "xai", "xai-grok"],
359
+ label: "Grok Build",
360
+ hint: ".grok/rules/sdd.md + AGENTS.md (terminal TUI from xAI)",
361
+ requiresCli: false,
362
+ cliBinary: "grok",
363
+ installUrl: "https://docs.x.ai",
364
+ // Single rules file: Grok auto-loads all .grok/rules/*.md
365
+ rolePath: () => `.grok/rules/sdd.md`,
366
+ rolesToInstall: ["sdd"],
367
+ agentsMdRow: "| Grok Build | `.grok/rules/sdd.md` + `AGENTS.md` (reads protocol + active-context; run `sdd` in shell) |"
368
+ },
369
+ {
370
+ id: "ollama",
371
+ key: "ollama",
372
+ aliases: ["local", "llama", "ollama-local"],
373
+ label: "Ollama (local)",
374
+ hint: "Local models via `ollama` CLI + AGENTS.md / .ollama/sdd.md (set SDD_OLLAMA_MODEL)",
375
+ requiresCli: true,
376
+ cliBinary: "ollama",
377
+ installUrl: "https://ollama.com",
378
+ // Single project brief — Ollama does not auto-load multi-role agent trees
379
+ rolePath: () => `.ollama/sdd.md`,
380
+ rolesToInstall: ["sdd"],
381
+ agentsMdRow: "| Ollama (local) | `.ollama/sdd.md` + `AGENTS.md` + handoff; launch: `ollama run $SDD_OLLAMA_MODEL` |"
382
+ },
383
+ {
384
+ id: "kilo",
385
+ key: "kilo",
386
+ aliases: ["kilocode", "kilo-code", "kilo-ai"],
387
+ label: "Kilo Code",
388
+ hint: ".kilo/sdd.md + AGENTS.md (terminal CLI from kilo.ai)",
389
+ requiresCli: true,
390
+ cliBinary: "kilo",
391
+ installUrl: "https://kilo.ai/docs/code-with-ai/platforms/cli",
392
+ // Single project brief — Kilo loads AGENTS.md and supports instruction files
393
+ rolePath: () => `.kilo/sdd.md`,
394
+ rolesToInstall: ["sdd"],
395
+ agentsMdRow: "| Kilo Code | `.kilo/sdd.md` + `AGENTS.md` (reads protocol + active-context; run `kilo` or `sdd` in shell) |"
396
+ }
397
+ ];
398
+ var ALL_AGENT_TARGETS = AGENT_INTEGRATIONS.map((i) => i.id);
399
+ var DEFAULT_INIT_INTEGRATION = "copilot";
400
+ var AGENT_TARGET_OPTIONS = AGENT_INTEGRATIONS.map(
401
+ ({ id, key, label, hint, requiresCli, installUrl }) => ({
402
+ id,
403
+ key,
404
+ label,
405
+ hint,
406
+ requiresCli,
407
+ installUrl
408
+ })
409
+ );
410
+ var IDE_NAMES = /* @__PURE__ */ new Set(["intellij", "idea", "jetbrains", "vscode", "vs-code", "cursor"]);
411
+ function getIntegration(target) {
412
+ const found = AGENT_INTEGRATIONS.find((i) => i.id === target);
413
+ if (!found) throw new Error(`Unknown agent integration: ${target}`);
414
+ return found;
415
+ }
416
+ function integrationKeyFor(target) {
417
+ return getIntegration(target).key;
418
+ }
419
+ function optionForTarget(target) {
420
+ return getIntegration(target);
421
+ }
422
+ function agentHostPaths(target) {
423
+ switch (target) {
424
+ case "copilot":
425
+ return [".github/agents"];
426
+ case "claude-code":
427
+ return [".claude/agents"];
428
+ case "grok":
429
+ return [".grok/rules"];
430
+ case "ollama":
431
+ return [".ollama"];
432
+ case "kilo":
433
+ return [".kilo"];
434
+ default:
435
+ return [];
436
+ }
437
+ }
438
+ var LEGACY_AGENT_PATHS = [".idea/sdd-agent-notes.md"];
439
+ var PRUNABLE_PARENTS = [".claude", ".idea", ".grok", ".ollama", ".kilo"];
440
+ async function pathKind(full) {
441
+ try {
442
+ const st = await lstat2(full);
443
+ if (st.isSymbolicLink()) return "symlink";
444
+ if (st.isDirectory()) return "dir";
445
+ if (st.isFile()) return "file";
446
+ return "other";
447
+ } catch (err) {
448
+ const code = err.code;
449
+ if (code === "ENOENT" || code === "ENOTDIR") return "missing";
450
+ throw err;
451
+ }
452
+ }
453
+ async function symlinkOnPath(projectRoot, rel) {
454
+ let cur = "";
455
+ for (const part of rel.split("/")) {
456
+ cur = cur ? `${cur}/${part}` : part;
457
+ const kind = await pathKind(join4(projectRoot, cur));
458
+ if (kind === "symlink") return cur;
459
+ if (kind === "missing") return null;
460
+ }
461
+ return null;
462
+ }
463
+ async function canAccess(full, mode) {
464
+ try {
465
+ await access(full, mode);
466
+ return true;
467
+ } catch {
468
+ return false;
469
+ }
470
+ }
471
+ function fsCode(err) {
472
+ return err?.code;
473
+ }
474
+ function describeFsError(err, action, rel) {
475
+ const code = fsCode(err);
476
+ const why = code === "EACCES" || code === "EPERM" ? "permission denied" : code === "EISDIR" ? "it is a directory, not a file" : code === "ENOTDIR" ? "a parent path is a file, not a directory" : code === "ELOOP" ? "too many levels of symbolic links" : code === "ENOENT" ? "it does not exist (dangling symbolic link?)" : err?.message ?? String(err);
477
+ return `Cannot ${action} ${rel}: ${why}${code ? ` (${code})` : ""}.`;
478
+ }
479
+ var AgentInstallPreflightError = class extends SddError {
480
+ problems;
481
+ constructor(problems) {
482
+ super(
483
+ [
484
+ "sdd cannot set up the AI agent files. Nothing was written.",
485
+ ...problems.map((pr) => ` - ${pr}`),
486
+ "Fix the paths above, then re-run."
487
+ ].join("\n"),
488
+ { code: "AGENT_PREFLIGHT" }
489
+ );
490
+ this.name = "AgentInstallPreflightError";
491
+ this.problems = problems;
492
+ }
493
+ };
494
+ async function planOtherHostRemoval(projectRoot, keep) {
495
+ const remove = [];
496
+ const kept = [];
497
+ const candidates = [];
498
+ for (const integ of AGENT_INTEGRATIONS) {
499
+ if (integ.id === keep) continue;
500
+ candidates.push(...agentHostPaths(integ.id));
501
+ }
502
+ candidates.push(...LEGACY_AGENT_PATHS);
503
+ const visit = async (rel) => {
504
+ const full = join4(projectRoot, rel);
505
+ const kind = await pathKind(full);
506
+ if (kind === "missing") return;
507
+ if (kind === "symlink") {
508
+ kept.push({ path: rel, reason: "symlink" });
509
+ return;
510
+ }
511
+ if (kind === "dir") {
512
+ let names;
513
+ try {
514
+ names = (await readdir(full)).sort();
515
+ } catch {
516
+ kept.push({ path: rel, reason: "unreadable" });
517
+ return;
518
+ }
519
+ for (const name of names) await visit(`${rel}/${name}`);
520
+ return;
521
+ }
522
+ if (kind !== "file") {
523
+ kept.push({ path: rel, reason: "not-generated" });
524
+ return;
525
+ }
526
+ let state;
527
+ try {
528
+ state = generatedState(await readText(full));
529
+ } catch {
530
+ kept.push({ path: rel, reason: "unreadable" });
531
+ return;
532
+ }
533
+ if (state === "unmodified") remove.push(rel);
534
+ else kept.push({ path: rel, reason: state });
535
+ };
536
+ for (const rel of candidates) {
537
+ const link = await symlinkOnPath(projectRoot, rel);
538
+ if (link) {
539
+ if (!kept.some((k) => k.path === link)) kept.push({ path: link, reason: "symlink" });
540
+ continue;
541
+ }
542
+ await visit(rel);
543
+ }
544
+ return { remove, kept };
545
+ }
546
+ async function removalProblems(projectRoot, plan) {
547
+ const problems = [];
548
+ for (const rel of plan.remove) {
549
+ const dir = dirname2(rel);
550
+ if (!await canAccess(join4(projectRoot, dir), constants.W_OK)) {
551
+ problems.push(
552
+ `Cannot remove ${rel} (another host's sdd file): ${dir}/ is not writable (permission denied). Make it writable or delete the file yourself.`
553
+ );
554
+ }
555
+ }
556
+ return problems;
557
+ }
558
+ async function executeRemoval(projectRoot, plan) {
559
+ const removed = [];
560
+ const ancestors = /* @__PURE__ */ new Set();
561
+ for (const rel of plan.remove) {
562
+ try {
563
+ await unlink(join4(projectRoot, rel));
564
+ } catch (err) {
565
+ throw new SddError(describeFsError(err, "remove", rel), { cause: err });
566
+ }
567
+ removed.push(rel);
568
+ let dir = dirname2(rel);
569
+ while (dir !== "." && dir !== "") {
570
+ const top = !dir.includes("/");
571
+ if (top && !PRUNABLE_PARENTS.includes(dir)) break;
572
+ ancestors.add(dir);
573
+ dir = dirname2(dir);
574
+ }
575
+ }
576
+ const dirs = [...ancestors].sort((a, b) => b.split("/").length - a.split("/").length);
577
+ for (const rel of dirs) {
578
+ const full = join4(projectRoot, rel);
579
+ if (await pathKind(full) !== "dir") continue;
580
+ if ((await readdir(full)).length !== 0) continue;
581
+ try {
582
+ await rmdir(full);
583
+ removed.push(rel);
584
+ } catch {
585
+ }
586
+ }
587
+ return removed;
588
+ }
589
+ async function removeOtherAgentHostsDetailed(projectRoot, keep) {
590
+ const plan = await planOtherHostRemoval(projectRoot, keep);
591
+ const problems = await removalProblems(projectRoot, plan);
592
+ if (problems.length) throw new AgentInstallPreflightError(problems);
593
+ const removed = await executeRemoval(projectRoot, plan);
594
+ return { removed, kept: plan.kept };
595
+ }
596
+ async function removeOtherAgentHosts(projectRoot, keep) {
597
+ return (await removeOtherAgentHostsDetailed(projectRoot, keep)).removed;
598
+ }
599
+ function parseIntegration(raw) {
600
+ const p = raw.trim().toLowerCase();
601
+ if (!p) {
602
+ throw new Error(
603
+ `Expected one AI coding agent. Choose from: ${AGENT_INTEGRATIONS.map((i) => i.key).join(", ")}`
604
+ );
605
+ }
606
+ if (IDE_NAMES.has(p)) {
607
+ throw new Error(
608
+ `"${p}" is an IDE, not an AI coding agent. Choose: ${AGENT_INTEGRATIONS.map((i) => i.key).join(", ")}.`
609
+ );
610
+ }
611
+ for (const integ of AGENT_INTEGRATIONS) {
612
+ if (integ.id === p || integ.key === p || integ.aliases.includes(p)) {
613
+ return integ.id;
614
+ }
615
+ }
616
+ throw new Error(
617
+ `Unknown AI coding agent "${raw}". Choose from: ${AGENT_INTEGRATIONS.map((i) => i.key).join(", ")}`
618
+ );
619
+ }
620
+ function parseAgentTargets(raw) {
621
+ const parts = (Array.isArray(raw) ? raw : [raw]).flatMap((s) => String(s).split(",")).map((s) => s.trim()).filter(Boolean);
622
+ if (!parts.length) {
623
+ throw new Error("No AI coding agent specified");
624
+ }
625
+ const out = [];
626
+ for (const p of parts) {
627
+ const id = parseIntegration(p);
628
+ if (!out.includes(id)) out.push(id);
629
+ }
630
+ return out;
631
+ }
632
+ var SDD_AGENT_ROLES = [
633
+ {
634
+ id: "sdd",
635
+ description: "Default SDD agent. Use for any Spec-Driven Development work on the active change pack.",
636
+ roleLine: "Router: inspect stage in active-context; plan or implement accordingly.",
637
+ roleRules: [
638
+ "If stage is intent/feature/design/tasks/stories/research: act as planner (artifacts, little or no product code).",
639
+ "If stage is implement: act as implementer (scoped code only).",
640
+ "If stage is local_verify: act as reviewer (gaps vs acceptance; fixes only).",
641
+ "If kickoff/brief says refine or analyze: follow <changes_path>/<id>/refine-brief.md \u2014 stage-scoped refine; never edit constitution; do not sdd next."
642
+ ]
643
+ },
644
+ {
645
+ id: "sdd-planner",
646
+ description: "SDD planner. Use when filling intent, design, research, stories, or tasks \u2014 not large code changes.",
647
+ roleLine: "Planner: produce/update stage markdown only.",
648
+ roleRules: [
649
+ "Do not make large product code changes unless the user explicitly overrides.",
650
+ "Keep artifacts short and actionable."
651
+ ]
652
+ },
653
+ {
654
+ id: "sdd-implementer",
655
+ description: "SDD implementer. Use when coding the active change (implement stage).",
656
+ roleLine: "Implementer: code only for the active change pack.",
657
+ roleRules: [
658
+ "Follow tasks.md / acceptance; honor arb-decision and design constraints.",
659
+ "Do not expand scope beyond the change title/intent."
660
+ ]
661
+ },
662
+ {
663
+ id: "sdd-reviewer",
664
+ description: "SDD reviewer. Use before verify/complete or to check work against the change pack.",
665
+ roleLine: "Reviewer: compare work to acceptance and stage artifacts.",
666
+ roleRules: [
667
+ "List gaps; suggest minimal fixes only.",
668
+ "Remind user to run sdd verify before sdd complete."
669
+ ]
670
+ }
671
+ ];
672
+ function agentFiles(integ, config) {
673
+ const roles = integ.rolesToInstall?.length ? SDD_AGENT_ROLES.filter((r) => integ.rolesToInstall.includes(r.id)) : SDD_AGENT_ROLES;
674
+ return [
675
+ { rel: join4(".sdd", "protocol.md"), content: renderProtocolMd(config) },
676
+ ...roles.map((role) => ({
677
+ rel: integ.rolePath(role.id),
678
+ content: renderThinAgent(role, config)
679
+ })),
680
+ { rel: "AGENTS.md", content: renderAgentsMd(integ, config) }
681
+ ];
682
+ }
683
+ async function planAgentIntegration(opts) {
684
+ const root = opts.projectRoot;
685
+ const force = opts.force ?? false;
686
+ const integ = getIntegration(opts.target);
687
+ const problems = [];
688
+ const writes = [];
689
+ const config = await pathExists(configPath(root)) ? await loadConfig(root) : defaultConfig();
690
+ for (const { rel, content } of agentFiles(integ, config)) {
691
+ const full = join4(root, rel);
692
+ if (!rel.startsWith(".sdd/")) {
693
+ const link = await symlinkOnPath(root, rel);
694
+ if (link) {
695
+ writes.push({ rel, content, action: "keep", keptReason: "symlink", keptPath: link });
696
+ continue;
697
+ }
698
+ }
699
+ const kind = await pathKind(full);
700
+ if (kind === "missing") {
701
+ let anc = dirname2(rel);
702
+ while (anc !== "." && await pathKind(join4(root, anc)) === "missing") anc = dirname2(anc);
703
+ const ancFull = anc === "." ? root : join4(root, anc);
704
+ const ancKind = await pathKind(ancFull);
705
+ if (ancKind !== "dir" && ancKind !== "symlink") {
706
+ problems.push(`Cannot create ${rel}: ${anc} is a file, not a directory.`);
707
+ } else if (!await canAccess(ancFull, constants.W_OK)) {
708
+ problems.push(
709
+ `Cannot create ${rel}: ${anc === "." ? "the project folder" : `${anc}/`} is not writable (permission denied).`
710
+ );
711
+ }
712
+ writes.push({ rel, content, action: "create" });
713
+ continue;
714
+ }
715
+ if (kind === "dir") {
716
+ problems.push(
717
+ `Cannot write ${rel}: it is a directory, not a file (EISDIR). Rename or move it.`
718
+ );
719
+ writes.push({ rel, content, action: "keep", keptReason: "not-generated" });
720
+ continue;
721
+ }
722
+ if (kind !== "file") {
723
+ writes.push({ rel, content, action: "keep", keptReason: "not-generated" });
724
+ continue;
725
+ }
726
+ let state;
727
+ try {
728
+ state = generatedState(await readText(full));
729
+ } catch (err) {
730
+ problems.push(
731
+ `${describeFsError(err, "read", rel)} sdd must read it to tell whether it generated the file; fix its permissions or move it.`
732
+ );
733
+ writes.push({ rel, content, action: "keep", keptReason: "unreadable" });
734
+ continue;
735
+ }
736
+ if (state !== "unmodified") {
737
+ writes.push({ rel, content, action: "keep", keptReason: state });
738
+ } else if (!force) {
739
+ writes.push({ rel, content, action: "skip" });
740
+ } else {
741
+ if (!await canAccess(full, constants.W_OK)) {
742
+ problems.push(
743
+ `Cannot regenerate ${rel} (--force): the file is read-only (permission denied). Make it writable or delete it.`
744
+ );
745
+ }
746
+ writes.push({ rel, content, action: "overwrite" });
747
+ }
748
+ }
749
+ const removal = await planOtherHostRemoval(root, opts.target);
750
+ problems.push(...await removalProblems(root, removal));
751
+ return { writes, removal, problems };
752
+ }
753
+ async function assertAgentInstallable(opts) {
754
+ const plan = await planAgentIntegration(opts);
755
+ if (plan.problems.length) throw new AgentInstallPreflightError(plan.problems);
756
+ }
757
+ async function installAgentIntegration(opts) {
758
+ const integ = getIntegration(opts.target);
759
+ const created = [];
760
+ const skipped = [];
761
+ const root = opts.projectRoot;
762
+ const force = opts.force ?? false;
763
+ const plan = await planAgentIntegration({ projectRoot: root, target: opts.target, force });
764
+ if (plan.problems.length) throw new AgentInstallPreflightError(plan.problems);
765
+ const removed = await executeRemoval(root, plan.removal);
766
+ const kept = [...plan.removal.kept];
767
+ const byRel = new Map(plan.writes.map((w) => [w.rel, w]));
768
+ const write = async (rel) => {
769
+ const w = byRel.get(rel);
770
+ if (w.action === "keep") {
771
+ kept.push({ path: w.keptPath ?? rel, reason: w.keptReason });
772
+ return;
773
+ }
774
+ if (w.action === "skip") {
775
+ skipped.push(rel);
776
+ return;
777
+ }
778
+ try {
779
+ await writeText(join4(root, rel), markGenerated(w.content));
780
+ } catch (err) {
781
+ throw new SddError(describeFsError(err, "write", rel), { cause: err });
782
+ }
783
+ created.push(rel);
784
+ };
785
+ await write(join4(".sdd", "protocol.md"));
786
+ await refreshActiveAgentContext(root);
787
+ for (const w of plan.writes) {
788
+ if (w.rel !== join4(".sdd", "protocol.md") && w.rel !== "AGENTS.md") await write(w.rel);
789
+ }
790
+ await write("AGENTS.md");
791
+ const snapshotRel = join4(".sdd", "agents.json");
792
+ if (await pathExists(join4(root, snapshotRel)) && !force) {
793
+ skipped.push(snapshotRel);
794
+ } else {
795
+ await writeText(
796
+ join4(root, snapshotRel),
797
+ JSON.stringify(
798
+ {
799
+ version: 3,
800
+ mode: "agents-only",
801
+ protocol: ".sdd/protocol.md",
802
+ activeContext: ".sdd/active-context.md",
803
+ roles: SDD_AGENT_ROLES.map((r) => r.id),
804
+ /** Speckit-style public key */
805
+ ai: integ.key,
806
+ integration: integ.key,
807
+ installed: [integ.id],
808
+ updated: (/* @__PURE__ */ new Date()).toISOString()
809
+ },
810
+ null,
811
+ 2
812
+ ) + "\n"
813
+ );
814
+ created.push(snapshotRel);
815
+ }
816
+ return { created, skipped, kept, removed, target: opts.target };
817
+ }
818
+ async function installAgentIntegrations(opts) {
819
+ if (!opts.targets?.length) {
820
+ throw new Error(
821
+ `Specify one AI coding agent: ${AGENT_INTEGRATIONS.map((i) => i.key).join(", ")}`
822
+ );
823
+ }
824
+ const created = [];
825
+ const skipped = [];
826
+ for (const target of opts.targets) {
827
+ const r = await installAgentIntegration({
828
+ projectRoot: opts.projectRoot,
829
+ target,
830
+ force: opts.force
831
+ });
832
+ created.push(...r.created);
833
+ skipped.push(...r.skipped);
834
+ }
835
+ return { created, skipped, targets: opts.targets };
836
+ }
837
+ function renderThinAgent(role, config) {
838
+ const paths = guidancePaths(config ?? { memory_path: "memory", changes_path: "changes" });
839
+ const rules = role.roleRules.map((r) => `- ${r.replaceAll("<changes_path>", paths.changes)}`).join("\n");
840
+ return `---
841
+ name: ${role.id}
842
+ description: ${role.description}
843
+ ---
844
+
845
+ # ${role.id}
846
+
847
+ ${role.roleLine}
848
+
849
+ ## Required reads (in order)
850
+
851
+ 1. \`.sdd/active-context.md\` \u2014 current change and stage
852
+ 2. \`.sdd/protocol.md\` \u2014 SDD rules for this repo (single playbook)
853
+ 3. \`${paths.constitution}\` if present \u2014 non-negotiables
854
+ 4. Active change files under \`${paths.changes}/<id>/\` as listed in active-context
855
+
856
+ ## Role rules
857
+
858
+ ${rules}
859
+
860
+ ## Done
861
+
862
+ Remind the human: \`sdd verify\` then \`sdd next\` / \`sdd complete\` as appropriate.
863
+ Do not claim the change is complete without local verification when the workflow requires it.
864
+ `;
865
+ }
866
+ async function loadInstalledAgent(projectRoot) {
867
+ const path = join4(sddRoot(projectRoot), "agents.json");
868
+ if (!await pathExists(path)) return null;
869
+ try {
870
+ const raw = JSON.parse(await readText(path));
871
+ const key = raw.ai ?? raw.integration ?? raw.installed?.[0];
872
+ if (!key || typeof key !== "string") return null;
873
+ const target = parseIntegration(key);
874
+ return { ai: integrationKeyFor(target), target, integration: getIntegration(target) };
875
+ } catch {
876
+ return null;
877
+ }
878
+ }
879
+ async function writeAgentHandoff(projectRoot, config, changeId, budget, runtime) {
880
+ const ctx = await buildContext(projectRoot, config, changeId);
881
+ const body = await buildAgentPrompt(ctx, config, projectRoot, budget, runtime);
882
+ const outPath = join4(sddRoot(projectRoot), "handoff.md");
883
+ const header = `# SDD handoff (auto)
884
+
885
+ > Generated for the AI coding agent configured at init.
886
+ > Change: **${ctx.meta.title}** \xB7 \`${ctx.id}\`
887
+
888
+ Also read: \`.sdd/active-context.md\`, \`.sdd/protocol.md\`, \`${guidancePaths(config).constitution}\` if present.
889
+
890
+ ---
891
+
892
+ `;
893
+ await writeText(outPath, header + body + "\n");
894
+ return outPath;
895
+ }
896
+ function agentKickoffMessage(opts) {
897
+ const event = opts.event ?? "context updated";
898
+ const paths = guidancePaths({
899
+ memory_path: opts.memoryPath ?? "memory",
900
+ changes_path: opts.changesPath ?? "changes"
901
+ });
902
+ if (opts.instructions?.trim()) {
903
+ return [
904
+ `SDD: ${event}.`,
905
+ `Title: ${opts.title}`,
906
+ `Change: ${opts.changeId}`,
907
+ `Stage: ${opts.stage}`,
908
+ opts.instructions.trim()
909
+ ].join(" ");
910
+ }
911
+ return [
912
+ `SDD: ${event}.`,
913
+ `Title: ${opts.title}`,
914
+ `Change: ${opts.changeId}`,
915
+ `Stage: ${opts.stage}`,
916
+ `Read in order: .sdd/active-context.md, .sdd/handoff.md, .sdd/protocol.md, ${paths.constitution} (if present).`,
917
+ `Do the work for the current stage only under ${paths.changes}/${opts.changeId}/. Do not expand scope.`,
918
+ `When the stage is done, tell the human to run: sdd next (or sdd verify / sdd complete as appropriate).`
919
+ ].join(" ");
920
+ }
921
+ async function refreshGeneratedAgentFiles(projectRoot, config) {
922
+ const installed = await loadInstalledAgent(projectRoot);
923
+ const files = installed ? agentFiles(installed.integration, config) : [{ rel: join4(".sdd", "protocol.md"), content: renderProtocolMd(config) }];
924
+ for (const { rel, content } of files) {
925
+ const full = join4(projectRoot, rel);
926
+ if (!await pathExists(full)) {
927
+ if (rel === join4(".sdd", "protocol.md")) {
928
+ await writeText(full, markGenerated(content));
929
+ }
930
+ continue;
931
+ }
932
+ const existing = await readText(full);
933
+ if (generatedState(existing) !== "unmodified") continue;
934
+ if (existing === markGenerated(content)) continue;
935
+ await writeText(full, markGenerated(content));
936
+ }
937
+ }
938
+ async function refreshActiveAgentContext(projectRoot) {
939
+ const markerDir = sddRoot(projectRoot);
940
+ if (!await pathExists(join4(markerDir, "config.yaml"))) {
941
+ return null;
942
+ }
943
+ const config = await loadConfig(projectRoot);
944
+ await refreshConfiguredGuidance(projectRoot, config);
945
+ await refreshGeneratedAgentFiles(projectRoot, config);
946
+ const activeId = await getActiveChangeId(projectRoot, config);
947
+ const outPath = join4(markerDir, "active-context.md");
948
+ if (!activeId) {
949
+ const empty = `# SDD active context
950
+
951
+ _No active change._ Run \`sdd new "\u2026"\` or **SDD: New Change** in the IDE.
952
+
953
+ Then re-run \`sdd agents refresh\` (or advance a stage) to update this file.
954
+
955
+ Protocol: \`.sdd/protocol.md\`
956
+ `;
957
+ await writeText(outPath, empty);
958
+ return outPath;
959
+ }
960
+ const ctx = await buildContext(projectRoot, config, activeId);
961
+ const handoff = await buildAgentPrompt(ctx, config, projectRoot);
962
+ const status = formatStatus(ctx);
963
+ const body = `# SDD active context
964
+
965
+ > Auto-generated for coding agents.
966
+ > Change: **${ctx.meta.title}** \xB7 \`${ctx.id}\` \xB7 stage **${ctx.meta.stage}**
967
+ > Protocol: \`.sdd/protocol.md\`
968
+
969
+ ## Status
970
+
971
+ \`\`\`
972
+ ${status}
973
+ \`\`\`
974
+
975
+ ${handoff}
976
+ `;
977
+ await writeText(outPath, body);
978
+ return outPath;
979
+ }
980
+ var PROTOCOL_MD = renderProtocolMd({ memory_path: "memory", changes_path: "changes" });
981
+ function renderProtocolMd(config) {
982
+ const paths = guidancePaths(config);
983
+ const fence = "```";
984
+ return [
985
+ "# SDD protocol",
986
+ "",
987
+ "This repo uses **Structured Vibe Coding** (`sdd`). Agents must follow this file plus `.sdd/active-context.md`. Do not invent a parallel process.",
988
+ "",
989
+ "## Source of truth",
990
+ "",
991
+ "| What | Where |",
992
+ "|------|--------|",
993
+ `| Process state | \`${paths.changes}/<id>/meta.yaml\` (workflow, stage, gates) |`,
994
+ "| Current task snapshot | `.sdd/active-context.md` |",
995
+ `| Non-negotiables | \`${paths.constitution}\` if present |`,
996
+ `| Stable product/tech rules | \`${paths.memory}/*.md\` (start at \`${paths.memoryIndex}\` if present) |`,
997
+ `| Stage artifacts | files in \`${paths.changes}/<id>/\` for the active change |`,
998
+ "",
999
+ "## Required read order",
1000
+ "",
1001
+ "1. `.sdd/active-context.md`",
1002
+ "2. This file (`.sdd/protocol.md`)",
1003
+ `3. \`${paths.constitution}\` if present (non-negotiables agents must not violate)`,
1004
+ "4. `meta.yaml` + artifacts for the **current stage** (and prior stages if needed)",
1005
+ `5. \`${paths.memoryIndex}\` (documentation map) if present, then linked memory pages when architecture or conventions matter`,
1006
+ "",
1007
+ "## Stage behavior",
1008
+ "",
1009
+ "| Stage kind | Agent focus |",
1010
+ "|------------|-------------|",
1011
+ "| intent / feature / stories | Clarify scope; write short artifacts |",
1012
+ "| design / lld / db / research / hl_arb | Specs and decisions; minimal product code |",
1013
+ "| tasks | Break work into implementable checklist |",
1014
+ "| implement | Code only for this change; follow tasks/acceptance |",
1015
+ "| local_verify | Check acceptance; fix only; prepare for complete |",
1016
+ "| brainstorm (optional) | Multiple approaches; parallel exploration; no final design yet |",
1017
+ "| clarify_* (optional) | Ask clarifying questions; write Q&A; or tell human to skip |",
1018
+ "",
1019
+ "## Optional stages",
1020
+ "",
1021
+ "- Stages marked **optional** / kind `brainstorm` or `clarify` may be skipped: ",
1022
+ ' `sdd skip <stage_id> -r "not needed"`',
1023
+ "- Do **not** invent a skip by jumping stages. `sdd next` blocks on empty required stubs (e.g. design.md).",
1024
+ "- If the tool falls back to an earlier stage, **complete that stage** before advancing again.",
1025
+ "",
1026
+ "## Hard rules",
1027
+ "",
1028
+ "1. **Do not skip hard gates.** If blocked, tell the human to run `sdd gate approve` (or fix the gate).",
1029
+ "2. **Stay in scope** of the active change title/intent. No drive-by refactors or new features.",
1030
+ "3. **Honor constraints** in `arb-decision.md`, design, and memory non-negotiables.",
1031
+ "4. **Local verify** is part of done when the workflow has a verify stage: `sdd verify`.",
1032
+ "5. Prefer small, reviewable diffs.",
1033
+ "6. **Required stage artifacts must be substantive** \u2014 not empty templates. Design cannot be skipped via empty design.md.",
1034
+ "",
1035
+ "## Commands (human / shell)",
1036
+ "",
1037
+ `${fence}bash`,
1038
+ "sdd status",
1039
+ "sdd next",
1040
+ "sdd agent # print handoff",
1041
+ "sdd agents refresh # refresh active-context.md",
1042
+ "sdd verify",
1043
+ "sdd complete",
1044
+ fence,
1045
+ "",
1046
+ "## Out of scope for agents",
1047
+ "",
1048
+ "- Replacing CI/CD or team process tools",
1049
+ "- Deleting `.sdd` / completed change history",
1050
+ "- Claiming complete without verify when required",
1051
+ ""
1052
+ ].join("\n");
1053
+ }
1054
+ function renderAgentsMd(integ, config) {
1055
+ const paths = guidancePaths(config ?? { memory_path: "memory", changes_path: "changes" });
1056
+ return [
1057
+ "# Agents",
1058
+ "",
1059
+ "This repo uses **SDD agents only** (no skills).",
1060
+ "",
1061
+ "AI coding agents are **not** the same as IDEs: VS Code, Cursor, and IntelliJ host tools like Copilot or Claude Code.",
1062
+ "",
1063
+ "| Read first | |",
1064
+ "|------------|--|",
1065
+ "| Live task | `.sdd/active-context.md` |",
1066
+ "| Playbook | `.sdd/protocol.md` |",
1067
+ `| Constitution | \`${paths.constitution}\` if present |`,
1068
+ `| Doc map (stable) | \`${paths.memoryIndex}\` if present |`,
1069
+ `| Active change | \`${paths.changes}/<id>/\` |`,
1070
+ "",
1071
+ "| AI agent | Files |",
1072
+ "|----------|--------|",
1073
+ integ.agentsMdRow,
1074
+ "",
1075
+ "Refresh context: `sdd agents refresh`.",
1076
+ ""
1077
+ ].join("\n");
1078
+ }
1079
+
1080
+ export {
1081
+ contentHash,
1082
+ markGenerated,
1083
+ generatedState,
1084
+ loadGeneratedManifest,
1085
+ saveGeneratedManifest,
1086
+ reconcileGeneratedTree,
1087
+ describeKept,
1088
+ defaultsRoot,
1089
+ defaultWorkflowsDir,
1090
+ defaultTemplatesDir,
1091
+ defaultMemoryDir,
1092
+ defaultMcpYamlPath,
1093
+ defaultPricesYamlPath,
1094
+ defaultSearchYamlPath,
1095
+ renderSddReadme,
1096
+ renderMemoryIndex,
1097
+ refreshConfiguredGuidance,
1098
+ AGENT_INTEGRATIONS,
1099
+ ALL_AGENT_TARGETS,
1100
+ DEFAULT_INIT_INTEGRATION,
1101
+ AGENT_TARGET_OPTIONS,
1102
+ getIntegration,
1103
+ integrationKeyFor,
1104
+ optionForTarget,
1105
+ agentHostPaths,
1106
+ describeFsError,
1107
+ AgentInstallPreflightError,
1108
+ removeOtherAgentHostsDetailed,
1109
+ removeOtherAgentHosts,
1110
+ parseIntegration,
1111
+ parseAgentTargets,
1112
+ SDD_AGENT_ROLES,
1113
+ planAgentIntegration,
1114
+ assertAgentInstallable,
1115
+ installAgentIntegration,
1116
+ installAgentIntegrations,
1117
+ renderThinAgent,
1118
+ loadInstalledAgent,
1119
+ writeAgentHandoff,
1120
+ agentKickoffMessage,
1121
+ refreshActiveAgentContext,
1122
+ PROTOCOL_MD,
1123
+ renderProtocolMd,
1124
+ renderAgentsMd
1125
+ };