@structured-vibe-coding/core 0.5.1 → 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.
- package/dist/{agents-MIMBQVHE.js → agents-5YI2CWAB.js} +7 -1
- package/dist/{chunk-IO6AS6JI.js → chunk-CHDYZIOH.js} +122 -430
- package/dist/index.d.ts +75 -53
- package/dist/index.js +370 -34
- package/package.json +1 -1
|
@@ -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
|
-
|
|
637
|
-
|
|
638
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
|
669
|
+
return getIntegration(target).key;
|
|
965
670
|
}
|
|
966
671
|
function optionForTarget(target) {
|
|
967
|
-
return
|
|
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()
|
|
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
|
-
|
|
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
|
|
1036
|
-
|
|
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
|
-
|
|
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(
|
|
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:
|
|
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
|
-
|
|
1077
|
-
|
|
1078
|
-
integration:
|
|
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
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
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(
|
|
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
|
-
${
|
|
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
|
};
|