@felixdotgo/aidlc-kit 0.0.1 β†’ 0.1.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 (123) hide show
  1. package/DEVELOPMENT.md +1 -1
  2. package/README.md +20 -9
  3. package/dist/assets/.agents/aidlc/conventions.md +8 -8
  4. package/dist/assets/.agents/aidlc/orchestrator.md +15 -10
  5. package/dist/assets/.agents/aidlc/phase-build.md +7 -7
  6. package/dist/assets/.agents/aidlc/phase-clarify.md +3 -3
  7. package/dist/assets/.agents/aidlc/phase-plan.md +6 -6
  8. package/dist/assets/.agents/aidlc/phase-wrap.md +1 -1
  9. package/dist/assets/.agents/aidlc/schemas/manifest.schema.json +1 -1
  10. package/dist/assets/.agents/aidlc/scripts/assignment.mjs +36 -0
  11. package/dist/assets/.agents/aidlc/scripts/context.mjs +2 -0
  12. package/dist/assets/.agents/aidlc/scripts/gate-check.mjs +2 -0
  13. package/dist/assets/.agents/aidlc/scripts/gate-view.mjs +2 -0
  14. package/dist/assets/.agents/aidlc/scripts/lib/assignment-store.mjs +181 -0
  15. package/dist/assets/.agents/aidlc/scripts/lib/authority.mjs +3 -0
  16. package/dist/assets/.agents/aidlc/scripts/lib/context-runtime.mjs +13 -1
  17. package/dist/assets/.agents/aidlc/scripts/lib/feature-store.mjs +70 -0
  18. package/dist/assets/.agents/aidlc/scripts/lib/loop-drivers.mjs +220 -0
  19. package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet-cli.mjs +183 -0
  20. package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet-remote.mjs +100 -0
  21. package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet-supervisor.mjs +58 -0
  22. package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet.mjs +320 -0
  23. package/dist/assets/.agents/aidlc/scripts/lib/loop-git.mjs +284 -0
  24. package/dist/assets/.agents/aidlc/scripts/lib/loop-intake.mjs +51 -0
  25. package/dist/assets/.agents/aidlc/scripts/lib/loop-lead-artifacts.mjs +63 -0
  26. package/dist/assets/.agents/aidlc/scripts/lib/loop-lead-broker.mjs +53 -0
  27. package/dist/assets/.agents/aidlc/scripts/lib/loop-lead-local-broker.mjs +44 -0
  28. package/dist/assets/.agents/aidlc/scripts/lib/loop-lead-sandbox.mjs +88 -0
  29. package/dist/assets/.agents/aidlc/scripts/lib/loop-lease.mjs +89 -0
  30. package/dist/assets/.agents/aidlc/scripts/lib/loop-local-gate-receipt.mjs +32 -0
  31. package/dist/assets/.agents/aidlc/scripts/lib/loop-local-workers.mjs +162 -0
  32. package/dist/assets/.agents/aidlc/scripts/lib/loop-planner.mjs +13 -0
  33. package/dist/assets/.agents/aidlc/scripts/lib/loop-runtime.mjs +260 -0
  34. package/dist/assets/.agents/aidlc/scripts/lib/loop-supervisor-local.mjs +131 -0
  35. package/dist/assets/.agents/aidlc/scripts/lib/loop-supervisor-run-local.mjs +55 -0
  36. package/dist/assets/.agents/aidlc/scripts/lib/loop-supervisor.mjs +101 -0
  37. package/dist/assets/.agents/aidlc/scripts/lib/loop-worker-docker.mjs +32 -0
  38. package/dist/assets/.agents/aidlc/scripts/lib/loop-worker-snapshot.mjs +66 -0
  39. package/dist/assets/.agents/aidlc/scripts/lib/remote-state-client.mjs +112 -0
  40. package/dist/assets/.agents/aidlc/scripts/lib/store.mjs +41 -6
  41. package/dist/assets/.agents/aidlc/scripts/loop-entry.mjs +90 -0
  42. package/dist/assets/.agents/aidlc/scripts/loop-gate.mjs +58 -0
  43. package/dist/assets/.agents/aidlc/scripts/loop.mjs +36 -0
  44. package/dist/assets/.agents/aidlc/scripts/remote-state.mjs +311 -0
  45. package/dist/assets/.agents/aidlc/scripts/render.mjs +2 -0
  46. package/dist/assets/.agents/aidlc/scripts/state.mjs +71 -11
  47. package/dist/assets/.agents/aidlc/scripts/task-next.mjs +2 -0
  48. package/dist/assets/.agents/aidlc/templates/model-contract.md +3 -3
  49. package/dist/assets/.agents/aidlc/templates/workplan.md +2 -2
  50. package/dist/assets/.agents/skills/aidlc-build/SKILL.md +2 -2
  51. package/dist/assets/.agents/skills/aidlc-clarify/SKILL.md +2 -2
  52. package/dist/assets/.agents/skills/aidlc-plan/SKILL.md +2 -2
  53. package/dist/assets/.agents/skills/aidlc-wrap/SKILL.md +1 -1
  54. package/dist/services/aidlc-coordination/.env.fleet.example +11 -0
  55. package/dist/services/aidlc-coordination/compose.yaml +23 -2
  56. package/dist/services/aidlc-coordination/package-lock.json +1 -0
  57. package/dist/services/aidlc-coordination/package.json +2 -1
  58. package/dist/services/aidlc-coordination/src/coordination.mjs +1 -1
  59. package/dist/services/aidlc-coordination/src/fleet-client.mjs +36 -0
  60. package/dist/services/aidlc-coordination/src/fleet-run.mjs +18 -0
  61. package/dist/services/aidlc-coordination/src/fleet.mjs +233 -0
  62. package/dist/services/aidlc-coordination/src/git-gateway.mjs +50 -0
  63. package/dist/services/aidlc-coordination/src/lifecycle-core.mjs +41 -6
  64. package/dist/services/aidlc-coordination/src/repository.mjs +167 -4
  65. package/dist/services/aidlc-coordination/src/server.mjs +53 -9
  66. package/dist/services/aidlc-coordination/test/fleet.test.mjs +351 -0
  67. package/dist/services/aidlc-coordination/test/git-gateway.test.mjs +75 -0
  68. package/dist/services/aidlc-coordination/test/supervisor-fencing.test.mjs +59 -0
  69. package/dist/src/adapters.js +32 -5
  70. package/dist/src/cli.js +146 -70
  71. package/dist/src/context.js +2 -0
  72. package/dist/src/gate.d.ts +1 -0
  73. package/dist/src/gate.js +51 -38
  74. package/dist/src/installer.d.ts +10 -1
  75. package/dist/src/installer.js +157 -37
  76. package/dist/src/managed-content.js +1 -1
  77. package/dist/src/model.d.ts +16 -1
  78. package/dist/src/model.js +1 -1
  79. package/dist/src/profiles.d.ts +9 -1
  80. package/dist/src/profiles.js +37 -3
  81. package/dist/src/project-migration.d.ts +1 -0
  82. package/dist/src/project-migration.js +7 -0
  83. package/dist/src/state/archive.d.ts +17 -0
  84. package/dist/src/state/archive.js +60 -0
  85. package/dist/src/state/items.d.ts +25 -0
  86. package/dist/src/state/items.js +146 -0
  87. package/dist/src/state/lessons.d.ts +7 -0
  88. package/dist/src/state/lessons.js +67 -0
  89. package/dist/src/state/lifecycle.d.ts +5 -0
  90. package/dist/src/state/lifecycle.js +78 -0
  91. package/dist/src/state/lock.d.ts +7 -0
  92. package/dist/src/state/lock.js +124 -0
  93. package/dist/src/state/memory.d.ts +8 -0
  94. package/dist/src/state/memory.js +88 -0
  95. package/dist/src/state/render.d.ts +26 -0
  96. package/dist/src/state/render.js +29 -0
  97. package/dist/src/state/shared.d.ts +29 -0
  98. package/dist/src/state/shared.js +71 -0
  99. package/dist/src/state/store.d.ts +4 -0
  100. package/dist/src/state/store.js +81 -0
  101. package/dist/src/state/transitions.d.ts +21 -0
  102. package/dist/src/state/transitions.js +93 -0
  103. package/dist/src/state/validate.d.ts +4 -0
  104. package/dist/src/state/validate.js +100 -0
  105. package/dist/src/state.d.ts +15 -110
  106. package/dist/src/state.js +15 -764
  107. package/dist/src/tracker.d.ts +7 -0
  108. package/dist/src/tracker.js +3 -0
  109. package/dist/src/upgrade-board.d.ts +5 -0
  110. package/dist/src/upgrade-board.js +89 -0
  111. package/dist/src/upgrade.d.ts +5 -6
  112. package/dist/src/upgrade.js +211 -193
  113. package/dist/src/workflow.d.ts +1 -0
  114. package/dist/src/workflow.js +3 -2
  115. package/docs/README.md +4 -4
  116. package/docs/architecture.md +10 -2
  117. package/docs/command-reference.md +72 -11
  118. package/docs/configuration.md +26 -4
  119. package/docs/coordination-service.md +59 -3
  120. package/docs/development.md +16 -2
  121. package/docs/operating-workflow.md +53 -14
  122. package/docs/testing-and-release.md +53 -10
  123. package/package.json +3 -3
