@felixdotgo/aidlc-kit 0.1.1 → 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 (134) hide show
  1. package/DEVELOPMENT.md +14 -0
  2. package/README.md +11 -13
  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/scripts/lib/loop-authority.mjs +245 -0
  11. package/dist/assets/.agents/aidlc/scripts/lib/loop-commit-message.mjs +45 -0
  12. package/dist/assets/.agents/aidlc/scripts/lib/loop-daemon.mjs +232 -0
  13. package/dist/assets/.agents/aidlc/scripts/lib/loop-drivers.mjs +15 -3
  14. package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet-cli.mjs +86 -28
  15. package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet-remote.mjs +7 -0
  16. package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet-supervisor.mjs +22 -6
  17. package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet.mjs +40 -15
  18. package/dist/assets/.agents/aidlc/scripts/lib/loop-git.mjs +14 -6
  19. package/dist/assets/.agents/aidlc/scripts/lib/loop-intake.mjs +23 -5
  20. package/dist/assets/.agents/aidlc/scripts/lib/loop-local-workers.mjs +42 -15
  21. package/dist/assets/.agents/aidlc/scripts/lib/loop-runtime.mjs +75 -5
  22. package/dist/assets/.agents/aidlc/scripts/lib/loop-stream.mjs +60 -0
  23. package/dist/assets/.agents/aidlc/scripts/lib/loop-supervisor-run-local.mjs +142 -5
  24. package/dist/assets/.agents/aidlc/scripts/lib/loop-supervisor.mjs +9 -1
  25. package/dist/assets/.agents/aidlc/scripts/lib/loop-worker-docker.mjs +31 -5
  26. package/dist/assets/.agents/aidlc/scripts/lib/loop-worker-image.mjs +64 -0
  27. package/dist/assets/.agents/aidlc/scripts/lib/store.mjs +57 -2
  28. package/dist/assets/.agents/aidlc/scripts/loop-entry.mjs +85 -73
  29. package/dist/assets/.agents/aidlc/scripts/loop-gate.mjs +19 -7
  30. package/dist/assets/.agents/aidlc/scripts/loop.mjs +2 -1
  31. package/dist/assets/.agents/aidlc/templates/design.md +9 -0
  32. package/dist/assets/.agents/aidlc/templates/intent.md +4 -0
  33. package/dist/assets/.agents/aidlc/templates/workplan.md +1 -1
  34. package/dist/assets/.agents/skills/aidlc-plan/SKILL.md +1 -1
  35. package/dist/assets/ui/bulma.min.css +3 -0
  36. package/dist/assets/ui/dashboard.css +414 -0
  37. package/dist/services/aidlc-coordination/Dockerfile +11 -3
  38. package/dist/services/aidlc-coordination/compose.yaml +12 -0
  39. package/dist/services/aidlc-coordination/package-lock.json +3 -1
  40. package/dist/services/aidlc-coordination/package.json +9 -4
  41. package/dist/services/aidlc-coordination/src/lifecycle-core.mjs +57 -2
  42. package/dist/services/aidlc-coordination/src/repository.mjs +66 -0
  43. package/dist/services/aidlc-coordination/src/server.mjs +9 -3
  44. package/dist/services/aidlc-coordination/test/fleet.test.mjs +21 -0
  45. package/dist/services/aidlc-coordination/test/server-health-auth.test.mjs +67 -0
  46. package/dist/src/adapters.d.ts +0 -3
  47. package/dist/src/adapters.js +3 -5
  48. package/dist/src/cli/argv.d.ts +12 -0
  49. package/dist/src/cli/argv.js +62 -0
  50. package/dist/src/cli/commands.d.ts +8 -0
  51. package/dist/src/cli/commands.js +129 -0
  52. package/dist/src/cli/options.d.ts +25 -0
  53. package/dist/src/cli/options.js +32 -0
  54. package/dist/src/cli/prompt.d.ts +9 -0
  55. package/dist/src/cli/prompt.js +66 -0
  56. package/dist/src/cli.js +25 -191
  57. package/dist/src/gate.js +3 -2
  58. package/dist/src/installer.d.ts +14 -7
  59. package/dist/src/installer.js +38 -141
  60. package/dist/src/legacy.js +1 -0
  61. package/dist/src/managed-content.d.ts +7 -0
  62. package/dist/src/managed-content.js +15 -4
  63. package/dist/src/managed-planning.d.ts +65 -0
  64. package/dist/src/managed-planning.js +219 -0
  65. package/dist/src/mcp-setup.js +3 -1
  66. package/dist/src/model/config-contracts.d.ts +65 -0
  67. package/dist/src/model/config-contracts.js +1 -0
  68. package/dist/src/model/install-contracts.d.ts +29 -0
  69. package/dist/src/model/install-contracts.js +1 -0
  70. package/dist/src/model/lifecycle-enums.d.ts +13 -0
  71. package/dist/src/model/lifecycle-enums.js +1 -0
  72. package/dist/src/model/loop.d.ts +20 -0
  73. package/dist/src/model/loop.js +1 -0
  74. package/dist/src/model/manifest.d.ts +16 -0
  75. package/dist/src/model/manifest.js +1 -0
  76. package/dist/src/model/records.d.ts +138 -0
  77. package/dist/src/model/records.js +1 -0
  78. package/dist/src/model.d.ts +11 -264
  79. package/dist/src/model.js +1 -1
  80. package/dist/src/profiles.d.ts +2 -0
  81. package/dist/src/profiles.js +53 -9
  82. package/dist/src/state/archive-record.d.ts +9 -0
  83. package/dist/src/state/archive-record.js +26 -0
  84. package/dist/src/state/commands.d.ts +18 -0
  85. package/dist/src/state/commands.js +27 -0
  86. package/dist/src/state/domain.d.ts +9 -0
  87. package/dist/src/state/domain.js +9 -0
  88. package/dist/src/state/guards.d.ts +7 -0
  89. package/dist/src/state/guards.js +17 -0
  90. package/dist/src/state/io.d.ts +10 -0
  91. package/dist/src/state/io.js +35 -0
  92. package/dist/src/state/items.d.ts +3 -4
  93. package/dist/src/state/items.js +58 -57
  94. package/dist/src/state/lessons.js +14 -7
  95. package/dist/src/state/lifecycle.js +2 -2
  96. package/dist/src/state/lock.js +7 -6
  97. package/dist/src/state/memory.js +24 -12
  98. package/dist/src/state/{archive.js → migrate.js} +7 -7
  99. package/dist/src/state/partition.d.ts +15 -0
  100. package/dist/src/state/partition.js +90 -0
  101. package/dist/src/state/paths.d.ts +11 -0
  102. package/dist/src/state/paths.js +12 -0
  103. package/dist/src/state/render.js +9 -6
  104. package/dist/src/state/store.js +9 -15
  105. package/dist/src/state/transitions.js +9 -7
  106. package/dist/src/state/validate.js +50 -7
  107. package/dist/src/state.d.ts +16 -11
  108. package/dist/src/state.js +16 -11
  109. package/dist/src/task-ui.d.ts +54 -0
  110. package/dist/src/task-ui.js +1570 -0
  111. package/dist/src/upgrade-board.js +10 -10
  112. package/dist/src/upgrade.d.ts +0 -1
  113. package/dist/src/upgrade.js +19 -83
  114. package/dist/src/workflow.js +3 -3
  115. package/docs/README.md +6 -2
  116. package/docs/architecture.md +9 -8
  117. package/docs/command-reference.md +81 -20
  118. package/docs/configuration.md +28 -8
  119. package/docs/coordination-service.md +18 -6
  120. package/docs/design-decisions.md +83 -0
  121. package/docs/development.md +27 -2
  122. package/docs/loop-local-run.md +495 -0
  123. package/docs/loop-runbook.md +235 -0
  124. package/docs/operating-workflow.md +2 -2
  125. package/docs/testing-and-release.md +5 -1
  126. package/docs/troubleshooting.md +125 -0
  127. package/package.json +4 -1
  128. package/dist/src/ledger.d.ts +0 -22
  129. package/dist/src/ledger.js +0 -119
  130. package/dist/src/state/shared.d.ts +0 -29
  131. package/dist/src/state/shared.js +0 -71
  132. package/dist/src/tracker.d.ts +0 -36
  133. package/dist/src/tracker.js +0 -44
  134. /package/dist/src/state/{archive.d.ts → migrate.d.ts} +0 -0
