@felixdotgo/aidlc-kit 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DEVELOPMENT.md +14 -0
- package/README.md +14 -6
- package/dist/assets/.agents/aidlc/docker/Dockerfile.claude +7 -0
- package/dist/assets/.agents/aidlc/docker/Dockerfile.codex +9 -0
- package/dist/assets/.agents/aidlc/docker/Dockerfile.opencode +8 -0
- package/dist/assets/.agents/aidlc/orchestrator.md +1 -1
- package/dist/assets/.agents/aidlc/phase-clarify.md +1 -1
- package/dist/assets/.agents/aidlc/phase-plan.md +2 -2
- package/dist/assets/.agents/aidlc/schemas/config.schema.json +2 -1
- package/dist/assets/.agents/aidlc/schemas/manifest.schema.json +1 -1
- package/dist/assets/.agents/aidlc/scripts/lib/context-runtime.mjs +5 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-authority.mjs +245 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-commit-message.mjs +45 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-daemon.mjs +232 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-drivers.mjs +21 -4
- package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet-cli.mjs +86 -28
- package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet-remote.mjs +7 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet-supervisor.mjs +22 -6
- package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet.mjs +46 -15
- package/dist/assets/.agents/aidlc/scripts/lib/loop-git.mjs +22 -9
- package/dist/assets/.agents/aidlc/scripts/lib/loop-intake.mjs +23 -5
- package/dist/assets/.agents/aidlc/scripts/lib/loop-local-workers.mjs +43 -15
- package/dist/assets/.agents/aidlc/scripts/lib/loop-runtime.mjs +82 -6
- package/dist/assets/.agents/aidlc/scripts/lib/loop-stream.mjs +60 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-supervisor-run-local.mjs +142 -5
- package/dist/assets/.agents/aidlc/scripts/lib/loop-supervisor.mjs +9 -1
- package/dist/assets/.agents/aidlc/scripts/lib/loop-worker-docker.mjs +31 -5
- package/dist/assets/.agents/aidlc/scripts/lib/loop-worker-image.mjs +64 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-worker-snapshot.mjs +3 -2
- package/dist/assets/.agents/aidlc/scripts/lib/store.mjs +59 -4
- package/dist/assets/.agents/aidlc/scripts/loop-entry.mjs +85 -73
- package/dist/assets/.agents/aidlc/scripts/loop-gate.mjs +19 -7
- package/dist/assets/.agents/aidlc/scripts/loop.mjs +2 -1
- package/dist/assets/.agents/aidlc/scripts/remote-state.mjs +2 -0
- package/dist/assets/.agents/aidlc/scripts/state.mjs +4 -0
- package/dist/assets/.agents/aidlc/templates/design.md +9 -0
- package/dist/assets/.agents/aidlc/templates/intent.md +4 -0
- package/dist/assets/.agents/aidlc/templates/workplan.md +1 -1
- package/dist/assets/.agents/skills/aidlc-plan/SKILL.md +1 -1
- package/dist/assets/ui/bulma.min.css +3 -0
- package/dist/assets/ui/dashboard.css +414 -0
- package/dist/services/aidlc-coordination/Dockerfile +11 -3
- package/dist/services/aidlc-coordination/compose.yaml +12 -0
- package/dist/services/aidlc-coordination/package-lock.json +3 -1
- package/dist/services/aidlc-coordination/package.json +9 -4
- package/dist/services/aidlc-coordination/src/lifecycle-core.mjs +59 -4
- package/dist/services/aidlc-coordination/src/repository.mjs +66 -0
- package/dist/services/aidlc-coordination/src/server.mjs +9 -3
- package/dist/services/aidlc-coordination/test/fleet.test.mjs +21 -0
- package/dist/services/aidlc-coordination/test/server-health-auth.test.mjs +67 -0
- package/dist/src/adapters.d.ts +0 -3
- package/dist/src/adapters.js +11 -6
- package/dist/src/cli/argv.d.ts +12 -0
- package/dist/src/cli/argv.js +62 -0
- package/dist/src/cli/commands.d.ts +8 -0
- package/dist/src/cli/commands.js +129 -0
- package/dist/src/cli/options.d.ts +25 -0
- package/dist/src/cli/options.js +32 -0
- package/dist/src/cli/prompt.d.ts +9 -0
- package/dist/src/cli/prompt.js +66 -0
- package/dist/src/cli.js +67 -217
- package/dist/src/context.js +2 -0
- package/dist/src/gate.d.ts +1 -0
- package/dist/src/gate.js +51 -37
- package/dist/src/installer.d.ts +19 -7
- package/dist/src/installer.js +111 -178
- package/dist/src/legacy.js +1 -0
- package/dist/src/managed-content.d.ts +7 -0
- package/dist/src/managed-content.js +15 -4
- package/dist/src/managed-planning.d.ts +65 -0
- package/dist/src/managed-planning.js +219 -0
- package/dist/src/mcp-setup.js +3 -1
- package/dist/src/model/config-contracts.d.ts +65 -0
- package/dist/src/model/config-contracts.js +1 -0
- package/dist/src/model/install-contracts.d.ts +29 -0
- package/dist/src/model/install-contracts.js +1 -0
- package/dist/src/model/lifecycle-enums.d.ts +13 -0
- package/dist/src/model/lifecycle-enums.js +1 -0
- package/dist/src/model/loop.d.ts +20 -0
- package/dist/src/model/loop.js +1 -0
- package/dist/src/model/manifest.d.ts +16 -0
- package/dist/src/model/manifest.js +1 -0
- package/dist/src/model/records.d.ts +138 -0
- package/dist/src/model/records.js +1 -0
- package/dist/src/model.d.ts +11 -264
- package/dist/src/model.js +1 -1
- package/dist/src/profiles.d.ts +8 -0
- package/dist/src/profiles.js +63 -8
- package/dist/src/project-migration.d.ts +1 -0
- package/dist/src/project-migration.js +7 -0
- package/dist/src/state/archive-record.d.ts +9 -0
- package/dist/src/state/archive-record.js +26 -0
- package/dist/src/state/commands.d.ts +18 -0
- package/dist/src/state/commands.js +27 -0
- package/dist/src/state/domain.d.ts +9 -0
- package/dist/src/state/domain.js +9 -0
- package/dist/src/state/guards.d.ts +7 -0
- package/dist/src/state/guards.js +17 -0
- package/dist/src/state/io.d.ts +10 -0
- package/dist/src/state/io.js +35 -0
- package/dist/src/state/items.d.ts +24 -0
- package/dist/src/state/items.js +147 -0
- package/dist/src/state/lessons.d.ts +7 -0
- package/dist/src/state/lessons.js +74 -0
- package/dist/src/state/lifecycle.d.ts +5 -0
- package/dist/src/state/lifecycle.js +78 -0
- package/dist/src/state/lock.d.ts +7 -0
- package/dist/src/state/lock.js +125 -0
- package/dist/src/state/memory.d.ts +8 -0
- package/dist/src/state/memory.js +100 -0
- package/dist/src/state/migrate.d.ts +17 -0
- package/dist/src/state/migrate.js +60 -0
- package/dist/src/state/partition.d.ts +15 -0
- package/dist/src/state/partition.js +90 -0
- package/dist/src/state/paths.d.ts +11 -0
- package/dist/src/state/paths.js +12 -0
- package/dist/src/state/render.d.ts +26 -0
- package/dist/src/state/render.js +32 -0
- package/dist/src/state/store.d.ts +4 -0
- package/dist/src/state/store.js +75 -0
- package/dist/src/state/transitions.d.ts +21 -0
- package/dist/src/state/transitions.js +95 -0
- package/dist/src/state/validate.d.ts +4 -0
- package/dist/src/state/validate.js +143 -0
- package/dist/src/state.d.ts +20 -117
- package/dist/src/state.js +20 -815
- package/dist/src/task-ui.d.ts +54 -0
- package/dist/src/task-ui.js +1570 -0
- package/dist/src/upgrade-board.d.ts +5 -0
- package/dist/src/upgrade-board.js +89 -0
- package/dist/src/upgrade.d.ts +3 -5
- package/dist/src/upgrade.js +174 -255
- package/dist/src/workflow.js +4 -4
- package/docs/README.md +6 -2
- package/docs/architecture.md +12 -8
- package/docs/command-reference.md +83 -19
- package/docs/configuration.md +30 -7
- package/docs/coordination-service.md +20 -5
- package/docs/design-decisions.md +83 -0
- package/docs/development.md +41 -2
- package/docs/loop-local-run.md +495 -0
- package/docs/loop-runbook.md +235 -0
- package/docs/operating-workflow.md +5 -2
- package/docs/testing-and-release.md +11 -7
- package/docs/troubleshooting.md +125 -0
- package/package.json +5 -2
- package/dist/src/ledger.d.ts +0 -22
- package/dist/src/ledger.js +0 -119
- package/dist/src/tracker.d.ts +0 -29
- package/dist/src/tracker.js +0 -41
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# Loop setup runbook
|
|
2
|
+
|
|
3
|
+
> [!WARNING]
|
|
4
|
+
> The loop is **experimental** and pre-1.0 (`0.x`): the `loop.*` fields, provider mappings, environment variables, and diagnostics can change incompatibly in any release before `1.0.0`. **Local mode is usable** on a single Linux host today; **fleet mode is still under active development** and is not yet stable.
|
|
5
|
+
|
|
6
|
+
This runbook walks through the three most common operational journeys for the end-to-end loop, in order:
|
|
7
|
+
|
|
8
|
+
1. [Enable the loop from `off`](#enable-the-loop-from-off)
|
|
9
|
+
2. [Run the loop in `local` mode](#local-mode)
|
|
10
|
+
3. [Run the loop across multiple machines (`fleet`)](#fleet-multi-machine)
|
|
11
|
+
|
|
12
|
+
It is a step-by-step guide, not a reference. For the full field, flag, and environment reference, see [Configuration](./configuration.md), [Command reference](./command-reference.md), and [AI-DLC Coordination service](./coordination-service.md); this runbook links to those pages for depth instead of repeating them.
|
|
13
|
+
|
|
14
|
+
## The loop in one paragraph
|
|
15
|
+
|
|
16
|
+
The loop drives a task end-to-end. A **lead** agent runs the clarify, plan, and wrap phases, while **workers** execute build items. `local` mode uses the project's canonical local state (`state.mjs`) with a host-executed lead and Docker-isolated workers on one Linux host. `fleet` mode delegates authority to a coordination service and can spread workers across machines. Both modes use the same loop daemon, intake, and dashboard; only the authority differs. Selecting a mode only configures the loop; nothing runs until you call `aidlc-kit loop start` explicitly.
|
|
17
|
+
|
|
18
|
+
## Enable the loop from `off`
|
|
19
|
+
|
|
20
|
+
`loop.mode` defaults to `off` (an absent `loop` field also means `off`). Enabling it means setting `loop.mode` to `local` or `fleet` and providing `loop.agents` with one `lead` and one or more named `workers`, each mapped to `codex`, `claude`, or `opencode`:
|
|
21
|
+
|
|
22
|
+
```json
|
|
23
|
+
{
|
|
24
|
+
"loop": {
|
|
25
|
+
"mode": "local",
|
|
26
|
+
"agents": { "lead": "codex", "workers": { "worker-a": "codex", "worker-b": "opencode" } }
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Step 1 — Choose how to write the config
|
|
32
|
+
|
|
33
|
+
Pick one of three ways to set `loop.mode` and `loop.agents`:
|
|
34
|
+
|
|
35
|
+
- **Edit `.agents/config.json` directly.** This is the project-owned config; the fields are described in [Configuration](./configuration.md#optional-loop-configuration).
|
|
36
|
+
- **Use `init`** for a new install:
|
|
37
|
+
```sh
|
|
38
|
+
npx --package @felixdotgo/aidlc-kit@0.2.0 aidlc-kit init . \
|
|
39
|
+
--loop-mode local --loop-lead codex --loop-workers a=codex,b=opencode
|
|
40
|
+
```
|
|
41
|
+
- **Use the human-only `upgrade`** for an existing install, which preserves the existing config when you omit the loop flags and lets an authorised human review the change before applying:
|
|
42
|
+
```sh
|
|
43
|
+
npx --package @felixdotgo/aidlc-kit@0.2.0 aidlc-kit upgrade . \
|
|
44
|
+
--loop-mode local --loop-lead codex --loop-workers a=codex,b=opencode --dry-run
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Step 2 — Verify the loop is enabled
|
|
48
|
+
|
|
49
|
+
`aidlc-kit loop start` fails closed unless the config is complete:
|
|
50
|
+
|
|
51
|
+
- `loop.mode` must be `local` or `fleet`.
|
|
52
|
+
- `loop.agents` must have a valid `lead` provider and at least one valid `worker` mapping (`Loop requires a configured lead and worker providers`).
|
|
53
|
+
- The requested `--mode` must match the configured `loop.mode` (`Requested mode does not match configured authority`).
|
|
54
|
+
|
|
55
|
+
These checks run on any `aidlc-kit loop` invocation before anything starts, so a wrong configuration fails closed with a clear message rather than silently running. A valid configuration is only a selection: it does not install or start a service, authorise an upgrade, or grant gate approval. Run `aidlc-kit loop start` to actually start the loop, and keep provider credentials out of the config object.
|
|
56
|
+
|
|
57
|
+
## Local mode
|
|
58
|
+
|
|
59
|
+
Local mode is the single-machine journey. The lead runs on the host (Codex, Claude Code, or OpenCode) for clarify/plan/wrap, and build items fan out to Docker-isolated workers. It uses the canonical local state and requires local authority; see [Command reference](./command-reference.md#loop) for the authority boundary.
|
|
60
|
+
|
|
61
|
+
### Prerequisites
|
|
62
|
+
|
|
63
|
+
| Requirement | Notes |
|
|
64
|
+
| --- | --- |
|
|
65
|
+
| Linux host | Loop/fleet features require Docker and a POSIX shell; `loop-local-workers.mjs` rejects non-Linux hosts. |
|
|
66
|
+
| Docker engine | The worker batch preflights `docker info` and the worker image. |
|
|
67
|
+
| Non-root host user | Docker workers require a non-root uid/gid. |
|
|
68
|
+
| Worker Docker image | Auto-built from the packaged Dockerfile on first build dispatch (`aidlc-kit loop worker-image`); set `AIDLC_WORKER_DOCKER_IMAGE` only to override the per-provider default. |
|
|
69
|
+
| Provider credentials | Exported in the environment; never written into `.agents/config.json`. |
|
|
70
|
+
|
|
71
|
+
### Step 1 — Configure `local`
|
|
72
|
+
|
|
73
|
+
Set `loop.mode` to `local` with a lead and workers (see [Enable the loop from `off`](#enable-the-loop-from-off)):
|
|
74
|
+
|
|
75
|
+
```json
|
|
76
|
+
{
|
|
77
|
+
"loop": {
|
|
78
|
+
"mode": "local",
|
|
79
|
+
"agents": { "lead": "codex", "workers": { "worker-a": "codex", "worker-b": "opencode" } }
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Step 2 — Set the environment
|
|
85
|
+
|
|
86
|
+
| Variable | Required | Purpose |
|
|
87
|
+
| --- | --- | --- |
|
|
88
|
+
| `AIDLC_WORKER_DOCKER_IMAGE` | no | Optional single-image override. The per-provider default `aidlc-worker-<provider>:local` is auto-built and recorded in `loop.workerImage`. |
|
|
89
|
+
| `AIDLC_OPENCODE_MODEL` | if any provider is `opencode` | `provider/model[#variant]` (for example `opencode-go/gpt-6-luna`). The turn fails closed without a valid value. |
|
|
90
|
+
| `OPENAI_API_KEY` | if a provider is `codex` | Codex worker credential. |
|
|
91
|
+
| `ANTHROPIC_API_KEY` | if a provider is `claude` | Claude Code worker credential. |
|
|
92
|
+
| `OPENCODE_API_KEY` | if a provider is `opencode` | OpenCode worker credential. The key follows the model provider: `openai/` → `OPENAI_API_KEY`, `anthropic/` → `ANTHROPIC_API_KEY`, otherwise `OPENCODE_API_KEY`. |
|
|
93
|
+
| `AIDLC_LOCAL_WORKTREES_ROOT` | no | Root for worker worktrees; defaults to a system temp directory. It must be outside the integration checkout. |
|
|
94
|
+
| `AIDLC_LOOP_EVENT_LOG` | no (see note) | Path to a JSONL event-log sidecar that feeds the web dashboard live log. The `aidlc-kit ui` **New task** action (when it starts the daemon) sets it to `.agents/data/loop/ui-events.jsonl` automatically; a **manually started** daemon must set it to the same path or the dashboard live log stays empty. |
|
|
95
|
+
|
|
96
|
+
Docker workers (local and fleet) authenticate OpenCode from the environment and redirect OpenCode's XDG data/config roots into the worker's ephemeral HOME. Only the **isolated lead** container does not support OpenCode yet (`OpenCode workers require file-based credentials and are not available in isolated containers yet`); the daemon runs the lead on the host, so an OpenCode lead works there.
|
|
97
|
+
|
|
98
|
+
### Step 3 — Build the worker image (usually automatic)
|
|
99
|
+
|
|
100
|
+
Build the worker image for each provider you use, or let the supervisor auto-build it on the first build dispatch:
|
|
101
|
+
|
|
102
|
+
```sh
|
|
103
|
+
npx @felixdotgo/aidlc-kit loop worker-image --provider codex # → aidlc-worker-codex:local
|
|
104
|
+
npx @felixdotgo/aidlc-kit loop worker-image --provider opencode # → aidlc-worker-opencode:local
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
The command records the tag into `loop.workerImage`; the local supervisor reuses it and auto-builds any missing per-provider image before dispatch. The batch preflight still inspects the image and runs each provider CLI with `--version` inside the container, so a missing or mismatched image fails before any worker starts. See [Testing and release](./testing-and-release.md#loop-docker-and-postgresql-verification) for the fixture build pattern used by the test suite.
|
|
108
|
+
|
|
109
|
+
### Step 4 — Start the daemon, add tasks, and watch
|
|
110
|
+
|
|
111
|
+
`start` launches the singleton daemon (one per project) and reads no requirement. Add tasks with the web dashboard **New task** or loop intake, which reads the requirement from stdin (capped at 8192 bytes) and never from an argument:
|
|
112
|
+
|
|
113
|
+
```sh
|
|
114
|
+
npx --package @felixdotgo/aidlc-kit@0.2.0 aidlc-kit loop start
|
|
115
|
+
echo "Add PDF report export" | npx --package @felixdotgo/aidlc-kit@0.2.0 aidlc-kit loop intake
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
The daemon runs only loop-owned tasks (created through intake), one bounded run per task in turn, and keeps polling after a task reaches a gate or completes. Stop it with `Ctrl-C`: the first signal stops after the current turn, the second cancels the turn, the third exits. A stopped run is not a pause; the task continues on the next `start`.
|
|
119
|
+
|
|
120
|
+
`status` prints the canonical task and its derived next action:
|
|
121
|
+
|
|
122
|
+
```sh
|
|
123
|
+
npx --package @felixdotgo/aidlc-kit@0.2.0 aidlc-kit loop status --task-id <task-id>
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
There is no `loop resume`. After a gate approval the daemon continues the task on its next poll. A **paused** task is not actionable: fix the cause, then press **resume** on the dashboard or run `node .agents/aidlc/scripts/state.mjs task status <task-id> --status active`.
|
|
127
|
+
|
|
128
|
+
As an alternative to typing these commands repeatedly, `aidlc-kit ui` starts a web dashboard to view task status, add tasks, resume paused tasks, and approve gates; it prints a login URL that signs the browser in. See [Command reference](./command-reference.md#ui) and [Loop local run](./loop-local-run.md#57-web-dashboard-aidlc-kit-ui).
|
|
129
|
+
|
|
130
|
+
### Step 5 — Approve gates from the trusted host terminal
|
|
131
|
+
|
|
132
|
+
Gate approval happens only through a human — the trusted host terminal or the signed-in dashboard — never from a lead or worker container, and never via piped input:
|
|
133
|
+
|
|
134
|
+
```sh
|
|
135
|
+
node .agents/aidlc/scripts/loop-gate.mjs <task-id>
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
The command prints the exact review (task, phase, gate, artifact digests, and — at `G2_codereview` — the integration commit), then prompts `Type approve to approve this exact review:`. Type `approve` to approve that exact review. If the review changes while you type, it fails closed and asks you to inspect the new review.
|
|
139
|
+
|
|
140
|
+
## Fleet (multi-machine)
|
|
141
|
+
|
|
142
|
+
Fleet mode spreads the loop across machines through the coordination service. It uses `remote-state.mjs` instead of the local `state.mjs`, and the remote workspace is canonical. Deployment, storage (SQLite for one host, PostgreSQL for multiple hosts), and credentials are covered in [Coordination service](./coordination-service.md); this section keeps the loop-specific steps and links there for the rest.
|
|
143
|
+
|
|
144
|
+
### Prerequisites
|
|
145
|
+
|
|
146
|
+
- A deployed coordination service with PostgreSQL for a multi-host topology, and a shared Git remote — see [Coordination service](./coordination-service.md#multi-machine-loop-deployment).
|
|
147
|
+
- `loop.mode` set to `fleet` with a lead and workers matching your worker machines.
|
|
148
|
+
- Three distinct credential classes (`MCP_FLEET_LEAD_TOKEN`, `MCP_FLEET_REVIEWER_TOKEN`, and `MCP_FLEET_WORKER_TOKENS`) configured on the service.
|
|
149
|
+
|
|
150
|
+
### Step 1 — Configure `fleet`
|
|
151
|
+
|
|
152
|
+
```json
|
|
153
|
+
{
|
|
154
|
+
"loop": {
|
|
155
|
+
"mode": "fleet",
|
|
156
|
+
"agents": { "lead": "codex", "workers": { "worker-a": "codex", "worker-b": "opencode" } }
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Step 2 — Set lead-machine environment
|
|
162
|
+
|
|
163
|
+
| Variable | Required | Purpose |
|
|
164
|
+
| --- | --- | --- |
|
|
165
|
+
| `AIDLC_FLEET_URL` | yes | The full HTTPS `/mcp` URL of the service. |
|
|
166
|
+
| `AIDLC_FLEET_WORKSPACE` | yes | The remote workspace id. |
|
|
167
|
+
| `AIDLC_FLEET_TOKEN` | yes | The lead credential. |
|
|
168
|
+
| `AIDLC_FLEET_INTEGRATION_BRANCH` | yes | The shared integration branch; it must exist on the remote. |
|
|
169
|
+
| `AIDLC_FLEET_PROJECT_ROOT` | no | Set automatically by `aidlc-kit loop`; required only for the direct `remote-state.mjs` / fleet-runner client. When set explicitly it must match the loop root. |
|
|
170
|
+
| `AIDLC_FLEET_WORKER_IDS` / `AIDLC_FLEET_WORKER_PROVIDERS` | no | Derived from `loop.agents.workers`; when set explicitly they must match it. |
|
|
171
|
+
| `AIDLC_FLEET_GIT_REMOTE` | no | Git remote name; defaults to `origin`. |
|
|
172
|
+
| `AIDLC_FLEET_MAX_TURNS`, `AIDLC_FLEET_TURN_TIMEOUT_MS`, `AIDLC_FLEET_MAX_STALL_TURNS`, `AIDLC_FLEET_POLL_MS` | no | Override `loop.limits` and `loop.pollMs`, which are the defaults. |
|
|
173
|
+
|
|
174
|
+
The lead agent comes from `loop.agents.lead`. Do not export `AIDLC_FLEET_REVIEWER_TOKEN` in the lead shell; the daemon strips it from every lead and worker process anyway.
|
|
175
|
+
|
|
176
|
+
### Step 3 — Start the daemon and add tasks
|
|
177
|
+
|
|
178
|
+
`aidlc-kit loop start --mode fleet` preflights the service (`fleet_get`) and the remote integration branch, then starts the same daemon as local mode, failing closed instead of falling back to local:
|
|
179
|
+
|
|
180
|
+
```sh
|
|
181
|
+
npx --package @felixdotgo/aidlc-kit@0.2.0 aidlc-kit loop start --mode fleet
|
|
182
|
+
echo "Add PDF report export" | npx --package @felixdotgo/aidlc-kit@0.2.0 aidlc-kit loop intake --mode fleet
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Intake creates the remote task under a short lead claim and writes `.agents/data/tasks/<task-id>/loop-intake.json` with `authority: "fleet"` on this lead machine. Only those tasks are fleet loop-owned; a remote task created any other way is never picked up. For each actionable task the daemon runs the fleet supervisor in a bounded mode: it claims the supervisor lease, runs fenced lead turns and worker dispatch through the coordinator, publishes the gate snapshot with its checkpoint, then returns and releases the lease. After the reviewer approves, the next poll re-claims the task and continues. An unreachable service shows up as `daemon_poll_error` and does not stop the daemon. Shutdown, backoff, and `status` behave as in local mode; there is no `loop resume` (re-activate a paused task with the dashboard **resume**).
|
|
186
|
+
|
|
187
|
+
The underlying fleet runner is also available directly from the service deployment (the installed `.agents/aidlc-coordination/`, or `dist/services/aidlc-coordination/` from a source checkout):
|
|
188
|
+
|
|
189
|
+
```sh
|
|
190
|
+
node .agents/aidlc-coordination/src/fleet-run.mjs supervisor <task-id>
|
|
191
|
+
node .agents/aidlc-coordination/src/fleet-run.mjs coordinator <task-id> --once
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Run directly, `supervisor` waits at each gate for the reviewer approval and resumes automatically; it never approves a gate.
|
|
195
|
+
|
|
196
|
+
### Step 4 — Run a worker on each worker machine
|
|
197
|
+
|
|
198
|
+
On each worker machine, point at the same `AIDLC_FLEET_URL`, workspace, and `AIDLC_FLEET_PROJECT_ROOT` (a checkout of the project), use that worker's own `AIDLC_FLEET_TOKEN`, then set:
|
|
199
|
+
|
|
200
|
+
| Variable | Required | Purpose |
|
|
201
|
+
| --- | --- | --- |
|
|
202
|
+
| `AIDLC_FLEET_WORKER_ID` | yes | This machine's worker id (must be a configured worker). |
|
|
203
|
+
| `AIDLC_FLEET_WORKTREES_ROOT` | yes | Root for this worker's isolated worktrees. |
|
|
204
|
+
| `AIDLC_FLEET_WORKER_AGENT` | recommended | This worker's provider (`codex`, `claude`, or `opencode`); falls back to this worker's entry in `AIDLC_FLEET_WORKER_PROVIDERS`, then to `AIDLC_FLEET_AGENT`. A worker with a provider rejects jobs for another provider. |
|
|
205
|
+
| `AIDLC_WORKER_DOCKER_IMAGE` | no | Image override. Otherwise the image is `loop.workerImage[provider]` from the project config or `aidlc-worker-<provider>:local`, auto-built from the packaged Dockerfile when missing. Without a known provider this variable is required. |
|
|
206
|
+
| `AIDLC_OPENCODE_MODEL` | if the provider is `opencode` | `provider/model[#variant]`. |
|
|
207
|
+
| worker credential | yes | `OPENAI_API_KEY` (codex), `ANTHROPIC_API_KEY` (claude), or for opencode the key matching the model prefix (`openai/`, `anthropic/`, otherwise `OPENCODE_API_KEY`). |
|
|
208
|
+
|
|
209
|
+
Run one bounded assignment per invocation, or omit `--once` to keep polling:
|
|
210
|
+
|
|
211
|
+
```sh
|
|
212
|
+
node .agents/aidlc-coordination/src/fleet-run.mjs worker --once
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
The worker resolves the image to its immutable `sha256:` id and preflights Docker and the provider CLI before claiming work.
|
|
216
|
+
|
|
217
|
+
### Step 5 — Approve gates as the reviewer
|
|
218
|
+
|
|
219
|
+
Use the reviewer credential in a connected reviewer conversation:
|
|
220
|
+
|
|
221
|
+
```sh
|
|
222
|
+
node .agents/aidlc/scripts/remote-state.mjs gate inbox
|
|
223
|
+
node .agents/aidlc/scripts/remote-state.mjs gate approve <task-id> --snapshot <snapshot-id> --source "<explicit user approval>"
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
The approve command checks that the snapshot is still current and uses the reviewer-only service operation. Run it only after the human explicitly approves the packet. Alternatively, start `aidlc-kit ui` with the fleet environment from Step 2 plus the reviewer credential (`AIDLC_FLEET_REVIEWER_TOKEN` or `--reviewer-token-file`): the dashboard then approves the current snapshot itself, passing the credential only to that one approve process. Without a reviewer credential the dashboard shows these commands instead and its approve API returns `403`.
|
|
227
|
+
|
|
228
|
+
## Authority boundary
|
|
229
|
+
|
|
230
|
+
`local` mode uses the canonical local state and requires local authority; `fleet` mode uses `remote-state.mjs`. When `AIDLC_FLEET_URL` is set, local lifecycle, context, gate, assignment, and standalone loop commands reject use so an unattended lead cannot accidentally mutate a second local authority. Unset `AIDLC_FLEET_URL` only when deliberately returning to a local run. See [Coordination service](./coordination-service.md#state-and-synchronization-contract) for the remote canonical-state contract.
|
|
231
|
+
|
|
232
|
+
## Where to go next
|
|
233
|
+
|
|
234
|
+
- [Operating the workflow](./operating-workflow.md) — gates, approvals, handoffs, and task ownership.
|
|
235
|
+
- [Troubleshooting](./troubleshooting.md) — symptom/error-code recovery, including fleet authority and Docker worker image diagnostics.
|
|
@@ -116,8 +116,11 @@ See [Configuration](./configuration.md) for project customisation and [Command r
|
|
|
116
116
|
|
|
117
117
|
## Optional end-to-end loop
|
|
118
118
|
|
|
119
|
-
|
|
119
|
+
> [!WARNING]
|
|
120
|
+
> **Local loop mode is usable** today; **fleet mode and the coordination service are still under active development**. The loop is **experimental** — because the package is still pre-1.0 (`0.x`), its commands, configuration, and recovery behaviour can change incompatibly in any release before `1.0.0`.
|
|
121
|
+
|
|
122
|
+
When the project enables `loop.mode`, `aidlc-kit loop start` accepts a requirement once (interactive prompt or stdin), creates exactly one task under the configured authority, and runs the loop to the first gate or to completion. `loop status` reports the canonical task and gate checkpoint; `loop resume` continues after an interruption. The intake is idempotent: re-running `start` with the same task ID and requirement reuses the existing task, and it refuses to create a second task while another one is actionable.
|
|
120
123
|
|
|
121
124
|
The loop never approves a gate itself. At each gate it publishes the review packet and waits; in local mode approval happens only through the trusted host terminal command, and in fleet mode only through the reviewer credential against the published snapshot and revision. After a real approval the running supervisor re-reads the canonical state and checkpoint, re-acquires its fenced lease, and resumes automatically. If the supervisor is not running at approval time, restart it and it reconciles from the checkpoint; a consumed, stale, or mismatched checkpoint stops for manual reconciliation instead of guessing. Restarting never resumes a paused or handed-off task, an outdated snapshot, or an uncertain Git result — those require the documented recovery actions.
|
|
122
125
|
|
|
123
|
-
The supervisor runs in the foreground or under a process supervisor you choose; there is no background daemon. Workers execute in Docker with their own scoped credentials and never inherit host access. Host-executed lead turns support `codex`, `claude`, or `opencode`; container-isolated lead turns
|
|
126
|
+
The supervisor runs in the foreground or under a process supervisor you choose; there is no background daemon. Workers execute in Docker with their own scoped credentials and never inherit host access. Host-executed lead turns support `codex`, `claude`, or `opencode`; container-isolated lead turns and fleet worker containers reject `opencode` because it requires file-based credentials, returning `OpenCode workers require file-based credentials and are not available in isolated containers yet` instead of falling back to the host. Local Docker worker batches accept `opencode` via environment credentials with OpenCode's XDG roots redirected into the worker's ephemeral HOME. When the loop uses `opencode`, set `AIDLC_OPENCODE_MODEL` to a `provider/model[#variant]` value. See [Command reference](./command-reference.md) for the loop commands and environment, and [Coordination service](./coordination-service.md) for fleet deployment, backends, and recovery.
|
|
@@ -16,6 +16,10 @@ npm test
|
|
|
16
16
|
|
|
17
17
|
If a test depends on child processes and a sandbox returns `EPERM`, rerun it in an appropriate local environment before deciding that the product failed.
|
|
18
18
|
|
|
19
|
+
## Lifecycle core sync
|
|
20
|
+
|
|
21
|
+
The TypeScript core (`src/state.ts`) and the dependency-free installed/runtime core (`assets/aidlc/scripts/lib/store.mjs`, mirrored to `services/aidlc-coordination/src/lifecycle-core.mjs`) must stay byte-identical. After editing `assets/aidlc/scripts/lib/store.mjs`, run `npm run sync-core` to copy it into the coordination service, then commit both files together. The byte-identity conformance test (`test/unit/lifecycle-core-conformance.test.ts`) enforces this link.
|
|
22
|
+
|
|
19
23
|
## Focused tests
|
|
20
24
|
|
|
21
25
|
Build first, then invoke a compiled test file directly:
|
|
@@ -77,7 +81,7 @@ AIDLC_TEST_PROJECT="$(mktemp -d /tmp/aidlc-project.XXXXXX)"
|
|
|
77
81
|
|
|
78
82
|
npm run build
|
|
79
83
|
npm pack --pack-destination "$AIDLC_PACK_DIR"
|
|
80
|
-
tar -xzf "$AIDLC_PACK_DIR/felixdotgo-aidlc-kit-0.0.
|
|
84
|
+
tar -xzf "$AIDLC_PACK_DIR/felixdotgo-aidlc-kit-0.2.0.tgz" -C "$AIDLC_EXTRACT_DIR"
|
|
81
85
|
node "$AIDLC_EXTRACT_DIR/package/dist/src/cli.js" init "$AIDLC_TEST_PROJECT" --agent codex --dry-run
|
|
82
86
|
node "$AIDLC_EXTRACT_DIR/package/dist/src/cli.js" init "$AIDLC_TEST_PROJECT" --agent codex --yes
|
|
83
87
|
node "$AIDLC_EXTRACT_DIR/package/dist/src/cli.js" status "$AIDLC_TEST_PROJECT"
|
|
@@ -100,7 +104,7 @@ node dist/dev/evaluator/cli.js run --runner local-simulated
|
|
|
100
104
|
|
|
101
105
|
Runners exchange JSON, and reports capture transport, transcript, normalised events, usage, and diagnostics. Real Codex, Claude Code, or OpenCode runs can consume credentials and model budget; they are not required for ordinary development verification.
|
|
102
106
|
|
|
103
|
-
The pinned `opencode-
|
|
107
|
+
The pinned `opencode-beam` runner uses adapter `opencode` with model `opencode-go/beam-free`, version `opencode-2.2.0`, protocol version `2`, real evidence, and a 300000 ms timeout; the `economy` category is pinned to the lighter `opencode-go/beam-free#medium` variant so heavier turns stay inside that timeout. It runs `opencode run --format json --standalone --auto` once per turn, resumes with `--session`, and writes `opencode.json` containing `{"mcp": {}}` into each scenario workspace so project MCP servers do not leak into the evaluation. OpenCode resolves its project directory from the inherited `PWD`, not the spawned process working directory, so the runner and the loop driver pin `PWD` to the workspace; without that pin, commands would silently run in the invoking repository. OpenCode has no environment-credential mode, so the runner reuses the local Console authentication; the residual risk is that user plugins or agents can still load. That isolation is workspace configuration, not a security boundary.
|
|
104
108
|
|
|
105
109
|
The release-eligible `aidlc-official-agentic` suite is version `2.5.0`. Its `supported-adapters` scenario asks the agent to explain that the workflow supports Codex, Claude Code, and OpenCode without executing remote code or shell strings. The suite also includes `build-multi-item-continuation`, which starts with three canonical build items and requires one agent run to complete all of them, record verification/review evidence, and stop only at a ready G2. This complements unit guards by exercising the model-facing continuation contract.
|
|
106
110
|
|
|
@@ -126,17 +130,17 @@ Publishing, tagging, committing, and pushing are separate human-authorised actio
|
|
|
126
130
|
|
|
127
131
|
## First npm publication
|
|
128
132
|
|
|
129
|
-
The first release is `@felixdotgo/aidlc-kit@0.
|
|
133
|
+
The first release is `@felixdotgo/aidlc-kit@0.1.1`, with public access and the `latest` dist-tag. Complete the release checks and the project's human review gates before publishing.
|
|
130
134
|
|
|
131
135
|
From the repository root, sign in to an npm account with permission to publish to the `@felixdotgo` scope and confirm the active account:
|
|
132
136
|
|
|
133
137
|
```sh
|
|
134
138
|
npm login --registry https://registry.npmjs.org
|
|
135
139
|
npm whoami --registry https://registry.npmjs.org
|
|
136
|
-
npm view @felixdotgo/aidlc-kit@0.
|
|
140
|
+
npm view @felixdotgo/aidlc-kit@0.1.1 version --registry https://registry.npmjs.org
|
|
137
141
|
```
|
|
138
142
|
|
|
139
|
-
An unpublished package normally returns `404` from `npm view`; that response does not establish scope ownership or publish permission. If `0.
|
|
143
|
+
An unpublished package normally returns `404` from `npm view`; that response does not establish scope ownership or publish permission. If `0.1.1` already exists, stop: npm versions cannot be overwritten. If `npm whoami` returns `401`, sign in again before publishing.
|
|
140
144
|
|
|
141
145
|
After inspecting the local tarball and documentation, publish from the repository root:
|
|
142
146
|
|
|
@@ -149,8 +153,8 @@ npm publish --access public --tag latest --registry https://registry.npmjs.org
|
|
|
149
153
|
Confirm the published version, dist-tag and artifact metadata:
|
|
150
154
|
|
|
151
155
|
```sh
|
|
152
|
-
npm view @felixdotgo/aidlc-kit@0.
|
|
156
|
+
npm view @felixdotgo/aidlc-kit@0.1.1 version dist.tarball dist.integrity --json --registry https://registry.npmjs.org
|
|
153
157
|
npm view @felixdotgo/aidlc-kit dist-tags --json --registry https://registry.npmjs.org
|
|
154
158
|
```
|
|
155
159
|
|
|
156
|
-
Expect version `0.
|
|
160
|
+
Expect version `0.1.1`, a tarball URL and integrity value, and `latest` pointing to `0.1.1`. Git tags, GitHub releases, commits, and pushes require their own delivery authorisation.
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Troubleshooting
|
|
2
|
+
|
|
3
|
+
Symptom- and error-code-oriented recovery guide for the AI-DLC workflow. Read [Operating the workflow](./operating-workflow.md) for the lifecycle model and [Command reference](./command-reference.md) for flag details.
|
|
4
|
+
|
|
5
|
+
## State lock held or times out
|
|
6
|
+
|
|
7
|
+
- Lock files live at `.agents/data/state/.aidlc-state.lock` and its companion `.agents/data/state/.aidlc-state.lock.guard`.
|
|
8
|
+
- Defaults: acquisition times out after **5 seconds** (`timeoutMs` 5000) and treats a lock as stale only when it is **30 seconds** old (`staleMs` 30000) **and** its owning PID is no longer alive.
|
|
9
|
+
- Contention under concurrent agents is normal; every mutation acquires the lock, including read-only `task list` and `task show`. Wait or serialize callers rather than deleting a live lock.
|
|
10
|
+
- A dead PID lets the lock be reclaimed automatically. Do not delete a lock whose PID is still alive — that corrupts in-flight state.
|
|
11
|
+
- Partially written or corrupt lock records are now reclaimed automatically instead of wedging mutations (recent release note). The reclamation is best-effort: if it fails, inspect the file before retrying.
|
|
12
|
+
- When a lock is corrupt, the owning process may be unable to release it; remove only an unreadable/partial file after confirming no live process holds it.
|
|
13
|
+
|
|
14
|
+
## Corrupt state or assignment files
|
|
15
|
+
|
|
16
|
+
- Corrupt local assignment files surface as `Corrupt local assignments for <task-id>`; corrupt loop/lease records surface as `Corrupt loop lease` / `Corrupt loop run record`. State JSON problems surface as `<label> is corrupted or unreadable: ...`.
|
|
17
|
+
- Restore from `.agents/data/state/backups/upgrade-<timestamp>` created by `upgrade`/migration, or from git. These backups are the audit trail for managed writes.
|
|
18
|
+
- `assignment-store` also guards against a corrupt assignment lock file (`Corrupt assignment lock for <task-id>`); inspect it before retrying rather than blindly deleting.
|
|
19
|
+
- Never hand-edit canonical state to "fix" corruption; restore from backup or git and re-run `state.mjs state migrate` if schema-versioned.
|
|
20
|
+
|
|
21
|
+
## `LESSON_INDEX_STALE` warning
|
|
22
|
+
|
|
23
|
+
- A mutation committed but the derived lesson-index rebuild failed. The mutation stays committed and the exit code stays `0`.
|
|
24
|
+
- Run `node .agents/aidlc/scripts/state.mjs lesson rebuild` (local authority required; throws `Fleet authority is configured` if `AIDLC_FLEET_URL` is set — use `remote-state.mjs`).
|
|
25
|
+
|
|
26
|
+
## `RENDER_FAILED` warning
|
|
27
|
+
|
|
28
|
+
- A mutation committed but the workplan render failed.
|
|
29
|
+
- Run `node .agents/aidlc/scripts/render.mjs <task-id>` (or `--all`).
|
|
30
|
+
|
|
31
|
+
## Continuation guard exit code 2 (`CONTINUATION_REQUIRED`)
|
|
32
|
+
|
|
33
|
+
- `task-next.mjs <task-id> --require-stop` exits `2` when `continuation.code = CONTINUATION_REQUIRED`. This is an expected pause, not a failure.
|
|
34
|
+
- Execute `continuation.command` instead of replying. Do not treat exit code 2 as an error.
|
|
35
|
+
- If a premature final response already slipped out, recover in the very next turn by running `task next <task-id>` and executing its returned command. Never re-approve a gate to catch up.
|
|
36
|
+
|
|
37
|
+
## Gate-check structural errors
|
|
38
|
+
|
|
39
|
+
`gate-check.mjs` returns diagnostics; fix the underlying artifact or record, never skip the check. Codes:
|
|
40
|
+
|
|
41
|
+
- `INTENT_HEADING` — intent artifact missing one of the five required headings: `## 📋 Problem`, `## 🗺️ Affected areas`, `## 💭 Assumptions`, `## ❓ Open questions`, `## 🎯 Scope`.
|
|
42
|
+
- `DESIGN_HEADING` — design artifact missing one of the four required headings: `## 🧩 Solution per affected area`, `## 📌 Spec traceability`, `## 🔗 Cross-service contracts`, `## ⚠️ Risks / edge cases`.
|
|
43
|
+
- `UNRESOLVED_DECISION` — a decision is still `unresolved`; resolve every decision before presenting G1.
|
|
44
|
+
- `TASKS_OPEN` — build tasks remain open (`todo`/`in_progress`).
|
|
45
|
+
- `VERIFY_EVIDENCE` — latest post-G1 verification evidence must pass for every affected area.
|
|
46
|
+
- `REVIEW_EVIDENCE` — latest post-G1 code-review evidence must pass.
|
|
47
|
+
- `REPAIR_BOUND` — repair bound exhausted (3 failed verify cycles per area, or 2 failed review passes); record a durable handoff instead of presenting G2.
|
|
48
|
+
- `TASK_UNKNOWN` — unknown task id.
|
|
49
|
+
- `GATE_STATE` — task is at a different gate than requested.
|
|
50
|
+
- `TASK_TERMINAL` — terminal task cannot approve a gate.
|
|
51
|
+
- `TASK_HANDOFF` — task has an unresolved handoff.
|
|
52
|
+
- `TASK_ID` — task key and id differ.
|
|
53
|
+
- `TASK_FIELDS` — task title or affected areas missing.
|
|
54
|
+
- `PARTITION_SCOPE` / `PARTITION_OVERLAP` — Approve plan parallel work partition; see [below](#parallel-work-partition-partition_overlap--partition_scope).
|
|
55
|
+
- `GATE_OK` — informational; no `ERROR` diagnostics (warnings may still be listed).
|
|
56
|
+
|
|
57
|
+
## Parallel work partition (`PARTITION_OVERLAP` / `PARTITION_SCOPE`)
|
|
58
|
+
|
|
59
|
+
The Approve plan check (`gate-check.mjs <task-id>`) inspects build items so parallel workers never edit the same files:
|
|
60
|
+
|
|
61
|
+
- `PARTITION_SCOPE` — an open item has no owned paths. Give it a scope: `node .agents/aidlc/scripts/state.mjs task item <task-id> <item-id> --status todo --label "<label>" --owns src/a,docs/b`.
|
|
62
|
+
- `PARTITION_OVERLAP` — two items that can run concurrently (no direct or transitive `dependsOn` between them) own overlapping paths. Overlap is per path segment: `src` overlaps `src/a.ts`, but not `srcx`. Split the paths, merge the items, or order them with `--depends-on <item-id>`.
|
|
63
|
+
- Severity follows the loop: `ERROR` when `loop.mode` is `local` or `fleet` (or inside a fleet-enabled coordination service), so the gate cannot be presented; `WARN` when the loop is `off`, and the gate still passes with `GATE_OK`.
|
|
64
|
+
- The rendered workplan shows each item's scope as `owns: … · after: …`; the intent and design templates carry **Work boundaries** / **Parallel work partition** sections to plan it.
|
|
65
|
+
|
|
66
|
+
## Build items deferred or `build dispatch failure: Unresolved assignment`
|
|
67
|
+
|
|
68
|
+
- A `build_deferred` event (with `itemId` and `reason`) is not an error. The local scheduler dispatches a batch of non-overlapping ready items, at most one per configured worker, and defers the rest (`overlaps <item>`, `busy path held by an active assignment`, `unresolved assignment`, `capacity`) to a later batch. The fleet coordinator applies the same overlap rule.
|
|
69
|
+
- The task pauses with `build dispatch failure: Unresolved assignment <id> (<state>) for <item> holds <paths>; inspect it, then run node .agents/aidlc/scripts/assignment.mjs reconcile <task-id> or … release <task-id> <assignment-id>` when a `reported` or `uncertain` assignment of an unfinished item (or a still-`leased` one, reported as `Active assignment`) blocks every ready item. The loop never releases it automatically.
|
|
70
|
+
- Inspect it with `node .agents/aidlc/scripts/assignment.mjs show <task-id>` and the worker's branch or worktree. Run `assignment.mjs reconcile <task-id>` when the outcome is recorded, or `assignment.mjs release <task-id> <assignment-id>` only after confirming the work is abandoned. Then re-activate the task (dashboard **resume** or `state.mjs task status <task-id> --status active`).
|
|
71
|
+
- `Build item <item> requires an owned path scope for Docker dispatch` means an item reached build without `--owns`; fix the plan (see `PARTITION_SCOPE`).
|
|
72
|
+
|
|
73
|
+
## `Git worktree must be clean before handoff or integration`
|
|
74
|
+
|
|
75
|
+
- The lead checkout must have no uncommitted or untracked changes before a worker handoff or integration. Commit, stash, or remove them, then re-activate the task.
|
|
76
|
+
- Host-local loop files under `.agents/data/loop/` (daemon lock, `daemon.log`, `ui-events.jsonl`) are ignored by this check, so a running daemon or dashboard does not trigger it.
|
|
77
|
+
|
|
78
|
+
## `Task already exists` / actionable-task guard
|
|
79
|
+
|
|
80
|
+
- `task create` fails with `Task already exists: <id>` when the id is already present; resume with `task next <id>`.
|
|
81
|
+
- While another task has `nextAction: run_phase`, `task create` fails with `Task create is blocked while other work is actionable: ...`. Pass `--switch-from <task-id>` **only** with explicit user direction; acknowledged tasks keep their state and remain the resume target (a resume-marker diagnostic is stamped). Never pause or mutate the acknowledged task.
|
|
82
|
+
- This guard is local-CLI enforcement; the coordination service does not enforce it.
|
|
83
|
+
|
|
84
|
+
## Fleet authority mismatch
|
|
85
|
+
|
|
86
|
+
- When `AIDLC_FLEET_URL` is set, local lifecycle scripts throw `Fleet authority is configured; use remote-state.mjs instead of local lifecycle scripts`. This is by design — never silently switch authorities.
|
|
87
|
+
- Use `node .agents/aidlc/scripts/remote-state.mjs ...` for all mutations. See [Coordination service](./coordination-service.md).
|
|
88
|
+
|
|
89
|
+
## Docker worker image missing (fleet e2e tests)
|
|
90
|
+
|
|
91
|
+
- `test/unit/loop-fleet-e2e.test.ts` skips cleanly with `Docker worker image unavailable` when the image (default `aidlc-worker-fleet-e2e-test:local`, overridable via `AIDLC_WORKER_DOCKER_IMAGE`) is missing. No failure is recorded.
|
|
92
|
+
- Build the worker image, or set `AIDLC_TEST_WORKER_IMAGE`/`AIDLC_WORKER_DOCKER_IMAGE`, to enable coverage. Postgres coverage additionally needs `TEST_POSTGRES_URL`.
|
|
93
|
+
|
|
94
|
+
## Worker image missing at build dispatch
|
|
95
|
+
|
|
96
|
+
- Local build dispatch auto-builds the per-provider image (`aidlc-worker-<provider>:local`) from the packaged Dockerfile when it is missing, so this error usually does not surface.
|
|
97
|
+
- If the build cannot complete (no Docker daemon, no network to fetch the CLI, or a removed Dockerfile), the worker batch fails with `Docker engine, image, or provider CLI preflight failed`. Run `aidlc-kit loop worker-image --provider <provider>` directly to see the build output, or set `AIDLC_WORKER_DOCKER_IMAGE` to an image you built yourself.
|
|
98
|
+
- Fleet workers resolve the image on their own machine: `AIDLC_WORKER_DOCKER_IMAGE`, else `loop.workerImage[provider]` from the project config, else `aidlc-worker-<provider>:local` (auto-built when missing). The provider comes from `AIDLC_FLEET_WORKER_AGENT`, else the worker's entry in `AIDLC_FLEET_WORKER_PROVIDERS`, else `AIDLC_FLEET_AGENT` (a copied lead env never overrides the worker's own mapping); without one, `AIDLC_WORKER_DOCKER_IMAGE is required when the worker provider is unknown`. To build ahead of time, run `aidlc-kit loop worker-image --provider codex|claude|opencode` on that machine.
|
|
99
|
+
|
|
100
|
+
## Loop daemon diagnostics
|
|
101
|
+
|
|
102
|
+
- `Loop daemon already running (pid N)` — one daemon per project (`.agents/data/loop/daemon.lock`); stop the running one first. A lock owned by a dead pid is reclaimed automatically.
|
|
103
|
+
- `daemon_poll_error` — the daemon could not list tasks (for example the fleet service is unreachable). It keeps polling and does not stop; fix connectivity or credentials and it recovers on the next poll.
|
|
104
|
+
- `daemon_turn_error`, or a `daemon_turn` with status `blocked`/`reconcile` — that task is retried with exponential backoff (capped at 10 minutes) while other tasks keep running. Inspect it with `task show` (local) or `remote-state.mjs task next` (fleet).
|
|
105
|
+
- `loop resume is removed …` — use `loop start`; re-activate a paused task with the dashboard **resume** or the task status command.
|
|
106
|
+
|
|
107
|
+
## Dashboard `401` / `403`
|
|
108
|
+
|
|
109
|
+
- `401 Invalid or missing approve token` — the browser has no current token. Open the login URL printed by `aidlc-kit ui` (the token changes on every restart unless you pass `--token-file`), or paste the token into **Settings**. The token is accepted only in the `x-aidlc-token` or `Authorization: Bearer` header, never as a form or body field.
|
|
110
|
+
- `403 Host header does not match this dashboard; open it at its bound address` — the page was opened under a host name other than the bound address (for example a LAN name while bound to `127.0.0.1`). Use the printed URL; any loopback name works when bound to loopback.
|
|
111
|
+
- `403 Cross-origin request rejected` — a state-changing request came from another origin. Use the dashboard page itself.
|
|
112
|
+
- `403 Fleet approval needs the reviewer credential …` — a fleet dashboard without a reviewer credential cannot approve. Restart `aidlc-kit ui` with `AIDLC_FLEET_REVIEWER_TOKEN` or `--reviewer-token-file <file>` (outside the project, mode `600`), or approve from a reviewer terminal with `remote-state.mjs gate approve <task-id> --snapshot <id> --source "<approval>"`.
|
|
113
|
+
- `Token file must …` — `--token-file`/`--reviewer-token-file` must live outside the project, be a regular non-symlink file with mode `600`/`400`, and contain one 16–256 character token.
|
|
114
|
+
|
|
115
|
+
## Loop / fleet experimental status
|
|
116
|
+
|
|
117
|
+
- Local loop mode is usable today; fleet mode and the coordination service are still under active development. The whole loop is experimental; pre-1.0 breaking changes are possible.
|
|
118
|
+
- Selecting a mode during `init` or `upgrade` never starts or approves anything. Only `aidlc-kit loop start` runs the loop daemon, in either mode.
|
|
119
|
+
- See the warning in [README](../README.md) and [Coordination service](./coordination-service.md).
|
|
120
|
+
|
|
121
|
+
## When to escalate
|
|
122
|
+
|
|
123
|
+
- Repair bound hit (`REPAIR_BOUND`): follow the returned handoff command — record a durable handoff (`task handoff`), reopen to plan, create a successor, supersede, or close — with evidence. Do not present G2.
|
|
124
|
+
- Security, migration, or contract changes reopen Approve plan (G1) even after a prior approval; re-run `gate-check` and present the gate again.
|
|
125
|
+
- Fleet/loop issues affecting canonical state: stop and use `remote-state.mjs`; never mix authorities.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@felixdotgo/aidlc-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Install a local, agent-adapted AI-DLC workflow into a project.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-dlc",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"license": "MIT",
|
|
23
23
|
"type": "module",
|
|
24
24
|
"bin": {
|
|
25
|
-
"aidlc-
|
|
25
|
+
"aidlc-kit": "dist/src/cli.js"
|
|
26
26
|
},
|
|
27
27
|
"files": [
|
|
28
28
|
"dist/src",
|
|
@@ -35,7 +35,10 @@
|
|
|
35
35
|
],
|
|
36
36
|
"scripts": {
|
|
37
37
|
"build": "node scripts/build-assets.mjs && tsc -p tsconfig.json",
|
|
38
|
+
"sync-core": "node scripts/sync-core.mjs",
|
|
38
39
|
"test": "npm run build && node --test dist/test/unit/*.test.js",
|
|
40
|
+
"test:fast": "node --test dist/test/unit/*.test.js",
|
|
41
|
+
"test:coverage": "node --test --experimental-test-coverage dist/test/unit/*.test.js",
|
|
39
42
|
"lint": "tsc --noEmit -p tsconfig.json",
|
|
40
43
|
"release:check": "node dist/dev/evaluator/release-check.js .agents/project/release-eval.json --adapters opencode",
|
|
41
44
|
"prepublishOnly": "npm test && npm run lint && npm run release:check"
|
package/dist/src/ledger.d.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import type { TaskState, WorkflowState } from "./model.js";
|
|
2
|
-
export declare const trackedTasksPath: (root: string) => string;
|
|
3
|
-
export declare const taskEventsPath: (root: string, taskId: string) => string;
|
|
4
|
-
export interface TaskLedgerEvent {
|
|
5
|
-
schemaVersion: 1;
|
|
6
|
-
id: string;
|
|
7
|
-
taskId: string;
|
|
8
|
-
parentDigest: string | null;
|
|
9
|
-
actor: string;
|
|
10
|
-
recordedAt: string;
|
|
11
|
-
snapshot: TaskState;
|
|
12
|
-
digest: string;
|
|
13
|
-
}
|
|
14
|
-
export declare const validateLedgerEvent: (value: unknown) => TaskLedgerEvent;
|
|
15
|
-
export declare const createLedgerEvent: (task: TaskState, parentDigest: string | null, actor: string, recordedAt?: string, id?: string) => TaskLedgerEvent;
|
|
16
|
-
export declare const loadTaskLedger: (root: string, taskId: string) => TaskLedgerEvent[];
|
|
17
|
-
export declare const reduceTaskLedger: (events: readonly TaskLedgerEvent[]) => {
|
|
18
|
-
task: TaskState;
|
|
19
|
-
headDigest: string;
|
|
20
|
-
} | undefined;
|
|
21
|
-
export declare const loadTrackedState: (root: string) => WorkflowState | undefined;
|
|
22
|
-
export declare const appendTaskLedgerEvent: (root: string, task: TaskState, actor: string) => TaskLedgerEvent;
|
package/dist/src/ledger.js
DELETED
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
-
import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, renameSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { dirname, join, resolve } from "node:path";
|
|
4
|
-
import { validateState } from "./state.js";
|
|
5
|
-
export const trackedTasksPath = (root) => join(resolve(root), ".agents/workflow/tasks");
|
|
6
|
-
export const taskEventsPath = (root, taskId) => join(trackedTasksPath(root), taskId, "events");
|
|
7
|
-
const digest = (value) => createHash("sha256").update(JSON.stringify(value)).digest("hex");
|
|
8
|
-
const eventContent = (event) => `${JSON.stringify(event, null, 2)}\n`;
|
|
9
|
-
const eventPath = (root, event) => join(taskEventsPath(root, event.taskId), `${event.id}.json`);
|
|
10
|
-
const taskIdPattern = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
11
|
-
const iso = (value) => typeof value === "string" && Number.isFinite(Date.parse(value)) && new Date(Date.parse(value)).toISOString() === value;
|
|
12
|
-
const assertSafeTaskId = (taskId) => {
|
|
13
|
-
if (!taskIdPattern.test(taskId))
|
|
14
|
-
throw new Error(`Invalid ledger task id: ${taskId}`);
|
|
15
|
-
};
|
|
16
|
-
const atomicWrite = (path, content) => {
|
|
17
|
-
mkdirSync(dirname(path), { recursive: true });
|
|
18
|
-
const temporary = `${path}.tmp-${process.pid}-${Date.now()}`;
|
|
19
|
-
writeFileSync(temporary, content, "utf8");
|
|
20
|
-
renameSync(temporary, path);
|
|
21
|
-
};
|
|
22
|
-
export const validateLedgerEvent = (value) => {
|
|
23
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
24
|
-
throw new Error("Ledger event must be an object");
|
|
25
|
-
const event = value;
|
|
26
|
-
if (event.schemaVersion !== 1 || typeof event.id !== "string" || !taskIdPattern.test(event.id) || typeof event.taskId !== "string" || !taskIdPattern.test(event.taskId) || (event.parentDigest !== null && !/^[a-f0-9]{64}$/.test(event.parentDigest ?? "")) || typeof event.actor !== "string" || !event.actor.trim() || !iso(event.recordedAt) || !event.snapshot || typeof event.digest !== "string" || !/^[a-f0-9]{64}$/.test(event.digest))
|
|
27
|
-
throw new Error("Invalid ledger event fields");
|
|
28
|
-
if (event.snapshot.id !== event.taskId)
|
|
29
|
-
throw new Error(`Ledger event task mismatch: ${event.id}`);
|
|
30
|
-
validateState({ schemaVersion: 4, tasks: { [event.taskId]: event.snapshot }, archive: {} });
|
|
31
|
-
const { digest: supplied, ...unsigned } = event;
|
|
32
|
-
if (digest(unsigned) !== supplied)
|
|
33
|
-
throw new Error(`Ledger event digest mismatch: ${event.id}`);
|
|
34
|
-
return event;
|
|
35
|
-
};
|
|
36
|
-
export const createLedgerEvent = (task, parentDigest, actor, recordedAt = new Date().toISOString(), id = randomUUID()) => {
|
|
37
|
-
assertSafeTaskId(task.id);
|
|
38
|
-
if (!actor.trim() || !iso(recordedAt) || !taskIdPattern.test(id))
|
|
39
|
-
throw new Error("Invalid ledger event metadata");
|
|
40
|
-
validateState({ schemaVersion: 4, tasks: { [task.id]: task }, archive: {} });
|
|
41
|
-
const unsigned = { schemaVersion: 1, id, taskId: task.id, parentDigest, actor, recordedAt, snapshot: task };
|
|
42
|
-
return { ...unsigned, digest: digest(unsigned) };
|
|
43
|
-
};
|
|
44
|
-
export const loadTaskLedger = (root, taskId) => {
|
|
45
|
-
assertSafeTaskId(taskId);
|
|
46
|
-
const directory = taskEventsPath(root, taskId);
|
|
47
|
-
if (!existsSync(directory))
|
|
48
|
-
return [];
|
|
49
|
-
if (lstatSync(directory).isSymbolicLink())
|
|
50
|
-
throw new Error(`Ledger path crosses a symlink: ${taskId}`);
|
|
51
|
-
const events = readdirSync(directory, { withFileTypes: true })
|
|
52
|
-
.filter((entry) => entry.isFile() && entry.name.endsWith(".json"))
|
|
53
|
-
.map((entry) => validateLedgerEvent(JSON.parse(readFileSync(join(directory, entry.name), "utf8"))))
|
|
54
|
-
.sort((left, right) => left.id.localeCompare(right.id));
|
|
55
|
-
if (events.some((event) => event.taskId !== taskId))
|
|
56
|
-
throw new Error(`Ledger event is in the wrong task directory: ${taskId}`);
|
|
57
|
-
return events;
|
|
58
|
-
};
|
|
59
|
-
export const reduceTaskLedger = (events) => {
|
|
60
|
-
if (!events.length)
|
|
61
|
-
return undefined;
|
|
62
|
-
const byDigest = new Map(events.map((event) => [event.digest, event]));
|
|
63
|
-
if (byDigest.size !== events.length)
|
|
64
|
-
throw new Error("Duplicate ledger event digest");
|
|
65
|
-
const roots = events.filter((event) => event.parentDigest === null);
|
|
66
|
-
if (roots.length !== 1)
|
|
67
|
-
throw new Error("Ledger must have exactly one root event");
|
|
68
|
-
const children = new Map();
|
|
69
|
-
for (const event of events)
|
|
70
|
-
if (event.parentDigest) {
|
|
71
|
-
if (!byDigest.has(event.parentDigest))
|
|
72
|
-
throw new Error(`Ledger event has unknown parent: ${event.id}`);
|
|
73
|
-
const siblings = children.get(event.parentDigest) ?? [];
|
|
74
|
-
siblings.push(event);
|
|
75
|
-
children.set(event.parentDigest, siblings);
|
|
76
|
-
}
|
|
77
|
-
const sibling = [...children.values()].find((items) => items.length > 1);
|
|
78
|
-
if (sibling)
|
|
79
|
-
throw new Error(`Concurrent ledger events require resolution: ${sibling.map((event) => event.id).join(", ")}`);
|
|
80
|
-
const visited = new Set();
|
|
81
|
-
let current = roots[0];
|
|
82
|
-
while (true) {
|
|
83
|
-
if (visited.has(current.digest))
|
|
84
|
-
throw new Error("Ledger event cycle");
|
|
85
|
-
visited.add(current.digest);
|
|
86
|
-
const next = children.get(current.digest) ?? [];
|
|
87
|
-
if (!next.length)
|
|
88
|
-
break;
|
|
89
|
-
current = next[0];
|
|
90
|
-
}
|
|
91
|
-
if (visited.size !== events.length)
|
|
92
|
-
throw new Error("Ledger contains disconnected events");
|
|
93
|
-
return { task: current.snapshot, headDigest: current.digest };
|
|
94
|
-
};
|
|
95
|
-
export const loadTrackedState = (root) => {
|
|
96
|
-
const directory = trackedTasksPath(root);
|
|
97
|
-
if (!existsSync(directory))
|
|
98
|
-
return undefined;
|
|
99
|
-
if (lstatSync(directory).isSymbolicLink())
|
|
100
|
-
throw new Error("Tracked ledger path crosses a symlink");
|
|
101
|
-
const tasks = {};
|
|
102
|
-
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
103
|
-
if (!entry.isDirectory() || !taskIdPattern.test(entry.name))
|
|
104
|
-
continue;
|
|
105
|
-
const reduced = reduceTaskLedger(loadTaskLedger(root, entry.name));
|
|
106
|
-
if (reduced)
|
|
107
|
-
tasks[entry.name] = reduced.task;
|
|
108
|
-
}
|
|
109
|
-
return validateState({ schemaVersion: 4, tasks, archive: {} });
|
|
110
|
-
};
|
|
111
|
-
export const appendTaskLedgerEvent = (root, task, actor) => {
|
|
112
|
-
const current = reduceTaskLedger(loadTaskLedger(root, task.id));
|
|
113
|
-
const event = createLedgerEvent(task, current?.headDigest ?? null, actor);
|
|
114
|
-
const path = eventPath(root, event);
|
|
115
|
-
if (existsSync(path))
|
|
116
|
-
throw new Error(`Ledger event already exists: ${event.id}`);
|
|
117
|
-
atomicWrite(path, eventContent(event));
|
|
118
|
-
return event;
|
|
119
|
-
};
|