@felixdotgo/aidlc-kit 0.1.1 → 0.3.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 (198) hide show
  1. package/DEVELOPMENT.md +14 -13
  2. package/README.md +37 -43
  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 +2 -2
  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/state.schema.json +2 -0
  11. package/dist/assets/.agents/aidlc/scripts/lib/assignment-store.mjs +94 -48
  12. package/dist/assets/.agents/aidlc/scripts/lib/context-runtime.mjs +5 -4
  13. package/dist/assets/.agents/aidlc/scripts/lib/loop-authority.mjs +261 -0
  14. package/dist/assets/.agents/aidlc/scripts/lib/loop-commit-message.mjs +45 -0
  15. package/dist/assets/.agents/aidlc/scripts/lib/loop-daemon.mjs +206 -0
  16. package/dist/assets/.agents/aidlc/scripts/lib/loop-drivers.mjs +100 -14
  17. package/dist/assets/.agents/aidlc/scripts/lib/loop-env.mjs +279 -0
  18. package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet-cli.mjs +105 -32
  19. package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet-remote.mjs +17 -1
  20. package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet-supervisor.mjs +24 -7
  21. package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet.mjs +42 -16
  22. package/dist/assets/.agents/aidlc/scripts/lib/loop-git.mjs +20 -6
  23. package/dist/assets/.agents/aidlc/scripts/lib/loop-holder.mjs +376 -0
  24. package/dist/assets/.agents/aidlc/scripts/lib/loop-intake.mjs +33 -7
  25. package/dist/assets/.agents/aidlc/scripts/lib/loop-lease.mjs +67 -61
  26. package/dist/assets/.agents/aidlc/scripts/lib/loop-leases.mjs +268 -0
  27. package/dist/assets/.agents/aidlc/scripts/lib/loop-local-workers.mjs +116 -28
  28. package/dist/assets/.agents/aidlc/scripts/lib/loop-runtime.mjs +114 -12
  29. package/dist/assets/.agents/aidlc/scripts/lib/loop-stream.mjs +117 -0
  30. package/dist/assets/.agents/aidlc/scripts/lib/loop-supervisor-local.mjs +16 -56
  31. package/dist/assets/.agents/aidlc/scripts/lib/loop-supervisor-run-local.mjs +149 -6
  32. package/dist/assets/.agents/aidlc/scripts/lib/loop-supervisor.mjs +9 -1
  33. package/dist/assets/.agents/aidlc/scripts/lib/loop-worker-docker.mjs +44 -6
  34. package/dist/assets/.agents/aidlc/scripts/lib/loop-worker-image.mjs +64 -0
  35. package/dist/assets/.agents/aidlc/scripts/lib/loop-worker-log.mjs +158 -0
  36. package/dist/assets/.agents/aidlc/scripts/lib/repo-head.mjs +22 -0
  37. package/dist/assets/.agents/aidlc/scripts/lib/store.mjs +68 -2
  38. package/dist/assets/.agents/aidlc/scripts/lib/task-runtime-migrate.mjs +75 -0
  39. package/dist/assets/.agents/aidlc/scripts/lib/task-runtime.mjs +97 -0
  40. package/dist/assets/.agents/aidlc/scripts/loop-entry.mjs +168 -73
  41. package/dist/assets/.agents/aidlc/scripts/loop-gate.mjs +19 -7
  42. package/dist/assets/.agents/aidlc/scripts/loop.cmd +101 -0
  43. package/dist/assets/.agents/aidlc/scripts/loop.mjs +2 -1
  44. package/dist/assets/.agents/aidlc/scripts/loop.sh +71 -0
  45. package/dist/assets/.agents/aidlc/scripts/remote-state.mjs +10 -4
  46. package/dist/assets/.agents/aidlc/scripts/state.mjs +16 -8
  47. package/dist/assets/.agents/aidlc/templates/design.md +9 -0
  48. package/dist/assets/.agents/aidlc/templates/intent.md +4 -0
  49. package/dist/assets/.agents/aidlc/templates/workplan.md +1 -1
  50. package/dist/assets/.agents/skills/aidlc-plan/SKILL.md +1 -1
  51. package/dist/assets/ui/bulma.min.css +3 -0
  52. package/dist/assets/ui/changes.js +83 -0
  53. package/dist/assets/ui/common.js +664 -0
  54. package/dist/assets/ui/dashboard.css +636 -0
  55. package/dist/assets/ui/dashboard.js +77 -0
  56. package/dist/assets/ui/detail.js +159 -0
  57. package/dist/assets/ui/live-log.js +332 -0
  58. package/dist/services/aidlc-coordination/Dockerfile +11 -3
  59. package/dist/services/aidlc-coordination/compose.yaml +12 -0
  60. package/dist/services/aidlc-coordination/package-lock.json +3 -1
  61. package/dist/services/aidlc-coordination/package.json +9 -4
  62. package/dist/services/aidlc-coordination/src/lifecycle-core.mjs +68 -2
  63. package/dist/services/aidlc-coordination/src/repository.mjs +66 -0
  64. package/dist/services/aidlc-coordination/src/server.mjs +9 -3
  65. package/dist/services/aidlc-coordination/test/fleet.test.mjs +21 -0
  66. package/dist/services/aidlc-coordination/test/server-health-auth.test.mjs +67 -0
  67. package/dist/src/adapters.d.ts +0 -3
  68. package/dist/src/adapters.js +3 -5
  69. package/dist/src/cli/argv.d.ts +12 -0
  70. package/dist/src/cli/argv.js +68 -0
  71. package/dist/src/cli/commands.d.ts +8 -0
  72. package/dist/src/cli/commands.js +159 -0
  73. package/dist/src/cli/loop-launcher.d.ts +47 -0
  74. package/dist/src/cli/loop-launcher.js +322 -0
  75. package/dist/src/cli/options.d.ts +25 -0
  76. package/dist/src/cli/options.js +32 -0
  77. package/dist/src/cli/prompt.d.ts +9 -0
  78. package/dist/src/cli/prompt.js +66 -0
  79. package/dist/src/cli.js +28 -191
  80. package/dist/src/gate.js +3 -2
  81. package/dist/src/installer.d.ts +14 -7
  82. package/dist/src/installer.js +42 -143
  83. package/dist/src/legacy.js +1 -0
  84. package/dist/src/managed-content.d.ts +7 -0
  85. package/dist/src/managed-content.js +15 -4
  86. package/dist/src/managed-planning.d.ts +65 -0
  87. package/dist/src/managed-planning.js +219 -0
  88. package/dist/src/mcp-setup.js +3 -1
  89. package/dist/src/model/config-contracts.d.ts +65 -0
  90. package/dist/src/model/config-contracts.js +1 -0
  91. package/dist/src/model/install-contracts.d.ts +29 -0
  92. package/dist/src/model/install-contracts.js +1 -0
  93. package/dist/src/model/lifecycle-enums.d.ts +13 -0
  94. package/dist/src/model/lifecycle-enums.js +1 -0
  95. package/dist/src/model/loop.d.ts +20 -0
  96. package/dist/src/model/loop.js +1 -0
  97. package/dist/src/model/manifest.d.ts +16 -0
  98. package/dist/src/model/manifest.js +1 -0
  99. package/dist/src/model/records.d.ts +142 -0
  100. package/dist/src/model/records.js +1 -0
  101. package/dist/src/model.d.ts +11 -264
  102. package/dist/src/model.js +1 -1
  103. package/dist/src/profiles.d.ts +2 -0
  104. package/dist/src/profiles.js +53 -9
  105. package/dist/src/state/archive-record.d.ts +9 -0
  106. package/dist/src/state/archive-record.js +26 -0
  107. package/dist/src/state/commands.d.ts +18 -0
  108. package/dist/src/state/commands.js +27 -0
  109. package/dist/src/state/domain.d.ts +9 -0
  110. package/dist/src/state/domain.js +9 -0
  111. package/dist/src/state/guards.d.ts +7 -0
  112. package/dist/src/state/guards.js +17 -0
  113. package/dist/src/state/io.d.ts +10 -0
  114. package/dist/src/state/io.js +35 -0
  115. package/dist/src/state/items.d.ts +3 -4
  116. package/dist/src/state/items.js +58 -57
  117. package/dist/src/state/lessons.js +14 -7
  118. package/dist/src/state/lifecycle.js +2 -2
  119. package/dist/src/state/lock.js +7 -6
  120. package/dist/src/state/memory.js +24 -12
  121. package/dist/src/state/{archive.js → migrate.js} +7 -7
  122. package/dist/src/state/partition.d.ts +15 -0
  123. package/dist/src/state/partition.js +90 -0
  124. package/dist/src/state/paths.d.ts +11 -0
  125. package/dist/src/state/paths.js +12 -0
  126. package/dist/src/state/render.js +9 -6
  127. package/dist/src/state/store.js +9 -15
  128. package/dist/src/state/transitions.js +9 -7
  129. package/dist/src/state/validate.js +55 -7
  130. package/dist/src/state.d.ts +16 -11
  131. package/dist/src/state.js +16 -11
  132. package/dist/src/task-ui.d.ts +33 -0
  133. package/dist/src/task-ui.js +33 -0
  134. package/dist/src/ui/assets.d.ts +14 -0
  135. package/dist/src/ui/assets.js +45 -0
  136. package/dist/src/ui/changes.d.ts +83 -0
  137. package/dist/src/ui/changes.js +498 -0
  138. package/dist/src/ui/containers.d.ts +71 -0
  139. package/dist/src/ui/containers.js +214 -0
  140. package/dist/src/ui/daemon.d.ts +75 -0
  141. package/dist/src/ui/daemon.js +232 -0
  142. package/dist/src/ui/data.d.ts +42 -0
  143. package/dist/src/ui/data.js +89 -0
  144. package/dist/src/ui/docs.d.ts +13 -0
  145. package/dist/src/ui/docs.js +39 -0
  146. package/dist/src/ui/files.d.ts +3 -0
  147. package/dist/src/ui/files.js +20 -0
  148. package/dist/src/ui/http.d.ts +14 -0
  149. package/dist/src/ui/http.js +80 -0
  150. package/dist/src/ui/live.d.ts +5 -0
  151. package/dist/src/ui/live.js +43 -0
  152. package/dist/src/ui/markdown.d.ts +2 -0
  153. package/dist/src/ui/markdown.js +127 -0
  154. package/dist/src/ui/options.d.ts +48 -0
  155. package/dist/src/ui/options.js +81 -0
  156. package/dist/src/ui/security.d.ts +34 -0
  157. package/dist/src/ui/security.js +38 -0
  158. package/dist/src/ui/server.d.ts +39 -0
  159. package/dist/src/ui/server.js +347 -0
  160. package/dist/src/ui/sse.d.ts +14 -0
  161. package/dist/src/ui/sse.js +281 -0
  162. package/dist/src/ui/views/board.d.ts +7 -0
  163. package/dist/src/ui/views/board.js +131 -0
  164. package/dist/src/ui/views/changes.d.ts +19 -0
  165. package/dist/src/ui/views/changes.js +154 -0
  166. package/dist/src/ui/views/components.d.ts +56 -0
  167. package/dist/src/ui/views/components.js +113 -0
  168. package/dist/src/ui/views/docs.d.ts +15 -0
  169. package/dist/src/ui/views/docs.js +64 -0
  170. package/dist/src/ui/views/layout.d.ts +44 -0
  171. package/dist/src/ui/views/layout.js +233 -0
  172. package/dist/src/ui/views/task.d.ts +25 -0
  173. package/dist/src/ui/views/task.js +320 -0
  174. package/dist/src/upgrade-board.js +10 -10
  175. package/dist/src/upgrade.d.ts +0 -1
  176. package/dist/src/upgrade.js +24 -85
  177. package/dist/src/workflow.d.ts +4 -0
  178. package/dist/src/workflow.js +36 -4
  179. package/docs/README.md +41 -22
  180. package/docs/architecture.md +101 -37
  181. package/docs/command-reference.md +289 -52
  182. package/docs/configuration.md +70 -30
  183. package/docs/coordination-service.md +91 -30
  184. package/docs/design-decisions.md +87 -0
  185. package/docs/development.md +124 -49
  186. package/docs/loop-local-run.md +228 -0
  187. package/docs/loop-runbook.md +214 -0
  188. package/docs/operating-workflow.md +55 -42
  189. package/docs/testing-and-release.md +72 -49
  190. package/docs/troubleshooting.md +221 -0
  191. package/package.json +4 -1
  192. package/dist/src/ledger.d.ts +0 -22
  193. package/dist/src/ledger.js +0 -119
  194. package/dist/src/state/shared.d.ts +0 -29
  195. package/dist/src/state/shared.js +0 -71
  196. package/dist/src/tracker.d.ts +0 -36
  197. package/dist/src/tracker.js +0 -44
  198. /package/dist/src/state/{archive.d.ts → migrate.d.ts} +0 -0
