@felixdotgo/aidlc-kit 0.2.0 → 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 +13 -26
- package/README.md +35 -39
- package/dist/assets/.agents/aidlc/orchestrator.md +1 -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 +21 -5
- package/dist/assets/.agents/aidlc/scripts/lib/loop-daemon.mjs +52 -78
- package/dist/assets/.agents/aidlc/scripts/lib/loop-drivers.mjs +88 -14
- package/dist/assets/.agents/aidlc/scripts/lib/loop-env.mjs +279 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet-cli.mjs +20 -5
- package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet-remote.mjs +10 -1
- package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet-supervisor.mjs +2 -1
- package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet.mjs +3 -2
- package/dist/assets/.agents/aidlc/scripts/lib/loop-git.mjs +9 -3
- package/dist/assets/.agents/aidlc/scripts/lib/loop-holder.mjs +376 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-intake.mjs +11 -3
- 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 +78 -17
- package/dist/assets/.agents/aidlc/scripts/lib/loop-runtime.mjs +42 -10
- package/dist/assets/.agents/aidlc/scripts/lib/loop-stream.mjs +66 -9
- 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 +12 -6
- package/dist/assets/.agents/aidlc/scripts/lib/loop-worker-docker.mjs +14 -2
- 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 +11 -0
- 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 +87 -4
- package/dist/assets/.agents/aidlc/scripts/loop.cmd +101 -0
- 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/ui/changes.js +83 -0
- package/dist/assets/ui/common.js +664 -0
- package/dist/assets/ui/dashboard.css +281 -59
- 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/package.json +1 -1
- package/dist/services/aidlc-coordination/src/lifecycle-core.mjs +11 -0
- package/dist/src/cli/argv.js +10 -4
- package/dist/src/cli/commands.js +52 -22
- package/dist/src/cli/loop-launcher.d.ts +47 -0
- package/dist/src/cli/loop-launcher.js +322 -0
- package/dist/src/cli.js +3 -0
- package/dist/src/installer.js +4 -2
- package/dist/src/model/records.d.ts +4 -0
- package/dist/src/state/validate.js +5 -0
- package/dist/src/task-ui.d.ts +31 -52
- package/dist/src/task-ui.js +25 -1562
- 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.js +5 -2
- package/dist/src/workflow.d.ts +4 -0
- package/dist/src/workflow.js +33 -1
- package/docs/README.md +48 -33
- package/docs/architecture.md +101 -38
- package/docs/command-reference.md +269 -93
- package/docs/configuration.md +62 -42
- package/docs/coordination-service.md +85 -36
- package/docs/design-decisions.md +35 -31
- package/docs/development.md +111 -61
- package/docs/loop-local-run.md +135 -402
- package/docs/loop-runbook.md +84 -105
- package/docs/operating-workflow.md +55 -42
- package/docs/testing-and-release.md +70 -51
- package/docs/troubleshooting.md +165 -69
- package/package.json +1 -1
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import { taskContainers } from "../containers.js";
|
|
2
|
+
import { ARTIFACT_NAMES, artifactPayload, pendingFeedback, readUiState, readUiTask, taskGatePayload } from "../data.js";
|
|
3
|
+
import { escapeHtml } from "../http.js";
|
|
4
|
+
import { markdownToHtml } from "../markdown.js";
|
|
5
|
+
import { uiMode } from "../options.js";
|
|
6
|
+
import { DECISION_TONE, feedbackPill, GATE_LABELS, gateLabel, gatePill, ITEM_TONE, phaseStepper, pill, relativeTime, renderGateViewHtml, RESULT_TONE, riskPill, statusPill } from "./components.js";
|
|
7
|
+
import { changesHref } from "./changes.js";
|
|
8
|
+
import { liveRegion, pageHead, pageScripts, renderHeader, renderModals, reviewQueue } from "./layout.js";
|
|
9
|
+
/* ---------------------------------------------------------------------------
|
|
10
|
+
* Task detail (page and side-panel fragment share the same body).
|
|
11
|
+
* ------------------------------------------------------------------------- */
|
|
12
|
+
/** The artifact a reviewer most likely needs at each gate opens by default. */
|
|
13
|
+
const GATE_ARTIFACT = { G0_confirm: "intent", G1_review: "design", G2_codereview: "workplan" };
|
|
14
|
+
/** Lines kept by the task page's live log and shown by the side panel's compact one. */
|
|
15
|
+
const FULL_LOG_LINES = 2000;
|
|
16
|
+
export const DRAWER_LOG_LINES = 20;
|
|
17
|
+
/** Task id as a copy-to-clipboard button (handled by the delegated \`[data-copy]\` click in common.js). */
|
|
18
|
+
export const copyIdButton = (id) => `<button type="button" class="ad-copy" data-copy="${escapeHtml(id)}" title="Copy task ID" aria-label="Copy task ID ${escapeHtml(id)}"><code>${escapeHtml(id)}</code><svg aria-hidden="true" viewBox="0 0 16 16" width="13" height="13" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="5" y="5" width="8.5" height="8.5" rx="1.5"/><path d="M10.5 5V3.5A1.5 1.5 0 0 0 9 2H3.5A1.5 1.5 0 0 0 2 3.5V9a1.5 1.5 0 0 0 1.5 1.5H5"/></svg></button>`;
|
|
19
|
+
const panel = (title, body, extra = "", flush = false) => `<section class="ad-panel" data-key="panel:${escapeHtml(title)}"><header class="ad-panel-head"><h2>${escapeHtml(title)}</h2>${extra}</header><div class="ad-panel-body${flush ? " is-flush" : ""}">${body}</div></section>`;
|
|
20
|
+
/* ---------------------------------------------------------------------------
|
|
21
|
+
* Containers panel (worker containers recorded under the task's runtime data)
|
|
22
|
+
* ------------------------------------------------------------------------- */
|
|
23
|
+
const UUID_PATTERN = /([0-9a-f]{8})-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i;
|
|
24
|
+
/** `aidlc-worker-3f2a1b9c-…` → `3f2a1b9c`; names without a uuid are shown whole. */
|
|
25
|
+
export const shortContainerName = (name) => UUID_PATTERN.exec(name)?.[1] ?? name;
|
|
26
|
+
/** `endedAt - startedAt` as `4s`, `3m 12s`, or `1h 05m`; empty when either timestamp is unusable. */
|
|
27
|
+
export const durationText = (startedAt, endedAt) => {
|
|
28
|
+
const start = Date.parse(startedAt ?? "");
|
|
29
|
+
const end = Date.parse(endedAt ?? "");
|
|
30
|
+
if (Number.isNaN(start) || Number.isNaN(end))
|
|
31
|
+
return "";
|
|
32
|
+
const seconds = Math.max(0, Math.round((end - start) / 1000));
|
|
33
|
+
if (seconds < 60)
|
|
34
|
+
return `${seconds}s`;
|
|
35
|
+
if (seconds < 3600)
|
|
36
|
+
return `${Math.floor(seconds / 60)}m ${String(seconds % 60).padStart(2, "0")}s`;
|
|
37
|
+
return `${Math.floor(seconds / 3600)}h ${String(Math.floor((seconds % 3600) / 60)).padStart(2, "0")}m`;
|
|
38
|
+
};
|
|
39
|
+
const containerStatusPill = (entry) => {
|
|
40
|
+
if (entry.running)
|
|
41
|
+
return pill("running", "is-success is-live", "the container is running");
|
|
42
|
+
if (!entry.endedAt)
|
|
43
|
+
return pill("unknown", "is-warning", "no end recorded and docker does not list the container");
|
|
44
|
+
if (entry.exitCode === null || entry.exitCode === undefined)
|
|
45
|
+
return pill("signal", "is-warning", "ended without an exit code (signal or finalized)");
|
|
46
|
+
return pill(`exited ${entry.exitCode}`, entry.exitCode === 0 ? "is-ghost" : "is-danger", `exit code ${entry.exitCode}`);
|
|
47
|
+
};
|
|
48
|
+
/** The Containers panel: a table of the task's recorded worker containers (newest first) and the log viewer. */
|
|
49
|
+
export const renderContainersPanel = (entries, docker) => {
|
|
50
|
+
const rows = [...entries].reverse().map((entry) => {
|
|
51
|
+
const logButton = entry.local
|
|
52
|
+
? `<button type="button" class="button is-small" data-container-log="${escapeHtml(entry.assignmentId)}" data-container-name="${escapeHtml(entry.name)}" data-container-running="${entry.running ? "1" : "0"}" title="Show the raw container output">Log</button>`
|
|
53
|
+
: `<button type="button" class="button is-small" disabled title="log is on another host">Log</button>`;
|
|
54
|
+
return `<tr data-key="container:${escapeHtml(entry.name)}:${escapeHtml(String(entry.turn ?? ""))}">
|
|
55
|
+
<td><code title="${escapeHtml(entry.name)}">${escapeHtml(shortContainerName(entry.name))}</code>${entry.local || !entry.host ? "" : `<div class="is-size-7 has-text-grey" title="recorded on another host">${escapeHtml(entry.host)}</div>`}</td>
|
|
56
|
+
<td class="is-size-7">${escapeHtml(entry.workerId ?? "")}${entry.agent ? `<div class="has-text-grey">${escapeHtml(entry.agent)}</div>` : ""}</td>
|
|
57
|
+
<td><code>${escapeHtml(entry.itemId ?? "")}</code></td>
|
|
58
|
+
<td class="is-size-7">${entry.turn === undefined ? "" : escapeHtml(String(entry.turn))}</td>
|
|
59
|
+
<td>${containerStatusPill(entry)}</td>
|
|
60
|
+
<td class="is-size-7 has-text-grey">${relativeTime(entry.startedAt)}${entry.endedAt ? `<div title="duration">${escapeHtml(durationText(entry.startedAt, entry.endedAt))}</div>` : ""}</td>
|
|
61
|
+
<td>${logButton}</td>
|
|
62
|
+
</tr>`;
|
|
63
|
+
});
|
|
64
|
+
const table = rows.length
|
|
65
|
+
? `<table class="table ad-table is-fullwidth"><thead><tr>${["container", "worker", "item", "turn", "status", "started", ""].map((h) => `<th>${escapeHtml(h)}</th>`).join("")}</tr></thead><tbody>${rows.join("\n")}</tbody></table>`
|
|
66
|
+
: `<div class="ad-empty-line"><p>No worker containers recorded for this task</p><p class="is-size-7 mt-1">Containers appear here while the loop builds the task's work items; a task driven from the terminal without the loop never starts any.</p></div>`;
|
|
67
|
+
/* The viewer is client-owned (detail.js): live updates never morph inside it, so an open log survives polling. */
|
|
68
|
+
const viewer = `<section class="ad-terminal ad-container-log" id="container-log" data-live-ignore hidden>
|
|
69
|
+
<div class="ad-terminal-head"><strong>Container log</strong><span class="ad-log-state" data-container-log-name></span><span class="ad-log-state" data-container-log-state></span>
|
|
70
|
+
<div class="ad-terminal-tools"><button type="button" data-container-follow aria-pressed="true" title="Re-fetch the tail while the container runs">Follow</button><button type="button" data-container-close>Close</button></div>
|
|
71
|
+
</div>
|
|
72
|
+
<pre class="log-panel" data-container-log-body role="log" aria-live="off"></pre>
|
|
73
|
+
</section>`;
|
|
74
|
+
const tools = `<span class="ad-panel-tools">${pill(`docker · ${docker}`, docker === "ok" ? "is-ghost" : "is-warning", docker === "ok" ? "docker ps answers; running state is live" : "docker did not answer; running state follows the index only", false)}<span class="ad-count">${entries.length}</span></span>`;
|
|
75
|
+
return panel("Containers", table + viewer, tools, true);
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* Task detail body shared by the full page (`detail` live region), the side
|
|
79
|
+
* panel (`fragment`, `drawer` live region), and `/api/live`. Undefined when the
|
|
80
|
+
* task does not exist. Keyed panels (`id`/`data-key`) let the client morph them
|
|
81
|
+
* in place: open artifacts, typed feedback, and the live log survive updates.
|
|
82
|
+
*/
|
|
83
|
+
export const renderTaskBody = async (options, state, id, fragment, maxAgeMs = 0) => {
|
|
84
|
+
const task = readUiTask(options, id, state);
|
|
85
|
+
const fleet = uiMode(options) === "fleet";
|
|
86
|
+
if (!task)
|
|
87
|
+
return undefined;
|
|
88
|
+
const waiting = task.status === "blocked_on_user" && task.gate !== "none";
|
|
89
|
+
// A gate whose structural checks now fail (e.g. an artifact edited after the
|
|
90
|
+
// wait was set) makes gateView throw; show the diagnostics instead of a 500.
|
|
91
|
+
let gateBlock = "";
|
|
92
|
+
// At code review the reviewer needs the diff: link the changes page from the gate panel.
|
|
93
|
+
const gateChanges = task.gate === "G2_codereview"
|
|
94
|
+
? `<p class="mt-3"><a class="button is-small is-link is-light" href="${changesHref(task.id)}" data-changes-link="gate">View code changes →</a></p>`
|
|
95
|
+
: "";
|
|
96
|
+
if (waiting) {
|
|
97
|
+
try {
|
|
98
|
+
const gate = await taskGatePayload(options, id, state, maxAgeMs);
|
|
99
|
+
if (gate.ok && gate.view)
|
|
100
|
+
gateBlock = renderGateViewHtml(gate.view, gateChanges);
|
|
101
|
+
else if (gate.ok && gate.snapshot)
|
|
102
|
+
gateBlock = `<section class="ad-panel is-warning" data-key="gate-view"><header class="ad-panel-head"><h2>${escapeHtml(gateLabel(task.gate))} · review snapshot</h2><span class="ad-pill is-ghost ad-panel-tools">${escapeHtml(gate.snapshot.id ?? "")}</span></header><div class="ad-panel-body"><div class="content artifact">${markdownToHtml(gate.snapshot.reviewText ?? "")}</div>${gateChanges}</div></section>`;
|
|
103
|
+
else if (fleet)
|
|
104
|
+
gateBlock = `<section class="ad-panel is-warning" data-key="gate-view"><header class="ad-panel-head"><h2>${escapeHtml(gateLabel(task.gate))}</h2></header><div class="ad-panel-body"><p>${escapeHtml(gate.error ?? "No review snapshot yet")}</p></div></section>`;
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
const problems = (error instanceof Error ? error.message : String(error)).split("; ").filter(Boolean);
|
|
108
|
+
gateBlock = `<section class="ad-panel is-danger" data-key="gate-view">
|
|
109
|
+
<header class="ad-panel-head"><span aria-hidden="true">⚠</span><h2>${escapeHtml(gateLabel(task.gate))} · gate check failing</h2><span class="ad-pill is-danger ad-panel-tools">${escapeHtml(task.gate)}</span></header>
|
|
110
|
+
<div class="ad-panel-body">
|
|
111
|
+
<p class="mb-3">The task waits at this gate, but its structural checks no longer pass. Approval will be rejected until these are fixed:</p>
|
|
112
|
+
<ul class="ad-plain-list">${problems.map((problem) => `<li>${pill("ERROR", "is-danger")} ${escapeHtml(problem)}</li>`).join("")}</ul>
|
|
113
|
+
<p class="is-size-7 has-text-grey mt-3">Re-check with <code>node .agents/aidlc/scripts/gate-check.mjs ${escapeHtml(task.id)} --gate ${escapeHtml(task.gate)}</code>.</p>
|
|
114
|
+
</div>
|
|
115
|
+
</section>`;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/* Pending feedback: `Cancelled gate wait` evidence with no later approval of
|
|
119
|
+
* that gate. While the task is active the lead is revising for it; while it
|
|
120
|
+
* waits again, the reviewer should check the revision reflects it. */
|
|
121
|
+
const pending = pendingFeedback(task);
|
|
122
|
+
const pendingBlock = !fragment && pending.length
|
|
123
|
+
? `<section class="ad-panel is-info" id="feedback-pending-panel">
|
|
124
|
+
<header class="ad-panel-head"><h2>Feedback pending</h2><span class="ad-pill is-info ad-panel-tools">${pending.length}</span></header>
|
|
125
|
+
<div class="ad-panel-body">
|
|
126
|
+
<p class="mb-2">${waiting
|
|
127
|
+
? "This gate was re-presented after human feedback. Check that the review artifact reflects it before approving; approving records the feedback as handled."
|
|
128
|
+
: "The task stays at its gate. The loop lead is revising the review artifact for this feedback and will re-present the same gate."}</p>
|
|
129
|
+
<ul class="ad-plain-list">${pending.map((entry) => `<li>${pill(gateLabel(entry.gate), "is-info", `gate: ${entry.gate}`, false)} ${escapeHtml(entry.text)} <span class="is-size-7 has-text-grey">· ${relativeTime(entry.recordedAt)}</span></li>`).join("")}</ul>
|
|
130
|
+
</div>
|
|
131
|
+
</section>`
|
|
132
|
+
: "";
|
|
133
|
+
/* Feedback never needs the fleet reviewer credential (it never approves), so
|
|
134
|
+
* the block renders in both modes whenever the task waits at a gate. */
|
|
135
|
+
const feedbackBlock = !fragment && waiting
|
|
136
|
+
? `<section class="ad-panel is-info" id="feedback-panel" data-gate="${escapeHtml(task.gate)}" data-gate-label="${escapeHtml(gateLabel(task.gate))}">
|
|
137
|
+
<header class="ad-panel-head"><h2>Feedback · ${escapeHtml(gateLabel(task.gate))}</h2><span class="ad-pill is-ghost ad-panel-tools">stays at this gate</span></header>
|
|
138
|
+
<div class="ad-panel-body">
|
|
139
|
+
<p class="mb-2">Not ready to approve? Send guidance back to the loop. The task stays at <strong>${escapeHtml(gateLabel(task.gate))}</strong>; the lead revises the review artifact and re-presents this gate.</p>
|
|
140
|
+
<div class="field">
|
|
141
|
+
<label class="label is-small" for="feedback">Feedback <span class="has-text-grey has-text-weight-normal">(required, sent to the loop lead)</span></label>
|
|
142
|
+
<div class="control"><textarea id="feedback" class="textarea" rows="3" placeholder="e.g. the scope misses the fleet path; split item B; the assumption about X is wrong"></textarea></div>
|
|
143
|
+
</div>
|
|
144
|
+
<div class="is-flex is-align-items-center" style="gap:0.75rem">
|
|
145
|
+
<button id="feedback-button" class="button is-info">✎ Send feedback (stay at gate)</button>
|
|
146
|
+
<span id="feedback-result" class="help m-0" role="status"></span>
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
</section>`
|
|
150
|
+
: "";
|
|
151
|
+
const approveBlock = !fragment && waiting && fleet && !options.reviewerToken
|
|
152
|
+
? `<section class="ad-panel" id="approve-panel">
|
|
153
|
+
<header class="ad-panel-head"><h2>Approve · ${escapeHtml(gateLabel(task.gate))}</h2><span class="ad-pill is-ghost ad-panel-tools">${escapeHtml(task.gate)}</span></header>
|
|
154
|
+
<div class="ad-panel-body">
|
|
155
|
+
<p class="mb-2">This dashboard has no fleet reviewer credential. Approve from a reviewer terminal, or restart <code>aidlc-kit ui</code> with <code>AIDLC_FLEET_REVIEWER_TOKEN</code> or <code>--reviewer-token-file</code>. Feedback above needs no reviewer credential.</p>
|
|
156
|
+
<pre class="is-size-7">node .agents/aidlc/scripts/remote-state.mjs gate inbox
|
|
157
|
+
node .agents/aidlc/scripts/remote-state.mjs gate approve ${escapeHtml(task.id)} --snapshot <snapshot-id> --source "<explicit approval>"</pre>
|
|
158
|
+
</div>
|
|
159
|
+
</section>`
|
|
160
|
+
: !fragment && waiting
|
|
161
|
+
? `<section class="ad-panel is-success" id="approve-panel" data-gate="${escapeHtml(task.gate)}" data-gate-label="${escapeHtml(gateLabel(task.gate))}">
|
|
162
|
+
<header class="ad-panel-head"><h2>Approve · ${escapeHtml(gateLabel(task.gate))}</h2><span class="ad-pill is-ghost ad-panel-tools">${escapeHtml(task.gate)}</span></header>
|
|
163
|
+
<div class="ad-panel-body">
|
|
164
|
+
<p class="mb-2">Approving moves the task to the next phase. To request changes instead, use <strong>Feedback</strong> above — the note below is provenance only, not guidance for the loop.</p>
|
|
165
|
+
${pending.length ? `<p class="help is-warning mb-3" id="approve-pending-warning">⚠ ${pending.length} feedback item${pending.length === 1 ? "" : "s"} still pending — make sure the revised review addresses ${pending.length === 1 ? "it" : "them"} before approving.</p>` : ""}
|
|
166
|
+
<div class="field">
|
|
167
|
+
<label class="label is-small" for="source">Approval note <span class="has-text-grey has-text-weight-normal">(required, recorded as provenance)</span></label>
|
|
168
|
+
<div class="control"><textarea id="source" class="textarea" rows="2" placeholder="e.g. reviewed intent and scope — ok"></textarea></div>
|
|
169
|
+
</div>
|
|
170
|
+
<div class="is-flex is-align-items-center" style="gap:0.75rem">
|
|
171
|
+
<button id="approve-button" class="button is-success">✓ Approve</button>
|
|
172
|
+
<span id="approve-result" class="help m-0" role="status"></span>
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
</section>`
|
|
176
|
+
: "";
|
|
177
|
+
/* Confirmation dialog: the only control that sends the approve request. */
|
|
178
|
+
const approveModal = !fragment && waiting && !(fleet && !options.reviewerToken)
|
|
179
|
+
? `<div id="approve-modal" class="modal" role="dialog" aria-modal="true" aria-labelledby="approve-modal-title" data-live-preserve="class">
|
|
180
|
+
<div class="modal-background" data-close></div>
|
|
181
|
+
<div class="modal-card">
|
|
182
|
+
<header class="modal-card-head">
|
|
183
|
+
<p class="modal-card-title" id="approve-modal-title">Approve ${escapeHtml(gateLabel(task.gate))}?</p>
|
|
184
|
+
<button class="delete" aria-label="close" data-close></button>
|
|
185
|
+
</header>
|
|
186
|
+
<section class="modal-card-body">
|
|
187
|
+
<p>This approves <strong>${escapeHtml(gateLabel(task.gate))}</strong> (<code>${escapeHtml(task.gate)}</code>) for <code>${escapeHtml(task.id)}</code> and moves the task to the next phase. It cannot be undone from the dashboard.</p>
|
|
188
|
+
<p class="mt-2">To request changes instead, cancel and use <strong>Feedback</strong> — the task then stays at this gate.</p>
|
|
189
|
+
<p class="is-size-7 has-text-grey mt-3 mb-1">Approval note</p>
|
|
190
|
+
<blockquote id="approve-modal-note" class="box is-size-7 py-2 px-3 mb-0"></blockquote>
|
|
191
|
+
</section>
|
|
192
|
+
<footer class="modal-card-foot">
|
|
193
|
+
<button class="button" data-close data-autofocus>Cancel</button>
|
|
194
|
+
<button id="approve-confirm" class="button is-success">✓ Confirm approve</button>
|
|
195
|
+
</footer>
|
|
196
|
+
</div>
|
|
197
|
+
</div>`
|
|
198
|
+
: "";
|
|
199
|
+
const openArtifact = waiting ? GATE_ARTIFACT[task.gate] : undefined;
|
|
200
|
+
const artifacts = ARTIFACT_NAMES.map((name) => {
|
|
201
|
+
const artifactPath = task.artifacts?.[name];
|
|
202
|
+
if (!artifactPath)
|
|
203
|
+
return "";
|
|
204
|
+
const payload = artifactPayload(options.root, id, name, task);
|
|
205
|
+
const content = payload.ok && payload.content !== undefined
|
|
206
|
+
? `<div class="content artifact">${markdownToHtml(payload.content)}</div>`
|
|
207
|
+
: `<p class="help is-danger px-5 pb-4">${escapeHtml(payload.error ?? "unavailable")}</p>`;
|
|
208
|
+
return `<details class="ad-artifact" data-key="artifact:${name}"${name === openArtifact ? " open" : ""}><summary>${escapeHtml(name)}<code>${escapeHtml(artifactPath)}</code></summary>${content}</details>`;
|
|
209
|
+
}).join("\n");
|
|
210
|
+
const table = (head, rows, empty) => rows.length
|
|
211
|
+
? `<table class="table ad-table is-fullwidth"><thead><tr>${head.map((h) => `<th>${escapeHtml(h)}</th>`).join("")}</tr></thead><tbody>${rows.join("\n")}</tbody></table>`
|
|
212
|
+
: `<p class="ad-empty-line">${escapeHtml(empty)}</p>`;
|
|
213
|
+
const decisions = table(["decision", "status", "resolution"], task.decisions.map((decision) => `<tr><td><code>${escapeHtml(decision.id)}</code><div class="is-size-7 has-text-grey mt-1">${escapeHtml(decision.label ?? "")}</div></td><td>${pill(decision.status, DECISION_TONE[decision.status] ?? "")}</td><td class="is-size-7">${escapeHtml(decision.resolution ?? "")}</td></tr>`), "No decisions recorded");
|
|
214
|
+
const doneItems = task.tasks.filter((item) => item.status === "done").length;
|
|
215
|
+
const workItems = table(["item", "status", "label"], task.tasks.map((item) => `<tr><td><code>${escapeHtml(item.id)}</code></td><td>${pill(item.status.replace("_", " "), ITEM_TONE[item.status] ?? "")}</td><td class="is-size-7">${escapeHtml(item.label ?? "")}</td></tr>`), "No work items yet");
|
|
216
|
+
const workProgress = task.tasks.length
|
|
217
|
+
? `<div class="px-5 pt-4 pb-1"><progress class="progress is-small is-primary mb-0" value="${doneItems}" max="${task.tasks.length}" aria-label="work items done">${doneItems}/${task.tasks.length}</progress></div>`
|
|
218
|
+
: "";
|
|
219
|
+
const evidence = table(["kind", "result", "gate", "source", "recorded"], task.evidence.map((entry) => `<tr><td><strong>${escapeHtml(entry.kind)}</strong>${entry.area ? `<div class="is-size-7 has-text-grey">${escapeHtml(entry.area)}</div>` : ""}</td><td>${pill(entry.result, RESULT_TONE[entry.result] ?? "")}</td><td class="is-size-7">${escapeHtml(entry.gate ?? "")}</td><td class="is-size-7">${escapeHtml(entry.source ?? "")}${entry.detail ? `<div class="has-text-grey mt-1">${escapeHtml(entry.detail)}</div>` : ""}</td><td class="is-size-7 has-text-grey">${relativeTime(entry.recordedAt)}</td></tr>`), "No verification evidence yet");
|
|
220
|
+
/* Worker containers (full page only): the task's runtime record, the same in every
|
|
221
|
+
* mode; `docker ps` only refines `running` and is shared across live polls. */
|
|
222
|
+
let containersPanel = "";
|
|
223
|
+
if (!fragment) {
|
|
224
|
+
const containers = await taskContainers(options, id);
|
|
225
|
+
containersPanel = containers.status === 200 ? renderContainersPanel(containers.payload.containers, containers.payload.docker) : renderContainersPanel([], "unavailable");
|
|
226
|
+
}
|
|
227
|
+
const handoff = task.handoff
|
|
228
|
+
? `<section class="ad-panel" data-key="handoff" style="box-shadow:0 0 0 1px var(--bulma-danger)"><header class="ad-panel-head"><h2>Handed off · ${escapeHtml(task.handoff.kind)}</h2></header><div class="ad-panel-body"><p>${escapeHtml(task.handoff.reason)}</p><p class="is-size-7 has-text-grey mt-2">Follow the handoff actions from <code>node .agents/aidlc/scripts/state.mjs task next ${escapeHtml(task.id)}</code>.</p></div></section>`
|
|
229
|
+
: "";
|
|
230
|
+
const meta = `<dl class="ad-meta">
|
|
231
|
+
<dt>Status</dt><dd>${statusPill(task.status, Boolean(task.handoff))}</dd>
|
|
232
|
+
<dt>Gate</dt><dd>${task.gate === "none" ? '<span class="has-text-grey">none</span>' : `${escapeHtml(gateLabel(task.gate))}<div class="is-size-7 has-text-grey">${escapeHtml(task.gate)}</div>`}</dd>
|
|
233
|
+
<dt>Risk</dt><dd>${riskPill(task.risk)}</dd>
|
|
234
|
+
<dt>Type</dt><dd>${escapeHtml(task.type)}</dd>
|
|
235
|
+
<dt>Language</dt><dd>${escapeHtml(task.language)}</dd>
|
|
236
|
+
<dt>Areas</dt><dd>${escapeHtml(task.areas.join(", "))}</dd>
|
|
237
|
+
<dt>Branch</dt><dd><code>${escapeHtml(task.branch)}</code></dd>
|
|
238
|
+
<dt>Created</dt><dd>${relativeTime(task.createdAt)}</dd>
|
|
239
|
+
<dt>Updated</dt><dd>${relativeTime(task.updatedAt)}</dd>
|
|
240
|
+
</dl>`;
|
|
241
|
+
/* Live log (assets/ui/live-log.js): client-owned SSE lines, so live updates never
|
|
242
|
+
* morph inside it ([data-live-ignore]). The full page gets the grouped log with its
|
|
243
|
+
* filter and Raw JSON switch; the side panel a compact tail of recent lines keyed by
|
|
244
|
+
* task, so switching tasks replaces it and a re-render keeps it. */
|
|
245
|
+
const logAttrs = (mode, limit) => `data-live-log data-log-mode="${mode}" data-log-limit="${limit}" data-task-id="${escapeHtml(task.id)}" data-gate-labels="${escapeHtml(JSON.stringify(GATE_LABELS))}"`;
|
|
246
|
+
const liveLog = fragment
|
|
247
|
+
? `<section class="ad-terminal ad-live-log is-compact" data-key="drawer-log:${escapeHtml(task.id)}" data-live-ignore ${logAttrs("compact", DRAWER_LOG_LINES)}>
|
|
248
|
+
<div class="ad-terminal-head"><strong>Live log</strong><span class="ad-log-state" data-log-state>connecting…</span>
|
|
249
|
+
<div class="ad-terminal-tools"><a href="/tasks/${encodeURIComponent(task.id)}#live-log" data-log-open>Open full log →</a></div>
|
|
250
|
+
</div>
|
|
251
|
+
<div class="log-panel is-compact" data-log-body role="log" aria-live="off"><span class="ad-log-muted">connecting…</span></div>
|
|
252
|
+
</section>`
|
|
253
|
+
: `<section class="ad-terminal ad-live-log" id="live-log" data-live-ignore ${logAttrs("full", FULL_LOG_LINES)}>
|
|
254
|
+
<div class="ad-terminal-head"><span class="ad-lights" aria-hidden="true"><span></span><span></span><span></span></span><strong>Live log</strong><span id="log-state" class="ad-log-state" data-log-state>connecting…</span>
|
|
255
|
+
<div class="ad-terminal-tools">
|
|
256
|
+
<span class="ad-log-switch" role="group" aria-label="Which events to show"><button type="button" data-log-filter="important" aria-pressed="true" title="Hide bookkeeping events; show the first line of agent messages">Important</button><button type="button" data-log-filter="all" aria-pressed="false" title="Every event, agent messages in full">All</button></span>
|
|
257
|
+
<button type="button" data-log-raw aria-pressed="false" title="Show each event's original JSON">Raw JSON</button>
|
|
258
|
+
<button type="button" id="log-follow" data-log-follow aria-pressed="true">Follow</button><button type="button" id="log-clear" data-log-clear>Clear</button>
|
|
259
|
+
</div>
|
|
260
|
+
</div>
|
|
261
|
+
<div id="log" class="log-panel" data-log-body role="log" aria-live="off"><span class="ad-log-muted">connecting…</span></div>
|
|
262
|
+
</section>`;
|
|
263
|
+
/* The side panel stays read-only; when a human action is due, point at the
|
|
264
|
+
* full page that hosts it right at the top. */
|
|
265
|
+
const href = `/tasks/${encodeURIComponent(task.id)}`;
|
|
266
|
+
const drawerCta = !fragment ? ""
|
|
267
|
+
: task.handoff
|
|
268
|
+
? `<div class="ad-callout is-danger ad-drawer-cta" data-key="drawer-cta"><span>Handed off · ${escapeHtml(task.handoff.kind)} — follow the handoff actions on the full page.</span><a class="button is-small is-danger" href="${href}">Open task →</a></div>`
|
|
269
|
+
: waiting
|
|
270
|
+
? `<div class="ad-callout ad-drawer-cta" data-key="drawer-cta"><span>Waiting at <strong>${escapeHtml(gateLabel(task.gate))}</strong>${pending.length ? ` · ${pending.length} feedback pending` : ""} — send feedback or approve on the full page.</span><a class="button is-small is-warning" href="${href}" data-review-cta>Open review →</a></div>`
|
|
271
|
+
: "";
|
|
272
|
+
const body = `${drawerCta}<h1 class="ad-page-title">${escapeHtml(task.title)}</h1>
|
|
273
|
+
<div class="ad-page-sub">${copyIdButton(task.id)}${statusPill(task.status, Boolean(task.handoff))}${gatePill(task.gate)}${riskPill(task.risk)}${feedbackPill(pending.length)}</div>
|
|
274
|
+
${phaseStepper(task.phase, task.gate, task.status, Boolean(task.handoff))}
|
|
275
|
+
${fragment ? liveLog : ""}
|
|
276
|
+
<div class="columns is-desktop">
|
|
277
|
+
<div class="column is-8-desktop">
|
|
278
|
+
${handoff}
|
|
279
|
+
${pendingBlock}
|
|
280
|
+
${gateBlock}
|
|
281
|
+
${feedbackBlock}
|
|
282
|
+
${approveBlock}
|
|
283
|
+
${panel("Artifacts", artifacts || '<p class="ad-empty-line">No artifacts recorded</p>', `<span class="ad-count">${ARTIFACT_NAMES.filter((name) => task.artifacts?.[name]).length}</span>`, true)}
|
|
284
|
+
${panel("Decisions", decisions, `<span class="ad-count">${task.decisions.length}</span>`, true)}
|
|
285
|
+
${panel("Work items", workProgress + workItems, `<span class="ad-panel-tools"><a class="button is-small" href="${changesHref(task.id)}" data-changes-link="items" title="Code changes of this task">Changes</a><span class="ad-count">${doneItems}/${task.tasks.length}</span></span>`, true)}
|
|
286
|
+
${containersPanel}
|
|
287
|
+
${panel("Evidence", evidence, `<span class="ad-count">${task.evidence.length}</span>`, true)}
|
|
288
|
+
${fragment ? "" : liveLog}
|
|
289
|
+
</div>
|
|
290
|
+
<aside class="column is-4-desktop">
|
|
291
|
+
<div class="ad-sticky">
|
|
292
|
+
${panel("Details", meta)}
|
|
293
|
+
</div>
|
|
294
|
+
</aside>
|
|
295
|
+
</div>`;
|
|
296
|
+
return { task, html: fragment ? body : `${body}${approveModal}` };
|
|
297
|
+
};
|
|
298
|
+
export const taskNotFound = (options, id) => `<div class="ad-empty"><div class="ad-empty-icon" aria-hidden="true">🔍</div><h2>Task not found</h2><p><code>${escapeHtml(id)}</code> is not in the ${uiMode(options) === "fleet" ? "fleet" : "local"} canonical state.</p><a class="button" href="/">← Back to dashboard</a></div>`;
|
|
299
|
+
export const drawerNotFound = (id) => `<p class="has-text-danger">Task not found: ${escapeHtml(id)}</p>`;
|
|
300
|
+
export const renderTaskDetail = async (options, id, fragment = false) => {
|
|
301
|
+
const state = await readUiState(options);
|
|
302
|
+
const rendered = await renderTaskBody(options, state, id, fragment);
|
|
303
|
+
if (fragment)
|
|
304
|
+
return rendered ? rendered.html : drawerNotFound(id);
|
|
305
|
+
const reviews = reviewQueue(state);
|
|
306
|
+
if (!rendered)
|
|
307
|
+
return `${pageHead("Task not found · Loop")}${renderHeader(options.root, uiMode(options), reviews)}<main class="section ad-main"><div class="container is-fluid">${taskNotFound(options, id)}</div></main>${renderModals()}${pageScripts("common")}</body></html>`;
|
|
308
|
+
return `${pageHead(`${rendered.task.title} · Loop`, rendered.task.id)}
|
|
309
|
+
${renderHeader(options.root, uiMode(options), reviews, rendered.task.id)}
|
|
310
|
+
<main class="section ad-main">
|
|
311
|
+
<div class="container is-fluid">
|
|
312
|
+
<a class="ad-breadcrumb" href="/">← Dashboard</a>
|
|
313
|
+
${liveRegion("detail", rendered.html)}
|
|
314
|
+
</div>
|
|
315
|
+
</main>
|
|
316
|
+
${renderModals()}
|
|
317
|
+
${pageScripts("common", "detail", "live-log")}
|
|
318
|
+
</body>
|
|
319
|
+
</html>`;
|
|
320
|
+
};
|
package/dist/src/upgrade.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, renameSync, rmdirSync, rmSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { chmodSync, copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, renameSync, rmdirSync, rmSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname, join, resolve } from "node:path";
|
|
3
3
|
import { findAdapter, resolvedAdapters } from "./adapters.js";
|
|
4
4
|
import { legacyV021AdapterContents, legacyV021Hashes } from "./legacy.js";
|
|
@@ -6,7 +6,7 @@ import { planProjectLayoutMigration } from "./project-migration.js";
|
|
|
6
6
|
import { legacyManifest, managedContentHash, planLoopSelection, planManagedWrites, preserveOwnedFile, readManifest, splitInventoryKey } from "./managed-planning.js";
|
|
7
7
|
import { resolveProjectPath, resolveProjectPathWithoutSymlinks } from "./project-path.js";
|
|
8
8
|
import { prepareV4Migration, validateState } from "./state.js";
|
|
9
|
-
import { contentHash, coreWorkflowFiles, initialProjectFiles, manifestSpec } from "./workflow.js";
|
|
9
|
+
import { contentHash, coreWorkflowFiles, initialProjectFiles, isExecutableAsset, manifestSpec } from "./workflow.js";
|
|
10
10
|
import { migrateLegacyBoard } from "./upgrade-board.js";
|
|
11
11
|
const safePath = (root, relative) => resolveProjectPath(root, relative, "Unsafe upgrade path");
|
|
12
12
|
const assertNoSymlinkPath = (root, relative) => { resolveProjectPathWithoutSymlinks(root, relative, "Unsafe upgrade path", "Refusing to upgrade symlink path"); };
|
|
@@ -151,6 +151,9 @@ const stageUpgradedWrites = (project, writes, backup, stage, journal) => {
|
|
|
151
151
|
const staged = join(stage, item.path);
|
|
152
152
|
mkdirSync(dirname(staged), { recursive: true });
|
|
153
153
|
writeFileSync(staged, item.contentEncoding === "base64" ? Buffer.from(item.content, "base64") : item.content);
|
|
154
|
+
// The commit step renames the staged file, so its mode carries over to the target.
|
|
155
|
+
if (isExecutableAsset(item.path) && process.platform !== "win32")
|
|
156
|
+
chmodSync(staged, 0o755);
|
|
154
157
|
if (item.path.endsWith(".json") && item.contentEncoding !== "base64")
|
|
155
158
|
JSON.parse(item.content);
|
|
156
159
|
}
|
package/dist/src/workflow.d.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { AgentId, FileSpec, WorkflowManifest } from "./model.js";
|
|
2
2
|
export declare const contentHash: (content: string | Uint8Array) => string;
|
|
3
3
|
export declare const coreWorkflowFiles: () => FileSpec[];
|
|
4
|
+
/** Commented template for the loop daemon's env file; seeded once and never overwritten. */
|
|
5
|
+
export declare const LOOP_ENV_TEMPLATE = "# AI-DLC loop environment, read by `loop.sh up` / `aidlc-kit loop start` (local mode).\n# Precedence: shell exports > this file > values detected from your tools' own config.\n# Keep real keys here only; .agents/.gitignore excludes this file from git.\n# See what the loop will use with: .agents/aidlc/scripts/loop.sh check\n#\n# --- OpenCode (lead or workers set to \"opencode\") ---\n# Model as provider/model[#variant] (list them with: opencode models).\n# When unset, \"model\" from opencode.json is used.\n# AIDLC_OPENCODE_MODEL=opencode-go/<model>\n# Worker key; when unset it is read from ~/.local/share/opencode/auth.json.\n# The key follows the model prefix: openai/ -> OPENAI_API_KEY, anthropic/ -> ANTHROPIC_API_KEY, otherwise OPENCODE_API_KEY.\n# OPENCODE_API_KEY=\n#\n# --- Codex workers ---\n# When unset, read from ~/.codex/auth.json (after: codex login --with-api-key).\n# OPENAI_API_KEY=\n#\n# --- Claude Code workers ---\n# When unset, read from \"env\" in .claude/settings.local.json, .claude/settings.json, or ~/.claude/settings.json.\n# ANTHROPIC_API_KEY=\n#\n# --- Optional ---\n# Override the auto-built worker image:\n# AIDLC_WORKER_DOCKER_IMAGE=\n# Where worker worktrees live (default: the system temp directory):\n# AIDLC_LOCAL_WORKTREES_ROOT=\n";
|
|
6
|
+
/** Installed files that must be executable (POSIX launchers). */
|
|
7
|
+
export declare const isExecutableAsset: (path: string) => boolean;
|
|
4
8
|
export declare const initialProjectFiles: () => FileSpec[];
|
|
5
9
|
export declare const manifestKey: (spec: Pick<FileSpec, "path" | "owner" | "strategy">) => string;
|
|
6
10
|
export declare const manifestFor: (files: FileSpec[], adapters: AgentId[]) => WorkflowManifest;
|
package/dist/src/workflow.js
CHANGED
|
@@ -17,11 +17,43 @@ export const coreWorkflowFiles = () => [
|
|
|
17
17
|
...collect(join(assetRoot, "aidlc"), ".agents/aidlc"),
|
|
18
18
|
...collect(join(assetRoot, "skills"), ".agents/skills")
|
|
19
19
|
];
|
|
20
|
+
/** Commented template for the loop daemon's env file; seeded once and never overwritten. */
|
|
21
|
+
export const LOOP_ENV_TEMPLATE = `# AI-DLC loop environment, read by \`loop.sh up\` / \`aidlc-kit loop start\` (local mode).
|
|
22
|
+
# Precedence: shell exports > this file > values detected from your tools' own config.
|
|
23
|
+
# Keep real keys here only; .agents/.gitignore excludes this file from git.
|
|
24
|
+
# See what the loop will use with: .agents/aidlc/scripts/loop.sh check
|
|
25
|
+
#
|
|
26
|
+
# --- OpenCode (lead or workers set to "opencode") ---
|
|
27
|
+
# Model as provider/model[#variant] (list them with: opencode models).
|
|
28
|
+
# When unset, "model" from opencode.json is used.
|
|
29
|
+
# AIDLC_OPENCODE_MODEL=opencode-go/<model>
|
|
30
|
+
# Worker key; when unset it is read from ~/.local/share/opencode/auth.json.
|
|
31
|
+
# The key follows the model prefix: openai/ -> OPENAI_API_KEY, anthropic/ -> ANTHROPIC_API_KEY, otherwise OPENCODE_API_KEY.
|
|
32
|
+
# OPENCODE_API_KEY=
|
|
33
|
+
#
|
|
34
|
+
# --- Codex workers ---
|
|
35
|
+
# When unset, read from ~/.codex/auth.json (after: codex login --with-api-key).
|
|
36
|
+
# OPENAI_API_KEY=
|
|
37
|
+
#
|
|
38
|
+
# --- Claude Code workers ---
|
|
39
|
+
# When unset, read from "env" in .claude/settings.local.json, .claude/settings.json, or ~/.claude/settings.json.
|
|
40
|
+
# ANTHROPIC_API_KEY=
|
|
41
|
+
#
|
|
42
|
+
# --- Optional ---
|
|
43
|
+
# Override the auto-built worker image:
|
|
44
|
+
# AIDLC_WORKER_DOCKER_IMAGE=
|
|
45
|
+
# Where worker worktrees live (default: the system temp directory):
|
|
46
|
+
# AIDLC_LOCAL_WORKTREES_ROOT=
|
|
47
|
+
`;
|
|
48
|
+
/** Installed files that must be executable (POSIX launchers). */
|
|
49
|
+
export const isExecutableAsset = (path) => path.endsWith(".sh");
|
|
20
50
|
export const initialProjectFiles = () => [
|
|
21
51
|
{ path: ".agents/config.json", owner: "aidlc-project", ownershipClass: "project", content: `${JSON.stringify(defaultConfig(), null, 2)}\n` },
|
|
22
52
|
{ path: ".agents/data/state/aidlc-state.json", owner: "aidlc-state", ownershipClass: "state", content: `${JSON.stringify(emptyState(), null, 2)}\n` },
|
|
23
53
|
{ path: ".agents/data/lessons/index.json", owner: "aidlc-state", ownershipClass: "state", content: `${JSON.stringify(createLessonIndex([], lessonStateDigest(emptyState())), null, 2)}\n` },
|
|
24
|
-
{ path: ".agents/data/memory/agentic-memory.json", owner: "aidlc-memory", ownershipClass: "project", content: `${JSON.stringify(emptyMemoryRegistry(), null, 2)}\n` }
|
|
54
|
+
{ path: ".agents/data/memory/agentic-memory.json", owner: "aidlc-memory", ownershipClass: "project", content: `${JSON.stringify(emptyMemoryRegistry(), null, 2)}\n` },
|
|
55
|
+
{ path: ".agents/loop.env", owner: "aidlc-project", ownershipClass: "project", content: LOOP_ENV_TEMPLATE },
|
|
56
|
+
{ path: ".agents/.gitignore", owner: "aidlc-project", ownershipClass: "project", content: "# Local loop credentials (aidlc-kit)\nloop.env\n# Loop daemon/process state and per-task runtime data (aidlc-kit)\ndata/loop/\ndata/tasks/*/runtime/\n" }
|
|
25
57
|
];
|
|
26
58
|
export const manifestKey = (spec) => spec.strategy === "managed-block" ? `${spec.path}#${spec.owner}` : spec.path;
|
|
27
59
|
export const manifestFor = (files, adapters) => ({
|
package/docs/README.md
CHANGED
|
@@ -1,35 +1,50 @@
|
|
|
1
1
|
# Documentation
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
|
26
|
-
|
|
|
27
|
-
| [
|
|
28
|
-
| [
|
|
29
|
-
| [
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
|
34
|
-
|
|
35
|
-
|
|
3
|
+
Pick a page by what you want to do. New here? Install with the [README](../README.md), then read [Operating the workflow](./operating-workflow.md).
|
|
4
|
+
|
|
5
|
+
> [!WARNING]
|
|
6
|
+
> The loop, fleet mode, and the coordination service are pre-1.0 and experimental. See [Status and stability](./loop-runbook.md#status-and-stability).
|
|
7
|
+
|
|
8
|
+
## Get started
|
|
9
|
+
|
|
10
|
+
| Page | Read it when… |
|
|
11
|
+
| --- | --- |
|
|
12
|
+
| [README](../README.md) | You are installing the workflow into a project. |
|
|
13
|
+
| [Loop setup runbook](./loop-runbook.md) | You want to enable the loop, locally or across machines. |
|
|
14
|
+
| [Local loop walkthrough](./loop-local-run.md) | The loop is set up and you want to add tasks, watch them, approve gates, and use the dashboard. |
|
|
15
|
+
|
|
16
|
+
## Use and operate
|
|
17
|
+
|
|
18
|
+
| Page | Read it when… |
|
|
19
|
+
| --- | --- |
|
|
20
|
+
| [Operating the workflow](./operating-workflow.md) | You want the lifecycle, task state, review gates, and handoffs. |
|
|
21
|
+
| [AI-DLC Coordination service](./coordination-service.md) | You are deploying remote state or fleet workers on several machines. |
|
|
22
|
+
|
|
23
|
+
## Reference
|
|
24
|
+
|
|
25
|
+
| Page | Read it when… |
|
|
26
|
+
| --- | --- |
|
|
27
|
+
| [Configuration](./configuration.md) | You are editing `.agents/config.json`: profiles, rules, agent permissions, loop settings. |
|
|
28
|
+
| [Command reference](./command-reference.md) | You need a command, flag, environment variable, or installed script. |
|
|
29
|
+
| [Troubleshooting](./troubleshooting.md) | You have an error code, a stuck or paused task, or a dashboard problem. |
|
|
30
|
+
|
|
31
|
+
## Contribute
|
|
32
|
+
|
|
33
|
+
| Page | Read it when… |
|
|
34
|
+
| --- | --- |
|
|
35
|
+
| [Development](./development.md) | You are setting up this repository, dogfooding its workflow, or changing source. |
|
|
36
|
+
| [Testing and release](./testing-and-release.md) | You are verifying a change or preparing a release. |
|
|
37
|
+
| [Architecture](./architecture.md) | You are changing a package boundary, installed runtime, migration, or asset layout. |
|
|
38
|
+
| [Design decisions](./design-decisions.md) | You need the reasoning behind a project-wide decision, or must record one. |
|
|
39
|
+
|
|
40
|
+
## Adding or changing a page
|
|
41
|
+
|
|
42
|
+
Pages are flat `docs/<slug>.md` files; the dashboard, tests, and external links depend on the names.
|
|
43
|
+
|
|
44
|
+
- **Shape:** `# Title`, a 1–2 sentence opening (purpose and audience), `##` headings by reader task, tables for lookups, `## Related` with 2–4 links.
|
|
45
|
+
- **One source of truth:** each fact lives on one page; other pages give one sentence and a link. Experimental features get only a one-line callout to [Status and stability](./loop-runbook.md#status-and-stability).
|
|
46
|
+
- **Links:** `./<slug>.md#anchor`. The dashboard keeps links to pages in `DOC_PAGES` (`src/ui/docs.ts`) in-app and opens the rest on GitHub. Don't rename a linked heading without updating the links.
|
|
47
|
+
- **Register it:** add the page to a table above; to show it in the dashboard, add it to `DOC_PAGES` and run `node --test dist/test/unit/ui-docs.test.js` after `npm run build`; if the package must always ship it, add it to the docs test in `test/unit/installer.test.ts`.
|
|
48
|
+
- **Versions:** pages with version-pinned commands go on the [version bump checklist](./testing-and-release.md#version-bump-checklist).
|
|
49
|
+
|
|
50
|
+
A public command, configuration field, or installed-layout change updates the relevant page and its test in the same change.
|