@@ -12,7 +12,7 @@ const parseFrontmatter = (content) => {
12
12
  const match = content.match(/^---\n([\s\S]*?)\n---/);
13
13
  if (!match)
14
14
  return {};
15
- return Object.fromEntries(match[1].split("\n").flatMap((line) => {
15
+ return Object.fromEntries((match[1] ?? "").split("\n").flatMap((line) => {
16
16
  const separator = line.indexOf(":");
17
17
  return separator < 0 ? [] : [[line.slice(0, separator).trim(), line.slice(separator + 1).trim().replace(/^['"]|['"]$/g, "")]];
18
18
  }));
@@ -30,7 +30,7 @@ const legacyEvidence = (phase, recordedAt) => {
30
30
  /** Maps one legacy BOARD row to a canonical task, copying its prose files into the stable artifact layout. */
31
31
  const taskFromLegacyRow = (root, files, row, recordedAt) => {
32
32
  const [id, title, phaseRaw, gateRaw, statusRaw, branch, areasRaw, doc] = row;
33
- const oldDoc = safePath(root, doc);
33
+ const oldDoc = safePath(root, doc ?? "");
34
34
  const source = existsSync(oldDoc) ? readFileSync(oldDoc, "utf8") : "";
35
35
  const frontmatter = parseFrontmatter(source);
36
36
  const stableDir = `.agents/data/tasks/${id}`;
@@ -38,21 +38,21 @@ const taskFromLegacyRow = (root, files, row, recordedAt) => {
38
38
  const workplanPath = `${stableDir}/workplan.md`;
39
39
  if (source)
40
40
  files.push({ path: prosePath, owner: "aidlc-state", ownershipClass: "state", content: source });
41
- const oldWorkplan = doc.replace(/\.md$/, ".workplan.md");
41
+ const oldWorkplan = (doc ?? "").replace(/\.md$/, ".workplan.md");
42
42
  if (existsSync(safePath(root, oldWorkplan)))
43
43
  files.push({ path: `${stableDir}/legacy-workplan.md`, owner: "aidlc-state", ownershipClass: "state", content: readFileSync(safePath(root, oldWorkplan), "utf8") });
44
- const phase = ["clarify", "plan", "build", "wrap", "done"].includes(phaseRaw) ? phaseRaw : "clarify";
45
- const gate = ["none", "G0_confirm", "G1_review", "G2_codereview"].includes(gateRaw) ? gateRaw : "G0_confirm";
44
+ const phase = (phaseRaw !== undefined && ["clarify", "plan", "build", "wrap", "done"].includes(phaseRaw) ? phaseRaw : "clarify");
45
+ const gate = (gateRaw !== undefined && ["none", "G0_confirm", "G1_review", "G2_codereview"].includes(gateRaw) ? gateRaw : "G0_confirm");
46
46
  return {
47
- id,
48
- title,
49
- type: (["feature", "bug", "refactor", "infra"].includes(frontmatter.type) ? frontmatter.type : "infra"),
47
+ id: id ?? "",
48
+ title: title ?? "",
49
+ type: (["feature", "bug", "refactor", "infra"].includes(frontmatter.type ?? "") ? frontmatter.type : "infra"),
50
50
  phase,
51
51
  gate,
52
- status: (["active", "blocked_on_user", "paused", "done"].includes(statusRaw) ? statusRaw : "paused"),
52
+ status: (statusRaw !== undefined && ["active", "blocked_on_user", "paused", "done"].includes(statusRaw) ? statusRaw : "paused"),
53
53
  language: frontmatter.language === "en" ? "en" : "vi",
54
54
  risk: "normal",
55
- areas: areasRaw.split(",").map((area) => area.trim()).filter(Boolean),
55
+ areas: (areasRaw ?? "").split(",").map((area) => area.trim()).filter(Boolean),
56
56
  branch: branch || "—",
57
57
  artifacts: { intent: source ? prosePath : undefined, design: source ? prosePath : undefined, workplan: workplanPath },
58
58
  decisions: [],
@@ -1,5 +1,4 @@
1
1
  import type { LoopConfig, PlannedWrite } from "./model.js";
2
- export { migrateLegacyBoard } from "./upgrade-board.js";
3
2
  /** Plans an upgrade transaction from the installed manifest to the current package. */
4
3
  export declare const planUpgrade: (root: string, loop?: LoopConfig) => PlannedWrite[];
5
4
  export interface UpgradeResult {
@@ -1,95 +1,32 @@
1
1
  import { copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, renameSync, rmdirSync, rmSync, writeFileSync } from "node:fs";
2
2
  import { dirname, join, resolve } from "node:path";
3
- import { findAdapterForUpgrade, resolvedAdaptersForUpgrade } from "./adapters.js";
4
- import { installedHashFor, managedContentHash, planLoopSelection, readManifest } from "./installer.js";
3
+ import { findAdapter, resolvedAdapters } from "./adapters.js";
5
4
  import { legacyV021AdapterContents, legacyV021Hashes } from "./legacy.js";
6
5
  import { planProjectLayoutMigration } from "./project-migration.js";
7
- import { mergeManagedBlock } from "./managed-content.js";
6
+ import { legacyManifest, managedContentHash, planLoopSelection, planManagedWrites, preserveOwnedFile, readManifest, splitInventoryKey } from "./managed-planning.js";
8
7
  import { resolveProjectPath, resolveProjectPathWithoutSymlinks } from "./project-path.js";
9
8
  import { prepareV4Migration, validateState } from "./state.js";
10
9
  import { contentHash, coreWorkflowFiles, initialProjectFiles, manifestSpec } from "./workflow.js";
11
10
  import { migrateLegacyBoard } from "./upgrade-board.js";
12
- export { migrateLegacyBoard } from "./upgrade-board.js";
13
11
  const safePath = (root, relative) => resolveProjectPath(root, relative, "Unsafe upgrade path");
14
12
  const assertNoSymlinkPath = (root, relative) => { resolveProjectPathWithoutSymlinks(root, relative, "Unsafe upgrade path", "Refusing to upgrade symlink path"); };
15
- const legacyManifest = (root) => {
16
- const path = join(resolve(root), ".agents/aidlc/manifest.json");
17
- if (!existsSync(path))
18
- return false;
19
- try {
20
- const parsed = JSON.parse(readFileSync(path, "utf8"));
21
- return parsed.managedBy === "aidlc-workflow" && (parsed.schemaVersion === 0 || parsed.schemaVersion === 1);
22
- }
23
- catch {
24
- return false;
25
- } /* intentional: a malformed/missing manifest is not a legacy v0.x install */
26
- };
27
13
  /* --- Managed file planning -------------------------------------------------- */
28
- const planManaged = (root, spec, installedHash, legacy) => {
29
- const target = safePath(root, spec.path);
30
- if (!existsSync(target))
31
- return { ...spec, action: "create", reason: "new managed file" };
32
- const current = readFileSync(target, "utf8");
33
- const next = spec.strategy === "managed-block" ? mergeManagedBlock(spec, current) : spec.content;
34
- const currentHash = managedContentHash(spec, current);
35
- const nextHash = contentHash(spec.strategy === "managed-block" ? spec.content.trimEnd() : spec.content);
36
- if (currentHash === nextHash)
37
- return { ...spec, content: next, action: next === current ? "skip" : "update", reason: "managed content already current" };
38
- if (installedHash && currentHash === installedHash)
39
- return { ...spec, content: next, action: "update", reason: "unchanged managed file" };
40
- const legacyAdapter = legacyV021AdapterContents[spec.path];
41
- const legacyAdapterHash = legacyAdapter ? contentHash(spec.strategy === "managed-block" ? legacyAdapter.trimEnd() : legacyAdapter) : undefined;
42
- if (legacy && (legacyV021Hashes[spec.path] === currentHash || legacyAdapterHash === currentHash))
43
- return { ...spec, content: next, action: "update", reason: "recognized v0.2.1 baseline" };
44
- if (spec.strategy === "managed-block" && current.includes(`<!-- aidlc-installer:${spec.owner} -->`))
45
- return { ...spec, content: next, action: "conflict", reason: "managed prompt block was modified" };
46
- return { ...spec, content: next, action: "conflict", reason: legacy ? "legacy core differs from known baseline" : "managed file differs from installed hash" };
47
- };
48
- /** Merges a later owner of a shared managed-block path into the already-emitted plan entry. */
49
- const mergeSharedBlockOwner = (project, previous, spec, installed) => {
50
- if (!["create", "update", "skip"].includes(previous.action))
51
- return;
52
- const target = safePath(project, spec.path);
53
- const current = existsSync(target) ? readFileSync(target, "utf8") : "";
54
- // Every owner block is checked before merging so a user edit to a later
55
- // adapter's block is reported instead of being silently overwritten.
56
- const installedBlockHash = installedHashFor(installed?.files, spec);
57
- const currentBlockHash = managedContentHash(spec, current);
58
- if (installedBlockHash !== undefined && currentBlockHash !== undefined && installedBlockHash !== currentBlockHash) {
59
- previous.action = "conflict";
60
- previous.reason = "managed prompt block was modified";
61
- return;
62
- }
63
- previous.content = mergeManagedBlock(spec, previous.content);
64
- previous.action = existsSync(target) ? (previous.content === current ? "skip" : "update") : "create";
65
- previous.reason = "managed prompt block";
66
- };
67
- /** Plans managed replacements and shared managed-block writes across every owner. */
68
- const planManagedFiles = (project, managed, installed, legacy) => {
69
- const plan = [];
70
- const mergedBlocks = new Map();
71
- for (const spec of managed) {
72
- const installedHash = installedHashFor(installed?.files, spec);
73
- if (spec.strategy !== "managed-block") {
74
- plan.push(planManaged(project, spec, installedHash, legacy));
75
- continue;
76
- }
77
- const previous = mergedBlocks.get(spec.path);
78
- if (!previous) {
79
- const write = planManaged(project, spec, installedHash, legacy);
80
- plan.push(write);
81
- mergedBlocks.set(spec.path, write);
82
- continue;
83
- }
84
- mergeSharedBlockOwner(project, previous, spec, installed);
14
+ /** Upgrade's planning policy: ownership is proven against installed hashes and the frozen v0.2.1 baselines. */
15
+ const upgradePlanPolicy = (installed, legacy) => ({
16
+ flow: "upgrade",
17
+ installed,
18
+ legacy,
19
+ resolvePath: safePath,
20
+ legacyBaseline: (spec, currentHash) => {
21
+ const legacyAdapter = legacyV021AdapterContents[spec.path];
22
+ const legacyAdapterHash = legacyAdapter ? contentHash(spec.strategy === "managed-block" ? legacyAdapter.trimEnd() : legacyAdapter) : undefined;
23
+ return legacyV021Hashes[spec.path] === currentHash || legacyAdapterHash === currentHash;
85
24
  }
86
- return plan;
87
- };
88
- /* --- Obsolete files and project-owned files ---------------------------------- */
25
+ });
89
26
  /** Deletes obsolete managed files tracked by the installed manifest that no longer ship in this release. */
90
27
  const planObsoleteRemovals = (project, plan, installed, managed) => {
91
28
  for (const [key, item] of Object.entries(installed.files)) {
92
- const path = key.includes("#") ? key.slice(0, key.lastIndexOf("#")) : key;
29
+ const path = splitInventoryKey(key).path;
93
30
  // Shared prompt blocks are upgraded through the merged plan above; never delete their whole path here.
94
31
  if (item.strategy === "managed-block")
95
32
  continue;
@@ -107,8 +44,7 @@ const preserveProjectFiles = (project, plan, legacy) => {
107
44
  for (const spec of initialProjectFiles()) {
108
45
  if (legacy && spec.path === ".agents/data/state/aidlc-state.json" && !existsSync(safePath(project, spec.path)))
109
46
  continue;
110
- const target = safePath(project, spec.path);
111
- plan.push(existsSync(target) ? { ...spec, content: readFileSync(target, "utf8"), action: "preserve", reason: `${spec.ownershipClass}-owned file` } : { ...spec, action: "create", reason: `new ${spec.ownershipClass}-owned file` });
47
+ plan.push(preserveOwnedFile(safePath(project, spec.path), spec));
112
48
  }
113
49
  };
114
50
  /* --- Canonical state and legacy BOARD migration -------------------------------- */
@@ -171,11 +107,11 @@ const removeLegacyBoard = (project, plan) => {
171
107
  /* --- planUpgrade orchestration ------------------------------------------------ */
172
108
  /** Selects which adapters to upgrade: manifest-registered ones, else auto-detected installable ones. */
173
109
  const selectedUpgradeAgents = (project, installed) => {
174
- const upgradeAdapters = resolvedAdaptersForUpgrade();
110
+ const upgradeAdapters = resolvedAdapters();
175
111
  const requested = installed?.adapters?.length ? installed.adapters : upgradeAdapters.filter((adapter) => adapter.detect(project)).map((adapter) => adapter.id);
176
- return requested.filter((id) => findAdapterForUpgrade(id));
112
+ return requested.filter((id) => findAdapter(id));
177
113
  };
178
- const agentUpgradeFiles = (selected) => selected.flatMap((id) => findAdapterForUpgrade(id)?.files() ?? []).map((file) => ({ ...file, ownershipClass: "managed" }));
114
+ const agentUpgradeFiles = (selected) => selected.flatMap((id) => findAdapter(id)?.files() ?? []).map((file) => ({ ...file, ownershipClass: "managed" }));
179
115
  /** Plans an upgrade transaction from the installed manifest to the current package. */
180
116
  export const planUpgrade = (root, loop) => {
181
117
  const project = resolve(root);
@@ -185,7 +121,7 @@ export const planUpgrade = (root, loop) => {
185
121
  throw new Error("No owned AI-DLC installation found. Use init instead of upgrade.");
186
122
  const selected = selectedUpgradeAgents(project, installed);
187
123
  const managed = [...coreWorkflowFiles(), ...agentUpgradeFiles(selected)];
188
- const plan = planManagedFiles(project, managed, installed, legacy);
124
+ const plan = planManagedWrites(project, managed, upgradePlanPolicy(installed, legacy));
189
125
  if (installed)
190
126
  planObsoleteRemovals(project, plan, installed, managed);
191
127
  preserveProjectFiles(project, plan, legacy);
@@ -1,13 +1,13 @@
1
- import { createHash } from "node:crypto";
2
1
  import { readdirSync, readFileSync } from "node:fs";
3
2
  import { join } from "node:path";
4
3
  import { fileURLToPath } from "node:url";
5
4
  import { defaultConfig } from "./profiles.js";
6
- import { createLessonIndex, emptyMemoryRegistry, emptyState, lessonStateDigest } from "./state.js";
5
+ import { createLessonIndex, digest, emptyMemoryRegistry, emptyState, lessonStateDigest } from "./state.js";
7
6
  const owner = "aidlc-core";
8
7
  const assetRoot = fileURLToPath(new URL("../assets/.agents", import.meta.url));
9
8
  const packageJson = JSON.parse(readFileSync(fileURLToPath(new URL("../../package.json", import.meta.url)), "utf8"));
10
- export const contentHash = (content) => createHash("sha256").update(content).digest("hex");
9
+ // node:crypto update() hashes Uint8Array bytes unchanged; the cast only satisfies digest's narrower parameter type.
10
+ export const contentHash = (content) => digest(content);
11
11
  const collect = (directory, target) => readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
12
12
  const source = join(directory, entry.name);
13
13
  const destination = `${target}/${entry.name}`;
package/docs/README.md CHANGED
@@ -8,7 +8,8 @@ Use this page to choose the guide that matches your role and next task.
8
8
  2. Read [Operating the workflow](./operating-workflow.md) before asking an agent to change a project.
9
9
  3. Use [Configuration](./configuration.md) to configure project rules, profiles, and Codex, Claude Code, and OpenCode permissions.
10
10
  4. Keep [Command reference](./command-reference.md) nearby for safe CLI usage.
11
- 5. Read [AI-DLC Coordination service](./coordination-service.md) before enabling remote canonical state — the service and the loop are **experimental and unfinished**, with known bugs and possible breaking changes in every release before `1.0.0`.
11
+ 5. Read [AI-DLC Coordination service](./coordination-service.md) before enabling remote canonical state — the coordination service and **fleet** mode are **experimental and still under active development**, with known bugs and possible breaking changes in every release before `1.0.0`.
12
+ 6. Use the [Loop setup runbook](./loop-runbook.md) to run the loop end-to-end — enable from `off`, run in `local` mode, or spread across machines with `fleet`.
12
13
 
13
14
  ## For contributors and maintainers
14
15
 
@@ -26,6 +27,9 @@ Use this page to choose the guide that matches your role and next task.
26
27
  | [Development](./development.md) | Contributors | Dependencies, source layout, local commands, and implementation rules. |
27
28
  | [Testing and release](./testing-and-release.md) | Maintainers | Test strategy, evaluator, package smoke test, and release checks. |
28
29
  | [Architecture](./architecture.md) | Contributors | Boundaries, dependency direction, and source-to-consumer flow. |
29
- | [AI-DLC Coordination service](./coordination-service.md) | Operators | Opt-in service deployment, state authority, and credential safety. Experimental and unfinished: known bugs, breaking changes possible before `1.0.0`. |
30
+ | [AI-DLC Coordination service](./coordination-service.md) | Operators | Opt-in service deployment, state authority, and credential safety. Experimental and under active development: known bugs, breaking changes possible before `1.0.0`. |
31
+ | [Loop setup runbook](./loop-runbook.md) | Operators | Step-by-step loop setup: enable from `off`, run in `local` mode (usable today), or spread across machines with `fleet` (in development). Experimental: breaking changes possible before `1.0.0`. |
32
+ | [Troubleshooting](./troubleshooting.md) | Operators and contributors | Symptom/error-code recovery guide for locks, corrupt state, continuation guard, gate-check errors, and fleet authority. |
33
+ | [Design decisions](./design-decisions.md) | Contributors | ADR-lite: dual core, single JSON state, compact style, human-only upgrade, append-only evidence, and how to add a decision. |
30
34
 
31
35
  When a public command, configuration field, or installed layout changes, update the relevant guide and its validation in the same change.
@@ -26,14 +26,15 @@ The distinction matters:
26
26
 
27
27
  | Boundary | Modules | Responsibility |
28
28
  | --- | --- | --- |
29
- | CLI and installation | `src/cli.ts`, `src/installer.ts`, `src/workflow.ts`, `src/adapters.ts` | Parse commands, select adapters, plan deterministic writes, install managed assets, and expose local status/doctor checks. |
30
- | Lifecycle domain | `src/model.ts`, `src/state.ts`, `src/gate.ts`, `src/context.ts` | Validate canonical state, calculate transitions and gate readiness, and build bounded phase packets. |
31
- | Profiles and project integration | `src/profiles.ts`, `src/tracker.ts` | Resolve project configuration/profiles and optional tracker integration. |
32
- | Migration | `src/upgrade.ts`, `src/project-migration.ts`, `src/legacy.ts` | Plan and apply safe legacy-data and version-layout migration with audit backups. |
33
- | Shared file semantics | `src/project-path.ts`, `src/managed-content.ts` | Keep all paths inside the project, reject unsafe symlinks, and preserve managed-content ownership. |
29
+ | CLI and installation | `src/cli.ts` + `src/cli/` (`argv`, `options`, `prompt`, `commands` registry), `src/installer.ts`, `src/workflow.ts`, `src/adapters.ts`, `src/mcp-setup.ts` | Parse commands, select adapters, plan deterministic writes, install managed assets (including the coordination-service template), and expose local status/doctor checks. |
30
+ | Shared planning core | `src/managed-planning.ts` | Single manifest reading, inventory-key handling, and the managed-file planning ladder (create/skip/update/conflict) with explicit init vs upgrade policies; consumed by installation and migration. |
31
+ | Lifecycle domain | `src/model/` (barrel `src/model.ts`), `src/state/` (barrel `src/state.ts`: `io`, `paths`, `domain`, `guards`, `archive-record`, `items`, `commands`, `transitions`, `validate`, `store`, `memory`, `lessons`, `lifecycle`, `render`, `migrate`, `lock`), `src/gate.ts`, `src/context.ts` | Validate canonical state, calculate transitions and gate readiness, classify next actions, and build bounded phase packets. |
32
+ | Profiles and project integration | `src/profiles.ts` | Resolve project configuration/profiles, validate config (incl. the config-only `tracker` section — no runtime tracker consumer ships today), and expand effective rules. |
33
+ | Migration | `src/upgrade.ts`, `src/project-migration.ts`, `src/legacy.ts`, `src/upgrade-board.ts` | Plan and apply safe legacy-data and version-layout migration (incl. legacy `BOARD.md` parsing) with audit backups. |
34
+ | Shared file semantics | `src/project-path.ts`, `src/managed-content.ts` | Keep all paths inside the project, reject unsafe symlinks, and own the managed-block marker format as its single definition. |
34
35
  | Development evaluator | `dev/evaluator/` | Run repository-only evaluation; never expose evaluator assets through the published package CLI. |
35
36
 
36
- Dependencies flow inward: CLI, installation, and migration depend on lifecycle and shared helpers; lifecycle does not depend on installation; evaluator may provision an installation but is not part of published runtime behaviour.
37
+ Dependencies flow inward: CLI, installation, and migration depend on the shared planning core, lifecycle, and shared helpers; the planning core depends only on lifecycle/config/shared modules (`model`, `workflow`, `managed-content`, `profiles`) — never on the CLI, installer, or upgrade. Migration does not import installation (`upgrade.ts` consumes `managed-planning.ts` directly); the remaining one-directional `installer.ts` → `project-migration.ts` edge supports init-time legacy-layout planning. Lifecycle does not depend on installation; evaluator may provision an installation but is not part of published runtime behaviour. `src/ledger.ts` and `src/tracker.ts` were removed as runtime-dead code; the `tracker` config section remains schema-only (`TrackerConfig`).
37
38
 
38
39
  ## Installed workflow runtime
39
40
 
@@ -48,7 +49,7 @@ The installed kernel consists of contracts, templates, schemas, profiles, and de
48
49
  The state machine enforces the lifecycle; gates require explicit human approval; evidence is append-only. Installed scripts remain usable without an `aidlc-kit` executable or a duplicate board file.
49
50
 
50
51
  > [!WARNING]
51
- > The interactive-loop support and the end-to-end loop/fleet coordinator described below are **experimental and unfinished**: they contain known bugs and rough edges, and — because the package is still pre-1.0 (`0.x`) — their command surface, configuration fields, and state layout can change incompatibly in any release before `1.0.0`.
52
+ > The **local/interactive loop is usable** today; the end-to-end **fleet** coordinator described below is **still under active development**. Both are **experimental** — because the package is still pre-1.0 (`0.x`), their command surface, configuration fields, and state layout can change incompatibly in any release before `1.0.0`.
52
53
 
53
54
  The interactive loop runs inside the current Codex, Claude Code, or OpenCode conversation. Native workers use local assignment records under `.agents/data/loop/`; those records hold claims and checkpoints while `.agents/data/state/` remains lifecycle authority. The optional headless fleet runner uses the coordination service's PostgreSQL state, fenced lead and worker claims, and Git commit handoff. Its assignment records are operational metadata; task phase, evidence, and gates stay in the canonical lifecycle state. All hosts derive dispatch from task `nextAction` and dependency-ready work.
54
55
 
@@ -77,4 +78,4 @@ Installed entry points share a strict dependency-free argv parser. Parsing and c
77
78
  Use this guide with [Development](./development.md) and [Testing and release](./testing-and-release.md) before changing a cross-cutting boundary.
78
79
 
79
80
 
80
- The optional end-to-end loop coordinator is implemented for both authorities. Local mode runs a foreground supervisor over the canonical local state: Docker-isolated workers, a read-only lead sandbox whose structured proposals are applied by a host broker, and gate approval only through the trusted host terminal. Fleet mode runs the same loop against a coordination service: the service owns supervisor leases, gate checkpoints, and reviewer approval, workers execute in Docker with their own credentials, and Git integration is fenced per backend (SQLite on one host with a local volume, or PostgreSQL across hosts). Selecting a mode configures the loop; only an explicit `loop start` runs it. Host-executed lead turns accept Codex, Claude Code, or OpenCode; container-isolated lead turns and worker containers accept Codex and Claude Code only and reject OpenCode with an explicit diagnostic because its credentials are file-based. See [Coordination service](./coordination-service.md) for deployment and recovery constraints.
81
+ The optional end-to-end loop coordinator is implemented for both authorities. Local mode runs a foreground supervisor over the canonical local state: a host-executed lead, Docker-isolated workers, and gate approval only through the trusted host terminal. Fleet mode runs the same loop against a coordination service: the service owns supervisor leases, gate checkpoints, and reviewer approval, workers execute in Docker with their own credentials, and Git integration is fenced per backend (SQLite on one host with a local volume, or PostgreSQL across hosts). Selecting a mode configures the loop; only an explicit `loop start` runs it. Host-executed lead turns accept Codex, Claude Code, or OpenCode; container-isolated lead turns accept Codex and Claude Code only and reject OpenCode with an explicit diagnostic because its credentials are file-based. Local Docker worker batches accept Codex, Claude Code, or OpenCode (OpenCode workers authenticate from environment credentials with their XDG roots redirected into the ephemeral worker HOME); fleet worker containers reject OpenCode. See [Coordination service](./coordination-service.md) for deployment and recovery constraints.
@@ -1,39 +1,41 @@
1
1
  # Command reference
2
2
 
3
- All package commands operate on local files. The examples run version `0.1.1` through npm; `npx` may ask permission to fetch that package on first use. Replace `.` with the target project path when needed. For a source checkout, see the [development guide](./development.md).
3
+ All package commands operate on local files. The examples run version `0.2.0` through npm; `npx` may ask permission to fetch that package on first use. Replace `.` with the target project path when needed. For a source checkout, see the [development guide](./development.md).
4
+
5
+ Global flags available on every invocation: `--version` (or `-v`) prints the package version and exits; `--help` prints usage.
4
6
 
5
7
  ## `init`
6
8
 
7
9
  ```sh
8
- npx --package @felixdotgo/aidlc-kit@0.1.1 aidlc-kit init [path] [--agent <name[,name]> | --all] [--loop-mode off|local|fleet] [--loop-lead codex|claude|opencode] [--loop-workers <id=provider,...>] [--yes] [--dry-run] [--force]
10
+ npx --package @felixdotgo/aidlc-kit@0.2.0 aidlc-kit init [path] [--agent <name[,name]> | --all] [--loop-mode off|local|fleet] [--loop-lead codex|claude|opencode] [--loop-workers <id=provider,...>] [--yes] [--dry-run] [--force]
9
11
  ```
10
12
 
11
13
  Plans the installation, prints a preview, and then writes the workflow only after confirmation (or `--yes`). `--dry-run` prints the same plan without writing. If exactly one installed agent is detected, it can be selected automatically. In an interactive terminal, choose multiple agents with the checkbox prompt: use arrow keys to move, Space to toggle, A to toggle all, and Enter to confirm. For scripts and non-interactive terminals, use `--agent <name[,name]>` or `--all`.
12
14
 
13
- In interactive setup, choose a loop mode and, when enabled, the lead and worker providers. For non-interactive setup, omit loop options to keep the default off, or specify `--loop-mode local|fleet` together with `--loop-lead` and `--loop-workers` (for example, `--loop-workers a=codex,b=opencode`). The preview includes the project config change. These settings only configure the loop; run `aidlc-kit loop start` to actually start it. The loop and the coordination service are experimental: their flags and defaults may change incompatibly in any release before `1.0.0`.
15
+ In interactive setup, choose a loop mode and, when enabled, the lead and worker providers. For non-interactive setup, omit loop options to keep the default off, or specify `--loop-mode local|fleet` together with `--loop-lead` and `--loop-workers` (for example, `--loop-workers a=codex,b=opencode`). The preview includes the project config change. These settings only configure the loop; run `aidlc-kit loop start` to actually start it. Local loop mode is usable today; fleet mode is still under active development; the loop and the coordination service remain experimental: their flags and defaults may change incompatibly in any release before `1.0.0`.
14
16
 
15
17
  `--force` is for an intentional initial replacement of an unmanaged conflicting file. It is not an upgrade mechanism. If the project already has another workflow package version, use the human-run upgrade flow instead.
16
18
 
17
19
  ## `status`
18
20
 
19
21
  ```sh
20
- npx --package @felixdotgo/aidlc-kit@0.1.1 aidlc-kit status [path]
22
+ npx --package @felixdotgo/aidlc-kit@0.2.0 aidlc-kit status [path] [--json]
21
23
  ```
22
24
 
23
- Reports the installed version and local installation status. It does not query a registry.
25
+ Reports the installed version and local installation status. It does not query a registry. Pass `--json` to print the same data as a JSON object (`workflow`, `installedVersion`, `schema`, `detectedAgents`).
24
26
 
25
27
  ## `doctor`
26
28
 
27
29
  ```sh
28
- npx --package @felixdotgo/aidlc-kit@0.1.1 aidlc-kit doctor [path] [--strict]
30
+ npx --package @felixdotgo/aidlc-kit@0.2.0 aidlc-kit doctor [path] [--strict] [--json]
29
31
  ```
30
32
 
31
- Checks the local manifest, configuration, and canonical state. `--strict` turns compatibility warnings such as a legacy manifest into errors. It does not query a registry.
33
+ Checks the local manifest, configuration, and canonical state. `--strict` turns compatibility warnings such as a legacy manifest into errors. It does not query a registry. Pass `--json` to print `{level, message, strict}`; an `error` level sets a non-zero exit code.
32
34
 
33
35
  ## `profile validate`
34
36
 
35
37
  ```sh
36
- npx --package @felixdotgo/aidlc-kit@0.1.1 aidlc-kit profile validate [path]
38
+ npx --package @felixdotgo/aidlc-kit@0.2.0 aidlc-kit profile validate [path]
37
39
  ```
38
40
 
39
41
  Loads `.agents/config.json`, resolves its profile chain, and prints the effective profile order. Use it after changing `extends` or local profiles.
@@ -41,7 +43,7 @@ Loads `.agents/config.json`, resolves its profile chain, and prints the effectiv
41
43
  ## `uninstall`
42
44
 
43
45
  ```sh
44
- npx --package @felixdotgo/aidlc-kit@0.1.1 aidlc-kit uninstall [path] [--yes] [--dry-run]
46
+ npx --package @felixdotgo/aidlc-kit@0.2.0 aidlc-kit uninstall [path] [--yes] [--dry-run]
45
47
  ```
46
48
 
47
49
  Prints the removal plan before applying it. It removes eligible unchanged managed assets but preserves project configuration and state. Prefer `--dry-run` first when working in an existing project.
@@ -49,15 +51,17 @@ Prints the removal plan before applying it. It removes eligible unchanged manage
49
51
  ## `mcp setup`
50
52
 
51
53
  ```sh
52
- npx --package @felixdotgo/aidlc-kit@0.1.1 aidlc-kit mcp setup [path] [--dry-run] [--yes] [--storage sqlite|postgres] [--deployment docker|remote] [--bind localhost|network] [--workspace <id>] [--poll-ms <n>] [--enable]
54
+ npx --package @felixdotgo/aidlc-kit@0.2.0 aidlc-kit mcp setup [path] [--dry-run] [--yes] [--storage sqlite|postgres] [--deployment docker|remote] [--bind localhost|network] [--workspace <id>] [--poll-ms <n>] [--token-env <name>] [--providers <p,...>] [--enable]
53
55
  ```
54
56
 
55
57
  Previews the optional AI-DLC Coordination service configuration, then writes only after confirmation. Non-interactive use requires `--deployment`, `--storage`, `--bind`, and `--workspace`. It never starts Docker; `--enable` is explicit and remote mode has no local fallback. See [AI-DLC Coordination service](./coordination-service.md) for deployment and credential requirements.
56
58
 
59
+ `--token-env <name>` overrides the default `AIDLC_MCP_TOKEN` environment variable name used in the generated compose file. `--providers <p,...>` selects project-management connectors non-interactively (`jira`, `trello`, `github-issues`).
60
+
57
61
  ## `upgrade`
58
62
 
59
63
  ```sh
60
- npx --package @felixdotgo/aidlc-kit@0.1.1 aidlc-kit upgrade [path] [--loop-mode off|local|fleet] [--loop-lead codex|claude|opencode] [--loop-workers <id=provider,...>] [--dry-run]
64
+ npx --package @felixdotgo/aidlc-kit@0.2.0 aidlc-kit upgrade [path] [--loop-mode off|local|fleet] [--loop-lead codex|claude|opencode] [--loop-workers <id=provider,...>] [--dry-run]
61
65
  ```
62
66
 
63
67
  Upgrade is deliberately human-only. The command rejects `--yes` and `--force`; applying it requires an interactive terminal and an exact typed confirmation of the package version. Interactive confirmations use the same styled terminal prompt as `init`. Agents must not run, preview, detect, or suggest package upgrades on their own.
@@ -69,19 +73,76 @@ The upgrade plan handles supported legacy `.aidlc/` project data transactionally
69
73
  ## `loop`
70
74
 
71
75
  ```sh
72
- npx --package @felixdotgo/aidlc-kit@0.1.1 aidlc-kit loop start|status|resume [options]
76
+ npx --package @felixdotgo/aidlc-kit@0.2.0 aidlc-kit loop start [--root PATH] [--mode local|fleet]
77
+ npx --package @felixdotgo/aidlc-kit@0.2.0 aidlc-kit loop intake [--task-id ID] [--title TITLE] [--root PATH] [--mode local|fleet]
78
+ npx --package @felixdotgo/aidlc-kit@0.2.0 aidlc-kit loop status --task-id ID [--root PATH] [--mode local|fleet]
79
+ npx --package @felixdotgo/aidlc-kit@0.2.0 aidlc-kit loop worker-image [--provider opencode|codex|claude] [--tag TAG] [--root PATH]
73
80
  ```
74
81
 
75
82
  > [!WARNING]
76
- > The loop commands and fleet mode are **experimental and unfinished**: they contain known bugs and rough edges, and — because the package is still pre-1.0 (`0.x`) — their options, environment variables, and diagnostics can change incompatibly in any release before `1.0.0`.
83
+ > **Local loop mode is usable** today; **fleet mode is still under active development**. The loop commands remain **experimental** — because the package is still pre-1.0 (`0.x`), their options, environment variables, and diagnostics can change incompatibly in any release before `1.0.0`.
84
+
85
+ `--mode` defaults to the configured `loop.mode`; the command fails closed when it does not match (`Requested mode does not match configured authority`). Every action except `worker-image` requires an enabled `loop.mode` with a lead and worker mapping. Local and fleet mode share one daemon and the same actions:
86
+
87
+ | Action | Behaviour |
88
+ | --- | --- |
89
+ | `start` | Starts the singleton loop daemon for the project in the foreground (lock `.agents/data/loop/daemon.lock`; a second `start` fails with `Loop daemon already running (pid N)`). It reads **no** requirement. Fleet `start` first preflights the coordination service (`fleet_get`) and the remote integration branch and fails closed instead of falling back to local. |
90
+ | `intake` | Creates one loop-owned task without running it. The requirement is read from stdin (an interactive prompt on a TTY), at most 8192 bytes; the id and title are derived from its first line unless `--task-id`/`--title` are given. Local intake refuses while an interactive (non-loop) task is actionable. Fleet intake creates the remote task under a short lead claim. Both write `.agents/data/tasks/<id>/loop-intake.json` (`authority: "local"` or `"fleet"`). |
91
+ | `status` | Prints the canonical task and its derived next action; local mode adds the loop run record, fleet mode the durable gate checkpoint. |
92
+ | `worker-image` | Builds (or rebuilds) the shipped worker image without requiring loop configuration: `--provider` defaults to `opencode`, `--tag` to `aidlc-worker-<provider>:local`. It runs `docker build` against the packaged `assets/aidlc/docker/Dockerfile.<provider>` and records the tag in `config.loop.workerImage`. |
93
+
94
+ `loop resume` is removed in both modes; it fails with `loop resume is removed; the daemon auto-resumes actionable tasks — run 'loop start' …`. Re-activate a paused task with the dashboard **resume** button, `state.mjs task status <id> --status active` (local), or `node .agents/aidlc/scripts/loop-entry.mjs resume-task <id> .` (either mode).
95
+
96
+ The daemon only runs **loop-owned** tasks: locally, tasks with an intake record or a loop run record; in fleet mode, only tasks whose local intake record says `authority: "fleet"`. Tasks created by an interactive lead are never picked up. Every `loop.pollMs` (default `5000` ms) it lists actionable loop-owned tasks and runs one bounded supervised run per task in round-robin order (bounded by the per-run `loop.limits.maxTurns`). A run that throws, or ends `blocked` or `reconcile`, is retried with exponential backoff (poll interval doubled per failure, capped at 10 minutes). A gate wait or completion does not stop the daemon, and an unreachable authority (for example the fleet service) is reported as `daemon_poll_error` and retried on the next poll. In fleet mode a run returns at a gate (`gate`/`waiting`) and releases the supervisor lease; after the reviewer approves, the next poll re-claims the task and continues.
97
+
98
+ Shutdown is two-stage in both modes: the first `SIGINT`/`SIGTERM` stops after the current turn, the second cancels the in-flight agent turn (and any build worker batch), and the third exits immediately. A terminal `Ctrl-C` also reaches the agent child process, so the in-flight turn usually ends on the first press. Stopped runs are recorded as `stopped` (the task is not paused) and continue on the next `start`. Events go to stderr, carry their `taskId`, and are also appended to the JSONL file named by `AIDLC_LOOP_EVENT_LOG` when set. Daemon events include `daemon_start`, `daemon_poll`, `daemon_poll_error`, `daemon_turn`, `daemon_turn_error`, `build_deferred` (a ready build item waiting for a later batch), `gate`, and `loop_stopped`.
99
+
100
+ Local mode requires Docker plus the provider credential environment; the worker image is auto-built from the packaged Dockerfile on first dispatch and its tag is recorded automatically, so `AIDLC_WORKER_DOCKER_IMAGE` is only needed to override the default. Local approval happens through the trusted host terminal command `node .agents/aidlc/scripts/loop-gate.mjs <task-id>`, which refuses piped input, or the dashboard.
101
+
102
+ Fleet mode requires `AIDLC_FLEET_URL`, `AIDLC_FLEET_WORKSPACE`, the lead `AIDLC_FLEET_TOKEN`, and `AIDLC_FLEET_INTEGRATION_BRANCH` (optionally `AIDLC_FLEET_GIT_REMOTE`). The facade derives `AIDLC_FLEET_PROJECT_ROOT`, `AIDLC_FLEET_WORKER_IDS`, `AIDLC_FLEET_WORKER_PROVIDERS`, and the lead agent from the root and `loop.agents`; explicitly set values must match. `loop.limits` (`maxTurns`, `turnTimeoutMs`, `maxStallTurns`) and `loop.pollMs` become the fleet defaults, and an explicit `AIDLC_FLEET_MAX_TURNS`, `AIDLC_FLEET_TURN_TIMEOUT_MS`, `AIDLC_FLEET_MAX_STALL_TURNS`, or `AIDLC_FLEET_POLL_MS` still overrides them. Lead and worker processes never receive `AIDLC_FLEET_REVIEWER_TOKEN`. Fleet workers run separately through the fleet runner on each worker machine, and a reviewer approves gates with the reviewer credential as documented in [Coordination service](./coordination-service.md).
103
+
104
+ The installed `loop-entry.mjs` is the single entry point behind the CLI and the dashboard. Its positional form is `loop-entry.mjs <action> [task-id] [root] [mode] [provider] [tag] [title]`, for example `echo "<requirement>" | node .agents/aidlc/scripts/loop-entry.mjs intake "" .`. Besides the actions above it accepts `resume-task <id>` (re-activate a paused task, both modes) and the fleet-only dashboard JSON actions `tasks`, `task <id>`, `gate <id>` (current review snapshot with its checkpoint), and `approve <id>` (approval source on stdin; reviewer credential only from that process's `AIDLC_FLEET_REVIEWER_TOKEN`). A local dashboard reads local state in-process, so those read actions fail in local mode.
105
+
106
+ ## `ui`
107
+
108
+ ```sh
109
+ npx --package @felixdotgo/aidlc-kit@0.2.0 aidlc-kit ui [path] [--port 4173] [--host 127.0.0.1] [--open] [--token-file PATH] [--reviewer-token-file PATH]
110
+ ```
111
+
112
+ Starts a web dashboard (built on `node:http`, no extra dependencies) for viewing task status and operating the loop without typing CLI commands repeatedly. The project is the positional `path` (default `.`); `ui` has no `--root` flag. The authority comes from `loop.mode`: the header shows `authority · local` or `authority · fleet`. Local dashboards read canonical local state in-process; fleet dashboards read tasks, task details, and gate snapshots (review text from the coordination service) through the `loop-entry.mjs` JSON actions.
113
+
114
+ | Flag | Effect |
115
+ | --- | --- |
116
+ | `--port`, `--host` | Bind address; defaults to `127.0.0.1:4173`. Pass `--host 0.0.0.0` only as an explicit opt-in. |
117
+ | `--open` | Launches the system browser with the login URL (best effort; the URL is printed anyway). |
118
+ | `--token-file PATH` | Uses a fixed session token from `PATH` so the browser stays signed in across restarts. The file must live outside the project, be a regular non-symlink file with mode `600`/`400`, and hold one 16–256 character token; a missing file is created with a random token. |
119
+ | `--reviewer-token-file PATH` | Fleet only: reads the reviewer credential from `PATH` (same file rules, never created). `AIDLC_FLEET_REVIEWER_TOKEN` in the `ui` environment works the same way. |
120
+
121
+ Routes:
122
+
123
+ - `GET /` renders a **kanban board by default** (columns grouped by `phase`: clarify / plan / build / wrap / done) with a list/table toggle, plus **New task** and **Settings** modals. The page uses the full window width and is branded **Loop**. Clicking a task opens a **side panel** that slides in from the right (`/?task=<id>` opens it directly; Back or <kbd>Esc</kbd> closes it); ctrl/cmd-click or middle-click opens the full detail page.
124
+ - `GET /tasks/:id` renders the full task detail page: a horizontal progress stepper, a structured gate view (in fleet mode the review snapshot text), rendered `intent`/`design`/`workplan` artifacts (Markdown), decisions, work items, evidence, details, and a live log stream. `GET /partial/tasks/:id` returns the same detail body as an HTML fragment for the side panel.
125
+ - `GET /api/tasks` returns task summaries as JSON (`?archive=1`, `?statuses=`, `?query=`, `?limit=`, `?cursor=`); `GET /api/tasks/:id` returns one task; `GET /api/tasks/:id/gate` returns the gate view or fleet snapshot (`409` when the task is not waiting); `GET /api/tasks/:id/artifact/:name` returns raw artifact content for programmatic clients.
126
+ - `GET /api/loop/status?task-id=` returns `loop status` as JSON. `POST /api/loop/start` (field `requirement`) runs loop intake and starts the daemon if none is running (`201` with `taskId`, `title`, `daemon`; `409` with the intake error). `POST /api/loop/resume` (field `taskId`) re-activates a `paused` task (`409` for any other status). `POST /api/gate/approve` (fields `taskId`, `source`) runs the atomic `state.mjs gate approve` locally, or the fleet reviewer approval of the current snapshot. All spawn the installed scripts asynchronously as thin facades; a malformed or non-object JSON body is a `400`.
127
+ - `GET /api/events[?task-id=]` streams the event-log sidecar as SSE: on connect the last 200 matching lines within the most recent 256 KB, then new complete lines; it follows rotation/truncation and resumes from `Last-Event-ID`.
128
+
129
+ ### Dashboard authentication
130
+
131
+ At start the CLI prints a login URL such as `http://127.0.0.1:4173/#token=<token>`. Opening it stores the token in the browser's `localStorage` and strips the fragment from the address bar (a fragment is never sent to the server); **⚙ Settings** still accepts a pasted token. There is no unauthenticated mode. The rules:
77
132
 
78
- Options: `--task-id <id>` (optional for `start`, which derives one; required for `status`/`resume`), `--root <path>`, and `--mode local|fleet` (defaults to the configured `loop.mode`). `start` reads the requirement from an interactive prompt or stdin — the requirement is never passed as an argument. The command fails closed when the requested mode does not match the configured authority, when another task is actionable, or when a resume record is missing.
133
+ - The mutating actions (new task, resume, gate approve) require the token in a header — `x-aidlc-token` or `Authorization: Bearer <token>` — never in a form or JSON body field. A missing or wrong token is `401`.
134
+ - Every request whose `Host` header does not name the bound address is rejected `403` (any loopback name — `127.0.0.1`, `localhost`, `[::1]` — is accepted when bound to loopback). A wildcard bind (`0.0.0.0`, `::`) cannot pin the Host and relies on the token and the Origin check.
135
+ - A non-`GET` request whose `Origin` is not the dashboard's own origin is rejected `403`.
136
+ - In fleet mode, `POST /api/gate/approve` returns `403` unless the UI holds a reviewer credential (`AIDLC_FLEET_REVIEWER_TOKEN` or `--reviewer-token-file`); the detail page then shows the `remote-state.mjs gate inbox` / `gate approve` reviewer commands instead of the approve form. The credential is passed only to that one approve child process.
137
+ - The daemon and every other child process the UI spawns never inherit the UI token or the reviewer credential.
79
138
 
80
- `status` prints the canonical task, its derived next action, and — in fleet mode — the durable gate checkpoint. `start` and `resume` run the supervisor in the foreground until the next gate, a pause, a blocked state, or completion; the emitted events include each gate view and the waiting state.
139
+ These rules exist because a browser will send requests to `127.0.0.1` on behalf of any web page: a header-only token defeats cross-site form posts (CSRF), the Host check defeats DNS rebinding, and keeping tokens out of the daemon environment stops a host-executed lead from approving its own gate. Residual risk: a `--token-file` is readable by any process running as the same user, so prefer the per-session token unless you need restarts to keep the browser signed in.
81
140
 
82
- Local mode requires the Docker worker image configuration described in [Testing and release](./testing-and-release.md) plus the provider credential environment; approval happens through the trusted host terminal command `node .agents/aidlc/scripts/loop-gate.mjs <task-id>`, which refuses piped input.
141
+ Operational notes:
83
142
 
84
- Fleet mode requires the running coordination service (`AIDLC_FLEET_URL`, `AIDLC_FLEET_WORKSPACE`, lead `AIDLC_FLEET_TOKEN`, `AIDLC_FLEET_INTEGRATION_BRANCH`, and optionally `AIDLC_FLEET_GIT_REMOTE`); worker IDs must match the configured `loop.agents.workers`, and `AIDLC_FLEET_WORKER_PROVIDERS` may map each worker to `codex`, `claude`, or `opencode`. The facade preflights the service and the integration branch and fails closed with a diagnosis instead of falling back to local. Fleet worker containers reject `opencode` with the container diagnostic above; assignment validation accepts it so that blocker surfaces when the job executes rather than being reported as an invalid provider. Fleet workers run separately through the fleet runner (`worker --once`) on each worker machine with `AIDLC_WORKER_DOCKER_IMAGE` and their own worker credential; a reviewer approves gates through the reviewer credential as documented in [Coordination service](./coordination-service.md).
143
+ - The gate **approve** form renders only on the full task detail page (`/tasks/:id`) while the task is `blocked_on_user`; on the dashboard such a task shows an **approve** button that opens that page. The dashboard header shows whether the daemon is running.
144
+ - The **live log** on the detail page is an SSE stream of that task's events over the fixed file `.agents/data/loop/ui-events.jsonl` (rotated to `.jsonl.1` at 5 MB). A daemon started by **New task** gets `AIDLC_LOOP_EVENT_LOG` pointed at that file automatically and writes its stderr to `.agents/data/loop/daemon.log`; a **manually started** daemon must export the same variable, or the live log stays empty (see [Loop local run](./loop-local-run.md#51-set-environment-variables)).
145
+ - The dashboard **resume** button appears only on `paused` tasks and sets them back to `active` (locally equivalent to `node .agents/aidlc/scripts/state.mjs task status <id> --status active`; in fleet mode `loop-entry.mjs resume-task`); the daemon then picks the task up on its next poll if it is loop-owned.
85
146
 
86
147
  ## Installed lifecycle scripts
87
148
 
@@ -108,7 +169,7 @@ node .agents/aidlc/scripts/loop.mjs resume <task-id> [--max-turns N] [--turn-tim
108
169
  node .agents/aidlc/scripts/loop.mjs status <task-id> [--root PATH]
109
170
  ```
110
171
 
111
- `--agent opencode` requires `AIDLC_OPENCODE_MODEL` in `provider/model[#variant]` form; the turn fails closed without it. Docker-isolated lead turns, local Docker worker batches, and fleet worker containers reject OpenCode with `OpenCode workers require file-based credentials and are not available in isolated containers yet` and do not fall back to the host. Use `loop.mjs run --agent opencode` for host-executed lead turns, or use Codex or Claude Code where container isolation is required.
172
+ `--agent opencode` requires `AIDLC_OPENCODE_MODEL` in `provider/model[#variant]` form; the turn fails closed without it. The container-isolated lead rejects OpenCode with `OpenCode workers require file-based credentials and are not available in isolated containers yet` and does not fall back to the host. Local Docker worker batches and fleet worker containers accept OpenCode via environment credentials — the key follows the `AIDLC_OPENCODE_MODEL` prefix: `openai/` → `OPENAI_API_KEY`, `anthropic/` → `ANTHROPIC_API_KEY`, otherwise `OPENCODE_API_KEY` — with OpenCode's XDG data/config roots redirected into the worker's ephemeral HOME. Use `loop.mjs run --agent opencode` for host-executed lead turns, or use Codex or Claude Code where an isolated lead is required.
112
173
 
113
174
  The defaults are 12 turns and 10 minutes per turn; two completed turns without observable progress stop the run. Each run records its provider, and `resume` rejects a different agent. The runner never grants human approval. An interactive client can resume by reading `task next` and reconciling local assignment metadata; fleet execution uses fenced service leases and the remote canonical state.
114
175
 
@@ -122,7 +183,7 @@ node .agents/aidlc-coordination/src/fleet-run.mjs coordinator <task-id> --once
122
183
  node .agents/aidlc-coordination/src/fleet-run.mjs worker --once
123
184
  ```
124
185
 
125
- `supervisor` claims the task's supervisor lease, runs fenced lead turns through the coordinator, publishes each gate snapshot with its checkpoint in one service transaction, waits for the reviewer approval, and resumes automatically; it returns at a pause, blocked state, or completion, and never approves a gate. `coordinator --once` runs a single unattended pass without the supervisor lease. `worker --once` claims one ready assignment on a worker machine; it requires `AIDLC_WORKER_DOCKER_IMAGE` and that machine's worker credential and fails closed without them. `aidlc-kit loop start --mode fleet` wraps the same supervisor with requirement intake and preflight.
186
+ `supervisor` claims the task's supervisor lease, runs fenced lead turns through the coordinator, publishes each gate snapshot with its checkpoint in one service transaction, waits for the reviewer approval, and resumes automatically; it returns at a pause, blocked state, or completion, and never approves a gate. `coordinator --once` runs a single unattended pass without the supervisor lease. `worker --once` claims one ready assignment on a worker machine and fails closed without a usable worker image or that machine's worker credential; see [Coordination service](./coordination-service.md#multi-machine-loop-deployment) for how the image is resolved. `aidlc-kit loop start --mode fleet` runs the same supervisor inside the shared loop daemon in a bounded mode that returns at each gate instead of waiting for the reviewer.
126
187
 
127
188
  Fleet mode uses `remote-state.mjs` instead of local `state.mjs`. Set `AIDLC_FLEET_URL`, `AIDLC_FLEET_WORKSPACE`, and `AIDLC_FLEET_PROJECT_ROOT`; lead mutations additionally require `AIDLC_FLEET_TOKEN` and the current `AIDLC_FLEET_LEAD_EPOCH`. Claim an epoch before creating or changing a task:
128
189
 
@@ -158,7 +219,7 @@ node .agents/aidlc/scripts/state.mjs gate approve <task-id> --gate <gate> --sour
158
219
  node .agents/aidlc/scripts/state.mjs task transition <task-id> --to <phase> --mode audited --reason "<reason>" --source "<source>"
159
220
  ```
160
221
 
161
- Self-transitions are rejected, and `task create` rejects an id that collides case-insensitively with an existing task or archive record. `task create` also fails while another unrelated task is actionable; its `--switch-from <ids>` acknowledgement still requires explicit user direction. `--feature <feature-id>` permits sibling tasks in an existing feature without abandoning them. `feature link` checks membership and task dependencies; `task item ... --depends-on <item-id,...> --owns <path,...>` adds item edges and an optional Git path scope. Fleet dispatch requires a nonempty path scope. Path scopes are normalized (trailing slashes stripped) and may name either a file or a directory; directory scopes cover every file beneath them. Missing and cyclic dependencies fail, and an item or feature task cannot finish before its prerequisites. After reopening the Approve plan step, all non-deferred execution items return to `todo`; prior Review implementation, verification, and review evidence cannot satisfy the new build boundary, and repair-bound counters reset with it.
222
+ Self-transitions are rejected, and `task create` rejects an id that collides case-insensitively with an existing task or archive record. `task create` also fails while another unrelated task is actionable; its `--switch-from <ids>` acknowledgement still requires explicit user direction. `--feature <feature-id>` permits sibling tasks in an existing feature without abandoning them. `feature link` checks membership and task dependencies; `task item ... --depends-on <item-id,...> --owns <path,...>` adds item edges and an optional Git path scope. Loop dispatch (local and fleet) requires a nonempty path scope. When `loop.mode` is `local` or `fleet`, the Approve plan check also fails with `PARTITION_SCOPE` for an open item without `--owns`, and with `PARTITION_OVERLAP` when two items that can run concurrently (no direct or transitive `--depends-on` between them) own overlapping paths; with the loop off both are warnings. Overlap is per path segment: `src` overlaps `src/a.ts` but not `srcx`. The rendered workplan shows each item's scope as `owns: … · after: …`. Path scopes are normalized (trailing slashes stripped) and may name either a file or a directory; directory scopes cover every file beneath them. Missing and cyclic dependencies fail, and an item or feature task cannot finish before its prerequisites. After reopening the Approve plan step, all non-deferred execution items return to `todo`; prior Review implementation, verification, and review evidence cannot satisfy the new build boundary, and repair-bound counters reset with it.
162
223
 
163
224
  State writes and generated workplans use symlink-checked atomic replacement. State-lock release is owner-token checked and never removes a replacement owner's lock. If a process stops after writing a terminal archive record but before committing the catalog, a retry replaces that orphan only when the persisted catalog still contains the same active task identity and does not reference an archive record; malformed, referenced, or identity-conflicting records fail closed.
164
225
 
@@ -22,6 +22,18 @@ Each installed project owns its workflow configuration at `.agents/config.json`.
22
22
 
23
23
  Commands are stored as executable and argument arrays. Do not replace them with shell strings: this keeps execution inspectable and avoids accidental shell interpretation.
24
24
 
25
+ ## Optional `$schema` key
26
+
27
+ The generated `.agents/config.json` includes an optional top-level `$schema` key pointing at the relative path `./aidlc/schemas/config.schema.json` (the schema installed under `.agents/aidlc/schemas/`). Editors use it for autocompletion and validation of the configuration file, and it has no runtime effect. New installs receive it automatically through `init`; existing installs can add it manually:
28
+
29
+ ```json
30
+ {
31
+ "$schema": "./aidlc/schemas/config.schema.json"
32
+ }
33
+ ```
34
+
35
+ The value must be a string of at most 512 characters. The path is relative so validation works offline without a network dependency.
36
+
25
37
  G0, G1, and G2 always require explicit human approval. Configuration cannot auto-pass a gate.
26
38
 
27
39
  ## Configuration layers
@@ -42,10 +54,10 @@ Built-in topology profiles are:
42
54
  Arrays are stable-deduplicated. Command keys are last-wins, so a project can replace a profile command intentionally. Validate the resolved profile chain with:
43
55
 
44
56
  ```sh
45
- npx --package @felixdotgo/aidlc-kit@0.1.1 aidlc-kit profile validate .
57
+ npx --package @felixdotgo/aidlc-kit@0.2.0 aidlc-kit profile validate .
46
58
  ```
47
59
 
48
- This command uses the npm package at version `0.1.1`. To run from a source checkout instead, see the [development guide](./development.md).
60
+ This command uses the npm package at version `0.2.0`. To run from a source checkout instead, see the [development guide](./development.md).
49
61
 
50
62
  ## Rules, project data, and managed assets
51
63
 
@@ -71,24 +83,32 @@ Set the default risk level deliberately. Planning, migrations, cross-service con
71
83
 
72
84
  ## Optional AI-DLC Coordination
73
85
 
74
- `mcp` is absent (or `{ "enabled": false }`) by default. An enabled configuration requires an HTTP(S) `endpoint` and non-empty `workspace`; it may also name a token environment variable, polling interval from 1,000 to 3,600,000 ms, and the enabled Jira/Trello/GitHub Issues preflight providers. See [AI-DLC Coordination service](./coordination-service.md) for the remote-authority and deployment contract. The coordination service is **experimental and unfinished** (known bugs included), and it can change incompatibly in any release before `1.0.0`.
86
+ `mcp` is absent (or `{ "enabled": false }`) by default. An enabled configuration requires an HTTP(S) `endpoint` and non-empty `workspace`; it may also name a token environment variable, polling interval from 1,000 to 3,600,000 ms, and the enabled Jira/Trello/GitHub Issues preflight providers. See [AI-DLC Coordination service](./coordination-service.md) for the remote-authority and deployment contract. The coordination service (and fleet mode) is **experimental and still under active development** (known bugs included), and it can change incompatibly in any release before `1.0.0`.
75
87
 
76
88
  ## Optional loop configuration
77
89
 
78
90
  > [!WARNING]
79
- > The loop and the coordination service are **experimental and unfinished**: they contain known bugs and rough edges, and — because the package is still pre-1.0 (`0.x`) — the `loop.*` fields, provider mappings, and environment variables can change incompatibly in any release before `1.0.0`.
91
+ > The loop is **experimental** and pre-1.0 (`0.x`): the `loop.*` fields, provider mappings, and environment variables can change incompatibly in any release before `1.0.0`. **Local mode is usable** today; **fleet mode is still under active development**.
80
92
 
81
- `loop.mode` can be `off`, `local`, or `fleet`; an absent `loop` field means `off`. The optional `loop.agents` maps one lead and named workers to `codex`, `claude`, or `opencode`, independently of `mcp.providers`:
93
+ `loop.mode` can be `off`, `local`, or `fleet`; an absent `loop` field means `off`. Both enabled modes run the loop as the same persistent multi-task daemon (one per project, enforced by a lock): `loop start` launches it, and it polls the configured authority — canonical local state, or the coordination service in `fleet` mode — auto-picking every actionable **loop-owned** task without prompting for a requirement. A task is loop-owned when it was created through loop intake (the web dashboard **New task**, `aidlc-kit loop intake`, or `loop-entry.mjs intake`) or, locally, already has a loop run record; fleet ownership is recorded by the intake file `.agents/data/tasks/<id>/loop-intake.json` with `authority: "fleet"`. Tasks created by an interactive lead are never picked up. The optional `loop.agents` maps one lead and named workers to `codex`, `claude`, or `opencode`, independently of `mcp.providers`:
82
94
 
83
95
  ```json
84
96
  {
85
97
  "loop": {
86
98
  "mode": "local",
87
- "agents": { "lead": "codex", "workers": { "worker-a": "codex", "worker-b": "opencode" } }
99
+ "agents": { "lead": "codex", "workers": { "worker-a": "codex", "worker-b": "opencode" } },
100
+ "pollMs": 5000,
101
+ "limits": { "maxTurns": 12, "turnTimeoutMs": 600000, "maxStallTurns": 2 }
88
102
  }
89
103
  }
90
104
  ```
91
105
 
92
- When any selected provider is `opencode`, set `AIDLC_OPENCODE_MODEL` to a `provider/model[#variant]` value such as `opencode-go/gpt-6-luna`. The provider turn fails closed without a valid value. Docker-isolated lead turns, local Docker worker batches, and fleet worker containers reject OpenCode with `OpenCode workers require file-based credentials and are not available in isolated containers yet`; they do not fall back to the host.
106
+ The optional `loop.pollMs` (integer, `1000`–`300000`, default `5000`) sets the daemon's poll interval. The optional `loop.limits` bounds each supervised lead run: `maxTurns` (`1`–`1000`, default `12`) is the turn budget of **one run** — a resumed run starts a fresh budget, so a long multi-gate task is not permanently re-paused — `turnTimeoutMs` (`1`–`3600000`, default `600000`) caps a single agent turn, and `maxStallTurns` (`1`–`20`, default `2`) pauses after that many consecutive turns without observable progress. `loop.pollMs` and `loop.limits` apply to `fleet` mode as well: `loop start` passes them to the fleet supervisor as `AIDLC_FLEET_MAX_TURNS`, `AIDLC_FLEET_TURN_TIMEOUT_MS`, `AIDLC_FLEET_MAX_STALL_TURNS`, and `AIDLC_FLEET_POLL_MS`, and an explicitly exported value of any of those variables overrides the config.
107
+
108
+ The optional `loop.workerImage` maps a provider to a worker Docker image tag (for example `{ "opencode": "aidlc-worker-opencode:local" }`). `aidlc-kit loop worker-image` records it; local build dispatch and fleet worker machines use it, falling back to `aidlc-worker-<provider>:local`, which is auto-built from the packaged Dockerfile when missing. `AIDLC_WORKER_DOCKER_IMAGE` overrides it.
109
+
110
+ With `loop.mode` set to `local` or `fleet`, the Approve plan gate check enforces a parallel work partition: every open build item needs owned paths (`PARTITION_SCOPE`), and items that can run concurrently must not own overlapping paths (`PARTITION_OVERLAP`). With the loop `off` the same findings are warnings and the gate still passes. See [Troubleshooting](./troubleshooting.md#parallel-work-partition-partition_overlap--partition_scope).
111
+
112
+ When any selected provider is `opencode`, set `AIDLC_OPENCODE_MODEL` to a `provider/model[#variant]` value such as `opencode-go/gpt-6-luna`. The provider turn fails closed without a valid value. The container-isolated lead rejects OpenCode with `OpenCode workers require file-based credentials and are not available in isolated containers yet`; it does not fall back to the host. Local Docker worker batches and fleet worker containers accept OpenCode: they authenticate from the environment key matching the model prefix (`openai/` → `OPENAI_API_KEY`, `anthropic/` → `ANTHROPIC_API_KEY`, otherwise `OPENCODE_API_KEY`) and redirect OpenCode's XDG data/config roots into the worker's ephemeral HOME.
93
113
 
94
- Configure this field through the init or human-run upgrade preview, or edit the project-owned config deliberately. Provider credentials do not belong in this object. The configuration is an opt-in selection; it does not install or start a service, authorize an upgrade, or grant gate approval. Selecting a mode alone does not start the loop — run `aidlc-kit loop start` explicitly. Existing fleet operations and storage requirements are documented in [Coordination service](./coordination-service.md).
114
+ Configure this field through the init or human-run upgrade preview, or edit the project-owned config deliberately. Provider credentials do not belong in this object. The configuration is an opt-in selection; it does not install or start a service, authorize an upgrade, or grant gate approval. Selecting a mode alone does not start the loop — run `aidlc-kit loop start` to launch the daemon. Existing fleet operations and storage requirements are documented in [Coordination service](./coordination-service.md).