@@ -41,18 +41,30 @@ async function main() {
41
41
  console.log(review.view.trimEnd());
42
42
  console.log(`Review identity: ${JSON.stringify({ taskId, phase: review.phase, gate: review.gate, taskHash: review.taskHash, artifacts: review.artifacts, integrationCommit: review.commit })}`);
43
43
  const rl = createInterface({ input: stdin, output: stdout });
44
- let answer; try { answer = await rl.question("Type approve to approve this exact review: "); } finally { rl.close(); }
45
- if (answer.trim() !== "approve") throw new Error("Review was not approved");
44
+ let answer; try { answer = await rl.question("Type approve to approve this exact review, or type feedback to send guidance back to the loop: "); } finally { rl.close(); }
45
+ const trimmed = answer.trim();
46
+ if (!trimmed) throw new Error("Review was not approved");
46
47
  release = acquireStateLock(root);
47
48
  try {
48
49
  const state = loadState(root), fresh = snapshot(root, taskId, state);
49
50
  if (digest({ ...fresh, view: undefined }) !== digest({ ...review, view: undefined })) throw new Error("Review changed while waiting for approval; inspect the new review");
50
- const checkpoint = readLocalGateCheckpoint(root, state.tasks[taskId]);
51
+ const task = state.tasks[taskId];
51
52
  const reviewHash = digest({ ...review, view: undefined });
52
- const outcome = approveAndAdvance(root, state, taskId, review.gate, `Trusted host terminal: approve (${reviewHash})`);
53
- saveState(root, state);
54
- writeLocalGateReceipt(root, checkpoint, outcome.task, reviewHash);
55
- console.log(`Approved ${review.gate}; next action: ${JSON.stringify(outcome.nextAction)}`);
53
+ if (trimmed === "approve") {
54
+ const checkpoint = readLocalGateCheckpoint(root, task);
55
+ const outcome = approveAndAdvance(root, state, taskId, review.gate, `Trusted host terminal: approve (${reviewHash})`);
56
+ saveState(root, state);
57
+ writeLocalGateReceipt(root, checkpoint, outcome.task, reviewHash);
58
+ console.log(`Approved ${review.gate}; next action: ${JSON.stringify(outcome.nextAction)}`);
59
+ } else {
60
+ const recordedAt = new Date().toISOString();
61
+ task.evidence.push({ kind: "diagnostic", result: "pass", source: `Trusted host terminal: feedback (${reviewHash})`, detail: `Cancelled gate wait at ${task.gate}: ${trimmed}`, recordedAt });
62
+ delete task.legacyG2Wait;
63
+ task.status = "active";
64
+ task.updatedAt = recordedAt;
65
+ saveState(root, state);
66
+ console.log(`Feedback recorded at ${review.gate}; the loop will revise the review and re-present the gate`);
67
+ }
56
68
  } finally { release(); }
57
69
  }
