@dougharness/cli 1.1.1 → 1.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/dist/board-serve.js +40 -6
- package/dist/board-serve.js.map +1 -1
- package/dist/init.js +1 -1
- package/dist/init.js.map +1 -1
- package/dist/memory-setup.d.ts +7 -0
- package/dist/memory-setup.js +70 -46
- package/dist/memory-setup.js.map +1 -1
- package/node_modules/@dougharness/flow/.claude-plugin/plugin.json +6 -2
- package/node_modules/@dougharness/flow/agents/coder.md +23 -0
- package/node_modules/@dougharness/flow/agents/implementer.md +1 -1
- package/node_modules/@dougharness/flow/agents/planner.md +1 -1
- package/node_modules/@dougharness/flow/agents/researcher.md +2 -1
- package/node_modules/@dougharness/flow/agents/reviewer.md +3 -1
- package/node_modules/@dougharness/flow/agents/spec-writer.md +23 -0
- package/node_modules/@dougharness/flow/agents/tester.md +21 -0
- package/node_modules/@dougharness/flow/hooks/hooks.json +4 -1
- package/node_modules/@dougharness/flow/hooks/register.tsx +94 -0
- package/node_modules/@dougharness/flow/lib/autopilot-core.mjs +117 -0
- package/node_modules/@dougharness/flow/lib/autopilot-view.mjs +17 -0
- package/node_modules/@dougharness/flow/lib/board.mjs +91 -10
- package/node_modules/@dougharness/flow/lib/card-land.mjs +115 -0
- package/node_modules/@dougharness/flow/lib/card-loop.mjs +778 -0
- package/node_modules/@dougharness/flow/lib/coordinator.mjs +393 -0
- package/node_modules/@dougharness/flow/lib/cost.mjs +15 -5
- package/node_modules/@dougharness/flow/lib/decisions.mjs +4 -2
- package/node_modules/@dougharness/flow/lib/embeddings.mjs +144 -3
- package/node_modules/@dougharness/flow/lib/land.mjs +27 -8
- package/node_modules/@dougharness/flow/lib/memory.mjs +406 -76
- package/node_modules/@dougharness/flow/lib/plan.mjs +33 -5
- package/node_modules/@dougharness/flow/lib/worktrees.mjs +3 -3
- package/node_modules/@dougharness/flow/package.json +1 -1
- package/node_modules/@dougharness/flow/scripts/autopilot.mjs +440 -0
- package/node_modules/@dougharness/flow/scripts/board.mjs +107 -32
- package/node_modules/@dougharness/flow/scripts/cost.mjs +16 -3
- package/node_modules/@dougharness/flow/scripts/memory.mjs +33 -10
- package/node_modules/@dougharness/flow/scripts/plan.mjs +104 -30
- package/node_modules/@dougharness/flow/skills/doug-hand/SKILL.md +8 -2
- package/node_modules/@dougharness/flow/skills/doug-next/SKILL.md +30 -10
- package/node_modules/@dougharness/flow/skills/doug-start/SKILL.md +80 -0
- package/node_modules/@dougharness/flow/skills/doug-swarm/SKILL.md +1 -1
- package/node_modules/@dougharness/flow/skills/research/SKILL.md +1 -1
- package/node_modules/@dougharness/flow/skills/run-report/SKILL.md +6 -6
- package/node_modules/@dougharness/flow/workflows/card-queen.js +846 -0
- package/node_modules/@dougharness/flow/workflows/doug-implement.js +63 -23
- package/node_modules/@dougharness/gates/.claude-plugin/plugin.json +1 -1
- package/node_modules/@dougharness/gates/lib/anchor.mjs +29 -4
- package/node_modules/@dougharness/gates/lib/bash-rules.mjs +196 -6
- package/node_modules/@dougharness/gates/lib/gate-lock.mjs +40 -8
- package/node_modules/@dougharness/gates/lib/proposals.mjs +41 -5
- package/node_modules/@dougharness/gates/lib/scope.mjs +43 -4
- package/node_modules/@dougharness/gates/package.json +1 -1
- package/node_modules/@dougharness/gates/scripts/reanchor.mjs +2 -1
- package/node_modules/@dougharness/gates/scripts/research-cap.mjs +119 -15
- package/node_modules/@dougharness/gates/scripts/stop-gate.mjs +156 -14
- package/node_modules/@dougharness/gates/scripts/verify.mjs +8 -4
- package/package.json +3 -3
- package/templates/board-app.js +46 -0
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// node .doug/hooks/scripts/verify.mjs [--no-credit] [--json] [--dir <project>] [--cwd <tree dir>] [--timeout-ms <n>]
|
|
5
5
|
// [--record-dir <dir>] [--credit-dir <dir>]... [--by <writer>] [--command <cmd>]...
|
|
6
6
|
// Exit: 0 all green or credited; 1 any command failed or timed out; 2 usage error or no commands.
|
|
7
|
-
// No count ratchet and no scans here (no session baseline); the Stop gate does those. Takes the Stop gate's
|
|
7
|
+
// No count ratchet and no scans here (no session baseline); the Stop gate does those. Takes the Stop gate's gate.lock (under the git common dir, shared by every worktree) around the commands (card verify-takes-gate-lock).
|
|
8
8
|
|
|
9
9
|
import { spawnSync } from "node:child_process";
|
|
10
10
|
import { unlinkSync } from "node:fs";
|
|
@@ -78,6 +78,7 @@ function main() {
|
|
|
78
78
|
return 2;
|
|
79
79
|
}
|
|
80
80
|
let treeBefore = computeTree(cwd);
|
|
81
|
+
let lockWaitMs = 0;
|
|
81
82
|
|
|
82
83
|
// Credits a verified-tree record of the same tree when one exists; returns the exit code, or null.
|
|
83
84
|
const tryCredit = () => {
|
|
@@ -98,7 +99,7 @@ function main() {
|
|
|
98
99
|
const entries = record.commands.map((c) => ({ name: c.name, exit: c.exit, passed: passed(record.totals[c.command]) }));
|
|
99
100
|
if (o.json) {
|
|
100
101
|
const results = record.commands.map((c) => ({ name: c.name, command: c.command, ok: true, status: c.exit, timedOut: false, durationMs: c.durationMs, outputTail: "" }));
|
|
101
|
-
process.stdout.write(JSON.stringify({ ok: true, credited: "verified-tree", tree: treeBefore, record, results }) + "\n");
|
|
102
|
+
process.stdout.write(JSON.stringify({ ok: true, credited: "verified-tree", tree: treeBefore, record, results, lockWaitMs }) + "\n");
|
|
102
103
|
} else {
|
|
103
104
|
process.stdout.write(`[doug] verify: credited verified-tree record (tree ${treeBefore.slice(0, 7)}, by ${record.by || "unknown"}, ${record.at}, node ${record.node}); commands not re-run. --no-credit runs them.\n`);
|
|
104
105
|
process.stdout.write(gateLine(entries) + "\n");
|
|
@@ -142,7 +143,8 @@ function main() {
|
|
|
142
143
|
takeOverGateLock(dir, o.by || "verify");
|
|
143
144
|
lockOwned = true;
|
|
144
145
|
} else {
|
|
145
|
-
const
|
|
146
|
+
const waitStart = Date.now();
|
|
147
|
+
const deadline = waitStart + waitBoundMs;
|
|
146
148
|
let live = held;
|
|
147
149
|
const nap = new Int32Array(new SharedArrayBuffer(4));
|
|
148
150
|
while (true) {
|
|
@@ -150,6 +152,8 @@ function main() {
|
|
|
150
152
|
if (stale(live) || Date.now() >= deadline) break;
|
|
151
153
|
Atomics.wait(nap, 0, 0, 200);
|
|
152
154
|
}
|
|
155
|
+
lockWaitMs = Date.now() - waitStart;
|
|
156
|
+
if (!o.json && lockWaitMs > 0) process.stdout.write(`[doug] verify: waited ${lockWaitMs} ms on gate.lock\n`);
|
|
153
157
|
treeBefore = computeTree(cwd);
|
|
154
158
|
const again = tryCredit();
|
|
155
159
|
if (again !== null) return again;
|
|
@@ -186,7 +190,7 @@ function main() {
|
|
|
186
190
|
} else {
|
|
187
191
|
removeRecord(recordDir);
|
|
188
192
|
}
|
|
189
|
-
if (o.json) process.stdout.write(JSON.stringify({ ok, credited: null, tree: treeBefore, record: null, results }) + "\n");
|
|
193
|
+
if (o.json) process.stdout.write(JSON.stringify({ ok, credited: null, tree: treeBefore, record: null, results, lockWaitMs }) + "\n");
|
|
190
194
|
else process.stdout.write(gateLine(entries) + "\n");
|
|
191
195
|
return ok ? 0 : 1;
|
|
192
196
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dougharness/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "doug: tailored, approval-gated harness install for Claude Code.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"node": ">=22.16"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@dougharness/flow": "1.
|
|
27
|
-
"@dougharness/gates": "1.
|
|
26
|
+
"@dougharness/flow": "1.3.0",
|
|
27
|
+
"@dougharness/gates": "1.3.0"
|
|
28
28
|
},
|
|
29
29
|
"bundleDependencies": [
|
|
30
30
|
"@dougharness/flow",
|
package/templates/board-app.js
CHANGED
|
@@ -45,6 +45,15 @@ details{margin-top:6px}summary{font-size:12px;color:var(--accent);cursor:pointer
|
|
|
45
45
|
.move{margin-top:8px;display:flex;gap:6px;align-items:center}.move select{font-size:12px;padding:3px 6px}
|
|
46
46
|
.ro .card{cursor:default}.ro .move{display:none}
|
|
47
47
|
footer{padding:0 24px 24px;font-size:12px;color:var(--ink-soft)}
|
|
48
|
+
#autopilot{margin:14px 24px 0;padding:10px 14px;background:var(--surface);border:1px solid var(--rule-strong)}
|
|
49
|
+
#autopilot[hidden]{display:none}
|
|
50
|
+
#autopilot h2{font-family:"IBM Plex Mono",Menlo,monospace;font-size:12px;letter-spacing:.06em;text-transform:uppercase;font-weight:500;margin:0 0 6px}
|
|
51
|
+
#autopilot ul{list-style:none;margin:4px 0;padding:0;font-size:13px}
|
|
52
|
+
#autopilot li{display:flex;gap:8px;flex-wrap:wrap;align-items:baseline}
|
|
53
|
+
#autopilot .st{font-family:"IBM Plex Mono",Menlo,monospace;font-size:11px;text-transform:uppercase;color:var(--ink-soft);min-width:5.5em}
|
|
54
|
+
#autopilot .st.running{color:var(--accent);font-weight:600}
|
|
55
|
+
#autopilot .st.stopped,#autopilot .st.interrupted{color:var(--c-decision)}
|
|
56
|
+
#autopilot .caps,#autopilot .filed{font-size:12px;color:var(--ink-soft);margin-top:4px}
|
|
48
57
|
@media (max-width:720px){.board{grid-auto-columns:minmax(85vw,1fr)}}
|
|
49
58
|
`;
|
|
50
59
|
|
|
@@ -163,6 +172,12 @@ function connectLive(opts) {
|
|
|
163
172
|
if (source !== es) return;
|
|
164
173
|
opts.onBoard(JSON.parse(e.data));
|
|
165
174
|
});
|
|
175
|
+
es.addEventListener("autopilot", (e) => {
|
|
176
|
+
if (source !== es || !opts.onAutopilot) return;
|
|
177
|
+
let view = null;
|
|
178
|
+
try { view = JSON.parse(e.data); } catch (_) {}
|
|
179
|
+
opts.onAutopilot(view);
|
|
180
|
+
});
|
|
166
181
|
}
|
|
167
182
|
open();
|
|
168
183
|
|
|
@@ -246,6 +261,33 @@ function cardMatches(card, filters) {
|
|
|
246
261
|
return true;
|
|
247
262
|
}
|
|
248
263
|
|
|
264
|
+
// The autopilot panel's inner HTML for a view from GET /api/autopilot (lib/autopilot-view.mjs autopilotView);
|
|
265
|
+
// "" for no view, which leaves the panel hidden.
|
|
266
|
+
function autopilotHtml(view) {
|
|
267
|
+
if (!view || typeof view !== "object") return "";
|
|
268
|
+
const cards = Array.isArray(view.cards) ? view.cards : [];
|
|
269
|
+
const filed = Array.isArray(view.filed) ? view.filed : [];
|
|
270
|
+
const caps = view.caps || {};
|
|
271
|
+
const items = cards.map((c) => {
|
|
272
|
+
const shape = [c.size, c.class].filter(Boolean).join("/");
|
|
273
|
+
return `<li><span class="st ${esc(c.state)}">${esc(c.state)}</span><code>${esc(c.id)}</code>${c.title ? `<span>${esc(c.title)}</span>` : ""}${shape ? `<span class="meta">${esc(shape)}</span>` : ""}${c.detail ? `<span class="meta">${esc(c.detail)}</span>` : ""}</li>`;
|
|
274
|
+
}).join("");
|
|
275
|
+
const filedHtml = filed.length
|
|
276
|
+
? `<div class="filed">filed: ${filed.map((f) => `<code>${esc(f.id)}</code> (${esc(f.kind)}${f.from ? " from " + esc(f.from) : ""}) ${esc(f.position || "")}`).join("; ")}</div>`
|
|
277
|
+
: "";
|
|
278
|
+
return `<h2>${esc(view.header || "Autopilot")}</h2><ul>${items}</ul><div class="caps">usd ${esc(caps.usd || "")} · wall ${esc(caps.wall || "")} · context ${esc(caps.context || "")}</div>${filedHtml}`;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Shows a view in the #autopilot container, or hides the container for none.
|
|
282
|
+
function applyAutopilot(view, doc) {
|
|
283
|
+
doc = doc !== undefined ? doc : (typeof document !== "undefined" ? document : null);
|
|
284
|
+
const el = doc && doc.getElementById("autopilot");
|
|
285
|
+
if (!el) return;
|
|
286
|
+
const html = autopilotHtml(view);
|
|
287
|
+
el.innerHTML = html;
|
|
288
|
+
el.hidden = html === "";
|
|
289
|
+
}
|
|
290
|
+
|
|
249
291
|
// The complete document for a given board state; `doug board serve` renders it from the record on
|
|
250
292
|
// every request.
|
|
251
293
|
function renderDoc(board, appSource, config) {
|
|
@@ -265,6 +307,7 @@ ${tagFilter ? tagFilter + "\n" : ""}<span class="status" id="filter-note"></span
|
|
|
265
307
|
<span class="status" id="status"></span>
|
|
266
308
|
</div>
|
|
267
309
|
</header>
|
|
310
|
+
<section id="autopilot" aria-label="Autopilot" hidden></section>
|
|
268
311
|
<main class="board" id="board">
|
|
269
312
|
${board.columns.map((col) => columnHtml(col, board.cards, board.columns)).join("\n")}
|
|
270
313
|
</main>
|
|
@@ -509,6 +552,8 @@ function app() {
|
|
|
509
552
|
readOnly = false;
|
|
510
553
|
document.body.classList.remove("ro");
|
|
511
554
|
say("");
|
|
555
|
+
// The autopilot panel: the run's view now, then live over the same event stream.
|
|
556
|
+
fetch("/api/autopilot").then((r) => (r && r.status === 200 ? r.json() : null)).then((view) => applyAutopilot(view)).catch(() => {});
|
|
512
557
|
// Once served, follow the record live: another writer's move (or this tab's own, echoed
|
|
513
558
|
// back) re-renders in place, or reloads when the columns or filters themselves changed.
|
|
514
559
|
connectLive({
|
|
@@ -516,6 +561,7 @@ function app() {
|
|
|
516
561
|
url: "/api/events",
|
|
517
562
|
setTimeout: window.setTimeout.bind(window),
|
|
518
563
|
clearTimeout: window.clearTimeout.bind(window),
|
|
564
|
+
onAutopilot: (view) => applyAutopilot(view),
|
|
519
565
|
onBoard: (next) => {
|
|
520
566
|
const updated = applyLiveBoard(board, next, config);
|
|
521
567
|
if (updated) {
|