package/dist/src/cli.js CHANGED
@@ -1,29 +1,34 @@
1
1
  #!/usr/bin/env node
2
2
  import { stdin, stdout } from "node:process";
3
+ import { spawnSync } from "node:child_process";
4
+ import { resolve, join } from "node:path";
3
5
  import { fileURLToPath } from "node:url";
4
- import { join } from "node:path";
5
6
  import { installableAdapters } from "./adapters.js";
6
7
  import { applyPlan, detectedAgents, doctor, formatPlan, planInit, planUninstall, selectAgents, status } from "./installer.js";
7
- import { loadProjectConfig, resolveProfiles } from "./profiles.js";
8
+ import { LOOP_PROVIDERS } from "./model.js";
9
+ import { loadProjectConfig, resolveProfiles, validateLoopConfig } from "./profiles.js";
8
10
  import { applyUpgrade, planUpgrade } from "./upgrade.js";
9
11
  import { packageVersion } from "./workflow.js";
10
12
  import { applyMcpSetup, planMcpSetup } from "./mcp-setup.js";
11
13
  const usage = `Usage:
12
- npx @felixdotgo/aidlc-kit init [path] [--agent <name[,name]> | --all] [--yes] [--dry-run] [--force]
13
- npx @felixdotgo/aidlc-kit upgrade [path] [--dry-run]
14
+ npx @felixdotgo/aidlc-kit init [path] [--agent <name[,name]> | --all] [--loop-mode off|local|fleet] [--loop-lead ${LOOP_PROVIDERS.join("|")}] [--loop-workers <id=provider,...>] [--yes] [--dry-run] [--force]
15
+ npx @felixdotgo/aidlc-kit upgrade [path] [--loop-mode off|local|fleet] [--loop-lead ${LOOP_PROVIDERS.join("|")}] [--loop-workers <id=provider,...>] [--dry-run]
14
16
  npx @felixdotgo/aidlc-kit uninstall [path] [--yes] [--dry-run]
15
17
  npx @felixdotgo/aidlc-kit status [path]
16
18
  npx @felixdotgo/aidlc-kit doctor [path] [--strict]
17
19
  npx @felixdotgo/aidlc-kit profile validate [path]
20
+ npx @felixdotgo/aidlc-kit loop start|status|resume [--task-id ID] [--root PATH] [--mode local|fleet]
18
21
  npx @felixdotgo/aidlc-kit mcp setup [path] [--dry-run] [--yes] [--storage sqlite|postgres] [--deployment docker|remote] [--bind localhost|network] [--workspace <id>] [--poll-ms <n>] [--enable]`;
