@felixdotgo/aidlc-kit 0.1.0 β†’ 0.2.0

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 (150) hide show
  1. package/DEVELOPMENT.md +14 -0
  2. package/README.md +14 -6
  3. package/dist/assets/.agents/aidlc/docker/Dockerfile.claude +7 -0
  4. package/dist/assets/.agents/aidlc/docker/Dockerfile.codex +9 -0
  5. package/dist/assets/.agents/aidlc/docker/Dockerfile.opencode +8 -0
  6. package/dist/assets/.agents/aidlc/orchestrator.md +1 -1
  7. package/dist/assets/.agents/aidlc/phase-clarify.md +1 -1
  8. package/dist/assets/.agents/aidlc/phase-plan.md +2 -2
  9. package/dist/assets/.agents/aidlc/schemas/config.schema.json +2 -1
  10. package/dist/assets/.agents/aidlc/schemas/manifest.schema.json +1 -1
  11. package/dist/assets/.agents/aidlc/scripts/lib/context-runtime.mjs +5 -0
  12. package/dist/assets/.agents/aidlc/scripts/lib/loop-authority.mjs +245 -0
  13. package/dist/assets/.agents/aidlc/scripts/lib/loop-commit-message.mjs +45 -0
  14. package/dist/assets/.agents/aidlc/scripts/lib/loop-daemon.mjs +232 -0
  15. package/dist/assets/.agents/aidlc/scripts/lib/loop-drivers.mjs +21 -4
  16. package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet-cli.mjs +86 -28
  17. package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet-remote.mjs +7 -0
  18. package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet-supervisor.mjs +22 -6
  19. package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet.mjs +46 -15
  20. package/dist/assets/.agents/aidlc/scripts/lib/loop-git.mjs +22 -9
  21. package/dist/assets/.agents/aidlc/scripts/lib/loop-intake.mjs +23 -5
  22. package/dist/assets/.agents/aidlc/scripts/lib/loop-local-workers.mjs +43 -15
  23. package/dist/assets/.agents/aidlc/scripts/lib/loop-runtime.mjs +82 -6
  24. package/dist/assets/.agents/aidlc/scripts/lib/loop-stream.mjs +60 -0
  25. package/dist/assets/.agents/aidlc/scripts/lib/loop-supervisor-run-local.mjs +142 -5
  26. package/dist/assets/.agents/aidlc/scripts/lib/loop-supervisor.mjs +9 -1
  27. package/dist/assets/.agents/aidlc/scripts/lib/loop-worker-docker.mjs +31 -5
  28. package/dist/assets/.agents/aidlc/scripts/lib/loop-worker-image.mjs +64 -0
  29. package/dist/assets/.agents/aidlc/scripts/lib/loop-worker-snapshot.mjs +3 -2
  30. package/dist/assets/.agents/aidlc/scripts/lib/store.mjs +59 -4
  31. package/dist/assets/.agents/aidlc/scripts/loop-entry.mjs +85 -73
  32. package/dist/assets/.agents/aidlc/scripts/loop-gate.mjs +19 -7
  33. package/dist/assets/.agents/aidlc/scripts/loop.mjs +2 -1
  34. package/dist/assets/.agents/aidlc/scripts/remote-state.mjs +2 -0
  35. package/dist/assets/.agents/aidlc/scripts/state.mjs +4 -0
  36. package/dist/assets/.agents/aidlc/templates/design.md +9 -0
  37. package/dist/assets/.agents/aidlc/templates/intent.md +4 -0
  38. package/dist/assets/.agents/aidlc/templates/workplan.md +1 -1
  39. package/dist/assets/.agents/skills/aidlc-plan/SKILL.md +1 -1
  40. package/dist/assets/ui/bulma.min.css +3 -0
  41. package/dist/assets/ui/dashboard.css +414 -0
  42. package/dist/services/aidlc-coordination/Dockerfile +11 -3
  43. package/dist/services/aidlc-coordination/compose.yaml +12 -0
  44. package/dist/services/aidlc-coordination/package-lock.json +3 -1
  45. package/dist/services/aidlc-coordination/package.json +9 -4
  46. package/dist/services/aidlc-coordination/src/lifecycle-core.mjs +59 -4
  47. package/dist/services/aidlc-coordination/src/repository.mjs +66 -0
  48. package/dist/services/aidlc-coordination/src/server.mjs +9 -3
  49. package/dist/services/aidlc-coordination/test/fleet.test.mjs +21 -0
  50. package/dist/services/aidlc-coordination/test/server-health-auth.test.mjs +67 -0
  51. package/dist/src/adapters.d.ts +0 -3
  52. package/dist/src/adapters.js +11 -6
  53. package/dist/src/cli/argv.d.ts +12 -0
  54. package/dist/src/cli/argv.js +62 -0
  55. package/dist/src/cli/commands.d.ts +8 -0
  56. package/dist/src/cli/commands.js +129 -0
  57. package/dist/src/cli/options.d.ts +25 -0
  58. package/dist/src/cli/options.js +32 -0
  59. package/dist/src/cli/prompt.d.ts +9 -0
  60. package/dist/src/cli/prompt.js +66 -0
  61. package/dist/src/cli.js +67 -217
  62. package/dist/src/context.js +2 -0
  63. package/dist/src/gate.d.ts +1 -0
  64. package/dist/src/gate.js +51 -37
  65. package/dist/src/installer.d.ts +19 -7
  66. package/dist/src/installer.js +111 -178
  67. package/dist/src/legacy.js +1 -0
  68. package/dist/src/managed-content.d.ts +7 -0
  69. package/dist/src/managed-content.js +15 -4
  70. package/dist/src/managed-planning.d.ts +65 -0
  71. package/dist/src/managed-planning.js +219 -0
  72. package/dist/src/mcp-setup.js +3 -1
  73. package/dist/src/model/config-contracts.d.ts +65 -0
  74. package/dist/src/model/config-contracts.js +1 -0
  75. package/dist/src/model/install-contracts.d.ts +29 -0
  76. package/dist/src/model/install-contracts.js +1 -0
  77. package/dist/src/model/lifecycle-enums.d.ts +13 -0
  78. package/dist/src/model/lifecycle-enums.js +1 -0
  79. package/dist/src/model/loop.d.ts +20 -0
  80. package/dist/src/model/loop.js +1 -0
  81. package/dist/src/model/manifest.d.ts +16 -0
  82. package/dist/src/model/manifest.js +1 -0
  83. package/dist/src/model/records.d.ts +138 -0
  84. package/dist/src/model/records.js +1 -0
  85. package/dist/src/model.d.ts +11 -264
  86. package/dist/src/model.js +1 -1
  87. package/dist/src/profiles.d.ts +8 -0
  88. package/dist/src/profiles.js +63 -8
  89. package/dist/src/project-migration.d.ts +1 -0
  90. package/dist/src/project-migration.js +7 -0
  91. package/dist/src/state/archive-record.d.ts +9 -0
  92. package/dist/src/state/archive-record.js +26 -0
  93. package/dist/src/state/commands.d.ts +18 -0
  94. package/dist/src/state/commands.js +27 -0
  95. package/dist/src/state/domain.d.ts +9 -0
  96. package/dist/src/state/domain.js +9 -0
  97. package/dist/src/state/guards.d.ts +7 -0
  98. package/dist/src/state/guards.js +17 -0
  99. package/dist/src/state/io.d.ts +10 -0
  100. package/dist/src/state/io.js +35 -0
  101. package/dist/src/state/items.d.ts +24 -0
  102. package/dist/src/state/items.js +147 -0
  103. package/dist/src/state/lessons.d.ts +7 -0
  104. package/dist/src/state/lessons.js +74 -0
  105. package/dist/src/state/lifecycle.d.ts +5 -0
  106. package/dist/src/state/lifecycle.js +78 -0
  107. package/dist/src/state/lock.d.ts +7 -0
  108. package/dist/src/state/lock.js +125 -0
  109. package/dist/src/state/memory.d.ts +8 -0
  110. package/dist/src/state/memory.js +100 -0
  111. package/dist/src/state/migrate.d.ts +17 -0
  112. package/dist/src/state/migrate.js +60 -0
  113. package/dist/src/state/partition.d.ts +15 -0
  114. package/dist/src/state/partition.js +90 -0
  115. package/dist/src/state/paths.d.ts +11 -0
  116. package/dist/src/state/paths.js +12 -0
  117. package/dist/src/state/render.d.ts +26 -0
  118. package/dist/src/state/render.js +32 -0
  119. package/dist/src/state/store.d.ts +4 -0
  120. package/dist/src/state/store.js +75 -0
  121. package/dist/src/state/transitions.d.ts +21 -0
  122. package/dist/src/state/transitions.js +95 -0
  123. package/dist/src/state/validate.d.ts +4 -0
  124. package/dist/src/state/validate.js +143 -0
  125. package/dist/src/state.d.ts +20 -117
  126. package/dist/src/state.js +20 -815
  127. package/dist/src/task-ui.d.ts +54 -0
  128. package/dist/src/task-ui.js +1570 -0
  129. package/dist/src/upgrade-board.d.ts +5 -0
  130. package/dist/src/upgrade-board.js +89 -0
  131. package/dist/src/upgrade.d.ts +3 -5
  132. package/dist/src/upgrade.js +174 -255
  133. package/dist/src/workflow.js +4 -4
  134. package/docs/README.md +6 -2
  135. package/docs/architecture.md +12 -8
  136. package/docs/command-reference.md +83 -19
  137. package/docs/configuration.md +30 -7
  138. package/docs/coordination-service.md +20 -5
  139. package/docs/design-decisions.md +83 -0
  140. package/docs/development.md +41 -2
  141. package/docs/loop-local-run.md +495 -0
  142. package/docs/loop-runbook.md +235 -0
  143. package/docs/operating-workflow.md +5 -2
  144. package/docs/testing-and-release.md +11 -7
  145. package/docs/troubleshooting.md +125 -0
  146. package/package.json +5 -2
  147. package/dist/src/ledger.d.ts +0 -22
  148. package/dist/src/ledger.js +0 -119
  149. package/dist/src/tracker.d.ts +0 -29
  150. package/dist/src/tracker.js +0 -41
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Interactive layer: TTY detection, dynamic @inquirer/prompts loading, and the
3
+ * prompting flows (loop configuration, MCP setup selections, confirmations).
4
+ * Parsing of argv into options happens in ./argv.ts and ./options.ts.
5
+ */
6
+ import { stdin, stdout } from "node:process";
7
+ import { LOOP_PROVIDERS } from "../model.js";
8
+ import { validateLoopConfig } from "../profiles.js";
9
+ import { loopSelection, mcpArgSelections, parseWorkerMapping } from "./options.js";
10
+ export const interactive = () => Boolean(stdin.isTTY && stdout.isTTY);
11
+ const prompts = async () => import("@inquirer/prompts");
12
+ export const chooseLoop = async (args, upgrade) => {
13
+ let { mode, lead: leadArg, workers: workersArg } = loopSelection(args);
14
+ if (!mode && (leadArg || workersArg))
15
+ throw new Error("--loop-lead and --loop-workers require --loop-mode");
16
+ if (!mode && interactive()) {
17
+ const { select } = await prompts();
18
+ mode = await select({ message: "Loop execution mode", choices: [
19
+ ...(upgrade ? [{ name: "Keep existing loop configuration", value: "keep" }] : []),
20
+ { name: "Off", value: "off" }, { name: "Local", value: "local" }, { name: "Fleet", value: "fleet" }
21
+ ] });
22
+ }
23
+ if (!mode || mode === "keep")
24
+ return undefined;
25
+ if (mode === "off") {
26
+ if (leadArg || workersArg)
27
+ throw new Error("Disabled loop cannot specify agents");
28
+ return validateLoopConfig({ mode });
29
+ }
30
+ let lead = leadArg, rawWorkers = workersArg;
31
+ if (interactive() && (!lead || !rawWorkers)) {
32
+ const { select, input } = await prompts();
33
+ lead ??= await select({ message: "Lead agent", choices: LOOP_PROVIDERS.map((name) => ({ name, value: name })) });
34
+ rawWorkers ??= await input({ message: "Workers (id=provider, comma separated)", default: "codex-worker=codex,claude-worker=claude" });
35
+ }
36
+ if (!lead || !rawWorkers)
37
+ throw new Error("An enabled loop requires --loop-lead and --loop-workers");
38
+ return validateLoopConfig({ mode, agents: { lead, workers: parseWorkerMapping(rawWorkers) } });
39
+ };
40
+ export const mcpSetupSelections = async (args) => {
41
+ const selected = mcpArgSelections(args);
42
+ if ((!selected.deployment || !selected.storage || !selected.bind || !selected.workspace) && !interactive())
43
+ throw new Error("mcp setup outside a TTY requires --deployment, --storage, --bind and --workspace");
44
+ if (interactive()) {
45
+ const { input, select, checkbox } = await prompts();
46
+ selected.deployment ??= await select({ message: "State service deployment", choices: [{ name: "Docker on this machine", value: "docker" }, { name: "Remote service (configure endpoint later)", value: "remote" }] });
47
+ selected.storage ??= await select({ message: "Database", choices: [{ name: "SQLite (local Docker volume)", value: "sqlite" }, { name: "PostgreSQL (multi-host production)", value: "postgres" }] });
48
+ selected.bind ??= await select({ message: "Network binding", choices: [{ name: "localhost only", value: "localhost" }, { name: "network (requires token/TLS)", value: "network" }] });
49
+ selected.workspace ??= await input({ message: "Workspace ID", default: "default", required: true });
50
+ if (!selected.providers.length)
51
+ selected.providers = await checkbox({ message: "Project-management connectors to configure", choices: ["jira", "trello", "github-issues"].map((value) => ({ name: value, value })) });
52
+ }
53
+ if (!Number.isInteger(selected.pollMs) || selected.pollMs < 1_000 || selected.pollMs > 3_600_000)
54
+ throw new Error("--poll-ms must be an integer from 1000 to 3600000");
55
+ return selected;
56
+ };
57
+ export const confirmGeneral = async (options, operation) => {
58
+ if (options.dryRun)
59
+ return false;
60
+ if (options.yes)
61
+ return true;
62
+ if (!interactive())
63
+ return false;
64
+ const { confirm } = await prompts();
65
+ return confirm({ message: `${operation} this plan?`, default: false });
66
+ };
package/dist/src/cli.js CHANGED
@@ -1,90 +1,15 @@
1
1
  #!/usr/bin/env node