58
70
  main().catch((error) => { console.error(`loop-gate: ${error.message}`); process.exitCode = 1; });
@@ -0,0 +1,101 @@
1
+ @echo off
2
+ rem AI-DLC loop launcher (managed by aidlc-kit; upgrades overwrite local edits).
3
+ rem .agents\aidlc\scripts\loop.cmd [command] [options]
4
+ rem Edit .agents\config.json ("loop") and .agents\loop.env, then run "loop.cmd up".
5
+ rem The script only locates the aidlc-kit CLI and forwards to "aidlc-kit loop <command>";
6
+ rem it never installs or upgrades anything. Local workers need Docker Desktop; the loop is
7
+ rem verified on Linux, so treat native Windows as best effort.
8
+ setlocal
9
+ for %%I in ("%~dp0..\..\..") do set "ROOT=%%~fI"
10
+
11
+ where node >nul 2>nul
12
+ if errorlevel 1 (
13
+ echo loop.cmd: Node.js 20.17 or newer is required ^(https://nodejs.org^) 1>&2
14
+ exit /b 1
15
+ )
16
+
17
+ set "CMD=%~1"
18
+ rem Options without a command run the default command: "loop.cmd --open" = "loop.cmd up --open".
19
+ if "%CMD%"=="" goto default_cmd
20
+ if /i "%CMD%"=="-h" goto named_cmd
21
+ if /i "%CMD%"=="--help" goto named_cmd
22
+ if "%CMD:~0,1%"=="-" goto default_cmd
23
+ :named_cmd
24
+ shift
25
+ goto args
26
+ :default_cmd
27
+ set "CMD=up"
28
+ :args
29
+ set "ARGS="
30
+ :collect
31
+ if "%~1"=="" goto dispatch
32
+ set ARGS=%ARGS% %1
33
+ shift
34
+ goto collect
35
+
36
+ :dispatch
37
+ if /i "%CMD%"=="help" goto usage
38
+ if /i "%CMD%"=="-h" goto usage
39
+ if /i "%CMD%"=="--help" goto usage
40
+ if /i "%CMD%"=="check" set "CMD=env"
41
+ if /i "%CMD%"=="logs" goto logs
42
+ for %%C in (up down stop status env) do if /i "%CMD%"=="%%C" goto run
43
+ echo loop.cmd: unknown command: %CMD% 1>&2
44
+ call :usage 1>&2
45
+ exit /b 2
46
+
47
+ :run
48
+ call :cli loop %CMD% --root "%ROOT%" %ARGS%
49
+ exit /b %ERRORLEVEL%
50
+
51
+ :cli
52
+ if exist "%ROOT%\node_modules\.bin\aidlc-kit.cmd" (
53
+ call "%ROOT%\node_modules\.bin\aidlc-kit.cmd" %*
54
+ goto :eof
55
+ )
56
+ set "RECORDED_CLI="
57
+ for /f "usebackq delims=" %%P in (`node -e "try{const r=require(process.argv[1]);if(typeof r.cli==='string'&&require('fs').statSync(r.cli).isFile())process.stdout.write(r.cli)}catch{}" "%ROOT%\.agents\data\loop\cli.json" 2^>nul`) do set "RECORDED_CLI=%%P"
58
+ if defined RECORDED_CLI (
59
+ node "%RECORDED_CLI%" %*
60
+ goto :eof
61
+ )
62
+ where aidlc-kit >nul 2>nul
63
+ if not errorlevel 1 (
64
+ call aidlc-kit %*
65
+ goto :eof
66
+ )
67
+ set "VERSION=latest"
68
+ for /f "usebackq delims=" %%V in (`node -p "require(process.argv[1]).packageVersion" "%ROOT%\.agents\aidlc\manifest.json" 2^>nul`) do set "VERSION=%%V"
69
+ where npx >nul 2>nul
70
+ if not errorlevel 1 (
71
+ echo loop.cmd: aidlc-kit was not found in this project or recorded by init/upgrade; trying npx --no-install ^(if it fails: npm install --save-dev @felixdotgo/aidlc-kit@%VERSION%, or rerun init/upgrade from the package or .tgz you want^) 1>&2
72
+ call npx --no-install @felixdotgo/aidlc-kit %*
73
+ goto :eof
74
+ )
75
+ echo loop.cmd: the aidlc-kit CLI was not found: install it in this project ^(npm install --save-dev @felixdotgo/aidlc-kit@%VERSION%^), or rerun init/upgrade from the package or .tgz you want 1>&2
76
+ exit /b 1
77
+
78
+ :logs
79
+ set "LOG=%ROOT%\.agents\data\loop\daemon.log"
80
+ if exist "%LOG%" goto follow_log
81
+ echo No daemon log yet: "%LOG%"
82
+ exit /b 0
83
+ :follow_log
84
+ powershell -NoProfile -Command "Get-Content -LiteralPath $env:LOG -Tail 200 -Wait"
85
+ exit /b %ERRORLEVEL%
86
+
87
+ :usage
88
+ echo Usage: .agents\aidlc\scripts\loop.cmd [command] [options]
89
+ echo.
90
+ echo Commands:
91
+ echo up check the environment, start the loop daemon, and serve the dashboard (default)
92
+ echo options: --loop-env PATH --port 4173 --host 127.0.0.1 --open --no-ui
93
+ echo down stop the dashboard started by "up" and the loop daemon (--now cancels the running turn)
94
+ echo stop stop only the loop daemon (--now cancels the running turn)
95
+ echo status daemon, dashboard, and environment at a glance (--json)
96
+ echo check show providers, the env file, and every variable the loop needs (--json)
97
+ echo logs follow the daemon log
98
+ echo help show this help
99
+ echo.
100
+ echo Configure .agents\config.json ("loop" section) and .agents\loop.env (model and API keys).
101
+ exit /b 0
@@ -2,6 +2,7 @@
2
2
  import { resolve } from "node:path";
3
3
  import { loopStatus, runLoop } from "./lib/loop-runtime.mjs";
4
4
  import { LOOP_PROVIDERS, runAgentTurn } from "./lib/loop-drivers.mjs";
5
+ import { streamLog } from "./lib/loop-stream.mjs";
5
6
  import { requireLocalAuthority } from "./lib/authority.mjs";
6
7
 
7
8
  function parse(argv) {
@@ -27,7 +28,7 @@ function parse(argv) {
27
28
  try {
28
29
  requireLocalAuthority();
29
30
  const options = parse(process.argv.slice(2));
30
- const result = options.command === "status" ? loopStatus(options.root, options.taskId) : await runLoop({ root: options.root, taskId: options.taskId, agent: options.agent, mode: options.command, limits: options.limits, driver: runAgentTurn, preflight: true, onEvent: (event) => { if (event?.type === "diagnostic") console.error(event.message); if (event?.type === "resume" || event?.type === "context_changed") console.error(JSON.stringify(event)); } });
31
+ const result = options.command === "status" ? loopStatus(options.root, options.taskId) : await runLoop({ root: options.root, taskId: options.taskId, agent: options.agent, mode: options.command, limits: options.limits, driver: runAgentTurn, preflight: true, onEvent: (event) => { streamLog(event); if (event?.type === "diagnostic") console.error(event.message); if (event?.type === "resume" || event?.type === "context_changed") console.error(JSON.stringify(event)); } });
31
32
  console.log(JSON.stringify({ ok: true, result }, null, 2));
32
33
  if (result.status && !["complete", "gate"].includes(result.status)) process.exitCode = 2;
33
34
  } catch (error) {
@@ -0,0 +1,71 @@
1
+ #!/bin/sh
2
+ # AI-DLC loop launcher (managed by aidlc-kit; upgrades overwrite local edits).
3
+ #
4
+ # .agents/aidlc/scripts/loop.sh [command] [options]
5
+ #
6
+ # Edit .agents/config.json ("loop") and .agents/loop.env, then run `loop.sh up`.
7
+ # The script only locates the aidlc-kit CLI and forwards to `aidlc-kit loop <command>`;
8
+ # it never installs or upgrades anything.
9
+ set -eu
10
+
11
+ SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
12
+ ROOT=$(CDPATH= cd -- "$SCRIPT_DIR/../../.." && pwd)
13
+
14
+ usage() {
15
+ cat <<'EOF'
16
+ Usage: .agents/aidlc/scripts/loop.sh [command] [options]
17
+
18
+ Commands:
19
+ up check the environment, start the loop daemon, and serve the dashboard (default)
20
+ options: --loop-env PATH --port 4173 --host 127.0.0.1 --open --no-ui
21
+ down stop the dashboard started by `up` and the loop daemon (--now cancels the running turn)
22
+ stop stop only the loop daemon (--now cancels the running turn)
23
+ status daemon, dashboard, and environment at a glance (--json)
24
+ check show providers, the env file, and every variable the loop needs (--json)
25
+ logs follow the daemon log
26
+ help show this help
27
+
28
+ Configure:
29
+ .agents/config.json "loop": { "mode": "local", "agents": { "lead": "<provider>", "workers": { "worker-a": "<provider>" } } }
30
+ .agents/loop.env model and API keys (git-ignored); values your tools already store are detected
31
+ EOF
32
+ }
33
+
34
+ command -v node >/dev/null 2>&1 || { echo "loop.sh: Node.js 20.17 or newer is required (https://nodejs.org)" >&2; exit 1; }
35
+
36
+ # The installed workflow version, so hints name the matching package.
37
+ VERSION=$(node -p 'require(process.argv[1]).packageVersion' "$ROOT/.agents/aidlc/manifest.json" 2>/dev/null || echo latest)
38
+ INSTALL_HINT="install it in this project (npm install --save-dev @felixdotgo/aidlc-kit@$VERSION), or rerun init/upgrade from the package or .tgz you want"
39
+ # The CLI that installed this workflow (recorded by init/upgrade), if it still exists.
40
+ RECORDED_CLI=$(node -e 'try{const r=require(process.argv[1]);if(typeof r.cli==="string"&&require("fs").statSync(r.cli).isFile())process.stdout.write(r.cli)}catch{}' "$ROOT/.agents/data/loop/cli.json" 2>/dev/null || true)
41
+
42
+ run_cli() {
43
+ if [ -x "$ROOT/node_modules/.bin/aidlc-kit" ]; then exec "$ROOT/node_modules/.bin/aidlc-kit" "$@"; fi
44
+ if [ -n "$RECORDED_CLI" ]; then exec node "$RECORDED_CLI" "$@"; fi
45
+ if command -v aidlc-kit >/dev/null 2>&1; then exec aidlc-kit "$@"; fi
46
+ if command -v npx >/dev/null 2>&1; then
47
+ echo "loop.sh: aidlc-kit was not found in this project or recorded by init/upgrade; trying npx --no-install (if it fails: $INSTALL_HINT)" >&2
48
+ exec npx --no-install @felixdotgo/aidlc-kit "$@"
49
+ fi
50
+ echo "loop.sh: the aidlc-kit CLI was not found: $INSTALL_HINT" >&2
51
+ exit 1
52
+ }
53
+
54
+ # Options without a command run the default command: `loop.sh --open` = `loop.sh up --open`.
55
+ case "${1:-up}" in
56
+ -h|--help) cmd=help; shift ;;
57
+ -*) cmd=up ;;
58
+ *) cmd=${1:-up}; [ $# -gt 0 ] && shift ;;
59
+ esac
60
+
61
+ case "$cmd" in
62
+ up|down|stop|status) run_cli loop "$cmd" --root "$ROOT" "$@" ;;
63
+ check|env) run_cli loop env --root "$ROOT" "$@" ;;
64
+ logs)
65
+ log="$ROOT/.agents/data/loop/daemon.log"
66
+ if [ ! -f "$log" ]; then echo "No daemon log yet: $log"; exit 0; fi
67
+ exec tail -n 200 -f "$log"
68
+ ;;
69
+ help|-h|--help) usage ;;
70
+ *) echo "loop.sh: unknown command: $cmd" >&2; usage >&2; exit 2 ;;
71
+ esac
@@ -5,7 +5,9 @@ import { readFile } from "node:fs/promises";
5
5
  import { promisify } from "node:util";