19
22
  const interactive = () => Boolean(stdin.isTTY && stdout.isTTY);
20
23
  const prompts = async () => import("@inquirer/prompts");
24
+ /* --- Argv helpers --------------------------------------------------------- */
21
25
  const value = (args, name) => {
22
26
  const index = args.indexOf(name);
23
27
  return index < 0 ? undefined : args[index + 1];
24
28
  };
25
29
  const flags = (args) => new Set(args.filter((item) => item.startsWith("--")));
26
- const valueFlags = new Set(["--agent", "--storage", "--deployment", "--bind", "--workspace", "--poll-ms", "--token-env", "--providers"]);
30
+ const valueFlags = new Set(["--agent", "--storage", "--deployment", "--bind", "--workspace", "--poll-ms", "--token-env", "--providers", "--loop-mode", "--loop-lead", "--loop-workers"]);
31
+ const COMMAND_KEYWORDS = ["init", "upgrade", "uninstall", "status", "doctor", "validate", "run", "list"];
27
32
  const positional = (args) => {
28
33
  const result = [];
29
34
  for (let index = 0; index < args.length; index += 1) {
@@ -36,7 +41,48 @@ const positional = (args) => {
36
41
  }
37
42
  return result;
38
43
  };
39
- const rootArg = (args, fallback = ".") => positional(args).find((item) => !["init", "upgrade", "uninstall", "status", "doctor", "validate", "run", "list"].includes(item)) ?? fallback;
44
+ const rootArg = (args, fallback = ".") => positional(args).find((item) => !COMMAND_KEYWORDS.includes(item)) ?? fallback;
45
+ /* --- Loop configuration --------------------------------------------------- */
46
+ const parseWorkerMapping = (rawWorkers) => {
47
+ const workers = Object.create(null);
48
+ for (const entry of rawWorkers.split(",")) {
49
+ const parts = entry.split("=");
50
+ if (parts.length !== 2 || !parts[0] || !parts[1] || Object.hasOwn(workers, parts[0]))
51
+ throw new Error("Invalid or duplicate loop worker mapping");
52
+ workers[parts[0]] = parts[1];
53
+ }
54
+ return workers;
55
+ };
56
+ const chooseLoop = async (args, upgrade) => {
57
+ let mode = value(args, "--loop-mode");
58
+ const leadArg = value(args, "--loop-lead"), workersArg = value(args, "--loop-workers");
59
+ if (!mode && (leadArg || workersArg))
60
+ throw new Error("--loop-lead and --loop-workers require --loop-mode");
61
+ if (!mode && interactive()) {
62
+ const { select } = await prompts();
63
+ mode = await select({ message: "Loop execution mode", choices: [
64
+ ...(upgrade ? [{ name: "Keep existing loop configuration", value: "keep" }] : []),
65
+ { name: "Off", value: "off" }, { name: "Local", value: "local" }, { name: "Fleet", value: "fleet" }
66
+ ] });
67
+ }
68
+ if (!mode || mode === "keep")
69
+ return undefined;
70
+ if (mode === "off") {
71
+ if (leadArg || workersArg)
72
+ throw new Error("Disabled loop cannot specify agents");
73
+ return validateLoopConfig({ mode });
74
+ }
75
+ let lead = leadArg, rawWorkers = workersArg;
76
+ if (interactive() && (!lead || !rawWorkers)) {
77
+ const { select, input } = await prompts();
78
+ lead ??= await select({ message: "Lead agent", choices: LOOP_PROVIDERS.map((name) => ({ name, value: name })) });
79
+ rawWorkers ??= await input({ message: "Workers (id=provider, comma separated)", default: "codex-worker=codex,claude-worker=claude" });
80
+ }
81
+ if (!lead || !rawWorkers)
82
+ throw new Error("An enabled loop requires --loop-lead and --loop-workers");
83
+ return validateLoopConfig({ mode, agents: { lead, workers: parseWorkerMapping(rawWorkers) } });
84
+ };
85
+ /* --- Option builders and shared flows -------------------------------------- */
40
86
  const initOptions = (args) => ({
41
87
  root: rootArg(args),
42
88
  agents: (value(args, "--agent") ?? "").split(",").filter(Boolean),
@@ -80,7 +126,8 @@ const confirmGeneral = async (options, operation) => {
80
126
  const { confirm } = await prompts();
81
127
  return confirm({ message: `${operation} this plan?`, default: false });
82
128
  };
83
- const mcpSetupOptions = async (args) => {
129
+ /* --- MCP setup options ----------------------------------------------------- */
130
+ const mcpSetupSelections = async (args) => {
84
131
  const selected = {
85
132
  deployment: value(args, "--deployment"),
86
133
  storage: value(args, "--storage"),
@@ -103,12 +150,95 @@ const mcpSetupOptions = async (args) => {
103
150
  }
104
151
  if (!Number.isInteger(selected.pollMs) || selected.pollMs < 1_000 || selected.pollMs > 3_600_000)
105
152
  throw new Error("--poll-ms must be an integer from 1000 to 3600000");
153
+ return selected;
154
+ };
155
+ const mcpSetupOptions = async (args) => {
156
+ const selected = await mcpSetupSelections(args);
106
157
  return { root: rootArg(args), template: join(fileURLToPath(new URL("../services/aidlc-coordination", import.meta.url))), dryRun: flags(args).has("--dry-run"), deployment: selected.deployment, storage: selected.storage, bind: selected.bind, workspace: selected.workspace, pollMs: selected.pollMs, tokenEnv: selected.tokenEnv, providers: selected.providers, enable: flags(args).has("--enable") };
107
158
  };
159
+ /* --- Command handlers ------------------------------------------------------ */
160
+ const runLoopCommand = (args) => {
161
+ const action = args[0];
162
+ if (!["start", "status", "resume"].includes(action))
163
+ throw new Error("Expected loop start, status, or resume");
164
+ const parsed = new Map();
165
+ for (let index = 1; index < args.length; index += 2) {
166
+ const key = args[index], entry = args[index + 1];
167
+ if (!["--task-id", "--root", "--mode"].includes(key) || !entry || entry.startsWith("--") || parsed.has(key))
168
+ throw new Error("Invalid or duplicate loop option");
169
+ parsed.set(key, entry);
170
+ }
171
+ const root = resolve(parsed.get("--root") ?? ".");
172
+ const script = join(root, ".agents", "aidlc", "scripts", "loop-entry.mjs");
173
+ const child = spawnSync(process.execPath, [script, action, parsed.get("--task-id") ?? "", root, parsed.get("--mode") ?? ""], { stdio: "inherit" });
174
+ if (child.error)
175
+ throw child.error;
176
+ if (child.status !== 0)
177
+ process.exitCode = child.status ?? 1;
178
+ };
179
+ const runInit = async (args) => {
180
+ const options = { ...await chooseAgents(initOptions(args)), loop: await chooseLoop(args, false) };
181
+ const plan = planInit(options);
182
+ preview("INIT", plan);
183
+ if (plan.some((item) => item.action === "conflict"))
184
+ throw new Error("No files were written because the plan contains conflicts.");
185
+ if (!await confirmGeneral(options, "Apply"))
186
+ return;
187
+ if (plan.some((item) => item.action === "migrate" || item.action === "delete")) {
188
+ const result = applyUpgrade(options.root, plan);
189
+ console.log(`Installed local AI-DLC workflow assets and migrated legacy data. Backup: ${result.backup}; changed: ${result.changed}`);
190
+ }
191
+ else {
192
+ applyPlan(options.root, plan);
193
+ console.log("Installed local AI-DLC workflow assets.");
194
+ }
195
+ };
196
+ const runUninstall = async (args) => {
197
+ const options = initOptions(args);
198
+ const plan = planUninstall(options.root);
199
+ preview("UNINSTALL", plan);
200
+ if (!await confirmGeneral(options, "Remove"))
201
+ return;
202
+ applyPlan(options.root, plan);
203
+ console.log("Removed eligible managed assets; project config and state were preserved.");
204
+ };
205
+ const runUpgrade = async (args) => {
206
+ if (flags(args).has("--yes") || flags(args).has("--force"))
207
+ throw new Error("upgrade does not support --yes or --force; apply requires a human interactive TTY confirmation");
208
+ const root = rootArg(args);
209
+ const plan = planUpgrade(root, await chooseLoop(args, true));
210
+ preview("UPGRADE", plan);
211
+ if (plan.some((item) => item.action === "conflict"))
212
+ throw new Error("Upgrade contains conflicts; no files were written.");
213
+ if (flags(args).has("--dry-run"))
214
+ return;
215
+ if (!interactive())
216
+ throw new Error("Upgrade apply requires an interactive TTY. AI and non-interactive processes must not apply upgrades.");
217
+ const expected = packageVersion();
218
+ const { input } = await prompts();
219
+ if ((await input({ message: `Type ${expected} to apply this user-initiated upgrade` })).trim() !== expected)
220
+ throw new Error("Upgrade confirmation did not match the target version");
221
+ const result = applyUpgrade(root, plan);
222
+ console.log(`Upgrade complete. Backup: ${result.backup}; changed: ${result.changed}`);
223
+ };
224
+ const runMcpSetup = async (args) => {
225
+ const options = await mcpSetupOptions(args.slice(1));
226
+ const plan = planMcpSetup(options);
227
+ console.log(`β—† MCP SETUP preview β€” ${options.dryRun ? "no files will be written" : "review before applying"}`);
228
+ console.log(JSON.stringify(plan, null, 2));
229
+ if (options.dryRun)
230
+ return;
231
+ if (!await confirmGeneral({ ...initOptions(args), root: options.root }, "Apply MCP setup"))
232
+ return;
233
+ applyMcpSetup(options);
234
+ console.log(`MCP setup written to ${plan.target}. MCP remains ${options.enable ? "enabled" : "disabled"}; start Docker explicitly with docker compose --env-file .env -f ${join(plan.target, "compose.yaml")} up -d.`);
235
+ };
108
236
  const main = async () => {
109
237
  const [command = "help", ...args] = process.argv.slice(2);
110
238
  if (command === "help" || command === "--help")
111
239
  return void console.log(usage);
240
+ if (command === "loop")
241
+ return void runLoopCommand(args);
112
242
  if (command === "status")
113
243
  return void console.log(status(rootArg(args)));
114
244
  if (command === "doctor") {
@@ -118,68 +248,14 @@ const main = async () => {
118
248
  process.exitCode = 1;
119
249
  return;
120
250
  }
121
- if (command === "init") {
122
- const options = await chooseAgents(initOptions(args));
123
- const plan = planInit(options);
124
- preview("INIT", plan);
125
- if (plan.some((item) => item.action === "conflict"))
126
- throw new Error("No files were written because the plan contains conflicts.");
127
- if (!await confirmGeneral(options, "Apply"))
128
- return;
129
- const migratesLegacyData = plan.some((item) => item.action === "migrate" || item.action === "delete");
130
- if (migratesLegacyData) {
131
- const result = applyUpgrade(options.root, plan);
132
- console.log(`Installed local AI-DLC workflow assets and migrated legacy data. Backup: ${result.backup}; changed: ${result.changed}`);
133
- }
134
- else {
135
- applyPlan(options.root, plan);
136
- console.log("Installed local AI-DLC workflow assets.");
137
- }
138
- return;
139
- }
140
- if (command === "uninstall") {
141
- const options = initOptions(args);
142
- const plan = planUninstall(options.root);
143
- preview("UNINSTALL", plan);
144
- if (!await confirmGeneral(options, "Remove"))
145
- return;
146
- applyPlan(options.root, plan);
147
- console.log("Removed eligible managed assets; project config and state were preserved.");
148
- return;
149
- }
150
- if (command === "upgrade") {
151
- if (flags(args).has("--yes") || flags(args).has("--force"))
152
- throw new Error("upgrade does not support --yes or --force; apply requires a human interactive TTY confirmation");
153
- const root = rootArg(args);
154
- const plan = planUpgrade(root);
155
- preview("UPGRADE", plan);
156
- if (plan.some((item) => item.action === "conflict"))
157
- throw new Error("Upgrade contains conflicts; no files were written.");
158
- if (flags(args).has("--dry-run"))
159
- return;
160
- if (!interactive())
161
- throw new Error("Upgrade apply requires an interactive TTY. AI and non-interactive processes must not apply upgrades.");
162
- const expected = packageVersion();
163
- const { input } = await prompts();
164
- if ((await input({ message: `Type ${expected} to apply this user-initiated upgrade` })).trim() !== expected)
165
- throw new Error("Upgrade confirmation did not match the target version");
166
- const result = applyUpgrade(root, plan);
167
- console.log(`Upgrade complete. Backup: ${result.backup}; changed: ${result.changed}`);
168
- return;
169
- }
170
- if (command === "mcp" && args[0] === "setup") {
171
- const options = await mcpSetupOptions(args.slice(1));
172
- const plan = planMcpSetup(options);
173
- console.log(`β—† MCP SETUP preview β€” ${options.dryRun ? "no files will be written" : "review before applying"}`);
174
- console.log(JSON.stringify(plan, null, 2));
175
- if (options.dryRun)
176
- return;
177
- if (!await confirmGeneral({ ...initOptions(args), root: options.root }, "Apply MCP setup"))
178
- return;
179
- applyMcpSetup(options);
180
- console.log(`MCP setup written to ${plan.target}. MCP remains ${options.enable ? "enabled" : "disabled"}; start Docker explicitly with docker compose --env-file .env -f ${join(plan.target, "compose.yaml")} up -d.`);
181
- return;
182
- }
251
+ if (command === "init")
252
+ return void await runInit(args);
253
+ if (command === "uninstall")
254
+ return void await runUninstall(args);
255
+ if (command === "upgrade")
256
+ return void await runUpgrade(args);
257
+ if (command === "mcp" && args[0] === "setup")
258
+ return void await runMcpSetup(args);
183
259
  if (command === "profile" && args[0] === "validate") {
184
260
  const root = rootArg(args.slice(1));
185
261
  const config = loadProjectConfig(root);
@@ -189,4 +265,4 @@ const main = async () => {
189
265
  }
190
266
  throw new Error(`Unknown command: ${command}`);
191
267
  };
192
- main().catch((error) => { console.error(`aidlc-workflow: ${error instanceof Error ? error.message : String(error)}`); process.exitCode = 1; });
268
+ main().catch((error) => { console.error(`aidlc-kit: ${error instanceof Error ? error.message : String(error)}`); process.exitCode = 1; });
@@ -6,10 +6,12 @@ import { loadMemoryRegistry, nextAction, selectAgenticMemory } from "./state.js"
6
6
  const assetRoot = fileURLToPath(new URL("../assets/.agents/aidlc", import.meta.url));
7
7
  const phaseFile = (phase) => phase === "done" ? "phase-wrap.md" : `phase-${phase}.md`;
8
8
  const readAsset = (path) => readFileSync(join(assetRoot, path), "utf8");
9
+ /** Keeps only the latest test/lint/review record per (kind, area, gate) and strips prose details. */
9
10
  const requiredEvidence = (task) => task.evidence.filter((item, index, entries) => {
10
11
  const key = `${item.kind}:${item.area ?? ""}:${item.gate ?? ""}`;
11
12
  return !entries.slice(index + 1).some((candidate) => `${candidate.kind}:${candidate.area ?? ""}:${candidate.gate ?? ""}` === key);
12
13
  }).map(({ detail: _detail, ...item }) => item);
14
+ /** Compact canonical view of the task for a phase packet; `itemId` narrows execution items only. */
13
15
  const compactTask = (task, itemId) => JSON.stringify({
14
16
  id: task.id,
15
17
  title: task.title,
@@ -1,4 +1,5 @@
1
1
  import type { Diagnostic, Gate, TaskState, WorkflowState } from "./model.js";
2
+ /** Runs all diagnostics for a gate and returns them (empty-ish with GATE_OK when clean). */
2
3
  export declare const checkGate: (root: string, state: WorkflowState, taskId: string, gate: Gate) => Diagnostic[];
3
4
  export declare const approveAndAdvance: (root: string, state: WorkflowState, taskId: string, gate: Gate, source: string, recordedAt?: string) => {
4
5
  task: TaskState;
package/dist/src/gate.js CHANGED
@@ -11,7 +11,7 @@ const artifactContent = (root, path) => {
11
11
  }
12
12
  catch {
13
13
  return undefined;
14
- }
14
+ } /* intentional: unreadable/unsafe artifact is treated as absent */
15
15
  };
16
16
  const artifactDiagnostics = (root, task, names) => names.flatMap((name) => {
17
17
  const path = task.artifacts[name];
@@ -24,10 +24,39 @@ const artifactDiagnostics = (root, task, names) => names.flatMap((name) => {
24
24
  return [{ level: "ERROR", code: "ARTIFACT_UNSAFE", message: error instanceof Error ? error.message : String(error) }];
25
25
  }
26
26
  });
27
- export const checkGate = (root, state, taskId, gate) => {
28
- const task = state.tasks[taskId];
29
- if (!task)
30
- return [{ level: "ERROR", code: "TASK_UNKNOWN", message: `Unknown task: ${taskId}` }];
27
+ const missingHeadings = (content, headings) => headings.filter((heading) => !content.includes(heading));
28
+ const confirmGateDiagnostics = (root, task) => {
29
+ const diagnostics = artifactDiagnostics(root, task, ["intent"]);
30
+ const content = artifactContent(root, task.artifacts.intent);
31
+ if (content !== undefined)
32
+ for (const heading of missingHeadings(content, ["## πŸ“‹ Problem", "## πŸ—ΊοΈ Affected areas", "## πŸ’­ Assumptions", "## ❓ Open questions", "## 🎯 Scope"]))
33
+ diagnostics.push({ level: "ERROR", code: "INTENT_HEADING", message: `Intent is missing ${heading}` });
34
+ return diagnostics;
35
+ };
36
+ const reviewGateDiagnostics = (root, task) => {
37
+ const diagnostics = artifactDiagnostics(root, task, ["intent", "design", "workplan"]);
38
+ diagnostics.push(...transitionDiagnostics(task, "build").filter((item) => item.code === "UNRESOLVED_DECISION"));
39
+ const content = artifactContent(root, task.artifacts.design);
40
+ if (content !== undefined)
41
+ for (const heading of missingHeadings(content, ["## 🧩 Solution per affected area", "## πŸ“Œ Spec traceability", "## πŸ”— Cross-service contracts", "## ⚠️ Risks / edge cases"]))
42
+ diagnostics.push({ level: "ERROR", code: "DESIGN_HEADING", message: `Design is missing ${heading}` });
43
+ return diagnostics;
44
+ };
45
+ const codeReviewGateDiagnostics = (root, task) => {
46
+ const diagnostics = artifactDiagnostics(root, task, ["intent", "design", "workplan"]);
47
+ if (task.tasks.some((item) => item.status !== "done" && item.status !== "deferred"))
48
+ diagnostics.push({ level: "ERROR", code: "TASKS_OPEN", message: "Build tasks remain open" });
49
+ for (const area of task.areas)
50
+ if (!hasAreaVerification(task, area))
51
+ diagnostics.push({ level: "ERROR", code: "VERIFY_EVIDENCE", message: `Latest post-G1 verification evidence must pass for affected area: ${area}` });
52
+ if (!hasReview(task))
53
+ diagnostics.push({ level: "ERROR", code: "REVIEW_EVIDENCE", message: "Latest post-G1 review evidence must pass" });
54
+ const bounds = repairBounds(task);
55
+ if (!isLegacyG2Wait(task) && (bounds.verifyExhausted || bounds.reviewExhausted))
56
+ diagnostics.push({ level: "ERROR", code: "REPAIR_BOUND", message: "Repair bound exhausted; record a durable handoff instead of presenting G2" });
57
+ return diagnostics;
58
+ };
59
+ const generalGateDiagnostics = (taskId, task, gate) => {
31
60
  const diagnostics = [];
32
61
  if (task.gate !== gate)
33
62
  diagnostics.push({ level: "ERROR", code: "GATE_STATE", message: `Task is at ${task.gate}, not ${gate}` });
@@ -39,36 +68,20 @@ export const checkGate = (root, state, taskId, gate) => {
39
68
  diagnostics.push({ level: "ERROR", code: "TASK_ID", message: "Task key and id differ" });
40
69
  if (!task.title.trim() || !task.areas.length)
41
70
  diagnostics.push({ level: "ERROR", code: "TASK_FIELDS", message: "Task title and affected areas are required" });
42
- if (gate === "G0_confirm") {
43
- diagnostics.push(...artifactDiagnostics(root, task, ["intent"]));
44
- const content = artifactContent(root, task.artifacts.intent);
45
- if (content !== undefined)
46
- for (const heading of ["## πŸ“‹ Problem", "## πŸ—ΊοΈ Affected areas", "## πŸ’­ Assumptions", "## ❓ Open questions", "## 🎯 Scope"])
47
- if (!content.includes(heading))
48
- diagnostics.push({ level: "ERROR", code: "INTENT_HEADING", message: `Intent is missing ${heading}` });
49
- }
50
- if (gate === "G1_review") {
51
- diagnostics.push(...artifactDiagnostics(root, task, ["intent", "design", "workplan"]));
52
- diagnostics.push(...transitionDiagnostics(task, "build").filter((item) => item.code === "UNRESOLVED_DECISION"));
53
- const content = artifactContent(root, task.artifacts.design);
54
- if (content !== undefined)
55
- for (const heading of ["## 🧩 Solution per affected area", "## πŸ“Œ Spec traceability", "## πŸ”— Cross-service contracts", "## ⚠️ Risks / edge cases"])
56
- if (!content.includes(heading))
57
- diagnostics.push({ level: "ERROR", code: "DESIGN_HEADING", message: `Design is missing ${heading}` });
58
- }
59
- if (gate === "G2_codereview") {
60
- diagnostics.push(...artifactDiagnostics(root, task, ["intent", "design", "workplan"]));
61
- if (task.tasks.some((item) => item.status !== "done" && item.status !== "deferred"))
62
- diagnostics.push({ level: "ERROR", code: "TASKS_OPEN", message: "Build tasks remain open" });
63
- for (const area of task.areas)
64
- if (!hasAreaVerification(task, area))
65
- diagnostics.push({ level: "ERROR", code: "VERIFY_EVIDENCE", message: `Latest post-G1 verification evidence must pass for affected area: ${area}` });
66
- if (!hasReview(task))
67
- diagnostics.push({ level: "ERROR", code: "REVIEW_EVIDENCE", message: "Latest post-G1 review evidence must pass" });
68
- const bounds = repairBounds(task);
69
- if (!isLegacyG2Wait(task) && (bounds.verifyExhausted || bounds.reviewExhausted))
70
- diagnostics.push({ level: "ERROR", code: "REPAIR_BOUND", message: "Repair bound exhausted; record a durable handoff instead of presenting G2" });
71
- }
71
+ return diagnostics;
72
+ };
73
+ /** Runs all diagnostics for a gate and returns them (empty-ish with GATE_OK when clean). */
74
+ export const checkGate = (root, state, taskId, gate) => {
75
+ const task = state.tasks[taskId];
76
+ if (!task)
77
+ return [{ level: "ERROR", code: "TASK_UNKNOWN", message: `Unknown task: ${taskId}` }];
78
+ const diagnostics = generalGateDiagnostics(taskId, task, gate);
79
+ if (gate === "G0_confirm")
80
+ diagnostics.push(...confirmGateDiagnostics(root, task));
81
+ if (gate === "G1_review")
82
+ diagnostics.push(...reviewGateDiagnostics(root, task));
83
+ if (gate === "G2_codereview")
84
+ diagnostics.push(...codeReviewGateDiagnostics(root, task));
72
85
  if (!diagnostics.length)
73
86
  diagnostics.push({ level: "INFO", code: "GATE_OK", message: `${gate} checks passed for ${taskId}` });
74
87
  return diagnostics;
@@ -98,9 +111,9 @@ export const formatDiagnostics = (diagnostics) => diagnostics.map((item) => `${i
98
111
  const oneLine = (value) => value.replace(/[\r\n\t]+/g, " ").replace(/\s+/g, " ").replace(/ACTION\s+REQUIRED/gi, "ACTION-REQUIRED").trim();
99
112
  const markdownText = (value) => oneLine(value).replace(/([\\`*_{}\[\]()<>#+.!|>~])/g, "\\$1");
100
113
  const gateMeta = {
101
- G0_confirm: { icon: "🟒", title: "GATE G0 · CONFIRM INTENT", action: "Reply `ok` to approve intent, or state changes." },
102
- G1_review: { icon: "πŸ”΅", title: "GATE G1 Β· REVIEW PLAN", action: "Reply `approve` to start build, or state decision changes." },
103
- G2_codereview: { icon: "🟣", title: "GATE G2 · REVIEW CODE", action: "Approve to wrap, or point out what to fix." }
114
+ G0_confirm: { icon: "🟒", title: "Review step · Confirm requirements", action: "Reply `ok` to confirm requirements, or state changes." },
115
+ G1_review: { icon: "πŸ”΅", title: "Review step Β· Approve plan", action: "Reply `approve` to approve the plan and start implementation, or state decision changes." },
116
+ G2_codereview: { icon: "🟣", title: "Review step · Review implementation", action: "Approve the implementation to complete the task, or point out what to fix." }
104
117
  };
105
118
  const artifactRows = (task) => Object.entries(task.artifacts).flatMap(([name, path]) => path ? [{ name: oneLine(name), path: oneLine(path) }] : []);
106
119
  const markdownLinkTarget = (value) => encodeURI(value).replace(/[()#?]/g, (character) => `%${character.charCodeAt(0).toString(16).toUpperCase()}`);
@@ -1,11 +1,20 @@
1
- import type { AgentId, FileSpec, InitOptions, PlannedWrite, WorkflowManifest } from "./model.js";
1
+ import type { AgentId, FileSpec, InitOptions, LoopConfig, ManifestInventoryItem, PlannedWrite, WorkflowManifest } from "./model.js";
2
+ /** Managed prompt blocks share a path across owners, so inventory keys carry the owner. */
3
+ export declare const installedHashFor: (files: Record<string, ManifestInventoryItem> | undefined, spec: Pick<FileSpec, "path" | "owner" | "strategy">) => string | undefined;
2
4
  export declare const managedContentHash: (spec: Pick<FileSpec, "owner" | "strategy">, content: string) => string | undefined;
3
5
  export declare const readManifest: (root: string) => WorkflowManifest | undefined;
4
6
  export declare const detectedAgents: (root: string) => AgentId[];
5
7
  export declare const selectAgents: (options: Pick<InitOptions, "root" | "agents" | "all">) => AgentId[];
8
+ /** Include an explicitly selected loop config in the same preview/transaction as setup. */
9
+ export declare const planLoopSelection: (plan: PlannedWrite[], selection?: LoopConfig) => void;
10
+ /** Plans an init transaction: managed workflow files, manifest, and project-owned files. */
6
11
  export declare const planInit: (options: InitOptions) => PlannedWrite[];
12
+ /** Applies a planned init/uninstall transaction to disk. */
7
13
  export declare const applyPlan: (root: string, plan: PlannedWrite[]) => void;
8
14
  export declare const formatPlan: (plan: PlannedWrite[]) => string;
15
+ /** Plans an uninstall transaction that removes only unchanged managed assets. */
9
16
  export declare const planUninstall: (root: string) => PlannedWrite[];
17
+ /** Reports installed workflow version, schema, and detected agents. */
10
18
  export declare const status: (root: string) => string;
19
+ /** Validates the installed workflow manifest and local config/state. */
11
20
  export declare const doctor: (root: string, strict?: boolean) => string;