@structured-vibe-coding/core 0.5.2 → 0.5.4

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