@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.
Files changed (57) hide show
  1. package/dist/board-serve.js +40 -6
  2. package/dist/board-serve.js.map +1 -1
  3. package/dist/init.js +1 -1
  4. package/dist/init.js.map +1 -1
  5. package/dist/memory-setup.d.ts +7 -0
  6. package/dist/memory-setup.js +70 -46
  7. package/dist/memory-setup.js.map +1 -1
  8. package/node_modules/@dougharness/flow/.claude-plugin/plugin.json +6 -2
  9. package/node_modules/@dougharness/flow/agents/coder.md +23 -0
  10. package/node_modules/@dougharness/flow/agents/implementer.md +1 -1
  11. package/node_modules/@dougharness/flow/agents/planner.md +1 -1
  12. package/node_modules/@dougharness/flow/agents/researcher.md +2 -1
  13. package/node_modules/@dougharness/flow/agents/reviewer.md +3 -1
  14. package/node_modules/@dougharness/flow/agents/spec-writer.md +23 -0
  15. package/node_modules/@dougharness/flow/agents/tester.md +21 -0
  16. package/node_modules/@dougharness/flow/hooks/hooks.json +4 -1
  17. package/node_modules/@dougharness/flow/hooks/register.tsx +94 -0
  18. package/node_modules/@dougharness/flow/lib/autopilot-core.mjs +117 -0
  19. package/node_modules/@dougharness/flow/lib/autopilot-view.mjs +17 -0
  20. package/node_modules/@dougharness/flow/lib/board.mjs +91 -10
  21. package/node_modules/@dougharness/flow/lib/card-land.mjs +115 -0
  22. package/node_modules/@dougharness/flow/lib/card-loop.mjs +778 -0
  23. package/node_modules/@dougharness/flow/lib/coordinator.mjs +393 -0
  24. package/node_modules/@dougharness/flow/lib/cost.mjs +15 -5
  25. package/node_modules/@dougharness/flow/lib/decisions.mjs +4 -2
  26. package/node_modules/@dougharness/flow/lib/embeddings.mjs +144 -3
  27. package/node_modules/@dougharness/flow/lib/land.mjs +27 -8
  28. package/node_modules/@dougharness/flow/lib/memory.mjs +406 -76
  29. package/node_modules/@dougharness/flow/lib/plan.mjs +33 -5
  30. package/node_modules/@dougharness/flow/lib/worktrees.mjs +3 -3
  31. package/node_modules/@dougharness/flow/package.json +1 -1
  32. package/node_modules/@dougharness/flow/scripts/autopilot.mjs +440 -0
  33. package/node_modules/@dougharness/flow/scripts/board.mjs +107 -32
  34. package/node_modules/@dougharness/flow/scripts/cost.mjs +16 -3
  35. package/node_modules/@dougharness/flow/scripts/memory.mjs +33 -10
  36. package/node_modules/@dougharness/flow/scripts/plan.mjs +104 -30
  37. package/node_modules/@dougharness/flow/skills/doug-hand/SKILL.md +8 -2
  38. package/node_modules/@dougharness/flow/skills/doug-next/SKILL.md +30 -10
  39. package/node_modules/@dougharness/flow/skills/doug-start/SKILL.md +80 -0
  40. package/node_modules/@dougharness/flow/skills/doug-swarm/SKILL.md +1 -1
  41. package/node_modules/@dougharness/flow/skills/research/SKILL.md +1 -1
  42. package/node_modules/@dougharness/flow/skills/run-report/SKILL.md +6 -6
  43. package/node_modules/@dougharness/flow/workflows/card-queen.js +846 -0
  44. package/node_modules/@dougharness/flow/workflows/doug-implement.js +63 -23
  45. package/node_modules/@dougharness/gates/.claude-plugin/plugin.json +1 -1
  46. package/node_modules/@dougharness/gates/lib/anchor.mjs +29 -4
  47. package/node_modules/@dougharness/gates/lib/bash-rules.mjs +196 -6
  48. package/node_modules/@dougharness/gates/lib/gate-lock.mjs +40 -8
  49. package/node_modules/@dougharness/gates/lib/proposals.mjs +41 -5
  50. package/node_modules/@dougharness/gates/lib/scope.mjs +43 -4
  51. package/node_modules/@dougharness/gates/package.json +1 -1
  52. package/node_modules/@dougharness/gates/scripts/reanchor.mjs +2 -1
  53. package/node_modules/@dougharness/gates/scripts/research-cap.mjs +119 -15
  54. package/node_modules/@dougharness/gates/scripts/stop-gate.mjs +156 -14
  55. package/node_modules/@dougharness/gates/scripts/verify.mjs +8 -4
  56. package/package.json +3 -3
  57. 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 .doug/.state/gate.lock around the commands (card verify-takes-gate-lock).
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 deadline = Date.now() + waitBoundMs;
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.1.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.1.1",
27
- "@dougharness/gates": "1.1.1"
26
+ "@dougharness/flow": "1.3.0",
27
+ "@dougharness/gates": "1.3.0"
28
28
  },
29
29
  "bundleDependencies": [
30
30
  "@dougharness/flow",
@@ -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) {