2
- import { stdin, stdout } from "node:process";
3
- import { spawnSync } from "node:child_process";
4
- import { resolve } from "node:path";
5
- import { fileURLToPath } from "node:url";
6
- import { join } from "node:path";
7
- import { installableAdapters } from "./adapters.js";
8
- import { applyPlan, detectedAgents, doctor, formatPlan, planInit, planUninstall, selectAgents, status } from "./installer.js";
9
- import { LOOP_PROVIDERS } from "./model.js";
10
- import { loadProjectConfig, resolveProfiles, validateLoopConfig } from "./profiles.js";
2
+ import { resolvedAdapters } from "./adapters.js";
3
+ import { assertKnownOptions, flags, INIT_FLAGS, rootArg, UNINSTALL_FLAGS, UPGRADE_FLAGS, usage } from "./cli/argv.js";
4
+ import { runDoctor, runLoopCommand, runMcp, runProfile, runStatus, runUi } from "./cli/commands.js";
5
+ import { initOptions } from "./cli/options.js";
6
+ import { chooseLoop, confirmGeneral, interactive } from "./cli/prompt.js";
7
+ import { applyPlan, detectedAgents, formatPlan, planInit, planUninstall, selectAgents } from "./installer.js";
11
8
  import { applyUpgrade, planUpgrade } from "./upgrade.js";