6
6
  import { createRemoteReviewerClient, createRemoteStateClient, featureConfig, remoteConfig, reviewerConfig } from "./lib/remote-state-client.mjs";
7
7
  import { compileContext } from "./lib/context-runtime.mjs";
8
- import { checkGate, closeTask, handoffTask, nextAction, readyTaskItems, recordLesson, recordNoLessons, reopenTask, transitionTask, validateState } from "./lib/store.mjs";
8
+ import { explainFleetConflict } from "./lib/loop-holder.mjs";
9
+ import { checkGate, closeTask, handoffTask, nextAction, readyTaskItems, recordLesson, recordNoLessons, recordTaskCommit, reopenTask, transitionTask, validateState } from "./lib/store.mjs";
10
+ import { headCommit } from "./lib/repo-head.mjs";
9
11
 
10
12
  const parse = (argv) => {
11
13
  const positionals = []; const options = new Map();
@@ -54,9 +56,9 @@ export const runRemoteState = async (argv, client, root, git = {}) => {
54
56
  required(id, "task id");
55
57
  const allowed = {
56
58
  "task show": [], "task next": [], "task ready": [], "gate check": ["--gate"], "phase context": ["--phase", "--mode", "--item"],
57
- "task create": ["--title", "--type", "--language", "--risk", "--area", "--feature"],
59
+ "task create": ["--title", "--type", "--language", "--risk", "--area", "--feature", "--base-commit"],
58
60
  "task status": ["--status", "--mode", "--reason", "--source"], "task item": ["--status", "--label", "--depends-on", "--owns"],
59
- "task transition": ["--to", "--mode", "--reason", "--source"],
61
+ "task transition": ["--to", "--mode", "--reason", "--source", "--final-commit"],
60
62
  "task handoff": ["--kind", "--reason", "--source"], "task close": ["--reason", "--source"], "task reopen": ["--to", "--reason", "--source"],
61
63
  "decision set": ["--status", "--label", "--resolution"],
62
64
  "evidence add": ["--kind", "--result", "--gate", "--area", "--source", "--detail"],
@@ -118,6 +120,8 @@ export const runRemoteState = async (argv, client, root, git = {}) => {
118
120
  areas: (option("--area") ?? "root").split(",").map((item) => item.trim()).filter(Boolean), branch: "—",
119
121
  artifacts: { intent: `.agents/data/tasks/${id}/intent.md`, design: `.agents/data/tasks/${id}/design.md`, workplan: `.agents/data/tasks/${id}/workplan.md` },
120
122
  decisions: [], tasks: [], evidence: [], createdAt: recordedAt, updatedAt: recordedAt };
123
+ const baseCommit = option("--base-commit") ?? headCommit(root);
124
+ if (baseCommit !== undefined) recordTaskCommit(task, "baseCommit", baseCommit);
121
125
  } else {
122
126
  if (!previous) throw new Error(`Unknown task: ${id}`);
123
127
  task = structuredClone(previous);
@@ -161,6 +165,7 @@ export const runRemoteState = async (argv, client, root, git = {}) => {
161
165
  if (option("--mode") !== "audited") throw new Error("Transition requires --mode audited");
162
166
  const reason = required(option("--reason"), "--reason"); const source = required(option("--source"), "--source");
163
167
  const target = required(option("--to"), "--to");
168
+ if (target !== "done" && option("--final-commit") !== undefined) throw new Error("--final-commit is only accepted with --to done");
164
169
  if (target === "done" && task.featureId) {
165
170
  const manifest = await client.featureGet(task.featureId);
166
171
  const member = manifest?.feature?.tasks?.find((entry) => entry.id === id);
@@ -171,6 +176,7 @@ export const runRemoteState = async (argv, client, root, git = {}) => {
171
176
  const clone = structuredClone(state); clone.tasks[id] = task;
172
177
  transitionTask(clone, id, target);
173
178
  task = clone.tasks[id];
179
+ if (target === "done") { const finalCommit = option("--final-commit") ?? headCommit(root); if (finalCommit !== undefined) recordTaskCommit(task, "finalCommit", finalCommit); }
174
180
  task.evidence.push({ kind: "diagnostic", result: "pass", source, detail: `Audited transition ${previous.phase} → ${target}: ${reason}`, recordedAt: now() });
175
181
  } else if (route === "decision set") {
176
182
  const status = required(option("--status"), "--status");
@@ -307,5 +313,5 @@ if (isEntry) {
307
313
  const result = isReview ? await runRemoteReview(argv, createRemoteReviewerClient(config), config) : argv[0] === "feature" ? await runRemoteFeature(argv, createRemoteStateClient(config)) : argv[0] === "run" ? await runRemoteLease(argv, createRemoteStateClient(config)) : await runRemoteState(argv, createRemoteStateClient(config), config.projectRoot, config);
308
314
  console.log(JSON.stringify(result, null, 2));
309
315
  if (!result.ok) process.exitCode = 1;
310
- } catch (error) { console.error(JSON.stringify({ ok: false, error: error.message, code: error.code, currentRevision: error.current?.revision }, null, 2)); process.exitCode = 1; }
316
+ } catch (error) { console.error(JSON.stringify({ ok: false, error: error.code === "FLEET_CONFLICT" ? explainFleetConflict(error.message) : error.message, code: error.code, currentRevision: error.current?.revision }, null, 2)); process.exitCode = 1; }
311
317
  }
@@ -6,13 +6,14 @@
6
6
  import { acquireStateLock, approveAndAdvance, checkGate, closeTask, failEntry, handoffTask, listTaskSummaries, loadMemoryRegistry, loadState, loadTask, migrateState, nextAction, option, parseArguments, promoteAgenticMemory, rebuildLessonIndex, recordLesson, recordNoLessons, renderViews, reopenTask, retireAgenticMemory, rootOption, saveState, searchLessons, supersedeTask, transitionTask, validateArguments } from "./lib/runtime.mjs";
7
7
  import { readLiveLease } from "./lib/loop-lease.mjs";
8
8
  import { listFeatures, loadFeature, readyFeatureTasks, saveFeature } from "./lib/feature-store.mjs";
9
- import { readyTaskItems } from "./lib/store.mjs";
9
+ import { readyTaskItems, recordTaskCommit } from "./lib/store.mjs";
10
+ import { headCommit } from "./lib/repo-head.mjs";
10
11
  import { requireLocalAuthority } from "./lib/authority.mjs";
11
12
 
12
13
  try {
13
14
  requireLocalAuthority();
14
15
  const raw = process.argv.slice(2);
15
- const valueOptions = ["--root", "--status", "--limit", "--cursor", "--query", "--title", "--type", "--language", "--risk", "--area", "--mode", "--reason", "--source", "--to", "--kind", "--successor", "--label", "--resolution", "--result", "--gate", "--detail", "--summary", "--prevention", "--example", "--promotion", "--guidance", "--phase", "--priority", "--source-task", "--source-lesson", "--approved-by", "--switch-from", "--feature", "--depends-on", "--branch", "--owns"];
16
+ const valueOptions = ["--root", "--status", "--limit", "--cursor", "--query", "--title", "--type", "--language", "--risk", "--area", "--mode", "--reason", "--source", "--to", "--kind", "--successor", "--label", "--resolution", "--result", "--gate", "--detail", "--summary", "--prevention", "--example", "--promotion", "--guidance", "--phase", "--priority", "--source-task", "--source-lesson", "--approved-by", "--switch-from", "--feature", "--depends-on", "--branch", "--owns", "--base-commit", "--final-commit"];
16
17
  const parsed = parseArguments(raw, { valueOptions, booleanOptions: ["--include-archive"] });
17
18
  const args = parsed.positionals;
18
19
  const [group, action, rawId] = args;
@@ -24,9 +25,9 @@ const schemas = {
24
25
  "task find": { minPositionals: 2, valueOptions: ["--query", "--status", "--limit", "--cursor"], booleanOptions: ["--include-archive"], requiredOptions: ["--query"] },
25
26
  "task next": { minPositionals: 3 },
26
27
  "task ready": { minPositionals: 3 },
27
- "task create": { minPositionals: 3, valueOptions: ["--title", "--type", "--language", "--risk", "--area", "--switch-from", "--feature", "--depends-on"], requiredOptions: ["--title"] },
28
- "task transition": { minPositionals: 3, valueOptions: ["--mode", "--reason", "--source", "--to"], requiredOptions: ["--mode", "--reason", "--source", "--to"], usage: "task transition requires --mode audited, --reason, --source, and --to" },
29
- "task archive": { minPositionals: 3, valueOptions: ["--reason", "--source"], requiredOptions: ["--reason", "--source"] },
28
+ "task create": { minPositionals: 3, valueOptions: ["--title", "--type", "--language", "--risk", "--area", "--switch-from", "--feature", "--depends-on", "--base-commit"], requiredOptions: ["--title"] },
29
+ "task transition": { minPositionals: 3, valueOptions: ["--mode", "--reason", "--source", "--to", "--final-commit"], requiredOptions: ["--mode", "--reason", "--source", "--to"], usage: "task transition requires --mode audited, --reason, --source, and --to" },
30
+ "task archive": { minPositionals: 3, valueOptions: ["--reason", "--source", "--final-commit"], requiredOptions: ["--reason", "--source"] },
30
31
  "task handoff": { minPositionals: 3, valueOptions: ["--kind", "--reason", "--source"], requiredOptions: ["--kind", "--reason", "--source"] },
31
32
  "task close": { minPositionals: 3, valueOptions: ["--reason", "--source"], requiredOptions: ["--reason", "--source"] },
32
33
  "task supersede": { minPositionals: 3, valueOptions: ["--successor", "--reason", "--source"], requiredOptions: ["--successor", "--reason", "--source"] },
@@ -75,6 +76,7 @@ const summaryOptions = (query) => ({
75
76
  cursor: numberOption("--cursor", 0)
76
77
  });
77
78
  const warnings = [];
79
+ const recordFinalCommit = (task) => { const sha = option(parsed, "--final-commit") ?? headCommit(root); if (sha !== undefined) recordTaskCommit(task, "finalCommit", sha); };
78
80
  const derivedStep = (code, hint, step) => { try { step(); } catch (error) { warnings.push({ code, message: error instanceof Error ? error.message : String(error), hint }); } };
79
81
  const persist = (tasks, rebuildLessons = false) => {
80
82
  const archived = saveState(root, state);
@@ -158,6 +160,8 @@ if (group === "state" && action === "migrate") {
158
160
  artifacts: { intent: `.agents/data/tasks/${id}/intent.md`, design: `.agents/data/tasks/${id}/design.md`, workplan: `.agents/data/tasks/${id}/workplan.md` },
159
161
  decisions: [], tasks: [], evidence: [], createdAt: recordedAt, updatedAt: recordedAt
160
162
  };
163
+ const baseCommit = option(parsed, "--base-commit") ?? headCommit(root);
164
+ if (baseCommit !== undefined) recordTaskCommit(task, "baseCommit", baseCommit);
161
165
  const acknowledgedTasks = acknowledged.map((ack) => state.tasks[ack]);
162
166
  for (const prior of acknowledgedTasks) {
163
167
  prior.evidence.push({ kind: "diagnostic", result: "pass", source: `task create ${id}`, detail: `Switch acknowledged: created ${id} while ${prior.id} was actionable; ${prior.id} keeps its state — resume with task next ${prior.id}`, recordedAt });
@@ -172,13 +176,16 @@ if (group === "state" && action === "migrate") {
172
176
  } else if (group === "task" && action === "transition") {
173
177
  const mode = option(parsed, "--mode"); const reason = option(parsed, "--reason"); const source = option(parsed, "--source");
174
178
  if (mode !== "audited" || !reason?.trim() || !source?.trim()) throw new Error("task transition requires --mode audited, --reason, and --source");
175
- const from = state.tasks[id]?.phase; const target = option(parsed, "--to"); if (target === "done") requireFeaturePrerequisites(state.tasks[id]); const task = transitionTask(state, id, target);
179
+ const from = state.tasks[id]?.phase; const target = option(parsed, "--to"); if (target === "done") requireFeaturePrerequisites(state.tasks[id]);
180
+ if (target !== "done" && option(parsed, "--final-commit") !== undefined) throw new Error("--final-commit is only accepted with --to done");
181
+ const task = transitionTask(state, id, target);
182
+ if (target === "done") recordFinalCommit(task);
176
183
  task.evidence.push({ kind: "diagnostic", result: "pass", source, detail: `Audited transition ${from} → ${target}: ${reason}`, recordedAt: now() });
177
184
  persist([task]); respond(task);
178
185
  } else if (group === "task" && action === "archive") {
179
186
  const reason = option(parsed, "--reason"); const source = option(parsed, "--source");
180
187
  if (!reason?.trim() || !source?.trim()) throw new Error("task archive requires --reason and --source");
181
- requireFeaturePrerequisites(state.tasks[id]); const task = transitionTask(state, id, "done");
188
+ requireFeaturePrerequisites(state.tasks[id]); const task = transitionTask(state, id, "done"); recordFinalCommit(task);
182
189
  task.evidence.push({ kind: "diagnostic", result: "pass", source, detail: `Archive to done: ${reason}`, recordedAt: now() });
183
190
  persist([task], true); respond(task);
184
191
  } else if (group === "task" && action === "handoff") {
@@ -270,7 +277,8 @@ if (group === "state" && action === "migrate") {
270
277
  } else if (group === "gate" && action === "approve") {
271
278
  const gate = option(parsed, "--gate"); const source = option(parsed, "--source");
272
279
  if (!id || !gate || !source) throw new Error("gate approve requires active task id, --gate, and explicit --source");
273
- if (readLiveLease(root, id)) throw new Error(`Gate approval is unavailable while loop runner owns ${id}; wait for the runner to stop at the human gate`);
280
+ const runner = readLiveLease(root, id);
281
+ if (runner) throw new Error(`Gate approval is unavailable while loop runner owns ${id}; wait for the runner to stop at the human gate (task lease held by pid ${runner.pid}${runner.host ? ` on ${runner.host}` : ""}${runner.activity ? `, activity ${runner.activity}` : ""}${runner.expiresAt ? `, expires ${runner.expiresAt}` : ""}; inspect: node .agents/aidlc/scripts/loop-entry.mjs leases ${id})`);
274
282
  const outcome = approveAndAdvance(root, state, id, gate, source); persist([outcome.task]); emit({ task: outcome.task, idempotent: outcome.idempotent }, outcome.task);
275
283
  } else {
276
284
  throw new Error(usage);
@@ -11,5 +11,14 @@
11
11
  ## 🔗 Cross-service contracts
12
12
  - <contract or `none`>
13
13
 
14
+ ## 🔀 Parallel work partition
15
+ | item | owned paths | depends on | verification |
16
+ |------|-------------|------------|--------------|
17
+ | <item-id> | <paths it alone writes> | <item-ids or `none`> | <narrow command> |
18
+
19
+ - One owner per path among items that can run at the same time; overlapping items must be ordered with `dependsOn`.
20
+ - Shared surfaces (barrel/index files, shared config, cross-cutting docs, a single entry script) get exactly one owning item; others depend on it.
21
+ - Prefer file/module-level ownership over broad parent directories.
22
+
14
23
  ## ⚠️ Risks / edge cases
15
24
  - <risk and mitigation>
@@ -6,6 +6,10 @@
6
6
  ## 🗺️ Affected areas
7
7
  - `<area>` — <why> — spec: <indexed source or `none indexed`>
8
8
 
9
+ ## 🔀 Work boundaries
10
+ - `<work stream>` — <code areas/paths it touches> — <independent of / depends on>
11
+ - or `single stream` — <why the work cannot be split>
12
+
9
13
  ## 💭 Assumptions
10
14
  - <assumption>
11
15
 
@@ -6,4 +6,4 @@
6
6
  - <generated decision state>
7
7
 
8
8
  ## 🧩 Tasks (Review implementation — work items)
9
- - <generated task state>
9
+ - <generated task state — each line shows `owns: <paths> · after: <item-ids>`>
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: aidlc-plan
3
- description: "AI-DLC phase 2 (Approve plan): after intent is confirmed, run bounded discovery, then produce a detailed design, canonical decisions, and a per-submodule workplan. Triggers when the AI-DLC Orchestrator routes a task after requirements confirmation to planning. Ends at the executable Approve plan review for explicit user approval."
3
+ description: "AI-DLC phase 2 (Approve plan): after intent is confirmed, run bounded discovery, then produce a detailed design, canonical decisions, and a per-submodule workplan whose items own non-overlapping paths so parallel workers do not collide. Triggers when the AI-DLC Orchestrator routes a task after requirements confirmation to planning. Ends at the executable Approve plan review for explicit user approval."
4
4
  ---
5
5
 
6
6
  # AI-DLC · Plan (Approve plan) — adapter