@structured-vibe-coding/core 0.5.2 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -256,9 +256,6 @@ function defaultConfig() {
256
256
  return ConfigSchema.parse({});
257
257
  }
258
258
 
259
- // src/change.ts
260
- import { join as join3 } from "pathe";
261
-
262
259
  // src/slug.ts
263
260
  function slugify(input) {
264
261
  return input.toLowerCase().trim().replace(/['"]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60).replace(/-+$/g, "");
@@ -453,113 +450,7 @@ async function recommendWorkflow(projectRoot, title, config, opts) {
453
450
  };
454
451
  }
455
452
 
456
- // src/change.ts
457
- async function existingChangeIds(projectRoot, config) {
458
- const open = await listDirs(changesDir(projectRoot, config));
459
- const archived = await listDirs(archiveDir(projectRoot, config));
460
- return /* @__PURE__ */ new Set([...open, ...archived]);
461
- }
462
- async function createChange(input) {
463
- const { projectRoot, config, title, workflowName } = input;
464
- const workflow = await loadWorkflow(projectRoot, workflowName);
465
- if (!workflow.stages.length) {
466
- throw new Error(`Workflow ${workflowName} has no stages`);
467
- }
468
- const baseId = changeDirName(title);
469
- const id = uniqueChangeDirName(baseId, await existingChangeIds(projectRoot, config));
470
- const dir = changePath(projectRoot, config, id);
471
- await ensureDir(dir);
472
- let meta = ChangeMetaSchema.parse({
473
- id,
474
- title,
475
- workflow: workflowName,
476
- created: nowIso(),
477
- updated: nowIso(),
478
- status: "in_progress",
479
- stage: workflow.stages[0].id,
480
- domain: input.domain,
481
- branch: input.branch,
482
- flags: input.flags ?? {},
483
- overrides: {
484
- skip_stages: [],
485
- gates: {},
486
- extra_stages: []
487
- },
488
- skipped: [],
489
- gates: {}
490
- });
491
- const startId = firstActiveStageId(workflow, meta) ?? workflow.stages[0].id;
492
- meta = { ...meta, stage: startId };
493
- const firstStage = getStage(workflow, meta, startId);
494
- if (!firstStage) {
495
- throw new Error(`Workflow ${workflowName} has no active stages`);
496
- }
497
- await writeYaml(changeMetaPath(projectRoot, config, id), meta);
498
- await materializeStageArtifacts(projectRoot, config, id, workflow, meta, firstStage);
499
- await setActiveChange(projectRoot, config, id);
500
- try {
501
- const { refreshActiveAgentContext: refreshActiveAgentContext2 } = await import("./agents-MIMBQVHE.js");
502
- await refreshActiveAgentContext2(projectRoot);
503
- } catch {
504
- }
505
- return buildContext(projectRoot, config, id);
506
- }
507
- async function materializeStageArtifacts(projectRoot, config, changeId, workflow, meta, stage) {
508
- const created = [];
509
- const dir = changePath(projectRoot, config, changeId);
510
- const tdir = templatesDir(projectRoot);
511
- for (const artifact of stage.artifacts) {
512
- const target = join3(dir, artifact.path);
513
- if (await pathExists(target)) continue;
514
- if (artifact.path.endsWith("/")) {
515
- await ensureDir(target);
516
- created.push(artifact.path);
517
- continue;
518
- }
519
- let content = defaultArtifactContent(artifact.id, meta, stage, workflow);
520
- if (artifact.template) {
521
- const templatePath = join3(tdir, artifact.template.replace(/^templates\//, ""));
522
- const candidates = [
523
- join3(tdir, artifact.template),
524
- templatePath,
525
- join3(tdir, `${artifact.id}.md`)
526
- ];
527
- for (const c of candidates) {
528
- if (await pathExists(c)) {
529
- const raw = await readText(c);
530
- content = interpolate(raw, meta, stage, workflow);
531
- break;
532
- }
533
- }
534
- } else {
535
- const fallback = join3(tdir, `${artifact.id}.md`);
536
- if (await pathExists(fallback)) {
537
- content = interpolate(await readText(fallback), meta, stage, workflow);
538
- }
539
- }
540
- await writeText(target, content);
541
- created.push(artifact.path);
542
- }
543
- return created;
544
- }
545
- function interpolate(template, meta, stage, workflow) {
546
- 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));
547
- }
548
- function defaultArtifactContent(artifactId, meta, stage, workflow) {
549
- return `# ${artifactId}
550
-
551
- > Change: **${meta.title}** (\`${meta.id}\`)
552
- > Workflow: \`${workflow.name}\` \xB7 Stage: \`${stage.id}\`
553
-
554
- ## Summary
555
-
556
- <!-- Write the intent / content for this artifact -->
557
-
558
- ## Notes
559
-
560
- -
561
- `;
562
- }
453
+ // src/change-context.ts
563
454
  async function loadChangeMeta(projectRoot, config, changeId) {
564
455
  const path = changeMetaPath(projectRoot, config, changeId);
565
456
  if (!await pathExists(path)) {
@@ -633,231 +524,9 @@ async function resolveChangeId(projectRoot, config, changeId) {
633
524
  }
634
525
  return active;
635
526
  }
636
- async function canLeaveStage(ctx, config) {
637
- const stage = getStage(ctx.workflow, ctx.meta, ctx.meta.stage);
638
- const errors = [];
639
- const warnings = [];
640
- if (!stage) {
641
- return { ok: false, errors: [`Unknown stage: ${ctx.meta.stage}`], warnings };
642
- }
643
- for (const artifact of stage.artifacts) {
644
- if (!artifact.required) continue;
645
- const target = join3(ctx.path, artifact.path);
646
- if (!await pathExists(target)) {
647
- errors.push(`Missing required artifact: ${artifact.path}`);
648
- continue;
649
- }
650
- }
651
- const declared = stage.gate?.type ?? "soft";
652
- let effectiveGate = declared;
653
- if (config.policy.gates === "hard" && declared === "soft") {
654
- effectiveGate = "hard";
655
- }
656
- const gateState = ctx.meta.gates?.[stage.id];
657
- if (effectiveGate === "hard") {
658
- if (!gateState || gateState.status !== "approved" && gateState.status !== "waived") {
659
- errors.push(
660
- `Hard gate on stage "${stage.id}" is not approved. Run \`sdd gate approve\` or complete the checklist.`
661
- );
662
- }
663
- } else if (effectiveGate === "soft" && stage.gate?.checklist?.length) {
664
- if (!gateState || gateState.status === "pending") {
665
- warnings.push(
666
- `Soft gate checklist not signed off on "${stage.id}" (proceeding allowed).`
667
- );
668
- }
669
- }
670
- const requiredCmds = stage.verify?.commands?.filter((c) => c.required) ?? [];
671
- if (requiredCmds.length) {
672
- const verifyOk = ctx.meta.verify_results?.[stage.id]?.ok === true;
673
- const humanOverride = gateState?.status === "waived" || gateState?.status === "approved";
674
- if (!verifyOk && !humanOverride) {
675
- errors.push(
676
- `Required local verify commands have not passed for "${stage.id}". Run \`sdd verify\` (or \`sdd gate approve/waive\` with a note).`
677
- );
678
- } else if (!verifyOk && humanOverride) {
679
- warnings.push(
680
- `Stage "${stage.id}" gate signed off without a successful required verify run.`
681
- );
682
- }
683
- }
684
- return { ok: errors.length === 0, errors, warnings };
685
- }
686
- async function advanceStage(projectRoot, config, changeId, opts) {
687
- const ctx = await buildContext(projectRoot, config, changeId);
688
- const warnings = [];
689
- if (ctx.meta.status === "completed") {
690
- throw new Error("Change is already completed.");
691
- }
692
- const check = await canLeaveStage(ctx, config);
693
- warnings.push(...check.warnings);
694
- if (!check.ok && !opts?.force) {
695
- throw new Error(check.errors.join("\n"));
696
- }
697
- const from = ctx.meta.stage;
698
- const next = nextStageId(ctx.workflow, ctx.meta);
699
- if (!next) {
700
- return {
701
- ctx,
702
- from,
703
- to: null,
704
- completed: false,
705
- artifactsCreated: [],
706
- warnings: [...warnings, "No further stages. Run `sdd complete` when ready."]
707
- };
708
- }
709
- ctx.meta.stage = next;
710
- await saveChangeMeta(projectRoot, config, ctx.meta);
711
- const stage = getStage(ctx.workflow, ctx.meta, next);
712
- const artifactsCreated = await materializeStageArtifacts(
713
- projectRoot,
714
- config,
715
- changeId,
716
- ctx.workflow,
717
- ctx.meta,
718
- stage
719
- );
720
- try {
721
- const { refreshActiveAgentContext: refreshActiveAgentContext2 } = await import("./agents-MIMBQVHE.js");
722
- await refreshActiveAgentContext2(projectRoot);
723
- } catch {
724
- }
725
- const updated = await buildContext(projectRoot, config, changeId);
726
- return {
727
- ctx: updated,
728
- from,
729
- to: next,
730
- completed: false,
731
- artifactsCreated,
732
- warnings
733
- };
734
- }
735
- async function skipStage(projectRoot, config, changeId, stageId, reason) {
736
- if (!config.per_change.allow_skip) {
737
- throw new Error("Skipping stages is disabled in .sdd/config.yaml (per_change.allow_skip).");
738
- }
739
- if (config.per_change.require_reason_on_skip && !reason.trim()) {
740
- throw new Error("A reason is required to skip a stage.");
741
- }
742
- const ctx = await buildContext(projectRoot, config, changeId);
743
- const stage = getStage(ctx.workflow, ctx.meta, stageId);
744
- if (!stage) {
745
- throw new Error(`Stage "${stageId}" not found in workflow ${ctx.meta.workflow}`);
746
- }
747
- if (!stage.skippable) {
748
- throw new Error(`Stage "${stageId}" is not skippable.`);
749
- }
750
- const skip_stages = new Set(ctx.meta.overrides.skip_stages ?? []);
751
- skip_stages.add(stageId);
752
- ctx.meta.overrides.skip_stages = [...skip_stages];
753
- ctx.meta.skipped = [
754
- ...ctx.meta.skipped ?? [],
755
- { stage: stageId, reason, at: nowIso() }
756
- ];
757
- if (ctx.meta.stage === stageId) {
758
- const next = nextStageId(ctx.workflow, ctx.meta);
759
- if (next) {
760
- ctx.meta.stage = next;
761
- const nextStage = getStage(ctx.workflow, ctx.meta, next);
762
- if (nextStage) {
763
- await materializeStageArtifacts(
764
- projectRoot,
765
- config,
766
- changeId,
767
- ctx.workflow,
768
- ctx.meta,
769
- nextStage
770
- );
771
- }
772
- }
773
- }
774
- await saveChangeMeta(projectRoot, config, ctx.meta);
775
- return buildContext(projectRoot, config, changeId);
776
- }
777
- async function switchWorkflow(projectRoot, config, changeId, workflowName, reason) {
778
- const ctx = await buildContext(projectRoot, config, changeId);
779
- const workflow = await loadWorkflow(projectRoot, workflowName);
780
- if (config.allowed_workflows?.length && !config.allowed_workflows.includes(workflowName)) {
781
- throw new Error(
782
- `Workflow "${workflowName}" is not in allowed_workflows: ${config.allowed_workflows.join(", ")}`
783
- );
784
- }
785
- ctx.meta.workflow = workflowName;
786
- const hasStage = workflow.stages.some((s) => s.id === ctx.meta.stage);
787
- if (!hasStage) {
788
- ctx.meta.stage = workflow.stages[0].id;
789
- }
790
- if (reason) {
791
- ctx.meta.flags = {
792
- ...ctx.meta.flags,
793
- workflow_switch_reason: reason
794
- };
795
- }
796
- await saveChangeMeta(projectRoot, config, ctx.meta);
797
- const stage = getStage(workflow, ctx.meta, ctx.meta.stage);
798
- if (stage) {
799
- await materializeStageArtifacts(projectRoot, config, changeId, workflow, ctx.meta, stage);
800
- }
801
- return buildContext(projectRoot, config, changeId);
802
- }
803
- async function approveGate(projectRoot, config, changeId, stageId, note, status = "approved") {
804
- const ctx = await buildContext(projectRoot, config, changeId);
805
- const id = stageId ?? ctx.meta.stage;
806
- ctx.meta.gates = {
807
- ...ctx.meta.gates,
808
- [id]: { status, note, at: nowIso() }
809
- };
810
- await saveChangeMeta(projectRoot, config, ctx.meta);
811
- return buildContext(projectRoot, config, changeId);
812
- }
813
- async function completeChange(projectRoot, config, changeId) {
814
- const ctx = await buildContext(projectRoot, config, changeId);
815
- const next = nextStageId(ctx.workflow, ctx.meta);
816
- if (next) {
817
- const active = activeStages(ctx.workflow, ctx.meta);
818
- const last = active[active.length - 1];
819
- if (last && ctx.meta.stage !== last.id) {
820
- throw new Error(
821
- `Cannot complete: still on stage "${ctx.meta.stage}". Finish remaining stages or skip them. Next: ${next}`
822
- );
823
- }
824
- }
825
- const check = await canLeaveStage(ctx, config);
826
- if (!check.ok) {
827
- throw new Error(check.errors.join("\n"));
828
- }
829
- ctx.meta.status = "completed";
830
- ctx.meta.completed_at = nowIso();
831
- await saveChangeMeta(projectRoot, config, ctx.meta);
832
- await clearActiveIf(projectRoot, config, changeId);
833
- let archivedTo = null;
834
- const shouldArchive = config.persistence.archive_on_complete && ctx.workflow.on_complete?.archive !== false;
835
- if (shouldArchive) {
836
- const dest = join3(archiveDir(projectRoot, config), changeId);
837
- if (await pathExists(dest)) {
838
- throw new Error(`Archive already exists for change: ${changeId}`);
839
- }
840
- await ensureDir(archiveDir(projectRoot, config));
841
- await copyDir(ctx.path, dest);
842
- await removePath(ctx.path);
843
- archivedTo = dest;
844
- }
845
- if (archivedTo) {
846
- const meta = ChangeMetaSchema.parse(await readYaml(join3(archivedTo, "meta.yaml")));
847
- return {
848
- archivedTo,
849
- ctx: {
850
- id: changeId,
851
- path: archivedTo,
852
- meta,
853
- workflow: ctx.workflow,
854
- stages: ctx.stages,
855
- active: ctx.active
856
- }
857
- };
858
- }
859
- return { archivedTo, ctx: await buildContext(projectRoot, config, changeId) };
860
- }
527
+
528
+ // src/agent-handoff.ts
529
+ import { join as join3 } from "pathe";
861
530
  function formatStatus(ctx) {
862
531
  const lines = [];
863
532
  lines.push(`Change: ${ctx.meta.title}`);
@@ -881,7 +550,9 @@ function formatStatus(ctx) {
881
550
  const gate = ctx.meta.gates?.[stage.id];
882
551
  const gateInfo = gate ? ` [${gate.status}]` : "";
883
552
  const skipInfo = skipped ? " (skipped)" : "";
884
- lines.push(` [${mark}] ${stage.id}${stage.title ? ` \u2014 ${stage.title}` : ""}${gateInfo}${skipInfo}`);
553
+ lines.push(
554
+ ` [${mark}] ${stage.id}${stage.title ? ` \u2014 ${stage.title}` : ""}${gateInfo}${skipInfo}`
555
+ );
885
556
  }
886
557
  if (ctx.meta.overrides?.skip_stages?.length) {
887
558
  lines.push("");
@@ -900,7 +571,9 @@ async function buildAgentPrompt(ctx, config, projectRoot) {
900
571
  parts.push(`- Title: ${ctx.meta.title}`);
901
572
  parts.push(`- ID: ${ctx.id}`);
902
573
  parts.push(`- Workflow: ${ctx.meta.workflow}`);
903
- parts.push(`- Current stage: ${ctx.meta.stage}${stage?.title ? ` (${stage.title})` : ""}`);
574
+ parts.push(
575
+ `- Current stage: ${ctx.meta.stage}${stage?.title ? ` (${stage.title})` : ""}`
576
+ );
904
577
  parts.push(`- Change path: ${ctx.path}`);
905
578
  parts.push("");
906
579
  if (stage?.summary) {
@@ -935,64 +608,102 @@ async function buildAgentPrompt(ctx, config, projectRoot) {
935
608
  }
936
609
  }
937
610
  parts.push("");
938
- parts.push(`When done with this stage, the human will run \`sdd next\` or \`sdd verify\` / \`sdd complete\`.`);
611
+ parts.push(
612
+ `When done with this stage, the human will run \`sdd next\` or \`sdd verify\` / \`sdd complete\`.`
613
+ );
939
614
  return parts.join("\n");
940
615
  }
941
616
 
942
617
  // src/agents.ts
943
- var ALL_AGENT_TARGETS = ["copilot", "claude-code"];
944
- var DEFAULT_INIT_INTEGRATION = "copilot";
945
- var AGENT_TARGET_OPTIONS = [
618
+ var AGENT_INTEGRATIONS = [
946
619
  {
947
620
  id: "copilot",
948
621
  key: "copilot",
622
+ aliases: ["github-copilot", "gh-copilot"],
949
623
  label: "GitHub Copilot",
950
624
  hint: ".github/agents/*.agent.md (VS Code, Cursor, JetBrains, \u2026)",
951
625
  requiresCli: false,
952
- installUrl: "https://github.com/features/copilot"
626
+ installUrl: "https://github.com/features/copilot",
627
+ rolePath: (roleId) => `.github/agents/${roleId}.agent.md`,
628
+ agentsMdRow: "| GitHub Copilot | `.github/agents/*.agent.md` (same roles; any IDE that supports Copilot agents) |"
953
629
  },
954
630
  {
955
631
  id: "claude-code",
956
632
  key: "claude",
633
+ aliases: ["claude-code", "claudecode"],
957
634
  label: "Claude Code",
958
635
  hint: ".claude/agents/*.md (terminal agent)",
959
636
  requiresCli: true,
960
- installUrl: "https://docs.anthropic.com/en/docs/claude-code"
637
+ cliBinary: "claude",
638
+ installUrl: "https://docs.anthropic.com/en/docs/claude-code",
639
+ rolePath: (roleId) => `.claude/agents/${roleId}.md`,
640
+ agentsMdRow: "| Claude Code | `.claude/agents/` (`sdd`, `sdd-planner`, `sdd-implementer`, `sdd-reviewer`) |"
961
641
  }
962
642
  ];
643
+ var ALL_AGENT_TARGETS = AGENT_INTEGRATIONS.map((i) => i.id);
644
+ var DEFAULT_INIT_INTEGRATION = "copilot";
645
+ var AGENT_TARGET_OPTIONS = AGENT_INTEGRATIONS.map(
646
+ ({ id, key, label, hint, requiresCli, installUrl }) => ({
647
+ id,
648
+ key,
649
+ label,
650
+ hint,
651
+ requiresCli,
652
+ installUrl
653
+ })
654
+ );
655
+ var IDE_NAMES = /* @__PURE__ */ new Set([
656
+ "intellij",
657
+ "idea",
658
+ "jetbrains",
659
+ "vscode",
660
+ "vs-code",
661
+ "cursor"
662
+ ]);
663
+ function getIntegration(target) {
664
+ const found = AGENT_INTEGRATIONS.find((i) => i.id === target);
665
+ if (!found) throw new Error(`Unknown agent integration: ${target}`);
666
+ return found;
667
+ }
963
668
  function integrationKeyFor(target) {
964
- return AGENT_TARGET_OPTIONS.find((o) => o.id === target)?.key ?? target;
669
+ return getIntegration(target).key;
965
670
  }
966
671
  function optionForTarget(target) {
967
- return AGENT_TARGET_OPTIONS.find((o) => o.id === target);
672
+ return getIntegration(target);
673
+ }
674
+ function parseIntegration(raw) {
675
+ const p = raw.trim().toLowerCase();
676
+ if (!p) {
677
+ throw new Error(
678
+ `Expected one AI coding agent. Choose from: ${AGENT_INTEGRATIONS.map((i) => i.key).join(", ")}`
679
+ );
680
+ }
681
+ if (IDE_NAMES.has(p)) {
682
+ throw new Error(
683
+ `"${p}" is an IDE, not an AI coding agent. Choose: copilot or claude (Claude Code).`
684
+ );
685
+ }
686
+ for (const integ of AGENT_INTEGRATIONS) {
687
+ if (integ.id === p || integ.key === p || integ.aliases.includes(p)) {
688
+ return integ.id;
689
+ }
690
+ }
691
+ throw new Error(
692
+ `Unknown AI coding agent "${raw}". Choose from: ${AGENT_INTEGRATIONS.map((i) => i.key).join(", ")}`
693
+ );
968
694
  }
969
695
  function parseAgentTargets(raw) {
970
- const parts = (Array.isArray(raw) ? raw : [raw]).flatMap((s) => String(s).split(",")).map((s) => s.trim().toLowerCase()).filter(Boolean);
696
+ const parts = (Array.isArray(raw) ? raw : [raw]).flatMap((s) => String(s).split(",")).map((s) => s.trim()).filter(Boolean);
697
+ if (!parts.length) {
698
+ throw new Error("No AI coding agent specified");
699
+ }
971
700
  const out = [];
972
701
  for (const p of parts) {
973
- if (p === "intellij" || p === "idea" || p === "jetbrains" || p === "vscode" || p === "vs-code" || p === "cursor") {
974
- throw new Error(
975
- `"${p}" is an IDE, not an AI coding agent. Choose: copilot or claude (Claude Code).`
976
- );
977
- }
978
- const id = p === "claude" || p === "claude-code" || p === "claudecode" ? "claude-code" : p === "github-copilot" || p === "gh-copilot" || p === "copilot" ? "copilot" : p;
979
- if (!ALL_AGENT_TARGETS.includes(id)) {
980
- const keys = AGENT_TARGET_OPTIONS.map((o) => o.key).join(", ");
981
- throw new Error(`Unknown AI coding agent "${p}". Choose from: ${keys}`);
982
- }
702
+ const id = parseIntegration(p);
983
703
  if (!out.includes(id)) out.push(id);
984
704
  }
985
705
  return out;
986
706
  }
987
- function parseIntegration(raw) {
988
- const list = parseAgentTargets(raw);
989
- if (list.length !== 1) {
990
- throw new Error(
991
- `Expected one AI coding agent (like Speckit --integration), got: ${raw}`
992
- );
993
- }
994
- return list[0];
995
- }
996
707
  var SDD_AGENT_ROLES = [
997
708
  {
998
709
  id: "sdd",
@@ -1032,13 +743,8 @@ var SDD_AGENT_ROLES = [
1032
743
  ]
1033
744
  }
1034
745
  ];
1035
- async function installAgentIntegrations(opts) {
1036
- if (!opts.targets?.length) {
1037
- throw new Error(
1038
- `Specify at least one AI coding agent: ${ALL_AGENT_TARGETS.join(", ")} (not an IDE; do not install all by default)`
1039
- );
1040
- }
1041
- const targets = opts.targets;
746
+ async function installAgentIntegration(opts) {
747
+ const integ = getIntegration(opts.target);
1042
748
  const created = [];
1043
749
  const skipped = [];
1044
750
  const root = opts.projectRoot;
@@ -1055,47 +761,48 @@ async function installAgentIntegrations(opts) {
1055
761
  await write(join4(".sdd", "protocol.md"), PROTOCOL_MD);
1056
762
  await refreshActiveAgentContext(root);
1057
763
  for (const role of SDD_AGENT_ROLES) {
1058
- const body = renderThinAgent(role);
1059
- if (targets.includes("claude-code")) {
1060
- await write(`.claude/agents/${role.id}.md`, body);
1061
- }
1062
- if (targets.includes("copilot")) {
1063
- await write(`.github/agents/${role.id}.agent.md`, body);
1064
- }
764
+ await write(integ.rolePath(role.id), renderThinAgent(role));
1065
765
  }
1066
- await write("AGENTS.md", renderAgentsMd(targets));
766
+ await write("AGENTS.md", renderAgentsMd(integ));
1067
767
  await write(
1068
768
  join4(".sdd", "agents.json"),
1069
769
  JSON.stringify(
1070
770
  {
1071
- version: 2,
771
+ version: 3,
1072
772
  mode: "agents-only",
1073
773
  protocol: ".sdd/protocol.md",
1074
774
  activeContext: ".sdd/active-context.md",
1075
775
  roles: SDD_AGENT_ROLES.map((r) => r.id),
1076
- installed: targets,
1077
- /** Speckit-style primary integration key (first target). */
1078
- integration: targets[0] ? integrationKeyFor(targets[0]) : null,
776
+ /** Speckit-style public key */
777
+ ai: integ.key,
778
+ integration: integ.key,
779
+ installed: [integ.id],
1079
780
  updated: (/* @__PURE__ */ new Date()).toISOString()
1080
781
  },
1081
782
  null,
1082
783
  2
1083
784
  ) + "\n"
1084
785
  );
1085
- await write(
1086
- join4(".sdd", "init-options.json"),
1087
- JSON.stringify(
1088
- {
1089
- ai: targets[0] ? integrationKeyFor(targets[0]) : null,
1090
- integration: targets[0] ? integrationKeyFor(targets[0]) : null,
1091
- installed: targets.map(integrationKeyFor),
1092
- updated: (/* @__PURE__ */ new Date()).toISOString()
1093
- },
1094
- null,
1095
- 2
1096
- ) + "\n"
1097
- );
1098
- return { created, skipped, targets };
786
+ return { created, skipped, target: opts.target };
787
+ }
788
+ async function installAgentIntegrations(opts) {
789
+ if (!opts.targets?.length) {
790
+ throw new Error(
791
+ `Specify one AI coding agent: ${AGENT_INTEGRATIONS.map((i) => i.key).join(", ")}`
792
+ );
793
+ }
794
+ const created = [];
795
+ const skipped = [];
796
+ for (const target of opts.targets) {
797
+ const r = await installAgentIntegration({
798
+ projectRoot: opts.projectRoot,
799
+ target,
800
+ force: opts.force
801
+ });
802
+ created.push(...r.created);
803
+ skipped.push(...r.skipped);
804
+ }
805
+ return { created, skipped, targets: opts.targets };
1099
806
  }
1100
807
  function renderThinAgent(role) {
1101
808
  const rules = role.roleRules.map((r) => `- ${r}`).join("\n");
@@ -1223,18 +930,7 @@ sdd complete
1223
930
  - Deleting \`.sdd\` / archive history
1224
931
  - Claiming complete without verify when required
1225
932
  `;
1226
- function renderAgentsMd(targets) {
1227
- const rows = [];
1228
- if (targets.includes("claude-code")) {
1229
- rows.push(
1230
- "| Claude Code | `.claude/agents/` (`sdd`, `sdd-planner`, `sdd-implementer`, `sdd-reviewer`) |"
1231
- );
1232
- }
1233
- if (targets.includes("copilot")) {
1234
- rows.push(
1235
- "| GitHub Copilot | `.github/agents/*.agent.md` (same roles; any IDE that supports Copilot agents) |"
1236
- );
1237
- }
933
+ function renderAgentsMd(integ) {
1238
934
  return `# Agents
1239
935
 
1240
936
  This repo uses **SDD agents only** (no skills).
@@ -1248,7 +944,7 @@ AI coding agents are **not** the same as IDEs: VS Code, Cursor, and IntelliJ hos
1248
944
 
1249
945
  | AI agent | Files |
1250
946
  |----------|--------|
1251
- ${rows.join("\n")}
947
+ ${integ.agentsMdRow}
1252
948
 
1253
949
  Refresh context: \`sdd agents refresh\`.
1254
950
  `;
@@ -1304,33 +1000,29 @@ export {
1304
1000
  nowIso,
1305
1001
  changeDirName,
1306
1002
  uniqueChangeDirName,
1003
+ loadChangeMeta,
1004
+ saveChangeMeta,
1005
+ buildContext,
1006
+ listChanges,
1007
+ getActiveChangeId,
1008
+ clearActiveIf,
1009
+ setActiveChange,
1010
+ resolveChangeId,
1011
+ formatStatus,
1012
+ buildAgentPrompt,
1013
+ AGENT_INTEGRATIONS,
1307
1014
  ALL_AGENT_TARGETS,
1308
1015
  DEFAULT_INIT_INTEGRATION,
1309
1016
  AGENT_TARGET_OPTIONS,
1017
+ getIntegration,
1310
1018
  integrationKeyFor,
1311
1019
  optionForTarget,
1312
- parseAgentTargets,
1313
1020
  parseIntegration,
1021
+ parseAgentTargets,
1314
1022
  SDD_AGENT_ROLES,
1023
+ installAgentIntegration,
1315
1024
  installAgentIntegrations,
1316
1025
  renderThinAgent,
1317
1026
  refreshActiveAgentContext,
1318
- PROTOCOL_MD,
1319
- createChange,
1320
- loadChangeMeta,
1321
- saveChangeMeta,
1322
- buildContext,
1323
- listChanges,
1324
- getActiveChangeId,
1325
- clearActiveIf,
1326
- setActiveChange,
1327
- resolveChangeId,
1328
- canLeaveStage,
1329
- advanceStage,
1330
- skipStage,
1331
- switchWorkflow,
1332
- approveGate,
1333
- completeChange,
1334
- formatStatus,
1335
- buildAgentPrompt
1027
+ PROTOCOL_MD
1336
1028
  };