12
9
  import { packageVersion } from "./workflow.js";
13
- import { applyMcpSetup, planMcpSetup } from "./mcp-setup.js";
14
- const usage = `Usage:
15
- 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]
16
- npx @felixdotgo/aidlc-kit upgrade [path] [--loop-mode off|local|fleet] [--loop-lead ${LOOP_PROVIDERS.join("|")}] [--loop-workers <id=provider,...>] [--dry-run]
17
- npx @felixdotgo/aidlc-kit uninstall [path] [--yes] [--dry-run]
18
- npx @felixdotgo/aidlc-kit status [path]
19
- npx @felixdotgo/aidlc-kit doctor [path] [--strict]
20
- npx @felixdotgo/aidlc-kit profile validate [path]
21
- npx @felixdotgo/aidlc-kit loop start|status|resume [--task-id ID] [--root PATH] [--mode local|fleet]
22
- 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]`;
23
- const interactive = () => Boolean(stdin.isTTY && stdout.isTTY);
10
+ /* Kept in this file: test/unit/cli.test.ts pins these prompt sources (and the upgrade boundary strings below) to src/cli.ts; the shared interactive flows live in ./cli/prompt.ts. */
24
11
  const prompts = async () => import("@inquirer/prompts");
25
- const value = (args, name) => {
26
- const index = args.indexOf(name);
27
- return index < 0 ? undefined : args[index + 1];
28
- };
29
- const flags = (args) => new Set(args.filter((item) => item.startsWith("--")));
30
- const valueFlags = new Set(["--agent", "--storage", "--deployment", "--bind", "--workspace", "--poll-ms", "--token-env", "--providers", "--loop-mode", "--loop-lead", "--loop-workers"]);
31
- const positional = (args) => {
32
- const result = [];
33
- for (let index = 0; index < args.length; index += 1) {
34
- if (valueFlags.has(args[index])) {
35
- index += 1;
36
- continue;
37
- }
38
- if (!args[index].startsWith("--"))
39
- result.push(args[index]);
40
- }
41
- return result;
42
- };
43
- const rootArg = (args, fallback = ".") => positional(args).find((item) => !["init", "upgrade", "uninstall", "status", "doctor", "validate", "run", "list"].includes(item)) ?? fallback;
44
- const chooseLoop = async (args, upgrade) => {
45
- let mode = value(args, "--loop-mode");
46
- const leadArg = value(args, "--loop-lead"), workersArg = value(args, "--loop-workers");
47
- if (!mode && (leadArg || workersArg))
48
- throw new Error("--loop-lead and --loop-workers require --loop-mode");
49
- if (!mode && interactive()) {
50
- const { select } = await prompts();
51
- mode = await select({ message: "Loop execution mode", choices: [
52
- ...(upgrade ? [{ name: "Keep existing loop configuration", value: "keep" }] : []),
53
- { name: "Off", value: "off" }, { name: "Local", value: "local" }, { name: "Fleet", value: "fleet" }
54
- ] });
55
- }
56
- if (!mode || mode === "keep")
57
- return undefined;
58
- if (mode === "off") {
59
- if (leadArg || workersArg)
60
- throw new Error("Disabled loop cannot specify agents");
61
- return validateLoopConfig({ mode });
62
- }
63
- let lead = leadArg, rawWorkers = workersArg;
64
- if (interactive() && (!lead || !rawWorkers)) {
65
- const { select, input } = await prompts();
66
- lead ??= await select({ message: "Lead agent", choices: LOOP_PROVIDERS.map((name) => ({ name, value: name })) });
67
- rawWorkers ??= await input({ message: "Workers (id=provider, comma separated)", default: "codex-worker=codex,claude-worker=claude" });
68
- }
69
- if (!lead || !rawWorkers)
70
- throw new Error("An enabled loop requires --loop-lead and --loop-workers");
71
- const workers = Object.create(null);
72
- for (const entry of rawWorkers.split(",")) {
73
- const parts = entry.split("=");
74
- if (parts.length !== 2 || !parts[0] || !parts[1] || Object.hasOwn(workers, parts[0]))
75
- throw new Error("Invalid or duplicate loop worker mapping");
76
- workers[parts[0]] = parts[1];
77
- }
78
- return validateLoopConfig({ mode, agents: { lead, workers } });
79
- };
80
- const initOptions = (args) => ({
81
- root: rootArg(args),
82
- agents: (value(args, "--agent") ?? "").split(",").filter(Boolean),
83
- all: flags(args).has("--all"),
84
- dryRun: flags(args).has("--dry-run"),
85
- yes: flags(args).has("--yes"),
86
- force: flags(args).has("--force")
87
- });
12
+ /* --- Agent selection and plan preview -------------------------------------- */
88
13
  const chooseAgents = async (options) => {
89
14
  const selected = selectAgents(options);
90
15
  if (selected.length)
@@ -95,7 +20,7 @@ const chooseAgents = async (options) => {
95
20
  if (!interactive())
96
21
  throw new Error(`Unable to select an agent automatically. Use --agent <name> or --all. Detected: ${detected.join(", ") || "none"}`);
97
22
  const { checkbox } = await prompts();
98
- const choices = installableAdapters();
23
+ const choices = resolvedAdapters();
99
24
  const agents = await checkbox({
100
25
  message: "Select one or more agents",
101
26
  choices: choices.map((adapter) => ({
@@ -110,143 +35,68 @@ const preview = (operation, plan) => {
110
35
  console.log(`β—† ${operation} preview β€” changes have NOT been applied`);
111
36
  console.log(formatPlan(plan));
112
37
  };
113
- const confirmGeneral = async (options, operation) => {
114
- if (options.dryRun)
115
- return false;
116
- if (options.yes)
117
- return true;
118
- if (!interactive())
119
- return false;
120
- const { confirm } = await prompts();
121
- return confirm({ message: `${operation} this plan?`, default: false });
122
- };
123
- const mcpSetupOptions = async (args) => {
124
- const selected = {
125
- deployment: value(args, "--deployment"),
126
- storage: value(args, "--storage"),
127
- bind: value(args, "--bind"),
128
- workspace: value(args, "--workspace"),
129
- pollMs: Number(value(args, "--poll-ms") ?? 30_000),
130
- tokenEnv: value(args, "--token-env") ?? "AIDLC_MCP_TOKEN",
131
- providers: (value(args, "--providers") ?? "").split(",").filter(Boolean)
132
- };
133
- if ((!selected.deployment || !selected.storage || !selected.bind || !selected.workspace) && !interactive())
134
- throw new Error("mcp setup outside a TTY requires --deployment, --storage, --bind and --workspace");
135
- if (interactive()) {
136
- const { input, select, checkbox } = await prompts();
137
- selected.deployment ??= await select({ message: "State service deployment", choices: [{ name: "Docker on this machine", value: "docker" }, { name: "Remote service (configure endpoint later)", value: "remote" }] });
138
- selected.storage ??= await select({ message: "Database", choices: [{ name: "SQLite (local Docker volume)", value: "sqlite" }, { name: "PostgreSQL (multi-host production)", value: "postgres" }] });
139
- selected.bind ??= await select({ message: "Network binding", choices: [{ name: "localhost only", value: "localhost" }, { name: "network (requires token/TLS)", value: "network" }] });
140
- selected.workspace ??= await input({ message: "Workspace ID", default: "default", required: true });
141
- if (!selected.providers.length)
142
- selected.providers = await checkbox({ message: "Project-management connectors to configure", choices: ["jira", "trello", "github-issues"].map((value) => ({ name: value, value })) });
143
- }
144
- if (!Number.isInteger(selected.pollMs) || selected.pollMs < 1_000 || selected.pollMs > 3_600_000)
145
- throw new Error("--poll-ms must be an integer from 1000 to 3600000");
146
- 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") };
147
- };
148
- const main = async () => {
149
- const [command = "help", ...args] = process.argv.slice(2);
150
- if (command === "help" || command === "--help")
151
- return void console.log(usage);
152
- if (command === "loop") {
153
- const action = args[0];
154
- if (!["start", "status", "resume"].includes(action))
155
- throw new Error("Expected loop start, status, or resume");
156
- const parsed = new Map();
157
- for (let index = 1; index < args.length; index += 2) {
158
- const key = args[index], entry = args[index + 1];
159
- if (!["--task-id", "--root", "--mode"].includes(key) || !entry || entry.startsWith("--") || parsed.has(key))
160
- throw new Error("Invalid or duplicate loop option");
161
- parsed.set(key, entry);
162
- }
163
- const root = resolve(parsed.get("--root") ?? ".");
164
- const script = join(root, ".agents", "aidlc", "scripts", "loop-entry.mjs");
165
- const child = spawnSync(process.execPath, [script, action, parsed.get("--task-id") ?? "", root, parsed.get("--mode") ?? ""], { stdio: "inherit" });
166
- if (child.error)
167
- throw child.error;
168
- if (child.status !== 0)
169
- process.exitCode = child.status ?? 1;
38
+ /* --- Command handlers that consume the pinned prompts above ----------------- */
39
+ const runInit = async (args) => {
40
+ assertKnownOptions(args, INIT_FLAGS);
41
+ const options = { ...await chooseAgents(initOptions(args)), loop: await chooseLoop(args, false) };
42
+ const plan = planInit(options);
43
+ preview("INIT", plan);
44
+ if (plan.some((item) => item.action === "conflict"))
45
+ throw new Error("No files were written because the plan contains conflicts.");
46
+ if (!await confirmGeneral(options, "Apply"))
170
47
  return;
48
+ if (plan.some((item) => item.action === "migrate" || item.action === "delete")) {
49
+ const result = applyUpgrade(options.root, plan);
50
+ console.log(`Installed local AI-DLC workflow assets and migrated legacy data. Backup: ${result.backup}; changed: ${result.changed}`);
171
51
  }
172
- if (command === "status")
173
- return void console.log(status(rootArg(args)));
174
- if (command === "doctor") {
175
- const result = doctor(rootArg(args), flags(args).has("--strict"));
176
- console.log(result);
177
- if (result.startsWith("ERROR:"))
178
- process.exitCode = 1;
179
- return;
180
- }
181
- if (command === "init") {
182
- const options = { ...await chooseAgents(initOptions(args)), loop: await chooseLoop(args, false) };
183
- const plan = planInit(options);
184
- preview("INIT", plan);
185
- if (plan.some((item) => item.action === "conflict"))
186
- throw new Error("No files were written because the plan contains conflicts.");
187
- if (!await confirmGeneral(options, "Apply"))
188
- return;
189
- const migratesLegacyData = plan.some((item) => item.action === "migrate" || item.action === "delete");
190
- if (migratesLegacyData) {
191
- const result = applyUpgrade(options.root, plan);
192
- console.log(`Installed local AI-DLC workflow assets and migrated legacy data. Backup: ${result.backup}; changed: ${result.changed}`);
193
- }
194
- else {
195
- applyPlan(options.root, plan);
196
- console.log("Installed local AI-DLC workflow assets.");
197
- }
198
- return;
199
- }
200
- if (command === "uninstall") {
201
- const options = initOptions(args);
202
- const plan = planUninstall(options.root);
203
- preview("UNINSTALL", plan);
204
- if (!await confirmGeneral(options, "Remove"))
205
- return;
52
+ else {
206
53
  applyPlan(options.root, plan);
207
- console.log("Removed eligible managed assets; project config and state were preserved.");
208
- return;
54
+ console.log("Installed local AI-DLC workflow assets.");
209
55
  }
210
- if (command === "upgrade") {
211
- if (flags(args).has("--yes") || flags(args).has("--force"))
212
- throw new Error("upgrade does not support --yes or --force; apply requires a human interactive TTY confirmation");
213
- const root = rootArg(args);
214
- const plan = planUpgrade(root, await chooseLoop(args, true));
215
- preview("UPGRADE", plan);
216
- if (plan.some((item) => item.action === "conflict"))
217
- throw new Error("Upgrade contains conflicts; no files were written.");
218
- if (flags(args).has("--dry-run"))
219
- return;
220
- if (!interactive())
221
- throw new Error("Upgrade apply requires an interactive TTY. AI and non-interactive processes must not apply upgrades.");
222
- const expected = packageVersion();
223
- const { input } = await prompts();
224
- if ((await input({ message: `Type ${expected} to apply this user-initiated upgrade` })).trim() !== expected)
225
- throw new Error("Upgrade confirmation did not match the target version");
226
- const result = applyUpgrade(root, plan);
227
- console.log(`Upgrade complete. Backup: ${result.backup}; changed: ${result.changed}`);
228
- return;
229
- }
230
- if (command === "mcp" && args[0] === "setup") {
231
- const options = await mcpSetupOptions(args.slice(1));
232
- const plan = planMcpSetup(options);
233
- console.log(`β—† MCP SETUP preview β€” ${options.dryRun ? "no files will be written" : "review before applying"}`);
234
- console.log(JSON.stringify(plan, null, 2));
235
- if (options.dryRun)
236
- return;
237
- if (!await confirmGeneral({ ...initOptions(args), root: options.root }, "Apply MCP setup"))
238
- return;
239
- applyMcpSetup(options);
240
- 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.`);
56
+ };
57
+ const runUninstall = async (args) => {
58
+ assertKnownOptions(args, UNINSTALL_FLAGS);
59
+ const options = initOptions(args);
60
+ const plan = planUninstall(options.root);
61
+ preview("UNINSTALL", plan);
62
+ if (!await confirmGeneral(options, "Remove"))
241
63
  return;
242
- }
243
- if (command === "profile" && args[0] === "validate") {
244
- const root = rootArg(args.slice(1));
245
- const config = loadProjectConfig(root);
246
- const profiles = resolveProfiles(root, config.extends);
247
- console.log(`OK: ${profiles.map((item) => item.id).join(" β†’ ")}`);
64
+ applyPlan(options.root, plan);
65
+ console.log("Removed eligible managed assets; project config and state were preserved.");
66
+ };
67
+ const runUpgrade = async (args) => {
68
+ assertKnownOptions(args, UPGRADE_FLAGS);
69
+ if (flags(args).has("--yes") || flags(args).has("--force"))
70
+ throw new Error("upgrade does not support --yes or --force; apply requires a human interactive TTY confirmation");
71
+ const root = rootArg(args);
72
+ const plan = planUpgrade(root, await chooseLoop(args, true));
73
+ preview("UPGRADE", plan);
74
+ if (plan.some((item) => item.action === "conflict"))
75
+ throw new Error("Upgrade contains conflicts; no files were written.");
76
+ if (flags(args).has("--dry-run"))
248
77
  return;
249
- }
250
- throw new Error(`Unknown command: ${command}`);
78
+ if (!interactive())
79
+ throw new Error("Upgrade apply requires an interactive TTY. AI and non-interactive processes must not apply upgrades.");
80
+ const expected = packageVersion();
81
+ const { input } = await prompts();
82
+ if ((await input({ message: `Type ${expected} to apply this user-initiated upgrade` })).trim() !== expected)
83
+ throw new Error("Upgrade confirmation did not match the target version");
84
+ const result = applyUpgrade(root, plan);
85
+ console.log(`Upgrade complete. Backup: ${result.backup}; changed: ${result.changed}`);
86
+ };
87
+ const runHelp = () => console.log(usage);
88
+ const runVersion = () => console.log(packageVersion());
89
+ const REGISTRY = { init: runInit, upgrade: runUpgrade, uninstall: runUninstall, status: runStatus, doctor: runDoctor, loop: runLoopCommand, ui: runUi, mcp: runMcp, profile: runProfile, version: runVersion, help: runHelp };
90
+ const main = async () => {
91
+ const argv = process.argv.slice(2);
92
+ if (argv.includes("--version") || argv.includes("-v"))
93
+ return void console.log(packageVersion());
94
+ if (argv.includes("--help"))
95
+ return void console.log(usage);
96
+ const [command = "help", ...args] = argv;
97
+ const handler = Object.hasOwn(REGISTRY, command) ? REGISTRY[command] : undefined;
98
+ if (!handler)
99
+ throw new Error(`Unknown command: ${command}`);
100
+ return void await handler(args);
251
101
  };
252
- main().catch((error) => { console.error(`aidlc-workflow: ${error instanceof Error ? error.message : String(error)}`); process.exitCode = 1; });
102
+ 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
@@ -1,6 +1,6 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import { resolveProjectPathWithoutSymlinks } from "./project-path.js";
3
- import { hasAreaVerification, hasReview, isLegacyG2Wait, nextAction, repairBounds, transitionDiagnostics, transitionTask } from "./state.js";
3
+ import { hasAreaVerification, hasReview, isLegacyG2Wait, loopPartitionStrict, nextAction, partitionDiagnostics, repairBounds, transitionDiagnostics, transitionTask } from "./state.js";
4
4
  const safeArtifactTarget = (root, path) => resolveProjectPathWithoutSymlinks(root, path, "Unsafe artifact path", "Artifact path crosses a symlink");
5
5
  const artifactContent = (root, path) => {
6
6
  if (!path)
@@ -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,40 @@ 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
+ diagnostics.push(...partitionDiagnostics(task, { strict: loopPartitionStrict(root) }));
44
+ return diagnostics;
45
+ };
46
+ const codeReviewGateDiagnostics = (root, task) => {
47
+ const diagnostics = artifactDiagnostics(root, task, ["intent", "design", "workplan"]);
48
+ if (task.tasks.some((item) => item.status !== "done" && item.status !== "deferred"))
49
+ diagnostics.push({ level: "ERROR", code: "TASKS_OPEN", message: "Build tasks remain open" });
50
+ for (const area of task.areas)
51
+ if (!hasAreaVerification(task, area))
52
+ diagnostics.push({ level: "ERROR", code: "VERIFY_EVIDENCE", message: `Latest post-G1 verification evidence must pass for affected area: ${area}` });
53
+ if (!hasReview(task))
54
+ diagnostics.push({ level: "ERROR", code: "REVIEW_EVIDENCE", message: "Latest post-G1 review evidence must pass" });
55
+ const bounds = repairBounds(task);
56
+ if (!isLegacyG2Wait(task) && (bounds.verifyExhausted || bounds.reviewExhausted))
57
+ diagnostics.push({ level: "ERROR", code: "REPAIR_BOUND", message: "Repair bound exhausted; record a durable handoff instead of presenting G2" });
58
+ return diagnostics;
59
+ };
60
+ const generalGateDiagnostics = (taskId, task, gate) => {
31
61
  const diagnostics = [];
32
62
  if (task.gate !== gate)
33
63
  diagnostics.push({ level: "ERROR", code: "GATE_STATE", message: `Task is at ${task.gate}, not ${gate}` });
@@ -39,37 +69,21 @@ export const checkGate = (root, state, taskId, gate) => {
39
69
  diagnostics.push({ level: "ERROR", code: "TASK_ID", message: "Task key and id differ" });
40
70
  if (!task.title.trim() || !task.areas.length)
41
71
  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
- }
72
- if (!diagnostics.length)
72
+ return diagnostics;
73
+ };
74
+ /** Runs all diagnostics for a gate and returns them (empty-ish with GATE_OK when clean). */
75
+ export const checkGate = (root, state, taskId, gate) => {
76
+ const task = state.tasks[taskId];
77
+ if (!task)
78
+ return [{ level: "ERROR", code: "TASK_UNKNOWN", message: `Unknown task: ${taskId}` }];
79
+ const diagnostics = generalGateDiagnostics(taskId, task, gate);
80
+ if (gate === "G0_confirm")
81
+ diagnostics.push(...confirmGateDiagnostics(root, task));
82
+ if (gate === "G1_review")
83
+ diagnostics.push(...reviewGateDiagnostics(root, task));
84
+ if (gate === "G2_codereview")
85
+ diagnostics.push(...codeReviewGateDiagnostics(root, task));
86
+ if (!diagnostics.some((item) => item.level === "ERROR"))
73
87
  diagnostics.push({ level: "INFO", code: "GATE_OK", message: `${gate} checks passed for ${taskId}` });
74
88
  return diagnostics;
75
89
  };
@@ -1,15 +1,27 @@
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;
4
- export declare const managedContentHash: (spec: Pick<FileSpec, "owner" | "strategy">, content: string) => string | undefined;
5
- export declare const readManifest: (root: string) => WorkflowManifest | undefined;
1
+ import type { AgentId, InitOptions, PlannedWrite } from "./model.js";
2
+ /** Manifest reading lives in the managed-planning core; this export path stays stable for existing importers. */
3
+ export { readManifest } from "./managed-planning.js";
6
4
  export declare const detectedAgents: (root: string) => AgentId[];
7
5
  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;
6
+ /** Plans an init transaction: managed workflow files, manifest, and project-owned files. */
10
7
  export declare const planInit: (options: InitOptions) => PlannedWrite[];
8
+ /** Applies a planned init/uninstall transaction to disk. */
11
9
  export declare const applyPlan: (root: string, plan: PlannedWrite[]) => void;
12
10
  export declare const formatPlan: (plan: PlannedWrite[]) => string;
11
+ /** Plans an uninstall transaction that removes only unchanged managed assets. */
13
12
  export declare const planUninstall: (root: string) => PlannedWrite[];
13
+ export declare const statusData: (root: string) => {
14
+ workflow: string;
15
+ installedVersion: string;
16
+ schema: string;
17
+ detectedAgents: string[];
18
+ };
19
+ /** Reports installed workflow version, schema, and detected agents. */
14
20
  export declare const status: (root: string) => string;
21
+ /** Validates the installed workflow manifest and local config/state. */
15
22
  export declare const doctor: (root: string, strict?: boolean) => string;
23
+ export declare const doctorData: (root: string, strict?: boolean) => {
24
+ level: "ok" | "warn" | "error";
25
+ message: string;
26
+ strict: boolean;
27
+ };