@felixdotgo/aidlc-kit 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/DEVELOPMENT.md +31 -0
  2. package/LICENSE +21 -0
  3. package/README.md +59 -0
  4. package/dist/assets/.agents/aidlc/conventions.md +68 -0
  5. package/dist/assets/.agents/aidlc/orchestrator.md +73 -0
  6. package/dist/assets/.agents/aidlc/phase-build.md +23 -0
  7. package/dist/assets/.agents/aidlc/phase-clarify.md +22 -0
  8. package/dist/assets/.agents/aidlc/phase-index.md +14 -0
  9. package/dist/assets/.agents/aidlc/phase-plan.md +21 -0
  10. package/dist/assets/.agents/aidlc/phase-wrap.md +16 -0
  11. package/dist/assets/.agents/aidlc/profiles/generic/profile.json +9 -0
  12. package/dist/assets/.agents/aidlc/profiles/git-submodules/profile.json +9 -0
  13. package/dist/assets/.agents/aidlc/profiles/single/profile.json +9 -0
  14. package/dist/assets/.agents/aidlc/profiles/workspace/profile.json +12 -0
  15. package/dist/assets/.agents/aidlc/rules/gate-check.md +16 -0
  16. package/dist/assets/.agents/aidlc/rules/stack-runtime.md +3 -0
  17. package/dist/assets/.agents/aidlc/schemas/adapter.schema.json +13 -0
  18. package/dist/assets/.agents/aidlc/schemas/config.schema.json +26 -0
  19. package/dist/assets/.agents/aidlc/schemas/manifest.schema.json +12 -0
  20. package/dist/assets/.agents/aidlc/schemas/profile.schema.json +12 -0
  21. package/dist/assets/.agents/aidlc/schemas/state.schema.json +108 -0
  22. package/dist/assets/.agents/aidlc/scripts/context.mjs +15 -0
  23. package/dist/assets/.agents/aidlc/scripts/gate-check.mjs +16 -0
  24. package/dist/assets/.agents/aidlc/scripts/gate-view.mjs +16 -0
  25. package/dist/assets/.agents/aidlc/scripts/lib/context-runtime.mjs +238 -0
  26. package/dist/assets/.agents/aidlc/scripts/lib/runtime.mjs +2 -0
  27. package/dist/assets/.agents/aidlc/scripts/lib/store.mjs +341 -0
  28. package/dist/assets/.agents/aidlc/scripts/render.mjs +24 -0
  29. package/dist/assets/.agents/aidlc/scripts/state.mjs +222 -0
  30. package/dist/assets/.agents/aidlc/scripts/task-next.mjs +27 -0
  31. package/dist/assets/.agents/aidlc/templates/design.md +15 -0
  32. package/dist/assets/.agents/aidlc/templates/intent.md +17 -0
  33. package/dist/assets/.agents/aidlc/templates/lesson.md +28 -0
  34. package/dist/assets/.agents/aidlc/templates/model-contract.md +41 -0
  35. package/dist/assets/.agents/aidlc/templates/workplan.md +9 -0
  36. package/dist/assets/.agents/skills/aidlc-build/SKILL.md +19 -0
  37. package/dist/assets/.agents/skills/aidlc-clarify/SKILL.md +17 -0
  38. package/dist/assets/.agents/skills/aidlc-index/SKILL.md +17 -0
  39. package/dist/assets/.agents/skills/aidlc-plan/SKILL.md +17 -0
  40. package/dist/assets/.agents/skills/aidlc-wrap/SKILL.md +17 -0
  41. package/dist/services/aidlc-coordination/Dockerfile +10 -0
  42. package/dist/services/aidlc-coordination/compose.yaml +25 -0
  43. package/dist/services/aidlc-coordination/package-lock.json +1154 -0
  44. package/dist/services/aidlc-coordination/package.json +20 -0
  45. package/dist/services/aidlc-coordination/src/coordination.mjs +68 -0
  46. package/dist/services/aidlc-coordination/src/lifecycle-core.mjs +341 -0
  47. package/dist/services/aidlc-coordination/src/providers.mjs +23 -0
  48. package/dist/services/aidlc-coordination/src/repository.mjs +203 -0
  49. package/dist/services/aidlc-coordination/src/server.mjs +49 -0
  50. package/dist/services/aidlc-coordination/test/repository.test.mjs +211 -0
  51. package/dist/src/adapters.d.ts +7 -0
  52. package/dist/src/adapters.js +86 -0
  53. package/dist/src/cli.d.ts +2 -0
  54. package/dist/src/cli.js +192 -0
  55. package/dist/src/context.d.ts +14 -0
  56. package/dist/src/context.js +90 -0
  57. package/dist/src/gate.d.ts +40 -0
  58. package/dist/src/gate.js +176 -0
  59. package/dist/src/installer.d.ts +11 -0
  60. package/dist/src/installer.js +173 -0
  61. package/dist/src/ledger.d.ts +22 -0
  62. package/dist/src/ledger.js +119 -0
  63. package/dist/src/legacy.d.ts +2 -0
  64. package/dist/src/legacy.js +37 -0
  65. package/dist/src/managed-content.d.ts +3 -0
  66. package/dist/src/managed-content.js +16 -0
  67. package/dist/src/mcp-setup.d.ts +20 -0
  68. package/dist/src/mcp-setup.js +31 -0
  69. package/dist/src/model.d.ts +249 -0
  70. package/dist/src/model.js +1 -0
  71. package/dist/src/profiles.d.ts +28 -0
  72. package/dist/src/profiles.js +216 -0
  73. package/dist/src/project-migration.d.ts +2 -0
  74. package/dist/src/project-migration.js +144 -0
  75. package/dist/src/project-path.d.ts +2 -0
  76. package/dist/src/project-path.js +20 -0
  77. package/dist/src/state.d.ts +110 -0
  78. package/dist/src/state.js +764 -0
  79. package/dist/src/tracker.d.ts +29 -0
  80. package/dist/src/tracker.js +41 -0
  81. package/dist/src/upgrade.d.ts +11 -0
  82. package/dist/src/upgrade.js +270 -0
  83. package/dist/src/workflow.d.ts +7 -0
  84. package/dist/src/workflow.js +46 -0
  85. package/docs/README.md +31 -0
  86. package/docs/architecture.md +72 -0
  87. package/docs/command-reference.md +110 -0
  88. package/docs/configuration.md +72 -0
  89. package/docs/coordination-service.md +42 -0
  90. package/docs/development.md +106 -0
  91. package/docs/operating-workflow.md +87 -0
  92. package/docs/testing-and-release.md +113 -0
  93. package/package.json +53 -0
