@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
|
@@ -1,90 +1,102 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Loop entry point shared by `aidlc-kit loop`, the web dashboard, and operators.
|
|
4
|
+
*
|
|
5
|
+
* start start the singleton loop daemon for the configured mode (local or fleet)
|
|
6
|
+
* intake create a loop-owned task from a stdin requirement (β€ 8192 bytes) without running it
|
|
7
|
+
* status print the canonical task, loop record/checkpoint, and next action
|
|
8
|
+
* worker-image build and record a provider worker image
|
|
9
|
+
* Dashboard JSON actions (fleet authority; local dashboards read local state in-process):
|
|
10
|
+
* tasks | task <id> | gate <id> | resume-task <id> | approve <id> (source on stdin,
|
|
11
|
+
* reviewer credential only from AIDLC_FLEET_REVIEWER_TOKEN of this process)
|
|
12
|
+
*
|
|
13
|
+
* Positional arguments: [action, taskId, root, mode, provider, tag, title].
|
|
14
|
+
* Gate approval for local tasks stays on the trusted terminal (`loop-gate.mjs`).
|
|
15
|
+
*/
|
|
16
|
+
import { readFileSync } from "node:fs";
|
|
5
17
|
import { resolve, join } from "node:path";
|
|
6
18
|
import { createInterface } from "node:readline/promises";
|
|
7
19
|
import { stdin, stdout } from "node:process";
|
|
8
20
|
import { requireLocalAuthority } from "./lib/authority.mjs";
|
|
9
21
|
import { isLoopProvider } from "./lib/loop-drivers.mjs";
|
|
10
|
-
import {
|
|
22
|
+
import { createLoopAuthority } from "./lib/loop-authority.mjs";
|
|
23
|
+
import { runLoopDaemon } from "./lib/loop-daemon.mjs";
|
|
24
|
+
import { streamLog } from "./lib/loop-stream.mjs";
|
|
11
25
|
import * as localSupervisor from "./lib/loop-supervisor-run-local.mjs";
|
|
12
|
-
import {
|
|
26
|
+
import { buildWorkerImage, defaultWorkerImageTag, recordWorkerImage } from "./lib/loop-worker-image.mjs";
|
|
13
27
|
|
|
14
|
-
const [action, taskArg, rootArg, modeArg] = process.argv.slice(2);
|
|
28
|
+
const [action, taskArg, rootArg, modeArg, providerArg, tagArg, titleArg] = process.argv.slice(2);
|
|
15
29
|
const root = resolve(rootArg || ".");
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
30
|
+
const ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
31
|
+
const ACTIONS = ["start", "resume", "status", "intake", "tasks", "task", "gate", "resume-task", "approve"];
|
|
32
|
+
const readStdin = async (prompt) => {
|
|
33
|
+
if (stdin.isTTY) { const rl = createInterface({ input: stdin, output: stdout }); try { return await rl.question(prompt); } finally { rl.close(); } }
|
|
34
|
+
const chunks = []; for await (const chunk of stdin) { chunks.push(chunk); if (chunks.reduce((n, c) => n + c.length, 0) > 8192) throw new Error("Input exceeds 8192 bytes"); } return Buffer.concat(chunks).toString("utf8");
|
|
19
35
|
};
|
|
36
|
+
const requireTaskId = () => { if (!ID.test(taskArg ?? "")) throw new Error("Valid task ID required"); return taskArg; };
|
|
37
|
+
const print = (value) => console.log(JSON.stringify(value, null, 2));
|
|
38
|
+
|
|
20
39
|
try {
|
|
21
|
-
if (
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
if (
|
|
40
|
-
if (
|
|
41
|
-
if (
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
if (!existsSync(remoteState)) throw new Error(`Remote lifecycle CLI is missing: ${remoteState}`);
|
|
45
|
-
const callTool = ({ url: serviceUrl, token: serviceToken, name, args }) => callRemoteTool({ url: serviceUrl, token: serviceToken }, name, args);
|
|
46
|
-
const repository = createFleetRemoteRepository({ callTool, url, token, workspace, root });
|
|
47
|
-
if (action === "status") {
|
|
48
|
-
let snapshot = null;
|
|
49
|
-
try { snapshot = await repository.readTask({ taskId }); } catch (error) { if (!String(error?.message ?? error).includes("task not found")) throw error; }
|
|
50
|
-
const checkpoint = await repository.readGateCheckpoint({ taskId });
|
|
51
|
-
console.log(JSON.stringify({ taskId, authority: "fleet", task: snapshot?.task ?? null, nextAction: snapshot?.nextAction ?? null, checkpoint }, null, 2));
|
|
40
|
+
if (action === "worker-image") {
|
|
41
|
+
// Build (or rebuild) a worker image without requiring loop configuration.
|
|
42
|
+
const provider = providerArg || "opencode";
|
|
43
|
+
if (!isLoopProvider(provider)) throw new Error("Expected --provider codex, claude, or opencode");
|
|
44
|
+
const image = tagArg || defaultWorkerImageTag(provider);
|
|
45
|
+
buildWorkerImage({ root, provider, image });
|
|
46
|
+
recordWorkerImage({ root, provider, image });
|
|
47
|
+
console.log(`Built worker image: ${image}`);
|
|
48
|
+
console.log("Recorded worker image; the loop supervisor will use it automatically.");
|
|
49
|
+
} else {
|
|
50
|
+
if (!ACTIONS.includes(action)) throw new Error("Expected loop start, status, intake, or worker-image");
|
|
51
|
+
const config = JSON.parse(readFileSync(join(root, ".agents", "config.json"), "utf8"));
|
|
52
|
+
const mode = modeArg || config.loop?.mode || "off";
|
|
53
|
+
if (!["local", "fleet"].includes(mode)) throw new Error("Loop mode is off; configure an enabled loop mode first");
|
|
54
|
+
if (mode !== config.loop?.mode) throw new Error("Requested mode does not match configured authority");
|
|
55
|
+
const agent = config.loop?.agents?.lead;
|
|
56
|
+
const workers = config.loop?.agents?.workers ?? {};
|
|
57
|
+
if (!isLoopProvider(agent) || !Object.keys(workers).length || Object.entries(workers).some(([, value]) => !isLoopProvider(value))) throw new Error("Loop requires a configured lead and worker providers");
|
|
58
|
+
if (action === "resume") throw new Error("loop resume is removed; the daemon auto-resumes actionable tasks β run 'loop start', and re-activate a paused task from the dashboard (resume) or with the task status command");
|
|
59
|
+
if (mode === "local") requireLocalAuthority();
|
|
60
|
+
if (mode === "local" && action === "status") {
|
|
61
|
+
const supervisor = await localSupervisor.createIsolatedLocalSupervisor({ root, taskId: requireTaskId(), agent, workers, workerImages: config.loop?.workerImage, onEvent: streamLog });
|
|
62
|
+
print(await supervisor.status());
|
|
52
63
|
} else {
|
|
53
|
-
|
|
54
|
-
const recordPath = join(root, ".agents/data/tasks", taskId, "loop-intake.json");
|
|
55
|
-
if (!existsSync(recordPath)) throw new Error("Loop record missing; use start");
|
|
56
|
-
const record = JSON.parse(readFileSync(recordPath, "utf8"));
|
|
57
|
-
if (record.taskId !== taskId || record.authority !== "fleet") throw new Error("Loop record authority mismatch; use the recorded authority and task");
|
|
58
|
-
}
|
|
59
|
-
await callRemoteTool({ url, token }, "fleet_get", { workspace });
|
|
60
|
-
await readIntegrationCommit({ projectRoot: root, integrationBranch, gitRemote: env.AIDLC_FLEET_GIT_REMOTE ?? "origin" });
|
|
64
|
+
const authority = await createLoopAuthority({ root, config });
|
|
61
65
|
if (action === "start") {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
66
|
+
// Two-stage shutdown: 1st signal stops after the current turn, 2nd cancels
|
|
67
|
+
// the in-flight agent turn, 3rd exits immediately. Runs end as `stopped`
|
|
68
|
+
// (tasks are not paused) and resume on the next daemon start.
|
|
69
|
+
const stop = new AbortController(), cancel = new AbortController();
|
|
70
|
+
let received = 0;
|
|
71
|
+
const onSignal = () => {
|
|
72
|
+
received += 1;
|
|
73
|
+
if (received === 1) { process.stderr.write("\nloop: stopping after the current turn β send the signal again (Ctrl-C) to cancel it\n"); stop.abort(); }
|
|
74
|
+
else if (received === 2) { process.stderr.write("\nloop: cancelling the in-flight turn β send the signal again to exit immediately\n"); cancel.abort(); }
|
|
75
|
+
else process.exit(130);
|
|
76
|
+
};
|
|
77
|
+
for (const name of ["SIGINT", "SIGTERM"]) process.on(name, onSignal);
|
|
78
|
+
if (typeof authority.preflight === "function") await authority.preflight();
|
|
79
|
+
print(await runLoopDaemon({ root, authority, pollMs: config.loop?.pollMs, signal: stop.signal, cancelSignal: cancel.signal, onEvent: streamLog }));
|
|
80
|
+
} else if (action === "intake") {
|
|
81
|
+
print(await authority.intake({ requirement: await readStdin("Requirement: "), taskId: taskArg || undefined, title: titleArg || undefined }));
|
|
82
|
+
} else if (action === "resume-task") {
|
|
83
|
+
print(await authority.resumeTask(requireTaskId()));
|
|
84
|
+
} else if (mode !== "fleet") {
|
|
85
|
+
throw new Error(`loop ${action} is a fleet dashboard action; local dashboards read canonical local state directly`);
|
|
86
|
+
} else if (action === "status") {
|
|
87
|
+
const taskId = requireTaskId();
|
|
88
|
+
const snapshot = await authority.getTask(taskId);
|
|
89
|
+
print({ taskId, authority: "fleet", task: snapshot.task, nextAction: snapshot.nextAction, checkpoint: await authority.repository.readGateCheckpoint({ taskId }) });
|
|
90
|
+
} else if (action === "tasks") {
|
|
91
|
+
print({ ok: true, authority: "fleet", tasks: await authority.listTasks() });
|
|
92
|
+
} else if (action === "task") {
|
|
93
|
+
print({ ok: true, authority: "fleet", ...(await authority.getTask(requireTaskId())) });
|
|
94
|
+
} else if (action === "gate") {
|
|
95
|
+
print({ ok: true, authority: "fleet", ...(await authority.gate(requireTaskId())) });
|
|
96
|
+
} else if (action === "approve") {
|
|
97
|
+
const taskId = requireTaskId();
|
|
98
|
+
print(await authority.approve({ taskId, source: (await readStdin("Approval source: ")).trim(), reviewerToken: process.env.AIDLC_FLEET_REVIEWER_TOKEN }));
|
|
73
99
|
}
|
|
74
|
-
await runFleetCli(["supervisor", taskId], { callTool, env, out: (line) => console.log(line) });
|
|
75
100
|
}
|
|
76
|
-
} else {
|
|
77
|
-
requireLocalAuthority();
|
|
78
|
-
// The factory contract guarantees that the dispatcher is actually wired into
|
|
79
|
-
// the supervisor, not merely present in the package. No host fallback.
|
|
80
|
-
if (typeof localSupervisor.createIsolatedLocalSupervisor !== "function") throw new Error("Isolated local worker dispatcher is not integrated; loop execution is unavailable");
|
|
81
|
-
const supervisor = await localSupervisor.createIsolatedLocalSupervisor({ root, taskId, agent, workers: config.loop.agents.workers });
|
|
82
|
-
if (action === "status") {
|
|
83
|
-
console.log(JSON.stringify(await supervisor.status(), null, 2));
|
|
84
|
-
} else if (action === "start") {
|
|
85
|
-
const requirement = await readRequirement();
|
|
86
|
-
await prepareLoopIntake({ root, taskId, requirement, authority: "local", readTask: async (id) => loadTask(root, id), createTask: async ({ taskId: id, title }) => { execFileSync(process.execPath, [join(root, ".agents/aidlc/scripts/state.mjs"), "task", "create", id, "--title", title, "--root", root], { stdio: ["ignore", "pipe", "pipe"] }); } });
|
|
87
|
-
console.log(JSON.stringify(await supervisor.start(), null, 2));
|
|
88
|
-
} else console.log(JSON.stringify(await supervisor.resume(), null, 2));
|
|
89
101
|
}
|
|
90
102
|
} catch (error) { console.error(`loop: ${error.message}`); process.exitCode = 1; }
|
|
@@ -41,18 +41,30 @@ async function main() {
|
|
|
41
41
|
console.log(review.view.trimEnd());
|
|
42
42
|
console.log(`Review identity: ${JSON.stringify({ taskId, phase: review.phase, gate: review.gate, taskHash: review.taskHash, artifacts: review.artifacts, integrationCommit: review.commit })}`);
|
|
43
43
|
const rl = createInterface({ input: stdin, output: stdout });
|
|
44
|
-
let answer; try { answer = await rl.question("Type approve to approve this exact review: "); } finally { rl.close(); }
|
|
45
|
-
|
|
44
|
+
let answer; try { answer = await rl.question("Type approve to approve this exact review, or type feedback to send guidance back to the loop: "); } finally { rl.close(); }
|
|
45
|
+
const trimmed = answer.trim();
|
|
46
|
+
if (!trimmed) throw new Error("Review was not approved");
|
|
46
47
|
release = acquireStateLock(root);
|
|
47
48
|
try {
|
|
48
49
|
const state = loadState(root), fresh = snapshot(root, taskId, state);
|
|
49
50
|
if (digest({ ...fresh, view: undefined }) !== digest({ ...review, view: undefined })) throw new Error("Review changed while waiting for approval; inspect the new review");
|
|
50
|
-
const
|
|
51
|
+
const task = state.tasks[taskId];
|
|
51
52
|
const reviewHash = digest({ ...review, view: undefined });
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
if (trimmed === "approve") {
|
|
54
|
+
const checkpoint = readLocalGateCheckpoint(root, task);
|
|
55
|
+
const outcome = approveAndAdvance(root, state, taskId, review.gate, `Trusted host terminal: approve (${reviewHash})`);
|
|
56
|
+
saveState(root, state);
|
|
57
|
+
writeLocalGateReceipt(root, checkpoint, outcome.task, reviewHash);
|
|
58
|
+
console.log(`Approved ${review.gate}; next action: ${JSON.stringify(outcome.nextAction)}`);
|
|
59
|
+
} else {
|
|
60
|
+
const recordedAt = new Date().toISOString();
|
|
61
|
+
task.evidence.push({ kind: "diagnostic", result: "pass", source: `Trusted host terminal: feedback (${reviewHash})`, detail: `Cancelled gate wait at ${task.gate}: ${trimmed}`, recordedAt });
|
|
62
|
+
delete task.legacyG2Wait;
|
|
63
|
+
task.status = "active";
|
|
64
|
+
task.updatedAt = recordedAt;
|
|
65
|
+
saveState(root, state);
|
|
66
|
+
console.log(`Feedback recorded at ${review.gate}; the loop will revise the review and re-present the gate`);
|
|
67
|
+
}
|
|
56
68
|
} finally { release(); }
|
|
57
69
|
}
|
|
58
70
|
main().catch((error) => { console.error(`loop-gate: ${error.message}`); process.exitCode = 1; });
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import { loopStatus, runLoop } from "./lib/loop-runtime.mjs";
|
|
4
4
|
import { LOOP_PROVIDERS, runAgentTurn } from "./lib/loop-drivers.mjs";
|
|
5
|
+
import { streamLog } from "./lib/loop-stream.mjs";
|
|
5
6
|
import { requireLocalAuthority } from "./lib/authority.mjs";
|
|
6
7
|
|
|
7
8
|
function parse(argv) {
|
|
@@ -27,7 +28,7 @@ function parse(argv) {
|
|
|
27
28
|
try {
|
|
28
29
|
requireLocalAuthority();
|
|
29
30
|
const options = parse(process.argv.slice(2));
|
|
30
|
-
const result = options.command === "status" ? loopStatus(options.root, options.taskId) : await runLoop({ root: options.root, taskId: options.taskId, agent: options.agent, mode: options.command, limits: options.limits, driver: runAgentTurn, preflight: true, onEvent: (event) => { if (event?.type === "diagnostic") console.error(event.message); if (event?.type === "resume" || event?.type === "context_changed") console.error(JSON.stringify(event)); } });
|
|
31
|
+
const result = options.command === "status" ? loopStatus(options.root, options.taskId) : await runLoop({ root: options.root, taskId: options.taskId, agent: options.agent, mode: options.command, limits: options.limits, driver: runAgentTurn, preflight: true, onEvent: (event) => { streamLog(event); if (event?.type === "diagnostic") console.error(event.message); if (event?.type === "resume" || event?.type === "context_changed") console.error(JSON.stringify(event)); } });
|
|
31
32
|
console.log(JSON.stringify({ ok: true, result }, null, 2));
|
|
32
33
|
if (result.status && !["complete", "gate"].includes(result.status)) process.exitCode = 2;
|
|
33
34
|
} catch (error) {
|
|
@@ -69,6 +69,7 @@ export const runRemoteState = async (argv, client, root, git = {}) => {
|
|
|
69
69
|
if (!extra && ["task item", "decision set", "lesson record"].includes(route)) throw new Error(`${route} requires an item/decision/lesson id`);
|
|
70
70
|
|
|
71
71
|
const current = await client.get();
|
|
72
|
+
// --- Read-only routes ------------------------------------------------------
|
|
72
73
|
if (!Number.isSafeInteger(current?.revision) || !current?.state?.tasks) throw new Error("Remote coordination returned invalid canonical state");
|
|
73
74
|
const state = current.state;
|
|
74
75
|
const previous = state.tasks[id];
|
|
@@ -100,6 +101,7 @@ export const runRemoteState = async (argv, client, root, git = {}) => {
|
|
|
100
101
|
return { ok: true, result: await client.snapshot(current.revision, id, gate, reviewText, integrationCommit) };
|
|
101
102
|
}
|
|
102
103
|
|
|
104
|
+
// --- Mutating routes -------------------------------------------------------
|
|
103
105
|
let task;
|
|
104
106
|
if (route === "task create") {
|
|
105
107
|
if (previous || state.archive?.[id]) throw new Error(`Task already exists: ${id}`);
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* AI-DLC lifecycle CLI. Parses argv, dispatches to the canonical state module,
|
|
4
|
+
* and emits the JSON result with the nextAction for the lead.
|
|
5
|
+
*/
|
|
2
6
|
import { acquireStateLock, approveAndAdvance, checkGate, closeTask, failEntry, handoffTask, listTaskSummaries, loadMemoryRegistry, loadState, loadTask, migrateState, nextAction, option, parseArguments, promoteAgenticMemory, rebuildLessonIndex, recordLesson, recordNoLessons, renderViews, reopenTask, retireAgenticMemory, rootOption, saveState, searchLessons, supersedeTask, transitionTask, validateArguments } from "./lib/runtime.mjs";
|
|
3
7
|
import { readLiveLease } from "./lib/loop-lease.mjs";
|
|
4
8
|
import { listFeatures, loadFeature, readyFeatureTasks, saveFeature } from "./lib/feature-store.mjs";
|
|
@@ -11,5 +11,14 @@
|
|
|
11
11
|
## π Cross-service contracts
|
|
12
12
|
- <contract or `none`>
|
|
13
13
|
|
|
14
|
+
## π Parallel work partition
|
|
15
|
+
| item | owned paths | depends on | verification |
|
|
16
|
+
|------|-------------|------------|--------------|
|
|
17
|
+
| <item-id> | <paths it alone writes> | <item-ids or `none`> | <narrow command> |
|
|
18
|
+
|
|
19
|
+
- One owner per path among items that can run at the same time; overlapping items must be ordered with `dependsOn`.
|
|
20
|
+
- Shared surfaces (barrel/index files, shared config, cross-cutting docs, a single entry script) get exactly one owning item; others depend on it.
|
|
21
|
+
- Prefer file/module-level ownership over broad parent directories.
|
|
22
|
+
|
|
14
23
|
## β οΈ Risks / edge cases
|
|
15
24
|
- <risk and mitigation>
|
|
@@ -6,6 +6,10 @@
|
|
|
6
6
|
## πΊοΈ Affected areas
|
|
7
7
|
- `<area>` β <why> β spec: <indexed source or `none indexed`>
|
|
8
8
|
|
|
9
|
+
## π Work boundaries
|
|
10
|
+
- `<work stream>` β <code areas/paths it touches> β <independent of / depends on>
|
|
11
|
+
- or `single stream` β <why the work cannot be split>
|
|
12
|
+
|
|
9
13
|
## π Assumptions
|
|
10
14
|
- <assumption>
|
|
11
15
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: aidlc-plan
|
|
3
|
-
description: "AI-DLC phase 2 (Approve plan): after intent is confirmed, run bounded discovery, then produce a detailed design, canonical decisions, and a per-submodule workplan. Triggers when the AI-DLC Orchestrator routes a task after requirements confirmation to planning. Ends at the executable Approve plan review for explicit user approval."
|
|
3
|
+
description: "AI-DLC phase 2 (Approve plan): after intent is confirmed, run bounded discovery, then produce a detailed design, canonical decisions, and a per-submodule workplan whose items own non-overlapping paths so parallel workers do not collide. Triggers when the AI-DLC Orchestrator routes a task after requirements confirmation to planning. Ends at the executable Approve plan review for explicit user approval."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# AI-DLC Β· Plan (Approve plan) β adapter
|