@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/README.md +1 -1
- package/defaults/memory/index.md +36 -0
- package/dist/{agents-MIMBQVHE.js → agents-4Z3YZRUK.js} +7 -1
- package/dist/{chunk-IO6AS6JI.js → chunk-I45MAHM6.js} +126 -432
- package/dist/index.d.ts +76 -54
- package/dist/index.js +389 -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) {
|
|
@@ -921,6 +594,7 @@ async function buildAgentPrompt(ctx, config, projectRoot) {
|
|
|
921
594
|
parts.push("");
|
|
922
595
|
parts.push(`## Artifacts to read`);
|
|
923
596
|
const memoryHint = join3(projectRoot, config.memory_path);
|
|
597
|
+
parts.push(`- Documentation map (if present): ${memoryHint}/index.md`);
|
|
924
598
|
parts.push(`- Memory: ${memoryHint}/`);
|
|
925
599
|
for (const s of ctx.stages) {
|
|
926
600
|
for (const a of s.artifacts) {
|
|
@@ -935,64 +609,102 @@ async function buildAgentPrompt(ctx, config, projectRoot) {
|
|
|
935
609
|
}
|
|
936
610
|
}
|
|
937
611
|
parts.push("");
|
|
938
|
-
parts.push(
|
|
612
|
+
parts.push(
|
|
613
|
+
`When done with this stage, the human will run \`sdd next\` or \`sdd verify\` / \`sdd complete\`.`
|
|
614
|
+
);
|
|
939
615
|
return parts.join("\n");
|
|
940
616
|
}
|
|
941
617
|
|
|
942
618
|
// src/agents.ts
|
|
943
|
-
var
|
|
944
|
-
var DEFAULT_INIT_INTEGRATION = "copilot";
|
|
945
|
-
var AGENT_TARGET_OPTIONS = [
|
|
619
|
+
var AGENT_INTEGRATIONS = [
|
|
946
620
|
{
|
|
947
621
|
id: "copilot",
|
|
948
622
|
key: "copilot",
|
|
623
|
+
aliases: ["github-copilot", "gh-copilot"],
|
|
949
624
|
label: "GitHub Copilot",
|
|
950
625
|
hint: ".github/agents/*.agent.md (VS Code, Cursor, JetBrains, \u2026)",
|
|
951
626
|
requiresCli: false,
|
|
952
|
-
installUrl: "https://github.com/features/copilot"
|
|
627
|
+
installUrl: "https://github.com/features/copilot",
|
|
628
|
+
rolePath: (roleId) => `.github/agents/${roleId}.agent.md`,
|
|
629
|
+
agentsMdRow: "| GitHub Copilot | `.github/agents/*.agent.md` (same roles; any IDE that supports Copilot agents) |"
|
|
953
630
|
},
|
|
954
631
|
{
|
|
955
632
|
id: "claude-code",
|
|
956
633
|
key: "claude",
|
|
634
|
+
aliases: ["claude-code", "claudecode"],
|
|
957
635
|
label: "Claude Code",
|
|
958
636
|
hint: ".claude/agents/*.md (terminal agent)",
|
|
959
637
|
requiresCli: true,
|
|
960
|
-
|
|
638
|
+
cliBinary: "claude",
|
|
639
|
+
installUrl: "https://docs.anthropic.com/en/docs/claude-code",
|
|
640
|
+
rolePath: (roleId) => `.claude/agents/${roleId}.md`,
|
|
641
|
+
agentsMdRow: "| Claude Code | `.claude/agents/` (`sdd`, `sdd-planner`, `sdd-implementer`, `sdd-reviewer`) |"
|
|
961
642
|
}
|
|
962
643
|
];
|
|
644
|
+
var ALL_AGENT_TARGETS = AGENT_INTEGRATIONS.map((i) => i.id);
|
|
645
|
+
var DEFAULT_INIT_INTEGRATION = "copilot";
|
|
646
|
+
var AGENT_TARGET_OPTIONS = AGENT_INTEGRATIONS.map(
|
|
647
|
+
({ id, key, label, hint, requiresCli, installUrl }) => ({
|
|
648
|
+
id,
|
|
649
|
+
key,
|
|
650
|
+
label,
|
|
651
|
+
hint,
|
|
652
|
+
requiresCli,
|
|
653
|
+
installUrl
|
|
654
|
+
})
|
|
655
|
+
);
|
|
656
|
+
var IDE_NAMES = /* @__PURE__ */ new Set([
|
|
657
|
+
"intellij",
|
|
658
|
+
"idea",
|
|
659
|
+
"jetbrains",
|
|
660
|
+
"vscode",
|
|
661
|
+
"vs-code",
|
|
662
|
+
"cursor"
|
|
663
|
+
]);
|
|
664
|
+
function getIntegration(target) {
|
|
665
|
+
const found = AGENT_INTEGRATIONS.find((i) => i.id === target);
|
|
666
|
+
if (!found) throw new Error(`Unknown agent integration: ${target}`);
|
|
667
|
+
return found;
|
|
668
|
+
}
|
|
963
669
|
function integrationKeyFor(target) {
|
|
964
|
-
return
|
|
670
|
+
return getIntegration(target).key;
|
|
965
671
|
}
|
|
966
672
|
function optionForTarget(target) {
|
|
967
|
-
return
|
|
673
|
+
return getIntegration(target);
|
|
674
|
+
}
|
|
675
|
+
function parseIntegration(raw) {
|
|
676
|
+
const p = raw.trim().toLowerCase();
|
|
677
|
+
if (!p) {
|
|
678
|
+
throw new Error(
|
|
679
|
+
`Expected one AI coding agent. Choose from: ${AGENT_INTEGRATIONS.map((i) => i.key).join(", ")}`
|
|
680
|
+
);
|
|
681
|
+
}
|
|
682
|
+
if (IDE_NAMES.has(p)) {
|
|
683
|
+
throw new Error(
|
|
684
|
+
`"${p}" is an IDE, not an AI coding agent. Choose: copilot or claude (Claude Code).`
|
|
685
|
+
);
|
|
686
|
+
}
|
|
687
|
+
for (const integ of AGENT_INTEGRATIONS) {
|
|
688
|
+
if (integ.id === p || integ.key === p || integ.aliases.includes(p)) {
|
|
689
|
+
return integ.id;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
throw new Error(
|
|
693
|
+
`Unknown AI coding agent "${raw}". Choose from: ${AGENT_INTEGRATIONS.map((i) => i.key).join(", ")}`
|
|
694
|
+
);
|
|
968
695
|
}
|
|
969
696
|
function parseAgentTargets(raw) {
|
|
970
|
-
const parts = (Array.isArray(raw) ? raw : [raw]).flatMap((s) => String(s).split(",")).map((s) => s.trim()
|
|
697
|
+
const parts = (Array.isArray(raw) ? raw : [raw]).flatMap((s) => String(s).split(",")).map((s) => s.trim()).filter(Boolean);
|
|
698
|
+
if (!parts.length) {
|
|
699
|
+
throw new Error("No AI coding agent specified");
|
|
700
|
+
}
|
|
971
701
|
const out = [];
|
|
972
702
|
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
|
-
}
|
|
703
|
+
const id = parseIntegration(p);
|
|
983
704
|
if (!out.includes(id)) out.push(id);
|
|
984
705
|
}
|
|
985
706
|
return out;
|
|
986
707
|
}
|
|
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
708
|
var SDD_AGENT_ROLES = [
|
|
997
709
|
{
|
|
998
710
|
id: "sdd",
|
|
@@ -1032,13 +744,8 @@ var SDD_AGENT_ROLES = [
|
|
|
1032
744
|
]
|
|
1033
745
|
}
|
|
1034
746
|
];
|
|
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;
|
|
747
|
+
async function installAgentIntegration(opts) {
|
|
748
|
+
const integ = getIntegration(opts.target);
|
|
1042
749
|
const created = [];
|
|
1043
750
|
const skipped = [];
|
|
1044
751
|
const root = opts.projectRoot;
|
|
@@ -1055,47 +762,48 @@ async function installAgentIntegrations(opts) {
|
|
|
1055
762
|
await write(join4(".sdd", "protocol.md"), PROTOCOL_MD);
|
|
1056
763
|
await refreshActiveAgentContext(root);
|
|
1057
764
|
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
|
-
}
|
|
765
|
+
await write(integ.rolePath(role.id), renderThinAgent(role));
|
|
1065
766
|
}
|
|
1066
|
-
await write("AGENTS.md", renderAgentsMd(
|
|
767
|
+
await write("AGENTS.md", renderAgentsMd(integ));
|
|
1067
768
|
await write(
|
|
1068
769
|
join4(".sdd", "agents.json"),
|
|
1069
770
|
JSON.stringify(
|
|
1070
771
|
{
|
|
1071
|
-
version:
|
|
772
|
+
version: 3,
|
|
1072
773
|
mode: "agents-only",
|
|
1073
774
|
protocol: ".sdd/protocol.md",
|
|
1074
775
|
activeContext: ".sdd/active-context.md",
|
|
1075
776
|
roles: SDD_AGENT_ROLES.map((r) => r.id),
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
integration:
|
|
777
|
+
/** Speckit-style public key */
|
|
778
|
+
ai: integ.key,
|
|
779
|
+
integration: integ.key,
|
|
780
|
+
installed: [integ.id],
|
|
1079
781
|
updated: (/* @__PURE__ */ new Date()).toISOString()
|
|
1080
782
|
},
|
|
1081
783
|
null,
|
|
1082
784
|
2
|
|
1083
785
|
) + "\n"
|
|
1084
786
|
);
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
787
|
+
return { created, skipped, target: opts.target };
|
|
788
|
+
}
|
|
789
|
+
async function installAgentIntegrations(opts) {
|
|
790
|
+
if (!opts.targets?.length) {
|
|
791
|
+
throw new Error(
|
|
792
|
+
`Specify one AI coding agent: ${AGENT_INTEGRATIONS.map((i) => i.key).join(", ")}`
|
|
793
|
+
);
|
|
794
|
+
}
|
|
795
|
+
const created = [];
|
|
796
|
+
const skipped = [];
|
|
797
|
+
for (const target of opts.targets) {
|
|
798
|
+
const r = await installAgentIntegration({
|
|
799
|
+
projectRoot: opts.projectRoot,
|
|
800
|
+
target,
|
|
801
|
+
force: opts.force
|
|
802
|
+
});
|
|
803
|
+
created.push(...r.created);
|
|
804
|
+
skipped.push(...r.skipped);
|
|
805
|
+
}
|
|
806
|
+
return { created, skipped, targets: opts.targets };
|
|
1099
807
|
}
|
|
1100
808
|
function renderThinAgent(role) {
|
|
1101
809
|
const rules = role.roleRules.map((r) => `- ${r}`).join("\n");
|
|
@@ -1178,7 +886,7 @@ This repo uses **Structured Vibe Coding** (\`sdd\`). Agents must follow this fil
|
|
|
1178
886
|
|------|--------|
|
|
1179
887
|
| Process state | \`changes/<id>/meta.yaml\` (workflow, stage, gates) |
|
|
1180
888
|
| Current task snapshot | \`.sdd/active-context.md\` |
|
|
1181
|
-
| Stable product/tech rules | \`memory/*.md\` |
|
|
889
|
+
| Stable product/tech rules | \`memory/*.md\` (start at \`memory/index.md\` if present) |
|
|
1182
890
|
| Stage artifacts | files in \`changes/<id>/\` for the active change |
|
|
1183
891
|
|
|
1184
892
|
## Required read order
|
|
@@ -1186,7 +894,7 @@ This repo uses **Structured Vibe Coding** (\`sdd\`). Agents must follow this fil
|
|
|
1186
894
|
1. \`.sdd/active-context.md\`
|
|
1187
895
|
2. This file (\`.sdd/protocol.md\`)
|
|
1188
896
|
3. \`meta.yaml\` + artifacts for the **current stage** (and prior stages if needed)
|
|
1189
|
-
4. \`memory
|
|
897
|
+
4. \`memory/index.md\` (documentation map) if present, then linked memory pages when architecture or conventions matter
|
|
1190
898
|
|
|
1191
899
|
## Stage behavior
|
|
1192
900
|
|
|
@@ -1223,18 +931,7 @@ sdd complete
|
|
|
1223
931
|
- Deleting \`.sdd\` / archive history
|
|
1224
932
|
- Claiming complete without verify when required
|
|
1225
933
|
`;
|
|
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
|
-
}
|
|
934
|
+
function renderAgentsMd(integ) {
|
|
1238
935
|
return `# Agents
|
|
1239
936
|
|
|
1240
937
|
This repo uses **SDD agents only** (no skills).
|
|
@@ -1245,10 +942,11 @@ AI coding agents are **not** the same as IDEs: VS Code, Cursor, and IntelliJ hos
|
|
|
1245
942
|
|------------|--|
|
|
1246
943
|
| Live task | \`.sdd/active-context.md\` |
|
|
1247
944
|
| Playbook | \`.sdd/protocol.md\` |
|
|
945
|
+
| Doc map (stable) | \`memory/index.md\` if present |
|
|
1248
946
|
|
|
1249
947
|
| AI agent | Files |
|
|
1250
948
|
|----------|--------|
|
|
1251
|
-
${
|
|
949
|
+
${integ.agentsMdRow}
|
|
1252
950
|
|
|
1253
951
|
Refresh context: \`sdd agents refresh\`.
|
|
1254
952
|
`;
|
|
@@ -1304,33 +1002,29 @@ export {
|
|
|
1304
1002
|
nowIso,
|
|
1305
1003
|
changeDirName,
|
|
1306
1004
|
uniqueChangeDirName,
|
|
1005
|
+
loadChangeMeta,
|
|
1006
|
+
saveChangeMeta,
|
|
1007
|
+
buildContext,
|
|
1008
|
+
listChanges,
|
|
1009
|
+
getActiveChangeId,
|
|
1010
|
+
clearActiveIf,
|
|
1011
|
+
setActiveChange,
|
|
1012
|
+
resolveChangeId,
|
|
1013
|
+
formatStatus,
|
|
1014
|
+
buildAgentPrompt,
|
|
1015
|
+
AGENT_INTEGRATIONS,
|
|
1307
1016
|
ALL_AGENT_TARGETS,
|
|
1308
1017
|
DEFAULT_INIT_INTEGRATION,
|
|
1309
1018
|
AGENT_TARGET_OPTIONS,
|
|
1019
|
+
getIntegration,
|
|
1310
1020
|
integrationKeyFor,
|
|
1311
1021
|
optionForTarget,
|
|
1312
|
-
parseAgentTargets,
|
|
1313
1022
|
parseIntegration,
|
|
1023
|
+
parseAgentTargets,
|
|
1314
1024
|
SDD_AGENT_ROLES,
|
|
1025
|
+
installAgentIntegration,
|
|
1315
1026
|
installAgentIntegrations,
|
|
1316
1027
|
renderThinAgent,
|
|
1317
1028
|
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
|
|
1029
|
+
PROTOCOL_MD
|
|
1336
1030
|
};
|