@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.
- package/DEVELOPMENT.md +14 -13
- package/README.md +37 -43
- 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 +2 -2
- 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/state.schema.json +2 -0
- package/dist/assets/.agents/aidlc/scripts/lib/assignment-store.mjs +94 -48
- package/dist/assets/.agents/aidlc/scripts/lib/context-runtime.mjs +5 -4
- package/dist/assets/.agents/aidlc/scripts/lib/loop-authority.mjs +261 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-commit-message.mjs +45 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-daemon.mjs +206 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-drivers.mjs +100 -14
- package/dist/assets/.agents/aidlc/scripts/lib/loop-env.mjs +279 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet-cli.mjs +105 -32
- package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet-remote.mjs +17 -1
- package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet-supervisor.mjs +24 -7
- package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet.mjs +42 -16
- package/dist/assets/.agents/aidlc/scripts/lib/loop-git.mjs +20 -6
- package/dist/assets/.agents/aidlc/scripts/lib/loop-holder.mjs +376 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-intake.mjs +33 -7
- package/dist/assets/.agents/aidlc/scripts/lib/loop-lease.mjs +67 -61
- package/dist/assets/.agents/aidlc/scripts/lib/loop-leases.mjs +268 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-local-workers.mjs +116 -28
- package/dist/assets/.agents/aidlc/scripts/lib/loop-runtime.mjs +114 -12
- package/dist/assets/.agents/aidlc/scripts/lib/loop-stream.mjs +117 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-supervisor-local.mjs +16 -56
- package/dist/assets/.agents/aidlc/scripts/lib/loop-supervisor-run-local.mjs +149 -6
- package/dist/assets/.agents/aidlc/scripts/lib/loop-supervisor.mjs +9 -1
- package/dist/assets/.agents/aidlc/scripts/lib/loop-worker-docker.mjs +44 -6
- package/dist/assets/.agents/aidlc/scripts/lib/loop-worker-image.mjs +64 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-worker-log.mjs +158 -0
- package/dist/assets/.agents/aidlc/scripts/lib/repo-head.mjs +22 -0
- package/dist/assets/.agents/aidlc/scripts/lib/store.mjs +68 -2
- package/dist/assets/.agents/aidlc/scripts/lib/task-runtime-migrate.mjs +75 -0
- package/dist/assets/.agents/aidlc/scripts/lib/task-runtime.mjs +97 -0
- package/dist/assets/.agents/aidlc/scripts/loop-entry.mjs +168 -73
- package/dist/assets/.agents/aidlc/scripts/loop-gate.mjs +19 -7
- package/dist/assets/.agents/aidlc/scripts/loop.cmd +101 -0
- package/dist/assets/.agents/aidlc/scripts/loop.mjs +2 -1
- package/dist/assets/.agents/aidlc/scripts/loop.sh +71 -0
- package/dist/assets/.agents/aidlc/scripts/remote-state.mjs +10 -4
- package/dist/assets/.agents/aidlc/scripts/state.mjs +16 -8
- 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/changes.js +83 -0
- package/dist/assets/ui/common.js +664 -0
- package/dist/assets/ui/dashboard.css +636 -0
- package/dist/assets/ui/dashboard.js +77 -0
- package/dist/assets/ui/detail.js +159 -0
- package/dist/assets/ui/live-log.js +332 -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 +68 -2
- 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 +3 -5
- package/dist/src/cli/argv.d.ts +12 -0
- package/dist/src/cli/argv.js +68 -0
- package/dist/src/cli/commands.d.ts +8 -0
- package/dist/src/cli/commands.js +159 -0
- package/dist/src/cli/loop-launcher.d.ts +47 -0
- package/dist/src/cli/loop-launcher.js +322 -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 +28 -191
- package/dist/src/gate.js +3 -2
- package/dist/src/installer.d.ts +14 -7
- package/dist/src/installer.js +42 -143
- 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 +142 -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 +2 -0
- package/dist/src/profiles.js +53 -9
- 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 +3 -4
- package/dist/src/state/items.js +58 -57
- package/dist/src/state/lessons.js +14 -7
- package/dist/src/state/lifecycle.js +2 -2
- package/dist/src/state/lock.js +7 -6
- package/dist/src/state/memory.js +24 -12
- package/dist/src/state/{archive.js → migrate.js} +7 -7
- 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.js +9 -6
- package/dist/src/state/store.js +9 -15
- package/dist/src/state/transitions.js +9 -7
- package/dist/src/state/validate.js +55 -7
- package/dist/src/state.d.ts +16 -11
- package/dist/src/state.js +16 -11
- package/dist/src/task-ui.d.ts +33 -0
- package/dist/src/task-ui.js +33 -0
- package/dist/src/ui/assets.d.ts +14 -0
- package/dist/src/ui/assets.js +45 -0
- package/dist/src/ui/changes.d.ts +83 -0
- package/dist/src/ui/changes.js +498 -0
- package/dist/src/ui/containers.d.ts +71 -0
- package/dist/src/ui/containers.js +214 -0
- package/dist/src/ui/daemon.d.ts +75 -0
- package/dist/src/ui/daemon.js +232 -0
- package/dist/src/ui/data.d.ts +42 -0
- package/dist/src/ui/data.js +89 -0
- package/dist/src/ui/docs.d.ts +13 -0
- package/dist/src/ui/docs.js +39 -0
- package/dist/src/ui/files.d.ts +3 -0
- package/dist/src/ui/files.js +20 -0
- package/dist/src/ui/http.d.ts +14 -0
- package/dist/src/ui/http.js +80 -0
- package/dist/src/ui/live.d.ts +5 -0
- package/dist/src/ui/live.js +43 -0
- package/dist/src/ui/markdown.d.ts +2 -0
- package/dist/src/ui/markdown.js +127 -0
- package/dist/src/ui/options.d.ts +48 -0
- package/dist/src/ui/options.js +81 -0
- package/dist/src/ui/security.d.ts +34 -0
- package/dist/src/ui/security.js +38 -0
- package/dist/src/ui/server.d.ts +39 -0
- package/dist/src/ui/server.js +347 -0
- package/dist/src/ui/sse.d.ts +14 -0
- package/dist/src/ui/sse.js +281 -0
- package/dist/src/ui/views/board.d.ts +7 -0
- package/dist/src/ui/views/board.js +131 -0
- package/dist/src/ui/views/changes.d.ts +19 -0
- package/dist/src/ui/views/changes.js +154 -0
- package/dist/src/ui/views/components.d.ts +56 -0
- package/dist/src/ui/views/components.js +113 -0
- package/dist/src/ui/views/docs.d.ts +15 -0
- package/dist/src/ui/views/docs.js +64 -0
- package/dist/src/ui/views/layout.d.ts +44 -0
- package/dist/src/ui/views/layout.js +233 -0
- package/dist/src/ui/views/task.d.ts +25 -0
- package/dist/src/ui/views/task.js +320 -0
- package/dist/src/upgrade-board.js +10 -10
- package/dist/src/upgrade.d.ts +0 -1
- package/dist/src/upgrade.js +24 -85
- package/dist/src/workflow.d.ts +4 -0
- package/dist/src/workflow.js +36 -4
- package/docs/README.md +41 -22
- package/docs/architecture.md +101 -37
- package/docs/command-reference.md +289 -52
- package/docs/configuration.md +70 -30
- package/docs/coordination-service.md +91 -30
- package/docs/design-decisions.md +87 -0
- package/docs/development.md +124 -49
- package/docs/loop-local-run.md +228 -0
- package/docs/loop-runbook.md +214 -0
- package/docs/operating-workflow.md +55 -42
- package/docs/testing-and-release.md +72 -49
- package/docs/troubleshooting.md +221 -0
- package/package.json +4 -1
- package/dist/src/ledger.d.ts +0 -22
- package/dist/src/ledger.js +0 -119
- package/dist/src/state/shared.d.ts +0 -29
- package/dist/src/state/shared.js +0 -71
- package/dist/src/tracker.d.ts +0 -36
- package/dist/src/tracker.js +0 -44
- /package/dist/src/state/{archive.d.ts → migrate.d.ts} +0 -0
package/DEVELOPMENT.md
CHANGED
|
@@ -1,31 +1,32 @@
|
|
|
1
1
|
# Contributing to aidlc-kit
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Entry point for contributors. Details live in [`docs/`](./docs/README.md).
|
|
4
4
|
|
|
5
5
|
## Local bootstrap
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Node.js 20.17+ and npm.
|
|
8
8
|
|
|
9
9
|
```sh
|
|
10
10
|
npm install
|
|
11
|
-
npm test
|
|
12
|
-
npm run lint
|
|
11
|
+
npm test # build + unit tests
|
|
12
|
+
npm run lint # tsc --noEmit
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
+
To develop with the repository's own workflow (`.agents/` is gitignored), follow [Dogfooding this repository](./docs/development.md#dogfooding-this-repository).
|
|
16
|
+
|
|
15
17
|
## Contributor documentation
|
|
16
18
|
|
|
17
|
-
- [Development guide](./docs/development.md) —
|
|
18
|
-
- [Testing and release](./docs/testing-and-release.md) — focused tests, package smoke test,
|
|
19
|
-
- [Architecture](./docs/architecture.md) — module ownership
|
|
20
|
-
- [
|
|
19
|
+
- [Development guide](./docs/development.md) — layout, local commands, boundaries, code style, dogfooding.
|
|
20
|
+
- [Testing and release](./docs/testing-and-release.md) — focused tests, Docker/PostgreSQL verification, package smoke test, release checklist.
|
|
21
|
+
- [Architecture](./docs/architecture.md) — module ownership and source → bundle → installed flow.
|
|
22
|
+
- [Design decisions](./docs/design-decisions.md) — numbered decisions and how to add one.
|
|
21
23
|
|
|
22
24
|
## Documentation policy
|
|
23
25
|
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
- Do not duplicate canonical agent instructions from `assets/aidlc/` in overview documentation.
|
|
26
|
+
- `README.md` is the fastest path to install and use; everything else goes in `docs/` per [Adding or changing a page](./docs/README.md#adding-or-changing-a-page).
|
|
27
|
+
- A public contract change (command, config field, schema, installed asset) updates its guide and test in the same change.
|
|
28
|
+
- Never duplicate canonical agent instructions from `assets/aidlc/`; link to them.
|
|
28
29
|
|
|
29
30
|
## Delivery boundary
|
|
30
31
|
|
|
31
|
-
|
|
32
|
+
Passing verification does not authorise publishing, tagging, committing, or pushing; each needs separate human approval.
|
package/README.md
CHANGED
|
@@ -1,69 +1,63 @@
|
|
|
1
1
|
# aidlc-kit
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@felixdotgo/aidlc-kit)
|
|
4
|
+
[](https://github.com/felixdotgo/aidlc-kit/blob/main/LICENSE)
|
|
5
|
+
[](https://nodejs.org/)
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
Installs an AI-DLC workflow into an existing project: a shared task lifecycle, explicit human review gates, and project-owned state for Codex, Claude Code, and OpenCode. Optionally, a loop daemon runs tasks end-to-end on one machine or across a fleet.
|
|
6
8
|
|
|
7
9
|
> [!WARNING]
|
|
8
|
-
>
|
|
9
|
-
|
|
10
|
-
## Requirements
|
|
11
|
-
|
|
12
|
-
- Node.js 20.17 or newer.
|
|
13
|
-
- An existing project directory.
|
|
14
|
-
- Codex, Claude Code, or OpenCode.
|
|
10
|
+
> Pre-1.0 (`0.x`). The local loop is usable; fleet mode and the coordination service are still in development. See [Status and stability](./docs/loop-runbook.md#status-and-stability).
|
|
15
11
|
|
|
16
12
|
## Quick start
|
|
17
13
|
|
|
18
|
-
|
|
14
|
+
Requires Node.js 20.17+ and Codex, Claude Code, or OpenCode (Linux/macOS; Windows is best-effort).
|
|
19
15
|
|
|
20
16
|
```sh
|
|
21
|
-
npx --package @felixdotgo/aidlc-kit@0.
|
|
22
|
-
npx --package @felixdotgo/aidlc-kit@0.
|
|
17
|
+
npx --package @felixdotgo/aidlc-kit@0.3.0 aidlc-kit init . # previews, then installs after you confirm
|
|
18
|
+
npx --package @felixdotgo/aidlc-kit@0.3.0 aidlc-kit doctor . --strict
|
|
23
19
|
```
|
|
24
20
|
|
|
25
|
-
`
|
|
26
|
-
|
|
27
|
-
Use the adapter that matches the coding tool:
|
|
21
|
+
In a terminal, `init` asks for the adapter (unless exactly one is detected or you pass `--agent codex|claude|opencode` / `--all`) and the loop mode (unless `--loop-mode …`), previews the files, then asks you to confirm; `--yes` skips the confirmation, `--dry-run` writes nothing. The installed lifecycle scripts run locally and never contact a registry.
|
|
28
22
|
|
|
29
|
-
|
|
30
|
-
| --- | --- | --- |
|
|
31
|
-
| Codex | `codex` | `AGENTS.md` |
|
|
32
|
-
| Claude Code | `claude` | `CLAUDE.md` and phase skills |
|
|
33
|
-
| OpenCode | `opencode` | `AGENTS.md` |
|
|
23
|
+
Then ask your agent for a change:
|
|
34
24
|
|
|
35
|
-
|
|
25
|
+
```text
|
|
26
|
+
Add rate limiting to the login endpoint using the AI-DLC workflow.
|
|
27
|
+
```
|
|
36
28
|
|
|
37
|
-
Confirm the
|
|
29
|
+
The agent creates a task and stops at three review steps — Confirm requirements, Approve plan, Review implementation — each needing your explicit reply. The Codex and Claude Code adapters pre-approve only the lifecycle scripts, and OpenCode gets instructions only ([Agent permissions](./docs/configuration.md#agent-permissions)); for Codex, trust the project first.
|
|
38
30
|
|
|
39
|
-
|
|
40
|
-
npx --package @felixdotgo/aidlc-kit@0.1.1 aidlc-kit status .
|
|
41
|
-
npx --package @felixdotgo/aidlc-kit@0.1.1 aidlc-kit doctor . --strict
|
|
42
|
-
```
|
|
31
|
+
## Run the loop (optional)
|
|
43
32
|
|
|
44
|
-
|
|
33
|
+
A daemon drives tasks end-to-end on a Linux host with Docker, stopping at every review step for you:
|
|
45
34
|
|
|
46
|
-
|
|
35
|
+
1. In `.agents/config.json` update the `loop` section to match your environment, e.g.,
|
|
36
|
+
```json
|
|
37
|
+
...
|
|
38
|
+
"loop": {
|
|
39
|
+
"mode": "local",
|
|
40
|
+
"agents": {
|
|
41
|
+
"lead": "opencode",
|
|
42
|
+
"workers": {
|
|
43
|
+
"worker-a": "opencode"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
...
|
|
48
|
+
```
|
|
49
|
+
2. Put credentials in the git-ignored `.agents/loop.env`; keys your tools already store are detected.
|
|
50
|
+
3. `./.agents/aidlc/scripts/loop.sh up` checks the environment, starts the daemon, and serves the dashboard (`--open` launches the browser); `loop.sh down` stops both.
|
|
47
51
|
|
|
48
|
-
|
|
49
|
-
- [Operating the workflow](./docs/operating-workflow.md) — lifecycle, state, gates, and blocked work.
|
|
50
|
-
- [Configuration](./docs/configuration.md) — project configuration, profiles, rules, and adapters.
|
|
51
|
-
- [Command reference](./docs/command-reference.md) — every package CLI command and safety constraint.
|
|
52
|
-
- [Development](./docs/development.md) — local contributor setup, source layout, and implementation rules.
|
|
53
|
-
- [Testing and release](./docs/testing-and-release.md) — verification, evaluator, package smoke test, and release readiness.
|
|
54
|
-
- [Architecture](./docs/architecture.md) — package boundaries and the source-to-installed flow.
|
|
55
|
-
- [AI-DLC Coordination service](./docs/coordination-service.md) — opt-in Docker/remote coordination, security, and polling contract.
|
|
52
|
+
Setup: [Loop setup runbook](./docs/loop-runbook.md). Daily use: [Local loop walkthrough](./docs/loop-local-run.md).
|
|
56
53
|
|
|
57
|
-
##
|
|
54
|
+
## Documentation
|
|
58
55
|
|
|
59
|
-
|
|
56
|
+
[Index](./docs/README.md) · [Operating the workflow](./docs/operating-workflow.md) · [Configuration](./docs/configuration.md) · [Command reference](./docs/command-reference.md) · [Troubleshooting](./docs/troubleshooting.md) · [Coordination service (fleet)](./docs/coordination-service.md) · [Contributing](./DEVELOPMENT.md)
|
|
60
57
|
|
|
61
|
-
##
|
|
58
|
+
## Safety note
|
|
62
59
|
|
|
63
|
-
|
|
64
|
-
- Normalized Git path scopes (trailing slashes stripped) consistently across fleet, local-worker, and snapshot flows; directory scopes now work in all three.
|
|
65
|
-
- Hardened the state lock to reclaim partially written (corrupt) lock files instead of wedging mutations.
|
|
66
|
-
- Made the fleet end-to-end test skip cleanly when its Docker worker image is unavailable.
|
|
60
|
+
Only a human may upgrade: `upgrade` rejects `--yes` and `--force`, needs an interactive terminal, and asks you to type the target version ([details](./docs/command-reference.md#upgrade)).
|
|
67
61
|
|
|
68
62
|
## License
|
|
69
63
|
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# AI-DLC worker image (Claude Code). Shipped and managed by aidlc-kit — build it
|
|
2
|
+
# with `aidlc-kit loop worker-image --provider claude` instead of by hand.
|
|
3
|
+
#
|
|
4
|
+
# The agent CLI must be on the system PATH: workers run non-root with a read-only
|
|
5
|
+
# root filesystem, so a global npm install lands the binary in /usr/local/bin.
|
|
6
|
+
FROM node:24-bookworm-slim
|
|
7
|
+
RUN npm install -g @anthropic-ai/claude-code
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# AI-DLC worker image (OpenAI Codex CLI). Shipped and managed by aidlc-kit —
|
|
2
|
+
# build it with `aidlc-kit loop worker-image --provider codex` instead of by hand.
|
|
3
|
+
#
|
|
4
|
+
# The agent CLI must be on the system PATH: workers run non-root with a read-only
|
|
5
|
+
# root filesystem, so a global npm install lands the binary in /usr/local/bin.
|
|
6
|
+
# Use the scoped `@openai/codex` package (the unscoped `codex` npm package is
|
|
7
|
+
# unrelated).
|
|
8
|
+
FROM node:24-bookworm-slim
|
|
9
|
+
RUN npm install -g @openai/codex
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# AI-DLC worker image (OpenCode 2). Shipped and managed by aidlc-kit — build it
|
|
2
|
+
# with `aidlc-kit loop worker-image --provider opencode` instead of by hand.
|
|
3
|
+
#
|
|
4
|
+
# The agent CLI must be on the system PATH: workers run non-root with a read-only
|
|
5
|
+
# root filesystem, so a global npm install lands the binary in /usr/local/bin.
|
|
6
|
+
# Use `@opencode/cli` (OpenCode 2) — NOT `opencode-ai` (OpenCode 1).
|
|
7
|
+
FROM node:24-bookworm-slim
|
|
8
|
+
RUN npm install -g @opencode/cli
|
|
@@ -20,7 +20,7 @@ This file is the compact kernel contract. Project customization lives under `.ag
|
|
|
20
20
|
|
|
21
21
|
Read-only questions remain off-workflow and do not create state. The current Codex, Claude Code, or OpenCode conversation is the interactive lead for local mode. It keeps following fresh `nextAction` in the same conversation, uses native host delegation for independent items, and presents gates here. A later explicit user reply approves a gate. No nested Codex/Claude/OpenCode CLI process is needed for interactive mode.
|
|
22
22
|
|
|
23
|
-
On `resume <task-id>` in a reopened conversation, first read `task next` and `task show`, then run `assignment.mjs reconcile <task-id>` and inspect each `reported` or `uncertain` assignment against canonical item status, owned files, and worker output. Resume only work still required by canonical state. A stale assignment never authorizes replaying a lifecycle mutation or gate approval. Local assignment records under `.agents/data/
|
|
23
|
+
On `resume <task-id>` in a reopened conversation, first read `task next` and `task show`, then run `assignment.mjs reconcile <task-id>` and inspect each `reported` or `uncertain` assignment against canonical item status, owned files, and worker output. Resume only work still required by canonical state. A stale assignment never authorizes replaying a lifecycle mutation or gate approval. Local assignment records under `.agents/data/tasks/<task-id>/runtime/assignments.json` are scheduling metadata, not lifecycle authority. Fleet mode uses an explicitly selected remote authority and fenced service claims; never silently switch between local and remote state.
|
|
24
24
|
|
|
25
25
|
## Review steps
|
|
26
26
|
|
|
@@ -52,7 +52,7 @@ Economy models use the same bounded packet contract, gates, executable checks, a
|
|
|
52
52
|
- The lead is the single writer for lifecycle state, gate actions, and task review artifacts. Workers never approve or present gates, mutate lifecycle records, or make canonical artifact decisions; every human-gate and state-machine invariant remains in force.
|
|
53
53
|
- Each worker assignment must name a bounded objective, owned paths or read-only scope, dependencies, expected verification, and required return output (summary, changed paths, evidence, and concerns). The lead integrates and verifies worker results before recording evidence or advancing the task.
|
|
54
54
|
- In local interactive mode, before dispatching a canonical build item, run `assignment.mjs claim <task-id> <item-id> --worker <worker-id> --paths <comma-separated-owned-paths>`. Pass the returned assignment ID to the native worker. Use `assignment.mjs heartbeat` for long work and `assignment.mjs report` when it returns. After integration and canonical item completion, run `assignment.mjs reconcile`. An expired claim becomes `uncertain`; inspect its files and report before `assignment.mjs release` and any retry. Do not use assignments to store approvals or mark canonical items done.
|
|
55
|
-
- Assign one writer per path. Partition work so concurrent changes cannot overlap; workers may inspect shared files, but must report a needed cross-owner change to the lead rather than edit it. The lead resolves conflicts and performs final integration.
|
|
55
|
+
- Assign one writer per path. Partition work so concurrent changes cannot overlap; workers may inspect shared files, but must report a needed cross-owner change to the lead rather than edit it. The lead resolves conflicts and performs final integration. Canonical build items carry this partition as `ownedPaths`/`dependsOn`; the Approve plan gate check reports `PARTITION_OVERLAP`/`PARTITION_SCOPE` when concurrently-runnable items overlap or lack owned paths.
|
|
56
56
|
|
|
57
57
|
## Customization layers
|
|
58
58
|
|
|
@@ -7,7 +7,7 @@ When independent, read-only index or ambiguity checks exist, use the shared orch
|
|
|
7
7
|
1. Read the compact repository/spec indexes if available; do not scan implementation code.
|
|
8
8
|
2. Read `templates/model-contract.md` and apply its COSTARS clarify emphasis to the intent brief. Use CRITICS only for elevated risk, ambiguity, or model disagreement.
|
|
9
9
|
3. Canonical task state already exists from turn routing (`task create` runs there, before this packet). Never create it a second time; a repeated create fails with `Task already exists`.
|
|
10
|
-
4. Fill the intent template: problem, affected areas, assumptions, open questions with safe defaults, and decidable in/out scope.
|
|
10
|
+
4. Fill the intent template: problem, affected areas, work boundaries (independent streams and the paths each touches, or `single stream` with the reason), assumptions, open questions with safe defaults, and decidable in/out scope.
|
|
11
11
|
5. Run `node .agents/aidlc/scripts/gate-check.mjs <task-id> --gate G0_confirm` and fix every structural error. Only after it passes, set the gate wait with `node .agents/aidlc/scripts/state.mjs task status <task-id> --status blocked_on_user`, then render only this task with `node .agents/aidlc/scripts/render.mjs <task-id>`.
|
|
12
12
|
6. Run `node .agents/aidlc/scripts/gate-view.mjs <task-id>`, relay the output verbatim, and stop. Presenting the gate ends this turn.
|
|
13
13
|
|
|
@@ -6,9 +6,9 @@ Use the shared orchestration contract in `orchestrator.md` to fan out only indep
|
|
|
6
6
|
|
|
7
7
|
1. Retrieve only matched specs and impacted symbols. Parallelize bounded discovery when available; return summaries and exact spec anchors, not file dumps.
|
|
8
8
|
2. Read `templates/model-contract.md` and apply its COSTARS plan emphasis. Use CRITICS only for elevated risk, ambiguity, or model disagreement.
|
|
9
|
-
3. Fill the design template with the solution per affected area, exact sources for quantified rules, contracts, risks, and
|
|
9
|
+
3. Fill the design template with the solution per affected area, exact sources for quantified rules, contracts, risks, reuse candidates, and the Parallel work partition table matching the canonical items.
|
|
10
10
|
4. Add every real judgement call with `node .agents/aidlc/scripts/state.mjs decision set <task-id> <decision-id> --status unresolved --label <label> --resolution <proposed-default>`. Code/spec conflicts and ambiguous enumerations are always decisions.
|
|
11
|
-
5. Add implementation items with `node .agents/aidlc/scripts/state.mjs task item <task-id> <item-id> --status todo --label <label
|
|
11
|
+
5. Add implementation items with `node .agents/aidlc/scripts/state.mjs task item <task-id> <item-id> --status todo --label <label> --owns <comma-separated-paths> [--depends-on <item-ids>]` and configure narrow verification commands. Every item passes `--owns`; add `--depends-on` whenever it must touch a path that another concurrently-runnable item owns. With the loop enabled, the Approve plan gate check fails with `PARTITION_OVERLAP`/`PARTITION_SCOPE` until fixed (warning when the loop is off). Then render the workplan review artifact with `node .agents/aidlc/scripts/render.mjs <task-id>`.
|
|
12
12
|
6. Unresolved decisions block the Approve plan gate structurally. Present them to the user and apply each answer with `decision set <task-id> <decision-id> --status approved|changed|dropped --resolution <user-resolution>`. Do not treat a decision answer as gate approval.
|
|
13
13
|
7. When every decision is resolved, run `node .agents/aidlc/scripts/gate-check.mjs <task-id> --gate G1_review` and fix every structural error. Only after it passes, set the gate wait with `node .agents/aidlc/scripts/state.mjs task status <task-id> --status blocked_on_user`, run `node .agents/aidlc/scripts/gate-view.mjs <task-id>`, relay it verbatim, and stop. Approve plan always requires explicit human approval.
|
|
14
14
|
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
"context": { "type": "object", "required": ["maxChars"], "properties": { "maxChars": { "type": "integer", "minimum": 4000, "maximum": 128000 } } },
|
|
14
14
|
"agentState": { "type": "object", "additionalProperties": { "enum": ["native", "scripted"] } },
|
|
15
15
|
"tracker": { "type": "object", "additionalProperties": false, "required": ["enabled"], "properties": { "enabled": { "type": "boolean" }, "provider": { "enum": ["backlog"] }, "spaceUrl": { "type": "string", "format": "uri" }, "project": { "type": "string", "minLength": 1 }, "tokenEnv": { "type": "string", "pattern": "^[A-Za-z_][A-Za-z0-9_]*$" }, "workflow": { "enum": ["waterfall", "scrum"] }, "mapping": { "type": "object", "properties": { "statuses": { "type": "object", "additionalProperties": { "type": "integer", "minimum": 1 } }, "gateFieldId": { "type": "integer", "minimum": 1 } } } } },
|
|
16
|
-
"mcp": { "type": "object", "additionalProperties": false, "required": ["enabled"], "properties": { "enabled": { "type": "boolean" }, "endpoint": { "type": "string", "format": "uri" }, "workspace": { "type": "string", "minLength": 1 }, "tokenEnv": { "type": "string", "pattern": "^[A-Za-z_][A-Za-z0-9_]*$" }, "pollMs": { "type": "integer", "minimum": 1000, "maximum": 3600000 }, "providers": { "type": "array", "items": { "enum": ["jira", "trello", "github-issues"] }, "uniqueItems": true } } }
|
|
16
|
+
"mcp": { "type": "object", "additionalProperties": false, "required": ["enabled"], "properties": { "enabled": { "type": "boolean" }, "endpoint": { "type": "string", "format": "uri" }, "workspace": { "type": "string", "minLength": 1 }, "tokenEnv": { "type": "string", "pattern": "^[A-Za-z_][A-Za-z0-9_]*$" }, "pollMs": { "type": "integer", "minimum": 1000, "maximum": 3600000 }, "providers": { "type": "array", "items": { "enum": ["jira", "trello", "github-issues"] }, "uniqueItems": true } } },
|
|
17
|
+
"$schema": { "type": "string", "maxLength": 512 }
|
|
17
18
|
},
|
|
18
19
|
"$defs": {
|
|
19
20
|
"command": {
|
|
@@ -35,6 +35,8 @@
|
|
|
35
35
|
"legacyG2Wait": { "$ref": "#/$defs/legacyG2Wait" },
|
|
36
36
|
"predecessorTaskId": { "type": "string", "minLength": 1 },
|
|
37
37
|
"successorTaskId": { "type": "string", "minLength": 1 },
|
|
38
|
+
"baseCommit": { "type": "string", "pattern": "^[0-9a-f]{7,64}$" },
|
|
39
|
+
"finalCommit": { "type": "string", "pattern": "^[0-9a-f]{7,64}$" },
|
|
38
40
|
"createdAt": { "type": "string" },
|
|
39
41
|
"updatedAt": { "type": "string" }
|
|
40
42
|
},
|
|
@@ -1,27 +1,31 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { execFileSync } from "node:child_process";
|
|
3
|
-
import { closeSync, fsyncSync,
|
|
4
|
-
import { dirname, join
|
|
3
|
+
import { closeSync, fsyncSync, mkdirSync, openSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { acquireHolder, describeHolder } from "./loop-holder.mjs";
|
|
6
|
+
import { resolveTaskRuntimeRead, taskRuntimePath } from "./task-runtime.mjs";
|
|
7
|
+
import { migrateTaskRuntime } from "./task-runtime-migrate.mjs";
|
|
5
8
|
|
|
6
9
|
const idPattern = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
7
10
|
const states = new Set(["leased", "reported", "uncertain", "integrated", "released"]);
|
|
8
11
|
const safeId = (value, label) => { if (typeof value !== "string" || !idPattern.test(value)) throw new Error(`Invalid ${label}`); return value; };
|
|
9
12
|
const iso = () => new Date().toISOString();
|
|
10
|
-
const wait = (ms) => Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
11
13
|
|
|
14
|
+
/** Write location of the task's assignment records
|
|
15
|
+
* (`.agents/data/tasks/<task>/runtime/assignments.json`; `.lock` for the mutex),
|
|
16
|
+
* refusing symlinked path components. */
|
|
12
17
|
export function assignmentPath(root, taskId, suffix = ".json") {
|
|
13
18
|
safeId(taskId, "task id");
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
path = join(path, part);
|
|
17
|
-
try { if (lstatSync(path).isSymbolicLink()) throw new Error(`Assignment path crosses symlink: ${path}`); }
|
|
18
|
-
catch (error) { if (error?.code !== "ENOENT") throw error; }
|
|
19
|
-
}
|
|
20
|
-
return path;
|
|
19
|
+
if (![".json", ".lock"].includes(suffix)) throw new Error(`Unknown assignment file suffix: ${suffix}`);
|
|
20
|
+
return taskRuntimePath(root, taskId, suffix === ".lock" ? "assignments.lock" : "assignments.json");
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
/** Read location: the runtime file, else the pre-runtime
|
|
24
|
+
* `.agents/data/loop/assignments/<task>.json` until it has been migrated. */
|
|
25
|
+
export const assignmentReadPath = (root, taskId) => resolveTaskRuntimeRead(root, safeId(taskId, "task id"), "assignments.json");
|
|
26
|
+
|
|
23
27
|
function read(root, taskId) {
|
|
24
|
-
const path =
|
|
28
|
+
const path = assignmentReadPath(root, taskId);
|
|
25
29
|
let value;
|
|
26
30
|
try { value = JSON.parse(readFileSync(path, "utf8")); }
|
|
27
31
|
catch (error) {
|
|
@@ -56,44 +60,66 @@ function write(root, taskId, value) {
|
|
|
56
60
|
}
|
|
57
61
|
}
|
|
58
62
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
catch (error) { return error?.code !== "ESRCH"; }
|
|
62
|
-
}
|
|
63
|
-
|
|
63
|
+
/** Short mutex around one read-modify-write of `assignments/<task>.json`
|
|
64
|
+
* (kind lock, 30s TTL, no heartbeat). Waits up to 5s for another holder. */
|
|
64
65
|
function locked(root, taskId, action) {
|
|
66
|
+
// A legacy record moves under runtime/ before the first write, so the
|
|
67
|
+
// writer never has to choose between two locations.
|
|
68
|
+
migrateTaskRuntime(root, taskId, { names: ["assignments.json"] });
|
|
65
69
|
const path = assignmentPath(root, taskId, ".lock");
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
try {
|
|
71
|
-
const fd = openSync(path, "wx", 0o600);
|
|
72
|
-
try { writeFileSync(fd, JSON.stringify({ pid: process.pid, token })); fsyncSync(fd); }
|
|
73
|
-
finally { closeSync(fd); }
|
|
74
|
-
break;
|
|
75
|
-
} catch (error) {
|
|
76
|
-
if (error?.code !== "EEXIST") throw error;
|
|
77
|
-
let owner;
|
|
78
|
-
try { owner = JSON.parse(readFileSync(path, "utf8")); }
|
|
79
|
-
catch { throw new Error(`Corrupt assignment lock for ${taskId}; inspect before retry`); }
|
|
80
|
-
if (!Number.isSafeInteger(owner.pid) || typeof owner.token !== "string") throw new Error(`Corrupt assignment lock for ${taskId}`);
|
|
81
|
-
if (!alive(owner.pid)) {
|
|
82
|
-
const current = JSON.parse(readFileSync(path, "utf8"));
|
|
83
|
-
if (current.token === owner.token) unlinkSync(path);
|
|
84
|
-
continue;
|
|
85
|
-
}
|
|
86
|
-
if (Date.now() >= deadline) throw new Error(`Timed out waiting for assignment lock: ${taskId}`);
|
|
87
|
-
wait(10);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
70
|
+
const handle = acquireHolder({
|
|
71
|
+
path, kind: "lock", scope: `assignments:${taskId}`, purpose: "assignment read-modify-write", waitMs: 5000,
|
|
72
|
+
busyMessage: (entry) => `Timed out waiting for assignment lock: ${taskId}: ${describeHolder(entry)}`,
|
|
73
|
+
});
|
|
90
74
|
try { return action(); }
|
|
91
75
|
finally {
|
|
92
|
-
|
|
93
|
-
|
|
76
|
+
// A release failure must not mask the committed write; the lock then
|
|
77
|
+
// expires on its own after its 30s TTL.
|
|
78
|
+
try { handle.release(); } catch { /* expires via TTL */ }
|
|
94
79
|
}
|
|
95
80
|
}
|
|
96
81
|
|
|
82
|
+
/** User-facing labels for the persisted assignment (claim) states. */
|
|
83
|
+
export const ASSIGNMENT_STATE_LABELS = Object.freeze({
|
|
84
|
+
leased: "claim held",
|
|
85
|
+
expired: "claim expired; needs reconcile",
|
|
86
|
+
uncertain: "expired or outcome unknown; needs reconcile",
|
|
87
|
+
reported: "worker reported; awaiting integration",
|
|
88
|
+
integrated: "integrated",
|
|
89
|
+
released: "released",
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
/** Display state computed at read time: a `leased` claim past its
|
|
93
|
+
* leaseExpiresAt shows as `expired` (mutations persist it as `uncertain`). */
|
|
94
|
+
export function effectiveAssignmentState(assignment, now = Date.now()) {
|
|
95
|
+
return assignment.state === "leased" && Date.parse(assignment.leaseExpiresAt) <= now ? "expired" : assignment.state;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const reconcileCommand = (taskId) => `node .agents/aidlc/scripts/assignment.mjs reconcile ${taskId}`;
|
|
99
|
+
const releaseCommand = (taskId, id) => `node .agents/aidlc/scripts/assignment.mjs release ${taskId} ${id}`;
|
|
100
|
+
|
|
101
|
+
/** One-line description of a claim: id, item, worker, paths, state, expiry. */
|
|
102
|
+
export function describeAssignment(assignment, now = Date.now()) {
|
|
103
|
+
const state = effectiveAssignmentState(assignment, now);
|
|
104
|
+
return `assignment ${assignment.id} (item ${assignment.itemId}, worker ${assignment.workerId}, paths ${(assignment.ownedPaths ?? []).join(", ")}, state ${state}: ${ASSIGNMENT_STATE_LABELS[state] ?? state}, claim expires ${assignment.leaseExpiresAt})`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Remedy commands for a blocking claim. */
|
|
108
|
+
export function assignmentRemedy(taskId, assignment, now = Date.now()) {
|
|
109
|
+
const release = `${releaseCommand(taskId, assignment.id)} to drop the claim (discards the worker's unintegrated result)`;
|
|
110
|
+
const state = effectiveAssignmentState(assignment, now);
|
|
111
|
+
if (state === "leased") return `wait for the worker to report or for the claim to expire; if the worker is gone, run ${release}`;
|
|
112
|
+
if (state === "expired" || state === "uncertain") return `the claim expired or its outcome is unknown: run ${reconcileCommand(taskId)} (marks it integrated if the item is done); if the item is not done and the worker is gone, run ${release}`;
|
|
113
|
+
return `run ${reconcileCommand(taskId)} once the item is done in canonical state; or run ${release}`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const blockedBy = (taskId, assignment) => `${describeAssignment(assignment)}; ${assignmentRemedy(taskId, assignment)}`;
|
|
117
|
+
|
|
118
|
+
function notLeased(taskId, assignmentId, item, suffix = "") {
|
|
119
|
+
if (!item) return new Error(`Assignment is not leased${suffix}: assignment ${assignmentId} does not exist for ${taskId}; inspect: node .agents/aidlc/scripts/assignment.mjs show ${taskId}`);
|
|
120
|
+
return new Error(`Assignment is not leased${suffix}: ${describeAssignment(item)}; run ${reconcileCommand(taskId)}`);
|
|
121
|
+
}
|
|
122
|
+
|
|
97
123
|
function canonical(root, taskId) {
|
|
98
124
|
const raw = execFileSync(process.execPath, [join(root, ".agents/aidlc/scripts/state.mjs"), "task", "show", taskId, "--root", root], { cwd: root, encoding: "utf8", timeout: 30000, maxBuffer: 2 * 1024 * 1024 });
|
|
99
125
|
const result = JSON.parse(raw);
|
|
@@ -108,8 +134,12 @@ function expire(value, now) {
|
|
|
108
134
|
}
|
|
109
135
|
}
|
|
110
136
|
|
|
137
|
+
/** Read-only view: each assignment gains `effectiveState` and `stateLabel`
|
|
138
|
+
* (an overdue `leased` claim shows as `expired`); nothing is written. */
|
|
111
139
|
export function showLocalAssignments(root, taskId) {
|
|
112
|
-
|
|
140
|
+
const value = read(root, safeId(taskId, "task id"));
|
|
141
|
+
const now = Date.now();
|
|
142
|
+
return { ...value, assignments: value.assignments.map((assignment) => { const effectiveState = effectiveAssignmentState(assignment, now); return { ...assignment, effectiveState, stateLabel: ASSIGNMENT_STATE_LABELS[effectiveState] ?? effectiveState }; }) };
|
|
113
143
|
}
|
|
114
144
|
|
|
115
145
|
export function claimLocalAssignment(root, { taskId, itemId, workerId, ownedPaths, leaseMs = 300000, baseCommit = null, branch = null, sessionId = null }) {
|
|
@@ -124,9 +154,11 @@ export function claimLocalAssignment(root, { taskId, itemId, workerId, ownedPath
|
|
|
124
154
|
return locked(root, taskId, () => {
|
|
125
155
|
const value = read(root, taskId); expire(value, Date.now());
|
|
126
156
|
const active = value.assignments.filter((entry) => ["leased", "reported", "uncertain"].includes(entry.state));
|
|
127
|
-
|
|
128
|
-
if (
|
|
129
|
-
|
|
157
|
+
const leased = active.find((entry) => entry.itemId === itemId && entry.state === "leased");
|
|
158
|
+
if (leased) throw new Error(`Item already leased: ${itemId}: claimed by ${blockedBy(taskId, leased)}`);
|
|
159
|
+
const unresolved = value.assignments.find((entry) => entry.itemId === itemId && ["reported", "uncertain"].includes(entry.state));
|
|
160
|
+
if (unresolved) throw new Error(`Item has unresolved assignment; reconcile before retry: ${itemId}: ${blockedBy(taskId, unresolved)}`);
|
|
161
|
+
for (const other of active) for (const path of ownedPaths) for (const prior of other.ownedPaths) if (path === prior || path.startsWith(`${prior}/`) || prior.startsWith(`${path}/`)) throw new Error(`Owned path overlaps active assignment: ${path}: held by ${blockedBy(taskId, other)}`);
|
|
130
162
|
const attempt = 1 + Math.max(0, ...value.assignments.filter((entry) => entry.itemId === itemId).map((entry) => entry.attempt));
|
|
131
163
|
const assignment = { id: randomUUID(), taskId, itemId, workerId, ownedPaths, attempt, baseCommit, branch, sessionId, checkpoint: null, state: "leased", leaseExpiresAt: new Date(Date.now() + leaseMs).toISOString(), idempotencyKey: randomUUID(), createdAt: iso(), updatedAt: iso() };
|
|
132
164
|
value.assignments.push(assignment); write(root, taskId, value);
|
|
@@ -139,7 +171,7 @@ export function heartbeatLocalAssignment(root, taskId, assignmentId, leaseMs = 3
|
|
|
139
171
|
return locked(root, taskId, () => {
|
|
140
172
|
const value = read(root, taskId); expire(value, Date.now());
|
|
141
173
|
const item = value.assignments.find((entry) => entry.id === assignmentId);
|
|
142
|
-
if (!item || item.state !== "leased") throw
|
|
174
|
+
if (!item || item.state !== "leased") throw notLeased(taskId, assignmentId, item);
|
|
143
175
|
if (sessionId !== undefined) { if (typeof sessionId !== "string" || !sessionId || sessionId.length > 300) throw new Error("Invalid sessionId"); item.sessionId = sessionId; }
|
|
144
176
|
item.leaseExpiresAt = new Date(Date.now() + leaseMs).toISOString(); item.updatedAt = iso(); write(root, taskId, value); return item;
|
|
145
177
|
});
|
|
@@ -152,12 +184,26 @@ export function reportLocalAssignment(root, taskId, assignmentId, { checkpoint =
|
|
|
152
184
|
return locked(root, taskId, () => {
|
|
153
185
|
const value = read(root, taskId); expire(value, Date.now());
|
|
154
186
|
const item = value.assignments.find((entry) => entry.id === assignmentId);
|
|
155
|
-
if (!item || item.state !== "leased") throw
|
|
187
|
+
if (!item || item.state !== "leased") throw notLeased(taskId, assignmentId, item, "; reconcile before reporting");
|
|
156
188
|
for (const path of changedPaths) if (!item.ownedPaths.some((owned) => path === owned || path.startsWith(`${owned}/`))) throw new Error(`Changed path is outside assignment scope: ${path}`);
|
|
157
189
|
item.state = "reported"; item.checkpoint = checkpoint; item.commit = commit; item.changedPaths = changedPaths; item.updatedAt = iso(); write(root, taskId, value); return item;
|
|
158
190
|
});
|
|
159
191
|
}
|
|
160
192
|
|
|
193
|
+
/** Record the integration-branch commit created for a reported assignment, so
|
|
194
|
+
* the dashboard can show exactly what the item changed. It never changes the
|
|
195
|
+
* assignment state; `reconcile` still marks the item integrated. */
|
|
196
|
+
export function recordLocalIntegration(root, taskId, assignmentId, integrationCommit) {
|
|
197
|
+
safeId(taskId, "task id");
|
|
198
|
+
if (typeof integrationCommit !== "string" || !/^[a-f0-9]{40,64}$/.test(integrationCommit)) throw new Error("Invalid integration commit");
|
|
199
|
+
return locked(root, taskId, () => {
|
|
200
|
+
const value = read(root, taskId);
|
|
201
|
+
const item = value.assignments.find((entry) => entry.id === assignmentId);
|
|
202
|
+
if (!item || !["reported", "integrated"].includes(item.state)) throw new Error("Only a reported or integrated assignment can record its integration commit");
|
|
203
|
+
item.integrationCommit = integrationCommit; item.updatedAt = iso(); write(root, taskId, value); return item;
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
161
207
|
export function reconcileLocalAssignments(root, taskId) {
|
|
162
208
|
const task = canonical(root, taskId);
|
|
163
209
|
return locked(root, taskId, () => {
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { existsSync, lstatSync, readFileSync, readdirSync, realpathSync } from "node:fs";
|
|
6
6
|
import { dirname, isAbsolute, join, normalize, resolve } from "node:path";
|
|
7
7
|
import { loadMemoryRegistry, nextAction, selectAgenticMemory } from "./store.mjs";
|
|
8
|
+
import { resolveTaskRuntimeRead } from "./task-runtime.mjs";
|
|
8
9
|
|
|
9
10
|
const separator = process.platform === "win32" ? "\\" : "/";
|
|
10
11
|
|
|
@@ -228,10 +229,10 @@ export const compileContext = (root, task, phase = task.phase, options = {}) =>
|
|
|
228
229
|
const phaseContract = readFileSync(phasePath, "utf8");
|
|
229
230
|
let intake = "";
|
|
230
231
|
if (phase === "clarify") {
|
|
231
|
-
|
|
232
|
-
let cursor
|
|
233
|
-
|
|
234
|
-
if (
|
|
232
|
+
// The intake requirement (runtime/requirement.md, or the pre-runtime task-root copy); symlinked components are refused by the resolver.
|
|
233
|
+
let cursor;
|
|
234
|
+
try { cursor = resolveTaskRuntimeRead(root, task.id, "requirement.md"); } catch { throw new Error("Unsafe requirement path"); }
|
|
235
|
+
if (existsSync(cursor)) { const requirement = readFileSync(cursor, "utf8"); if (Buffer.byteLength(requirement) > 8192) throw new Error("Requirement exceeds intake limit"); intake = `## User requirement\n${requirement}`; }
|
|
235
236
|
}
|
|
236
237
|
let content = [`# AI-DLC phase packet — ${phase}`, `mode: ${options.mode ?? "standard"}${options.itemId ? ` · item: ${options.itemId}` : ""}`, "## Next action / stop contract", JSON.stringify(nextAction(task, root), null, 2), "## Phase contract", phaseContract, ...(intake ? [intake] : []), "## Canonical task state", compact, "## Resolved profiles", profiles.map((item) => JSON.stringify({ id: item.id, topology: item.topology, discovery: item.discovery, specs: item.specs, commands: item.commands })).join("\n"), "## Effective project configuration", JSON.stringify({ discovery: effective.discovery, specs: effective.specs, commands: effective.commands, risk: effective.risk, context: effective.context }, null, 2), "## Invariants", invariants].join("\n\n");
|
|
237
238
|
if (content.length > config.context.maxChars) throw new Error("Context budget is too small for the full phase contract, next action, canonical task state, and mandatory invariants");
|