@@ -0,0 +1,173 @@
1
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
2
+ import { dirname, join, resolve } from "node:path";
3
+ import { adapters, findAdapter, installableAdapters, resolvedAdapters } from "./adapters.js";
4
+ import { loadProjectConfig } from "./profiles.js";
5
+ import { loadState } from "./state.js";
6
+ import { planProjectLayoutMigration } from "./project-migration.js";
7
+ import { managedBlock, mergeManagedBlock } from "./managed-content.js";
8
+ import { resolveProjectPathWithoutSymlinks } from "./project-path.js";
9
+ import { contentHash, coreWorkflowFiles, initialProjectFiles, manifestSpec, packageVersion } from "./workflow.js";
10
+ const shortHash = (content) => contentHash(content).slice(0, 12);
11
+ const safeTarget = (root, path) => resolveProjectPathWithoutSymlinks(root, path, "Managed path escapes the project", "Managed path crosses a symlink");
12
+ export const managedContentHash = (spec, content) => {
13
+ const managed = managedBlock(spec, content);
14
+ return managed === undefined ? undefined : contentHash(managed);
15
+ };
16
+ export const readManifest = (root) => {
17
+ const path = join(resolve(root), ".agents/aidlc/manifest.json");
18
+ if (!existsSync(path))
19
+ return undefined;
20
+ try {
21
+ const value = JSON.parse(readFileSync(path, "utf8"));
22
+ const validFiles = value.files && typeof value.files === "object" && !Array.isArray(value.files) && Object.values(value.files).every((item) => item && typeof item.hash === "string" && typeof item.owner === "string" && ["replace", "managed-block"].includes(item.strategy));
23
+ return value.schemaVersion === 2 && typeof value.packageVersion === "string" && value.workflow === "AI-DLC" && value.source === "local-package-assets" && value.remoteUpdates === false && value.managedBy === "aidlc-workflow" && Array.isArray(value.adapters) && validFiles ? value : undefined;
24
+ }
25
+ catch {
26
+ return undefined;
27
+ }
28
+ };
29
+ export const detectedAgents = (root) => installableAdapters().filter((adapter) => adapter.detect(root)).map((adapter) => adapter.id);
30
+ export const selectAgents = (options) => {
31
+ if (options.all)
32
+ return adapters.map((adapter) => adapter.id);
33
+ return [...new Set(options.agents)];
34
+ };
35
+ const planFile = (root, spec, force, installed) => {
36
+ const target = safeTarget(root, spec.path);
37
+ if (spec.ownershipClass === "project" || spec.ownershipClass === "state") {
38
+ return existsSync(target)
39
+ ? { ...spec, content: readFileSync(target, "utf8"), action: "preserve", reason: `${spec.ownershipClass}-owned file` }
40
+ : { ...spec, action: "create", reason: `new ${spec.ownershipClass}-owned file` };
41
+ }
42
+ if (spec.strategy === "managed-block") {
43
+ const current = existsSync(target) ? readFileSync(target, "utf8") : "";
44
+ const next = mergeManagedBlock(spec, current);
45
+ return { ...spec, content: next, action: existsSync(target) ? (next === current ? "skip" : "update") : "create", reason: existsSync(target) ? "managed prompt block" : "new managed prompt" };
46
+ }
47
+ if (!existsSync(target))
48
+ return { ...spec, action: "create", reason: "new managed file" };
49
+ const current = readFileSync(target, "utf8");
50
+ if (current === spec.content)
51
+ return { ...spec, action: "skip", reason: "already current" };
52
+ if (spec.path === ".agents/aidlc/manifest.json" && installed)
53
+ return { ...spec, action: "update", reason: "refresh manifest inventory" };
54
+ const installedHash = installed?.files[spec.path]?.hash;
55
+ if (installedHash && contentHash(current) === installedHash)
56
+ return { ...spec, action: "update", reason: "unchanged managed file" };
57
+ if (force)
58
+ return { ...spec, action: "update", reason: "--force permits replacement of an unmanaged file" };
59
+ return { ...spec, action: "conflict", reason: "managed ownership cannot be proven; use upgrade or review --force" };
60
+ };
61
+ export const planInit = (options) => {
62
+ const root = resolve(options.root);
63
+ const installed = readManifest(root);
64
+ if (installed && installed.packageVersion !== packageVersion())
65
+ throw new Error(`AI-DLC ${installed.packageVersion} is already installed. Only a human may upgrade it through the documented npm/npx upgrade command.`);
66
+ const selected = [...new Set([...(installed?.adapters ?? []), ...selectAgents(options)])];
67
+ const unknown = selected.filter((id) => !findAdapter(id));
68
+ if (unknown.length)
69
+ throw new Error(`Unsupported adapter: ${unknown.join(", ")}`);
70
+ const agentFiles = selected.flatMap((id) => findAdapter(id)?.files() ?? []).map((file) => ({ ...file, ownershipClass: "managed" }));
71
+ const managedFiles = [...coreWorkflowFiles(), ...agentFiles];
72
+ const files = [...managedFiles, manifestSpec(managedFiles, selected), ...initialProjectFiles()];
73
+ const plan = files.map((spec) => planFile(root, spec, options.force, installed));
74
+ planProjectLayoutMigration(root, plan);
75
+ return plan;
76
+ };
77
+ export const applyPlan = (root, plan) => {
78
+ for (const write of plan.filter((item) => item.action === "delete"))
79
+ rmSync(safeTarget(root, write.path), { force: true });
80
+ for (const write of plan.filter((item) => ["create", "update", "migrate"].includes(item.action))) {
81
+ const target = safeTarget(root, write.path);
82
+ mkdirSync(dirname(target), { recursive: true });
83
+ writeFileSync(target, write.content, "utf8");
84
+ }
85
+ };
86
+ export const formatPlan = (plan) => plan.map((item) => `${item.action.padEnd(8)} ${item.path} (${item.reason}; sha=${shortHash(item.contentEncoding === "base64" ? Buffer.from(item.content, "base64") : item.content)})`).join("\n");
87
+ const hasOwnedManifest = (root) => {
88
+ const path = join(resolve(root), ".agents/aidlc/manifest.json");
89
+ if (!existsSync(path))
90
+ return false;
91
+ try {
92
+ return JSON.parse(readFileSync(path, "utf8")).managedBy === "aidlc-workflow";
93
+ }
94
+ catch {
95
+ return false;
96
+ }
97
+ };
98
+ export const planUninstall = (root) => {
99
+ const project = resolve(root);
100
+ if (!hasOwnedManifest(project))
101
+ throw new Error("No aidlc-workflow ownership manifest found; refusing to remove files.");
102
+ const installed = readManifest(project);
103
+ const currentSpecs = [...coreWorkflowFiles(), ...resolvedAdapters().flatMap((adapter) => adapter.files()).map((file) => ({ ...file, ownershipClass: "managed" }))];
104
+ const specs = installed ? Object.entries(installed.files).map(([path, item]) => currentSpecs.find((spec) => spec.path === path) ?? ({ path, owner: item.owner, strategy: item.strategy, content: "", ownershipClass: "managed" })) : currentSpecs;
105
+ const plan = specs.map((spec) => {
106
+ const target = safeTarget(project, spec.path);
107
+ if (!existsSync(target))
108
+ return { ...spec, action: "skip", reason: "not installed" };
109
+ const current = readFileSync(target, "utf8");
110
+ if (spec.strategy === "managed-block") {
111
+ const block = managedBlock(spec, current);
112
+ if (!block)
113
+ return { ...spec, action: "skip", reason: "owned block not found" };
114
+ const installedHash = installed?.files[spec.path]?.hash ?? contentHash(spec.content.trimEnd());
115
+ if (contentHash(block) !== installedHash)
116
+ return { ...spec, action: "preserve", reason: "managed block was modified" };
117
+ const start = `<!-- aidlc-installer:${spec.owner}:start -->`;
118
+ const end = `<!-- aidlc-installer:${spec.owner}:end -->`;
119
+ const next = current.replace(new RegExp(`${start}[\\s\\S]*?${end}\\n?`, "g"), "").replace(/\n{3,}/g, "\n\n").trimEnd();
120
+ return next ? { ...spec, content: `${next}\n`, action: "update", reason: "remove owned prompt block" } : { ...spec, content: "", action: "delete", reason: "remove owned prompt file" };
121
+ }
122
+ const installedHash = installed?.files[spec.path]?.hash ?? contentHash(spec.content);
123
+ return contentHash(current) === installedHash ? { ...spec, action: "delete", reason: "remove unchanged managed file" } : { ...spec, action: "preserve", reason: "managed file was modified" };
124
+ });
125
+ plan.push({ path: ".agents/aidlc/manifest.json", owner: "aidlc-core", content: "", ownershipClass: "managed", action: plan.some((item) => item.action === "preserve") ? "preserve" : "delete", reason: plan.some((item) => item.action === "preserve") ? "retained while modified managed files remain" : "remove ownership manifest" });
126
+ return plan;
127
+ };
128
+ export const status = (root) => {
129
+ const manifest = readManifest(root);
130
+ const legacyPath = join(resolve(root), ".agents/aidlc/manifest.json");
131
+ return [
132
+ `workflow: ${manifest || existsSync(legacyPath) ? "installed" : "not installed"}`,
133
+ `installed version: ${manifest?.packageVersion ?? (existsSync(legacyPath) ? "legacy/unknown" : "none")}`,
134
+ `schema: ${manifest?.schemaVersion ?? (existsSync(legacyPath) ? "legacy" : "none")}`,
135
+ `detected agents: ${detectedAgents(root).join(", ") || "none"}`
136
+ ].join("\n");
137
+ };
138
+ export const doctor = (root, strict = false) => {
139
+ const manifestPath = join(resolve(root), ".agents/aidlc/manifest.json");
140
+ if (!existsSync(manifestPath))
141
+ return "ERROR: .agents/aidlc/manifest.json is missing. Run npx @felixdotgo/aidlc-kit init --yes.";
142
+ let parsed;
143
+ try {
144
+ parsed = JSON.parse(readFileSync(manifestPath, "utf8"));
145
+ }
146
+ catch {
147
+ return "ERROR: workflow manifest is invalid JSON.";
148
+ }
149
+ if (parsed.remoteUpdates || parsed.source !== "local-package-assets" || parsed.managedBy !== "aidlc-workflow")
150
+ return "ERROR: workflow manifest violates local-only asset policy.";
151
+ if (typeof parsed.schemaVersion === "number" && parsed.schemaVersion > 2)
152
+ return `ERROR: installed manifest schema ${parsed.schemaVersion} is newer than this CLI supports.`;
153
+ const manifest = readManifest(root);
154
+ if (!manifest)
155
+ return strict ? "ERROR: legacy manifest requires an explicit user-run npm/npx upgrade." : "WARN: legacy manifest detected; no registry check was performed.";
156
+ try {
157
+ loadProjectConfig(root);
158
+ loadState(root);
159
+ }
160
+ catch (error) {
161
+ return `ERROR: local config/state is invalid: ${error instanceof Error ? error.message : String(error)}`;
162
+ }
163
+ const modified = Object.entries(manifest.files).filter(([path, item]) => {
164
+ const target = safeTarget(root, path);
165
+ if (!existsSync(target))
166
+ return true;
167
+ const spec = { owner: item.owner, strategy: item.strategy };
168
+ return managedContentHash(spec, readFileSync(target, "utf8")) !== item.hash;
169
+ }).map(([path]) => path);
170
+ if (modified.length)
171
+ return `${strict ? "ERROR" : "WARN"}: managed integrity differs: ${modified.join(", ")}`;
172
+ return `OK: local workflow ${manifest.packageVersion} is valid; remote updates are disabled.`;
173
+ };
@@ -0,0 +1,22 @@
1
+ import type { TaskState, WorkflowState } from "./model.js";
2
+ export declare const trackedTasksPath: (root: string) => string;
3
+ export declare const taskEventsPath: (root: string, taskId: string) => string;
4
+ export interface TaskLedgerEvent {
5
+ schemaVersion: 1;
6
+ id: string;
7
+ taskId: string;
8
+ parentDigest: string | null;
9
+ actor: string;
10
+ recordedAt: string;
11
+ snapshot: TaskState;
12
+ digest: string;
13
+ }
14
+ export declare const validateLedgerEvent: (value: unknown) => TaskLedgerEvent;
15
+ export declare const createLedgerEvent: (task: TaskState, parentDigest: string | null, actor: string, recordedAt?: string, id?: string) => TaskLedgerEvent;
16
+ export declare const loadTaskLedger: (root: string, taskId: string) => TaskLedgerEvent[];
17
+ export declare const reduceTaskLedger: (events: readonly TaskLedgerEvent[]) => {
18
+ task: TaskState;
19
+ headDigest: string;
20
+ } | undefined;
21
+ export declare const loadTrackedState: (root: string) => WorkflowState | undefined;
22
+ export declare const appendTaskLedgerEvent: (root: string, task: TaskState, actor: string) => TaskLedgerEvent;
@@ -0,0 +1,119 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, renameSync, writeFileSync } from "node:fs";
3
+ import { dirname, join, resolve } from "node:path";
4
+ import { validateState } from "./state.js";
5
+ export const trackedTasksPath = (root) => join(resolve(root), ".agents/workflow/tasks");
6
+ export const taskEventsPath = (root, taskId) => join(trackedTasksPath(root), taskId, "events");
7
+ const digest = (value) => createHash("sha256").update(JSON.stringify(value)).digest("hex");
8
+ const eventContent = (event) => `${JSON.stringify(event, null, 2)}\n`;
9
+ const eventPath = (root, event) => join(taskEventsPath(root, event.taskId), `${event.id}.json`);
10
+ const taskIdPattern = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
11
+ const iso = (value) => typeof value === "string" && Number.isFinite(Date.parse(value)) && new Date(Date.parse(value)).toISOString() === value;
12
+ const assertSafeTaskId = (taskId) => {
13
+ if (!taskIdPattern.test(taskId))
14
+ throw new Error(`Invalid ledger task id: ${taskId}`);
15
+ };
16
+ const atomicWrite = (path, content) => {
17
+ mkdirSync(dirname(path), { recursive: true });
18
+ const temporary = `${path}.tmp-${process.pid}-${Date.now()}`;
19
+ writeFileSync(temporary, content, "utf8");
20
+ renameSync(temporary, path);
21
+ };
22
+ export const validateLedgerEvent = (value) => {
23
+ if (!value || typeof value !== "object" || Array.isArray(value))
24
+ throw new Error("Ledger event must be an object");
25
+ const event = value;
26
+ if (event.schemaVersion !== 1 || typeof event.id !== "string" || !taskIdPattern.test(event.id) || typeof event.taskId !== "string" || !taskIdPattern.test(event.taskId) || (event.parentDigest !== null && !/^[a-f0-9]{64}$/.test(event.parentDigest ?? "")) || typeof event.actor !== "string" || !event.actor.trim() || !iso(event.recordedAt) || !event.snapshot || typeof event.digest !== "string" || !/^[a-f0-9]{64}$/.test(event.digest))
27
+ throw new Error("Invalid ledger event fields");
28
+ if (event.snapshot.id !== event.taskId)
29
+ throw new Error(`Ledger event task mismatch: ${event.id}`);
30
+ validateState({ schemaVersion: 4, tasks: { [event.taskId]: event.snapshot }, archive: {} });
31
+ const { digest: supplied, ...unsigned } = event;
32
+ if (digest(unsigned) !== supplied)
33
+ throw new Error(`Ledger event digest mismatch: ${event.id}`);
34
+ return event;
35
+ };
36
+ export const createLedgerEvent = (task, parentDigest, actor, recordedAt = new Date().toISOString(), id = randomUUID()) => {
37
+ assertSafeTaskId(task.id);
38
+ if (!actor.trim() || !iso(recordedAt) || !taskIdPattern.test(id))
39
+ throw new Error("Invalid ledger event metadata");
40
+ validateState({ schemaVersion: 4, tasks: { [task.id]: task }, archive: {} });
41
+ const unsigned = { schemaVersion: 1, id, taskId: task.id, parentDigest, actor, recordedAt, snapshot: task };
42
+ return { ...unsigned, digest: digest(unsigned) };
43
+ };
44
+ export const loadTaskLedger = (root, taskId) => {
45
+ assertSafeTaskId(taskId);
46
+ const directory = taskEventsPath(root, taskId);
47
+ if (!existsSync(directory))
48
+ return [];
49
+ if (lstatSync(directory).isSymbolicLink())
50
+ throw new Error(`Ledger path crosses a symlink: ${taskId}`);
51
+ const events = readdirSync(directory, { withFileTypes: true })
52
+ .filter((entry) => entry.isFile() && entry.name.endsWith(".json"))
53
+ .map((entry) => validateLedgerEvent(JSON.parse(readFileSync(join(directory, entry.name), "utf8"))))
54
+ .sort((left, right) => left.id.localeCompare(right.id));
55
+ if (events.some((event) => event.taskId !== taskId))
56
+ throw new Error(`Ledger event is in the wrong task directory: ${taskId}`);
57
+ return events;
58
+ };
59
+ export const reduceTaskLedger = (events) => {
60
+ if (!events.length)
61
+ return undefined;
62
+ const byDigest = new Map(events.map((event) => [event.digest, event]));
63
+ if (byDigest.size !== events.length)
64
+ throw new Error("Duplicate ledger event digest");
65
+ const roots = events.filter((event) => event.parentDigest === null);
66
+ if (roots.length !== 1)
67
+ throw new Error("Ledger must have exactly one root event");
68
+ const children = new Map();
69
+ for (const event of events)
70
+ if (event.parentDigest) {
71
+ if (!byDigest.has(event.parentDigest))
72
+ throw new Error(`Ledger event has unknown parent: ${event.id}`);
73
+ const siblings = children.get(event.parentDigest) ?? [];
74
+ siblings.push(event);
75
+ children.set(event.parentDigest, siblings);
76
+ }
77
+ const sibling = [...children.values()].find((items) => items.length > 1);
78
+ if (sibling)
79
+ throw new Error(`Concurrent ledger events require resolution: ${sibling.map((event) => event.id).join(", ")}`);
80
+ const visited = new Set();
81
+ let current = roots[0];
82
+ while (true) {
83
+ if (visited.has(current.digest))
84
+ throw new Error("Ledger event cycle");
85
+ visited.add(current.digest);
86
+ const next = children.get(current.digest) ?? [];
87
+ if (!next.length)
88
+ break;
89
+ current = next[0];
90
+ }
91
+ if (visited.size !== events.length)
92
+ throw new Error("Ledger contains disconnected events");
93
+ return { task: current.snapshot, headDigest: current.digest };
94
+ };
95
+ export const loadTrackedState = (root) => {
96
+ const directory = trackedTasksPath(root);
97
+ if (!existsSync(directory))
98
+ return undefined;
99
+ if (lstatSync(directory).isSymbolicLink())
100
+ throw new Error("Tracked ledger path crosses a symlink");
101
+ const tasks = {};
102
+ for (const entry of readdirSync(directory, { withFileTypes: true })) {
103
+ if (!entry.isDirectory() || !taskIdPattern.test(entry.name))
104
+ continue;
105
+ const reduced = reduceTaskLedger(loadTaskLedger(root, entry.name));
106
+ if (reduced)
107
+ tasks[entry.name] = reduced.task;
108
+ }
109
+ return validateState({ schemaVersion: 4, tasks, archive: {} });
110
+ };
111
+ export const appendTaskLedgerEvent = (root, task, actor) => {
112
+ const current = reduceTaskLedger(loadTaskLedger(root, task.id));
113
+ const event = createLedgerEvent(task, current?.headDigest ?? null, actor);
114
+ const path = eventPath(root, event);
115
+ if (existsSync(path))
116
+ throw new Error(`Ledger event already exists: ${event.id}`);
117
+ atomicWrite(path, eventContent(event));
118
+ return event;
119
+ };
@@ -0,0 +1,2 @@
1
+ export declare const legacyV021Hashes: Readonly<Record<string, string>>;
2
+ export declare const legacyV021AdapterContents: Readonly<Record<string, string>>;
@@ -0,0 +1,37 @@
1
+ export const legacyV021Hashes = Object.freeze({
2
+ ".agents/aidlc/conventions.md": "fa807e4430fcd1a0d21621c783becda61f8e61e9df9ab2e164e3ce0decc380db",
3
+ ".agents/aidlc/orchestrator.md": "b5d38c1a4031f2a1cb5b58f2fdf2965a34c5ff86699f6af103cd0d97587b9f44",
4
+ ".agents/aidlc/phase-build.md": "8384d67747acebfa87b06f0e11c58a0f2c661584d7d584ab8c382b6308263e29",
5
+ ".agents/aidlc/phase-clarify.md": "a31e69c68a924c689933f5dff69cd94ee09b587171f838458ee7dbb6dd4eacf6",
6
+ ".agents/aidlc/phase-index.md": "7473192914e44b39ce02a60ed79051a1827656305556ffa684ca7e3388827373",
7
+ ".agents/aidlc/phase-plan.md": "242da8e20e099889c7f05ac7c582902515b86ea62c159e02202231051f7ac5f9",
8
+ ".agents/aidlc/phase-wrap.md": "e2d108aa80a9fcee2e4a43b6ac53e451f18d4b26aee42728ca4788bba8883ef8",
9
+ ".agents/aidlc/rules/gate-check.md": "b542365d1698f179f12128d3932743e4799378a1475d89400c294f30b2cecd1d",
10
+ ".agents/aidlc/rules/stack-runtime.md": "023900a35372668fd69d23e11332ecc78f43df9ef9e75a86bdda2e7c442a0731",
11
+ ".agents/aidlc/templates/board.md": "caedf04fa8b19790549ec53238fce4f5a96492d12f367dff930ed9d9f6a8c593",
12
+ ".agents/aidlc/templates/design.md": "35849fb13083942eb32f201f86520961fe1c9971bc7e8d4ee9dd6921b32fcfaa",
13
+ ".agents/aidlc/templates/intent.md": "a55c97b3babebd18b07ce50fdba5326bbb11ca7cf4cf2e5b8eae7f84304cce78",
14
+ ".agents/aidlc/templates/lesson.md": "1ac7b7f5f2ed937084cb42d069d0a0f764ee7a71868f6d4fb3437231a81789ac",
15
+ ".agents/aidlc/templates/workplan.md": "2ad82fecd04cc87a211d5e8b2a813d1c1243180ee13544b0a5db58d2755c2b7b",
16
+ ".agents/skills/aidlc-build/SKILL.md": "4f9221a5a80a561406b1ae54cd7731c6543fb509bb9b38b1a2a4639e2842f552",
17
+ ".agents/skills/aidlc-clarify/SKILL.md": "6b481d5bf87d476d2bd09c1d627d85203da87e3bbb640851e34d32cf086f80f1",
18
+ ".agents/skills/aidlc-index/SKILL.md": "dedb6b91f666bc32f39a51e6f378760d0718ccfd8edbe01a667898e6e18599be",
19
+ ".agents/skills/aidlc-plan/SKILL.md": "141b3abd065562f95f249fcf2509ceb095d554a8ccc62fd45de214ab82a4b387",
20
+ ".agents/skills/aidlc-wrap/SKILL.md": "51e06614d074c8ed008575bb099fe97f0d402633c37225f4874b5408a5382be8"
21
+ });
22
+ const instruction = (agent) => [
23
+ `# AI-DLC for ${agent}`,
24
+ "",
25
+ "Read `.agents/aidlc/orchestrator.md` and `.agents/data/state/BOARD.md` before starting non-trivial work. Use phase skills under `.agents/skills/`; do not fetch remote workflow content. Respect more-specific instructions already present in this repository.",
26
+ ""
27
+ ].join("\n");
28
+ const block = (owner, body) => `<!-- aidlc-installer:${owner} -->\n${body}`;
29
+ export const legacyV021AdapterContents = Object.freeze({
30
+ "CLAUDE.md": block("claude", instruction("Claude Code")),
31
+ ".claude/skills/aidlc/SKILL.md": block("claude", instruction("Claude Code")),
32
+ "AGENTS.md": block("codex", instruction("Codex")),
33
+ ".cursor/rules/aidlc.mdc": block("cursor", `---\ndescription: AI-DLC workflow\nalwaysApply: true\n---\n\n${instruction("Cursor")}`),
34
+ ".agents/rules/aidlc.md": block("antigravity", instruction("Google Antigravity")),
35
+ ".kiro/steering/aidlc.md": block("kiro", instruction("Kiro")),
36
+ ".agents/aidlc/adapters/generic-instructions.md": block("generic", instruction("compatible agent"))
37
+ });
@@ -0,0 +1,3 @@
1
+ import type { FileSpec } from "./model.js";
2
+ export declare const managedBlock: (spec: Pick<FileSpec, "owner" | "strategy">, content: string) => string | undefined;
3
+ export declare const mergeManagedBlock: (spec: FileSpec, current: string) => string;
@@ -0,0 +1,16 @@
1
+ export const managedBlock = (spec, content) => {
2
+ if (spec.strategy !== "managed-block")
3
+ return content;
4
+ const start = `<!-- aidlc-installer:${spec.owner}:start -->`;
5
+ const end = `<!-- aidlc-installer:${spec.owner}:end -->`;
6
+ return content.match(new RegExp(`${start}[\\s\\S]*?${end}`))?.[0];
7
+ };
8
+ export const mergeManagedBlock = (spec, current) => {
9
+ const start = `<!-- aidlc-installer:${spec.owner}:start -->`;
10
+ const end = `<!-- aidlc-installer:${spec.owner}:end -->`;
11
+ const block = spec.content.trimEnd();
12
+ const matches = new RegExp(`${start}[\\s\\S]*?${end}\\n?`, "g");
13
+ const withoutOwnedBlocks = current.replace(matches, "").replace(/\n{3,}/g, "\n\n").trimEnd();
14
+ const next = `${withoutOwnedBlocks}${withoutOwnedBlocks ? "\n\n" : ""}${block}`;
15
+ return `${next.trimEnd()}\n`;
16
+ };
@@ -0,0 +1,20 @@
1
+ export interface McpSetupOptions {
2
+ root: string;
3
+ template: string;
4
+ dryRun: boolean;
5
+ deployment: "docker" | "remote";
6
+ storage: "sqlite" | "postgres";
7
+ bind: "localhost" | "network";
8
+ workspace: string;
9
+ pollMs: number;
10
+ tokenEnv: string;
11
+ providers: string[];
12
+ enable: boolean;
13
+ }
14
+ export interface McpSetupPlan {
15
+ target: string;
16
+ files: string[];
17
+ config: Record<string, unknown>;
18
+ }
19
+ export declare const planMcpSetup: (options: McpSetupOptions) => McpSetupPlan;
20
+ export declare const applyMcpSetup: (options: McpSetupOptions) => McpSetupPlan;
@@ -0,0 +1,31 @@
1
+ import { cpSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { join, resolve } from "node:path";
3
+ export const planMcpSetup = (options) => {
4
+ const target = join(resolve(options.root), ".agents/aidlc-coordination");
5
+ const endpoint = options.deployment === "docker" ? "http://127.0.0.1:8787/mcp" : "https://replace-with-service.example/mcp";
6
+ return {
7
+ target,
8
+ files: options.deployment === "docker" ? ["Dockerfile", "compose.yaml", "package.json", "package-lock.json", "src/", "test/", ".env.example"] : [".env.example"],
9
+ config: { enabled: options.enable, ...(options.enable ? { endpoint, workspace: options.workspace, tokenEnv: options.tokenEnv, pollMs: options.pollMs, providers: options.providers } : {}) }
10
+ };
11
+ };
12
+ export const applyMcpSetup = (options) => {
13
+ const plan = planMcpSetup(options);
14
+ if (options.dryRun)
15
+ return plan;
16
+ mkdirSync(plan.target, { recursive: true });
17
+ if (options.deployment === "docker")
18
+ cpSync(options.template, plan.target, { recursive: true, force: true });
19
+ const env = [
20
+ `MCP_STATE_DATABASE=${options.storage}`,
21
+ `MCP_STATE_HOST=${options.bind === "localhost" ? "127.0.0.1" : "0.0.0.0"}`,
22
+ `MCP_STATE_AUTH_TOKEN=${options.bind === "network" ? `set-${options.tokenEnv}-outside-git` : ""}`,
23
+ options.storage === "postgres" ? "MCP_STATE_POSTGRES_URL=postgres://aidlc:change-me@postgres:5432/aidlc" : "MCP_STATE_SQLITE_PATH=/data/mcp-state.db"
24
+ ].join("\n");
25
+ writeFileSync(join(plan.target, ".env.example"), `${env}\n`, "utf8");
26
+ const configPath = join(resolve(options.root), ".agents/config.json");
27
+ const current = existsSync(configPath) ? JSON.parse(readFileSync(configPath, "utf8")) : { schemaVersion: 3, extends: ["topology/single"], specs: { roots: [] }, commands: {}, rules: { include: [] }, risk: { default: "normal" }, context: { maxChars: 16000 }, agentState: {} };
28
+ current.mcp = plan.config;
29
+ writeFileSync(configPath, `${JSON.stringify(current, null, 2)}\n`, "utf8");
30
+ return plan;
31
+ };