@davidbalzan/groundwork-console 0.1.1 → 0.1.4
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/README.md +5 -1
- package/bin/groundwork-console.mjs +12 -3
- package/dist/answered.js +35 -2
- package/dist/groundwork.js +8 -3
- package/dist/kitresolve.js +17 -0
- package/dist/mcp.js +12 -1
- package/dist/provision.js +23 -2
- package/dist/seam.js +4 -0
- package/dist/server.js +69 -9
- package/dist/skills.js +13 -5
- package/package.json +1 -1
- package/public/assets/{abnfDiagram-N423BO3Z-DPhyKd1N.js → abnfDiagram-N423BO3Z-DjcI5EtN.js} +1 -1
- package/public/assets/{arc-DmkLmHHi.js → arc-CLreMRTi.js} +1 -1
- package/public/assets/architecture-TIHT7OUA-BxHBbCM_.js +1 -0
- package/public/assets/{architectureDiagram-T3A2C74G-BXFkjpze.js → architectureDiagram-T3A2C74G-GVsKASaE.js} +1 -1
- package/public/assets/{blockDiagram-VBNYF7ZC-Ddhca5b4.js → blockDiagram-VBNYF7ZC-kUIhKmE4.js} +1 -1
- package/public/assets/{c4Diagram-5PPSVZJV-D4M2Oz2j.js → c4Diagram-5PPSVZJV-BQFI1po0.js} +1 -1
- package/public/assets/channel-DiEdto8Z.js +1 -0
- package/public/assets/{chunk-2GRJ4B5K-B2cLnEJA.js → chunk-2GRJ4B5K-CAC9E6Lc.js} +1 -1
- package/public/assets/{chunk-4I5QYGJK-CVuqmZe1.js → chunk-4I5QYGJK-Do801ucH.js} +1 -1
- package/public/assets/{chunk-5RXB4S5H-CAj33Qry.js → chunk-5RXB4S5H-DgGb8Ei0.js} +1 -1
- package/public/assets/{chunk-6Q2QTUOP-B6kTvnZS.js → chunk-6Q2QTUOP-N3v574-9.js} +1 -1
- package/public/assets/{chunk-7Z6QIM7H-BCYMaGkK.js → chunk-7Z6QIM7H-BGVpdvGU.js} +1 -1
- package/public/assets/{chunk-GF5L2VYU-eAfQwP-A.js → chunk-GF5L2VYU-CyQ84gnX.js} +1 -1
- package/public/assets/{chunk-I66GZJ75-DyWmv43M.js → chunk-I66GZJ75-Bazokq-r.js} +1 -1
- package/public/assets/{chunk-JQJVKLGR-xpwVLHiM.js → chunk-JQJVKLGR-BXG1sCkI.js} +1 -1
- package/public/assets/{chunk-KBJHAD2P-BsIw_3BM.js → chunk-KBJHAD2P-Bi0agcVN.js} +1 -1
- package/public/assets/{chunk-NSK5VX7P-Dy4p7ORp.js → chunk-NSK5VX7P-CRNBZms6.js} +1 -1
- package/public/assets/{chunk-QR6OTTB3-D8k2NFkq.js → chunk-QR6OTTB3-BVNnO0nC.js} +1 -1
- package/public/assets/{chunk-RYQCIY6F-TbGMiTbW.js → chunk-RYQCIY6F-BDA4j6A-.js} +1 -1
- package/public/assets/{chunk-UBXNYLIW-By3VqDlw.js → chunk-UBXNYLIW-DGeBgwe-.js} +1 -1
- package/public/assets/{chunk-W5SLKNZC-B6TSWP8U.js → chunk-W5SLKNZC-xcy3l2eH.js} +1 -1
- package/public/assets/{chunk-WRU74C26-CqCOF3fo.js → chunk-WRU74C26-CIO3U59-.js} +1 -1
- package/public/assets/{chunk-XXDRQBXY-B6BeKGFV.js → chunk-XXDRQBXY-lsNR2odJ.js} +1 -1
- package/public/assets/classDiagram-JCYQIIEL-DQfOF2lx.js +1 -0
- package/public/assets/classDiagram-v2-OCEON4UE-DQfOF2lx.js +1 -0
- package/public/assets/{cose-bilkent-JH36ORCC-2ieLCSa1.js → cose-bilkent-JH36ORCC-Dgq33rVP.js} +1 -1
- package/public/assets/{cynefin-VYW2F7L2-BfajhBFb.js → cynefin-VYW2F7L2-BHP3DZqn.js} +1 -1
- package/public/assets/{cynefinDiagram-MW4NZA55-DZFY7WjS.js → cynefinDiagram-MW4NZA55-BmxpLFSH.js} +1 -1
- package/public/assets/{dagre-VZM6K2ZE-DECeuEL2.js → dagre-VZM6K2ZE-3sD0lAzD.js} +1 -1
- package/public/assets/{diagram-7IWD3JNH-DA6fCDep.js → diagram-7IWD3JNH-CyaD0_jh.js} +1 -1
- package/public/assets/{diagram-B4RE2ZJO-Dj8QoIQR.js → diagram-B4RE2ZJO-td2N4qE1.js} +1 -1
- package/public/assets/{diagram-LBJQPF4R-9JYAugOO.js → diagram-LBJQPF4R-D_wWDJ4F.js} +1 -1
- package/public/assets/{diagram-Q27KOJAE-ecTfssa6.js → diagram-Q27KOJAE-DKPs8Pt1.js} +1 -1
- package/public/assets/{diagram-UB23O5K3-DOGgwSDm.js → diagram-UB23O5K3-DMH09z4H.js} +1 -1
- package/public/assets/{dist-DTWyzWtG.js → dist-BFMCu1a5.js} +1 -1
- package/public/assets/{ebnfDiagram-BXEA7PRR-BGmWK80p.js → ebnfDiagram-BXEA7PRR-Cl2AEyY9.js} +1 -1
- package/public/assets/{erDiagram-JOGREHBK-CNl1QvhN.js → erDiagram-JOGREHBK-CyAJ5sjB.js} +1 -1
- package/public/assets/eventmodeling-45OFAUF4-CA3qsYNk.js +1 -0
- package/public/assets/flowDiagram-UKHOOZJN-BmCE-89P.js +1 -0
- package/public/assets/{ganttDiagram-PKOTCBZU-C01y-MjT.js → ganttDiagram-PKOTCBZU-YM9FA8H8.js} +1 -1
- package/public/assets/{gitGraph-TEB2WS4Q-BW4zQmf5.js → gitGraph-TEB2WS4Q-ws9TESN2.js} +1 -1
- package/public/assets/{gitGraphDiagram-DS77QQ5N-CnlU6XMJ.js → gitGraphDiagram-DS77QQ5N-CZu8bXTF.js} +1 -1
- package/public/assets/{index-DrqxoBmk.js → index-C84drWhX.js} +12 -12
- package/public/assets/{info-DKCQHKI2--A5cJC9V.js → info-DKCQHKI2-RSQw6txJ.js} +1 -1
- package/public/assets/{infoDiagram-6WML65LV-Cz2UOAMW.js → infoDiagram-6WML65LV-Bxmxzous.js} +1 -1
- package/public/assets/{ishikawaDiagram-WSZJBQD7-B4Iqm47_.js → ishikawaDiagram-WSZJBQD7-C80fxFdg.js} +1 -1
- package/public/assets/{journeyDiagram-NVQOT4AX-BU0js6EE.js → journeyDiagram-NVQOT4AX-DQTG0OdM.js} +1 -1
- package/public/assets/{kanban-definition-27J2QSJJ-DmeEPzz0.js → kanban-definition-27J2QSJJ-B5WxFLvF.js} +1 -1
- package/public/assets/{line-Dxi6NubD.js → line-D-jeoEtL.js} +1 -1
- package/public/assets/{linear-tama2Fqh.js → linear-7oKWtFDc.js} +1 -1
- package/public/assets/{mermaid-parser.core-bGhrBY9T.js → mermaid-parser.core-DG-hDTtF.js} +3 -3
- package/public/assets/{mermaid.core-_OoTZaAK.js → mermaid.core-CxtgV-Z8.js} +4 -4
- package/public/assets/{mindmap-definition-FAOFIHXS-BRJt4F9_.js → mindmap-definition-FAOFIHXS-DyT9O-XZ.js} +1 -1
- package/public/assets/{packet-7NZHBO7P-DAZsca60.js → packet-7NZHBO7P-fYfZRQ_r.js} +1 -1
- package/public/assets/{pegDiagram-VL7TDLO6-2k0rcz9N.js → pegDiagram-VL7TDLO6-BIKwmoZk.js} +1 -1
- package/public/assets/{pie-RZYD4A2V-ojW1YX_G.js → pie-RZYD4A2V-hHye9M3B.js} +1 -1
- package/public/assets/{pieDiagram-7S7Q4E2Y-CrrciDrS.js → pieDiagram-7S7Q4E2Y-B9IF9fZk.js} +1 -1
- package/public/assets/{quadrantDiagram-CIZ2JOQS-Dj5lMd95.js → quadrantDiagram-CIZ2JOQS-CriT2z0D.js} +1 -1
- package/public/assets/{radar-I7S5WNFK-flsKqrK_.js → radar-I7S5WNFK-UHbLAuY0.js} +1 -1
- package/public/assets/{railroad-3IZDKUUU-BX9H_OcC.js → railroad-3IZDKUUU-3enNyn3l.js} +1 -1
- package/public/assets/railroad-abnf-AHOZXSZD-CxQOPSx1.js +1 -0
- package/public/assets/railroad-ebnf-EBAXGLYW-CQ4P6ont.js +1 -0
- package/public/assets/railroad-peg-LSFZ7HO6-sSkmKuAv.js +1 -0
- package/public/assets/{railroadDiagram-AXF67PYL-BRNOv6Sh.js → railroadDiagram-AXF67PYL-Bkr9TUm0.js} +1 -1
- package/public/assets/{requirementDiagram-LRYGKXZP-BqJbwH3b.js → requirementDiagram-LRYGKXZP-CEp-ppYZ.js} +1 -1
- package/public/assets/{sankeyDiagram-W5VNT64P-BE7fRCAN.js → sankeyDiagram-W5VNT64P-CU-4IBMj.js} +1 -1
- package/public/assets/{sequenceDiagram-SI44F4Z6-Cwt-L1ko.js → sequenceDiagram-SI44F4Z6-Asi631mZ.js} +1 -1
- package/public/assets/{src-Bc2f8joo.js → src-CJc45UuW.js} +1 -1
- package/public/assets/{stateDiagram-OKZ733FA-etLHG0qi.js → stateDiagram-OKZ733FA-DkDphzLX.js} +1 -1
- package/public/assets/stateDiagram-v2-UEYNNEHI-DUHdaHkv.js +1 -0
- package/public/assets/{swimlanes-SLNWSIFB-B06NxeYI.js → swimlanes-SLNWSIFB-BVnRBmZ_.js} +1 -1
- package/public/assets/swimlanesDiagram-ULZ7WXOC-RkUs-XHd.js +8 -0
- package/public/assets/{timeline-definition-Z64GVDOM-BihNR1uq.js → timeline-definition-Z64GVDOM-B3jYbkKD.js} +1 -1
- package/public/assets/{treeView-QDETBFTQ-CImTfOYf.js → treeView-QDETBFTQ-FZndVHM1.js} +1 -1
- package/public/assets/{treemap-6X3UGDF4-DBRjbghn.js → treemap-6X3UGDF4-CKoOe0fa.js} +1 -1
- package/public/assets/{vennDiagram-T6HMQDX7-HcbmXJRd.js → vennDiagram-T6HMQDX7-B7qlSH-y.js} +1 -1
- package/public/assets/{wardley-OPB4EBWU-xY5d-6YU.js → wardley-OPB4EBWU-DlWXVdBE.js} +1 -1
- package/public/assets/{wardleyDiagram-T6FBY63Y-B6KN3TYi.js → wardleyDiagram-T6FBY63Y-Dk0uBYWd.js} +1 -1
- package/public/assets/{xychartDiagram-ELKLHX3M-AAW8Pwrl.js → xychartDiagram-ELKLHX3M-BDrlWQ3J.js} +1 -1
- package/public/index.html +1 -1
- package/public/assets/architecture-TIHT7OUA-MgcjoiPc.js +0 -1
- package/public/assets/channel-BAs2g7Wn.js +0 -1
- package/public/assets/classDiagram-JCYQIIEL-D8vKmZUF.js +0 -1
- package/public/assets/classDiagram-v2-OCEON4UE-D8vKmZUF.js +0 -1
- package/public/assets/eventmodeling-45OFAUF4-GcvHanur.js +0 -1
- package/public/assets/flowDiagram-UKHOOZJN-DdgmWP6g.js +0 -1
- package/public/assets/railroad-abnf-AHOZXSZD-DD8mz__c.js +0 -1
- package/public/assets/railroad-ebnf-EBAXGLYW-CWer5P7V.js +0 -1
- package/public/assets/railroad-peg-LSFZ7HO6-DDrRJYYw.js +0 -1
- package/public/assets/stateDiagram-v2-UEYNNEHI-Crgtg4pC.js +0 -1
- package/public/assets/swimlanesDiagram-ULZ7WXOC-C8S-XAyy.js +0 -8
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ npx @davidbalzan/groundwork-console # or: npm
|
|
|
12
12
|
First run creates `~/.groundwork-console/config.json` (add projects from Home → **Add project**) and `~/.groundwork-console/token`, prints the URL and a sign-in link, and opens the browser already signed in. Everything runs on one port (`:8790`).
|
|
13
13
|
|
|
14
14
|
```
|
|
15
|
-
groundwork-console [start] [--port 8790] [--host 127.0.0.1] [--no-open] [--no-auth] [--config <file>]
|
|
15
|
+
groundwork-console [start] [--port 8790] [--host 127.0.0.1 | --tailscale] [--no-open] [--no-auth] [--config <file>]
|
|
16
16
|
groundwork-console doctor # kit requires + which bins/paths were resolved
|
|
17
17
|
groundwork-console token # print the token
|
|
18
18
|
groundwork-console config # print the config path
|
|
@@ -20,6 +20,10 @@ groundwork-console config # print the config path
|
|
|
20
20
|
|
|
21
21
|
Requires on the host: Node ≥ 22, tmux (pane monitor / spawn), and either the three kit bins above on PATH or a kit checkout at `kitPath` in the config. `doctor` tells you which it found. Console requires: seam `^0.1.0` · agent-coord-mcp `^0.23.0` · groundwork `^0.3.0` · agent-coordination `^0.5.0`.
|
|
22
22
|
|
|
23
|
+
## From another machine
|
|
24
|
+
|
|
25
|
+
`groundwork-console --tailscale` binds this machine's Tailscale IP (found via `tailscale ip -4`) instead of loopback and prints a sign-in link you can open from any device on your tailnet; the token cookie does the rest (plain http — keep it on the tailnet, or put TLS in front). Without Tailscale: `ssh -N -L 8790:127.0.0.1:8790 <host>` and open `http://localhost:8790`.
|
|
26
|
+
|
|
23
27
|
## What it does
|
|
24
28
|
|
|
25
29
|
- **Home** — what needs you (DAVID_DECISION packets with age), fleet presence per project, roadmap strips.
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/* groundwork-console — one command: starts the API + serves the built UI on one port, prints the sign-in link, opens the browser.
|
|
3
|
-
Flags: --port 8790 · --host 127.0.0.1 · --no-open · --no-auth · --config <file>
|
|
3
|
+
Flags: --port 8790 · --host 127.0.0.1 · --tailscale (bind the tailnet IP, print the remote sign-in link) · --no-open · --no-auth · --config <file>
|
|
4
4
|
Subcommands: token (print the token) · doctor (kit requires + paths) · config (print config path) */
|
|
5
5
|
import { readFileSync, existsSync } from "node:fs";
|
|
6
|
+
import { execFileSync } from "node:child_process";
|
|
7
|
+
import os from "node:os";
|
|
6
8
|
import { homedir } from "node:os";
|
|
7
9
|
import path from "node:path";
|
|
8
10
|
import { fileURLToPath } from "node:url";
|
|
@@ -15,7 +17,14 @@ const has = (k) => args.includes(k);
|
|
|
15
17
|
const sub = args.find((a) => !a.startsWith("--") && !["true", "false"].includes(a) && !/^\d+$/.test(a) && !args[args.indexOf(a) - 1]?.startsWith("--"));
|
|
16
18
|
|
|
17
19
|
const port = String(flag("--port", process.env.PORT ?? "8790"));
|
|
18
|
-
|
|
20
|
+
/** Tailnet IPv4 of this machine: `tailscale ip -4` (CLI or the macOS app), else the first 100.64/10 interface address. */
|
|
21
|
+
function tailscaleIp() {
|
|
22
|
+
for (const bin of ["tailscale", "/Applications/Tailscale.app/Contents/MacOS/Tailscale"]) { try { const ip = execFileSync(bin, ["ip", "-4"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim().split("\n")[0]; if (/^100\./.test(ip)) return ip; } catch { /* next */ } }
|
|
23
|
+
for (const addrs of Object.values(os.networkInterfaces())) for (const a of addrs ?? []) if (a.family === "IPv4" && /^100\.(6[4-9]|[7-9]\d|1[01]\d|12[0-7])\./.test(a.address)) return a.address;
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
let host = String(flag("--host", process.env.GROUNDWORK_CONSOLE_HOST ?? "127.0.0.1"));
|
|
27
|
+
if (has("--tailscale")) { const ip = tailscaleIp(); if (!ip) { console.error("--tailscale: no tailnet address found (is Tailscale running? try `tailscale ip -4`)"); process.exit(1); } host = ip; if (!process.env.GROUNDWORK_CONSOLE_PUBLIC_API) process.env.GROUNDWORK_CONSOLE_PUBLIC_API = `http://${ip}:${port}/api`; }
|
|
19
28
|
if (flag("--config")) process.env.GROUNDWORK_CONSOLE_CONFIG = String(flag("--config"));
|
|
20
29
|
process.env.PORT = port; process.env.GROUNDWORK_CONSOLE_HOST = host;
|
|
21
30
|
if (has("--no-auth")) process.env.GROUNDWORK_CONSOLE_AUTH = "off";
|
|
@@ -43,5 +52,5 @@ const { CONFIG_FILE } = await import("../dist/config.js");
|
|
|
43
52
|
const base = `http://${host === "0.0.0.0" ? "localhost" : host}:${port}`;
|
|
44
53
|
const token = authEnabled ? loadToken() : null;
|
|
45
54
|
const url = token ? `${base}/api/auth/login?token=${encodeURIComponent(token)}&next=/` : `${base}/`;
|
|
46
|
-
console.log(`\n groundwork-console ${pkg.version}\n UI ${base}/\n config ${CONFIG_FILE}\n token ${authEnabled ? tokenFile : "auth off"}\n sign in ${url}\n`);
|
|
55
|
+
console.log(`\n groundwork-console ${pkg.version}\n UI ${base}/\n config ${CONFIG_FILE}\n token ${authEnabled ? tokenFile : "auth off"}\n sign in ${url}${has("--tailscale") ? `\n remote open the sign-in link above from any device on your tailnet (token cookie, 30 d). Plain http — keep it on the tailnet.` : ""}\n`);
|
|
47
56
|
if (!has("--no-open") && host !== "0.0.0.0") { try { const { default: open } = await import("open"); await open(url); } catch { /* headless: link is printed */ } }
|
package/dist/answered.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
/* Console-owned "answered" set for DAVID_DECISION packets (room posts + DMs), keyed by message id.
|
|
@@ -31,4 +31,37 @@ export function markAnswered(ids, meta) {
|
|
|
31
31
|
}
|
|
32
32
|
return n;
|
|
33
33
|
}
|
|
34
|
-
|
|
34
|
+
/* Bus-native answered set (kit ≥ 18ffbf4): any message FROM david carrying inReplyTo:<id> answers <id>. Scanned from ~/agent-coord inbox/*.jsonl + rooms/*.jsonl, cached 10 s. Unioned with the console-owned set above. */
|
|
35
|
+
let busCache = null;
|
|
36
|
+
let coordRoot = null;
|
|
37
|
+
export function setCoordDir(dir) { coordRoot = dir; }
|
|
38
|
+
function busAnswered() {
|
|
39
|
+
if (!coordRoot)
|
|
40
|
+
return new Set();
|
|
41
|
+
if (busCache && Date.now() - busCache.at < 10_000)
|
|
42
|
+
return busCache.ids;
|
|
43
|
+
const ids = new Set();
|
|
44
|
+
for (const sub of ["inbox", "rooms"]) {
|
|
45
|
+
const dir = path.join(coordRoot, sub);
|
|
46
|
+
if (!existsSync(dir))
|
|
47
|
+
continue;
|
|
48
|
+
for (const f of readdirSync(dir).filter((n) => n.endsWith(".jsonl"))) {
|
|
49
|
+
try {
|
|
50
|
+
for (const l of readFileSync(path.join(dir, f), "utf8").split("\n")) {
|
|
51
|
+
if (!l.includes("inReplyTo"))
|
|
52
|
+
continue;
|
|
53
|
+
try {
|
|
54
|
+
const m = JSON.parse(l);
|
|
55
|
+
if (m.from === "david" && typeof m.inReplyTo === "string")
|
|
56
|
+
ids.add(m.inReplyTo);
|
|
57
|
+
}
|
|
58
|
+
catch { /* skip */ }
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch { /* skip */ }
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
busCache = { at: Date.now(), ids };
|
|
65
|
+
return ids;
|
|
66
|
+
}
|
|
67
|
+
export const isAnswered = (id) => typeof id === "string" && (!!answered()[id] || busAnswered().has(id));
|
package/dist/groundwork.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/* Groundwork + playbook CLIs, JSON mode (shipped by grok 2026-08-16). Cached briefly; both are read-only commands. */
|
|
2
2
|
import { execFile } from "node:child_process";
|
|
3
|
-
import path from "node:path";
|
|
4
3
|
import { promisify } from "node:util";
|
|
5
4
|
const run = promisify(execFile);
|
|
6
5
|
const cache = new Map();
|
|
@@ -9,6 +8,11 @@ async function json(key, ttl, argv, fallback) {
|
|
|
9
8
|
if (hit && Date.now() - hit.at < ttl)
|
|
10
9
|
return hit.value;
|
|
11
10
|
let value;
|
|
11
|
+
if (argv[0] === MISSING) {
|
|
12
|
+
value = { ...fallback, ranAt: Date.now(), error: `${key.startsWith("pb") ? "agent-coordination" : "groundwork"} CLI not found — npm i -g (or set kitPath to a kit checkout)` };
|
|
13
|
+
cache.set(key, { at: Date.now(), value });
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
12
16
|
try {
|
|
13
17
|
const r = await run("node", argv, { timeout: 20_000, maxBuffer: 4 << 20 });
|
|
14
18
|
value = { ...JSON.parse(r.stdout), ranAt: Date.now() };
|
|
@@ -26,8 +30,9 @@ async function json(key, ttl, argv, fallback) {
|
|
|
26
30
|
return value;
|
|
27
31
|
}
|
|
28
32
|
import { groundworkCli, playbookCli } from "./kitresolve.js";
|
|
29
|
-
const
|
|
30
|
-
const
|
|
33
|
+
const MISSING = "/dev/null/missing-cli"; // resolver found nothing → the run fails fast with a clear message below instead of a fake kit path
|
|
34
|
+
const gw = (kit) => groundworkCli(kit) ?? MISSING;
|
|
35
|
+
const pb = (kit) => playbookCli(kit) ?? MISSING;
|
|
31
36
|
export const doctor = (kit, proj) => json(`doctor:${proj}`, 30_000, [gw(kit), "doctor", proj, "--json"], { ok: false, exit: 1, checks: [], ranAt: 0 });
|
|
32
37
|
export const status = (kit, proj) => json(`status:${proj}`, 15_000, [gw(kit), "status", proj, "--json"], { streams: [], next: null, queueFound: false, phases: [], ranAt: 0 });
|
|
33
38
|
export const list = (kit, proj) => json(`list:${proj}`, 60_000, [gw(kit), "list", proj, "--json"], { version: "", project: proj, skills: [], ranAt: 0 });
|
package/dist/kitresolve.js
CHANGED
|
@@ -77,3 +77,20 @@ export function requiresReport(kitPath) {
|
|
|
77
77
|
const r = { seam: check("seam"), mcp: check("mcp"), groundwork: check("groundwork"), playbook: check("playbook") };
|
|
78
78
|
return { ...r, paths: inst.paths, ok: Object.values(r).every((x) => x.ok !== false) };
|
|
79
79
|
}
|
|
80
|
+
/** Package root dir for a kit package: the checkout's packages/<dir> if present, else the installed npm package (found from its bin). Null when neither exists. */
|
|
81
|
+
export function packageRoot(kitPath, which) {
|
|
82
|
+
const dir = { groundwork: "packages/groundwork", playbook: "packages/coord-playbook", mcp: "packages/coord-mcp" }[which];
|
|
83
|
+
const local = path.join(kitPath, dir);
|
|
84
|
+
if (existsSync(path.join(local, "package.json")))
|
|
85
|
+
return local;
|
|
86
|
+
const bin = which === "groundwork" ? groundworkCli(kitPath) : which === "playbook" ? playbookCli(kitPath) : mcpServer(kitPath);
|
|
87
|
+
if (!bin)
|
|
88
|
+
return null;
|
|
89
|
+
let d = path.dirname(bin);
|
|
90
|
+
for (let i = 0; i < 6; i++) {
|
|
91
|
+
if (existsSync(path.join(d, "package.json")))
|
|
92
|
+
return d;
|
|
93
|
+
d = path.dirname(d);
|
|
94
|
+
}
|
|
95
|
+
return null;
|
|
96
|
+
}
|
package/dist/mcp.js
CHANGED
|
@@ -54,7 +54,8 @@ export class BusClient {
|
|
|
54
54
|
this.identity = identity;
|
|
55
55
|
this.mcpPath = mcpPath;
|
|
56
56
|
}
|
|
57
|
-
get serverPath() {
|
|
57
|
+
get serverPath() { const p = this.mcpPath ?? mcpServer(this.kitPath); if (!p)
|
|
58
|
+
throw new Error("agent-coord-mcp not found — npm i -g agent-coord-mcp (or set kitPath to a kit checkout)"); return p; }
|
|
58
59
|
get agentId() { return IDENTITIES[this.identity].agentId; }
|
|
59
60
|
async connect(log) {
|
|
60
61
|
if (this.client)
|
|
@@ -72,6 +73,13 @@ export class BusClient {
|
|
|
72
73
|
await client.connect(transport);
|
|
73
74
|
const reg = await client.callTool({ name: "register", arguments: { agentId: id.agentId, project: id.project, role: id.role, force: true } });
|
|
74
75
|
log?.(`register → ${JSON.stringify(reg.structuredContent ?? textOf(reg)).slice(0, 160)}`);
|
|
76
|
+
// Feature detection: which args each tool accepts (e.g. send_message.inReplyTo landed in kit 18ffbf4; the live bin may predate it).
|
|
77
|
+
try {
|
|
78
|
+
const tools = await client.listTools();
|
|
79
|
+
for (const t of tools.tools)
|
|
80
|
+
this.toolArgs.set(t.name, new Set(Object.keys(t.inputSchema?.properties ?? {})));
|
|
81
|
+
}
|
|
82
|
+
catch { /* older server: no detection, pass nothing extra */ }
|
|
75
83
|
this.client = client;
|
|
76
84
|
return client;
|
|
77
85
|
})();
|
|
@@ -82,6 +90,9 @@ export class BusClient {
|
|
|
82
90
|
this.connecting = null;
|
|
83
91
|
}
|
|
84
92
|
}
|
|
93
|
+
toolArgs = new Map();
|
|
94
|
+
/** True when the connected server's tool schema lists this argument (false when unknown or not connected yet). */
|
|
95
|
+
supports(tool, arg) { return this.toolArgs.get(tool)?.has(arg) ?? false; }
|
|
85
96
|
async call(name, args, log) {
|
|
86
97
|
const isWrite = WRITE_TOOLS_GATED.has(name);
|
|
87
98
|
if (!READ_ONLY_TOOLS.has(name) && !isWrite)
|
package/dist/provision.js
CHANGED
|
@@ -25,7 +25,7 @@ async function fleetWindow(project, startDir, log) {
|
|
|
25
25
|
log(`created window ${win} in session ${session} (first pane ${pane} reused for the first agent — no spare shell)`);
|
|
26
26
|
return { target: `${session}:${win}`, freshPane: pane };
|
|
27
27
|
}
|
|
28
|
-
export async function spawnAgent(req, isRegistered, log, isAttached = () => true) {
|
|
28
|
+
export async function spawnAgent(req, isRegistered, log, isAttached = () => true, roleOf = () => null) {
|
|
29
29
|
if (!/^[a-z0-9][a-z0-9-_]*$/i.test(req.agentId))
|
|
30
30
|
throw new Error("agentId: letters, digits, - _");
|
|
31
31
|
if (isRegistered(req.agentId))
|
|
@@ -91,7 +91,28 @@ export async function spawnAgent(req, isRegistered, log, isAttached = () => true
|
|
|
91
91
|
}
|
|
92
92
|
log(attached ? `✓ ${req.agentId} attached (tmux-push) — DMs will land in ${paneId}` : `✗ ${req.agentId} still not attached — DMs will sit in its inbox until it attaches (Fleets shows "not attached")`);
|
|
93
93
|
}
|
|
94
|
-
|
|
94
|
+
// Role: every spawned agent must carry its role type on the bus (presence bar, fleet graph, Fix→role routing all key on it). If it registered without one, tell it — into its own pane — and re-check.
|
|
95
|
+
const ROLE_RE = { coordinator: /coordinator/i, worker: /worker|repo-owner|consumer-owner|reference|infra|ops|lane/i, qa: /\bqa\b|quality/i, ci: /\bci\b/i, aide: /aide|liaison|curator/i, audit: /audit/i };
|
|
96
|
+
let roleOk = false;
|
|
97
|
+
if (registered) {
|
|
98
|
+
roleOk = ROLE_RE[req.role].test(roleOf(req.agentId) ?? "");
|
|
99
|
+
if (!roleOk) {
|
|
100
|
+
log(`⚠ registered without a ${req.role} role (role="${roleOf(req.agentId) ?? ""}") — asking it to declare one`);
|
|
101
|
+
await inject(`Declare your role on the coord bus now: call register (or join) again with agentId ${req.agentId} and role: { roleId: "coord-${req.role === "coordinator" ? "coordinator" : req.role}", displayName: "${req.role}" } — the console, fleet graph and Fix→role routing rely on it.`);
|
|
102
|
+
const d4 = Date.now() + 45_000;
|
|
103
|
+
while (Date.now() < d4) {
|
|
104
|
+
if (ROLE_RE[req.role].test(roleOf(req.agentId) ?? "")) {
|
|
105
|
+
roleOk = true;
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
await sleep(2_000);
|
|
109
|
+
}
|
|
110
|
+
log(roleOk ? `✓ role declared: ${roleOf(req.agentId)}` : `✗ still no ${req.role} role — Fleets will show it as "—"; DM it to fix`);
|
|
111
|
+
}
|
|
112
|
+
else
|
|
113
|
+
log(`✓ role: ${roleOf(req.agentId)}`);
|
|
114
|
+
}
|
|
115
|
+
return { agentId: req.agentId, pane: paneId, window: target, registered, attached, roleOk };
|
|
95
116
|
}
|
|
96
117
|
export function suggestId(project, role, existing) {
|
|
97
118
|
const base = `${project}-${role}`;
|
package/dist/seam.js
CHANGED
|
@@ -18,8 +18,12 @@ export function boardOf(projectPath) {
|
|
|
18
18
|
const dDoc = d ? parseWorkDoc(d) : null;
|
|
19
19
|
const facts = f ? parseFactsDoc(f) : null;
|
|
20
20
|
const now = Date.now();
|
|
21
|
+
// Onboarding state for the Roadmap empty state: is there a PRD, and what does its Status line say?
|
|
22
|
+
const prdSrc = read(path.join(docs, "PRD.md"));
|
|
23
|
+
const prd = prdSrc === null ? null : { status: prdSrc.match(/^\*\*Status\*\*:\s*([^\n]+)/m)?.[1]?.trim() ?? null, roadmap: existsSync(path.join(docs, "PRODUCTION_ROADMAP.md")) };
|
|
21
24
|
return {
|
|
22
25
|
files: { workstreams: !!ws, queue: !!q, done: !!d, facts: !!f },
|
|
26
|
+
prd,
|
|
23
27
|
workstreams: { schema: schema ?? (ws ? "unknown" : null), rows: board, issues: wsDoc ? workDocIssues(wsDoc) : [] },
|
|
24
28
|
queue: qDoc ? queueItemsOf(qDoc) : [],
|
|
25
29
|
done: dDoc ? doneEntriesOf(dDoc) : [],
|
package/dist/server.js
CHANGED
|
@@ -8,6 +8,7 @@ import { serve } from "@hono/node-server";
|
|
|
8
8
|
import { serveStatic } from "@hono/node-server/serve-static";
|
|
9
9
|
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
10
10
|
import path from "node:path";
|
|
11
|
+
import { hostname as osHostname } from "node:os";
|
|
11
12
|
import { loadConfig, saveProjects, validateProjectPath, CONFIG_FILE } from "./config.js";
|
|
12
13
|
import { boardOf, docOf, docsIndex, phaseTasksOf } from "./seam.js";
|
|
13
14
|
import { DocWriteError, sha256, writeDoc } from "./docwrite.js";
|
|
@@ -26,16 +27,17 @@ import { BusClient, AGENT_ID, busWrites, seamWrites, saveSwitches, SWITCHES_FILE
|
|
|
26
27
|
import { addQueueItem, reverifyFact, scopeAllows } from "./seamwrite.js";
|
|
27
28
|
import { fileURLToPath } from "node:url";
|
|
28
29
|
import { saveUpload, uploadPath, UPLOAD_DIR } from "./uploads.js";
|
|
29
|
-
import { answered, markAnswered, isAnswered } from "./answered.js";
|
|
30
|
+
import { answered, markAnswered, isAnswered, setCoordDir } from "./answered.js";
|
|
30
31
|
import { requireAuth, login, logout, isAuthed, authEnabled, tokenFile } from "./auth.js";
|
|
31
32
|
import { knowledgeIndex, knowledgeDoc, knowledgePath } from "./knowledge.js";
|
|
32
|
-
import { requiresReport, groundworkCli, REQUIRES } from "./kitresolve.js";
|
|
33
|
+
import { requiresReport, groundworkCli, playbookCli, packageRoot, REQUIRES } from "./kitresolve.js";
|
|
33
34
|
let cfg = loadConfig();
|
|
34
35
|
const port = Number(process.env.PORT ?? 8790);
|
|
35
36
|
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
36
37
|
const RULES = process.env.GROUNDWORK_CONSOLE_RULES ?? [path.join(process.env.HOME ?? "", ".groundwork-console", "rules.json"), path.resolve(HERE, "../groundwork-console.rules.json"), path.resolve(HERE, "../../../groundwork-console.rules.json")].find((f) => existsSync(f)) ?? path.resolve(HERE, "../../../groundwork-console.rules.json");
|
|
37
38
|
const PKG = JSON.parse(readFileSync(path.resolve(HERE, "../package.json"), "utf8"));
|
|
38
39
|
const runs = new Runs();
|
|
40
|
+
setCoordDir(cfg.coordDir);
|
|
39
41
|
const bus = new BusClient(cfg.kitPath, cfg.coordDir, "groundwork-console", process.env.AGENT_COORD_MCP);
|
|
40
42
|
const events = new EventEngine(cfg, RULES, runs, (args, log) => bus.call("send_message", args, log));
|
|
41
43
|
const notifier = new Notifier(cfg, runs, events);
|
|
@@ -46,6 +48,7 @@ const root = new Hono();
|
|
|
46
48
|
const app = new Hono().basePath("/api");
|
|
47
49
|
app.use("*", cors({ origin: (o) => o ?? "*", credentials: true }));
|
|
48
50
|
app.use("*", requireAuth());
|
|
51
|
+
app.onError((err, c) => { console.error(`[api] ${c.req.method} ${c.req.path}: ${err.message}`); return c.json({ error: err.message }, 500); });
|
|
49
52
|
app.get("/auth/status", (c) => c.json({ enabled: authEnabled, authed: isAuthed(c) }));
|
|
50
53
|
app.post("/auth/login", async (c) => { const b = (await c.req.json().catch(() => ({}))); if (!b.token || !login(c, b.token))
|
|
51
54
|
return c.json({ error: "wrong token — it's in ~/.groundwork-console/token (or GROUNDWORK_CONSOLE_TOKEN)" }, 401); return c.json({ ok: true }); });
|
|
@@ -54,7 +57,7 @@ app.post("/auth/logout", (c) => { logout(c); return c.json({ ok: true }); });
|
|
|
54
57
|
app.get("/auth/login", (c) => { const t = c.req.query("token") ?? ""; if (!login(c, t))
|
|
55
58
|
return c.json({ error: "wrong token" }, 401); return c.redirect(c.req.query("next") ?? "/"); });
|
|
56
59
|
const project = (name) => cfg.projects.find((p) => p.name === name);
|
|
57
|
-
app.get("/health", (c) => c.json({ ok: true, readOnly: !busWrites.enabled, busWrites: busWrites.enabled, version: PKG.version, requires: requiresReport(cfg.kitPath), config: CONFIG_FILE }));
|
|
60
|
+
app.get("/health", (c) => c.json({ ok: true, readOnly: !busWrites.enabled, busWrites: busWrites.enabled, version: PKG.version, requires: requiresReport(cfg.kitPath), config: CONFIG_FILE, hostname: osHostname().replace(/\.local$/, "") }));
|
|
58
61
|
app.get("/admin/bus-writes", (c) => c.json(busWrites));
|
|
59
62
|
app.get("/admin/seam-writes", (c) => c.json(seamWrites));
|
|
60
63
|
app.post("/admin/seam-writes", async (c) => {
|
|
@@ -111,7 +114,13 @@ app.post("/actions/send", async (c) => {
|
|
|
111
114
|
return c.json({ error: "room posts need a prefix (FYI / AGENT_ACTION / DAVID_DECISION / BLOCKER / RISK / DONE / GO / SCOPE CHANGE / VERDICT)" }, 400);
|
|
112
115
|
const target = b.to ? `→ ${b.to}` : `#${b.room.replace(/^#/, "")}`;
|
|
113
116
|
const r = await runs.exec({ kind: "bus", label: `send as ${who} ${target}`, sideEffects: true, args: { as: who, room: b.room, to: b.to, kind: b.kind, text: b.text.slice(0, 200) } }, async (log) => {
|
|
114
|
-
const
|
|
117
|
+
const cl = clientFor(who);
|
|
118
|
+
await cl.connect(log);
|
|
119
|
+
const parent = b.inReplyTo ?? b.answers?.[0];
|
|
120
|
+
const withReply = parent && cl.supports("send_message", "inReplyTo") ? { inReplyTo: parent } : {};
|
|
121
|
+
if (parent && !("inReplyTo" in withReply))
|
|
122
|
+
log("bus predates inReplyTo — using the console-side answered set only");
|
|
123
|
+
const out = await cl.call("send_message", { ...(b.to ? { to: b.to } : { room: b.room.replace(/^#/, "") }), text: b.text, ...(b.kind ? { kind: b.kind } : {}), ...withReply }, log);
|
|
115
124
|
// Answered bookkeeping (console-owned, until the bus has inReplyTo): explicit ids from the UI, plus — for a DM as David — every open DAVID_DECISION DM from that agent.
|
|
116
125
|
if (who === "david") {
|
|
117
126
|
const ids = [...(b.answers ?? [])];
|
|
@@ -182,7 +191,8 @@ app.get("/kit", (c) => {
|
|
|
182
191
|
app.get("/projects", (c) => {
|
|
183
192
|
const sc = scopes(cfg.coordDir);
|
|
184
193
|
const kitGw = (() => { try {
|
|
185
|
-
|
|
194
|
+
const r = packageRoot(cfg.kitPath, "groundwork");
|
|
195
|
+
return r ? JSON.parse(readFileSync(path.join(r, "package.json"), "utf8")).version : null;
|
|
186
196
|
}
|
|
187
197
|
catch {
|
|
188
198
|
return null;
|
|
@@ -505,6 +515,32 @@ app.get("/bus/unread", async (c) => {
|
|
|
505
515
|
}
|
|
506
516
|
});
|
|
507
517
|
/* ---- CLI updates (repo / global writes) as shell Runs; playbook has native --dry-run; groundwork update reports git status after ---- */
|
|
518
|
+
/* groundwork init — only for repos that are NOT inited (no docs/.groundwork/VERSION); the console never re-inits. Shell Run, typed confirm. */
|
|
519
|
+
app.post("/actions/groundwork-init/:name", async (c) => {
|
|
520
|
+
const p = project(c.req.param("name"));
|
|
521
|
+
if (!p)
|
|
522
|
+
return c.json({ error: "unknown project" }, 404);
|
|
523
|
+
const b = (await c.req.json().catch(() => ({})));
|
|
524
|
+
if (b.confirm !== p.name)
|
|
525
|
+
return c.json({ error: "confirm must equal the project name" }, 400);
|
|
526
|
+
if (existsSync(path.join(p.path, "docs/.groundwork/VERSION")))
|
|
527
|
+
return c.json({ error: "already inited (docs/.groundwork/VERSION exists) — use update" }, 409);
|
|
528
|
+
const cli = groundworkCli(cfg.kitPath);
|
|
529
|
+
if (!cli)
|
|
530
|
+
return c.json({ error: "groundwork CLI not found — npm i -g @davidbalzan/groundwork" }, 500);
|
|
531
|
+
const argv = [cli, "init", p.path, ...(b.minimal ? ["--minimal"] : [])];
|
|
532
|
+
const r = await runs.exec({ kind: "shell", label: `groundwork init · ${p.name}${b.minimal ? " --minimal" : ""}`, project: p.name, sideEffects: true, args: { argv: argv.slice(1) } }, async (log) => {
|
|
533
|
+
log(`$ node ${argv.map((a) => a.replace(cfg.kitPath, "<kit>").replace(p.path, "<repo>")).join(" ")}`);
|
|
534
|
+
const out = await execP("node", argv, { timeout: 120_000, maxBuffer: 8 << 20 }).catch((e) => { log(`exit: ${e.message}`); return { stdout: e.stdout ?? "", stderr: e.stderr ?? "" }; });
|
|
535
|
+
for (const l of (out.stdout + out.stderr).replace(/\x1b\[[0-9;]*m/g, "").split("\n").filter(Boolean).slice(0, 120))
|
|
536
|
+
log(l);
|
|
537
|
+
const st = await execP("git", ["status", "--porcelain"], { cwd: p.path }).then((x) => x.stdout).catch(() => "");
|
|
538
|
+
const changed = st.split("\n").filter(Boolean);
|
|
539
|
+
log(`git status: ${changed.length} path(s) changed — review, then commit (the console does not commit)`);
|
|
540
|
+
return { changed, cli: "groundwork init", inited: existsSync(path.join(p.path, "docs/.groundwork/VERSION")) };
|
|
541
|
+
});
|
|
542
|
+
return c.json(r, r.status === "ok" ? 200 : 500);
|
|
543
|
+
});
|
|
508
544
|
app.post("/actions/groundwork-update/:name", async (c) => {
|
|
509
545
|
const p = project(c.req.param("name"));
|
|
510
546
|
if (!p)
|
|
@@ -512,7 +548,9 @@ app.post("/actions/groundwork-update/:name", async (c) => {
|
|
|
512
548
|
const b = (await c.req.json().catch(() => ({})));
|
|
513
549
|
if (b.confirm !== p.name)
|
|
514
550
|
return c.json({ error: "confirm must equal the project name" }, 400);
|
|
515
|
-
const cli = groundworkCli(cfg.kitPath)
|
|
551
|
+
const cli = groundworkCli(cfg.kitPath);
|
|
552
|
+
if (!cli)
|
|
553
|
+
return c.json({ error: "groundwork CLI not found — npm i -g @davidbalzan/groundwork (or set kitPath to a kit checkout)" }, 500);
|
|
516
554
|
const argv = [cli, "update", p.path, ...(b.all ? ["--all"] : []), ...(b.docs ? ["--docs"] : [])];
|
|
517
555
|
const r = await runs.exec({ kind: "shell", label: `groundwork update · ${p.name}${b.all ? " --all" : ""}${b.docs ? " --docs" : ""}`, project: p.name, sideEffects: true, args: { argv: argv.slice(1) } }, async (log) => {
|
|
518
556
|
log(`$ node ${argv.map((a) => a.replace(cfg.kitPath, "<kit>").replace(p.path, "<repo>")).join(" ")}`);
|
|
@@ -531,7 +569,9 @@ app.post("/actions/playbook-update", async (c) => {
|
|
|
531
569
|
const dry = b.dryRun !== false;
|
|
532
570
|
if (!dry && b.confirm !== "UPDATE")
|
|
533
571
|
return c.json({ error: "confirm must be UPDATE to apply" }, 400);
|
|
534
|
-
const cli =
|
|
572
|
+
const cli = playbookCli(cfg.kitPath);
|
|
573
|
+
if (!cli)
|
|
574
|
+
return c.json({ error: "agent-coordination CLI not found — npm i -g agent-coordination (or set kitPath to a kit checkout)" }, 500);
|
|
535
575
|
const argv = [cli, "update", ...(dry ? ["--dry-run"] : [])];
|
|
536
576
|
const r = await runs.exec({ kind: "shell", label: `agent-coordination update${dry ? " (dry-run)" : ""}`, sideEffects: !dry, args: { argv: argv.slice(1) } }, async (log) => {
|
|
537
577
|
log(`$ node ${argv.map((a) => a.replace(cfg.kitPath, "<kit>")).join(" ")}`);
|
|
@@ -551,6 +591,7 @@ app.get("/projects/:name/fleet", async (c) => {
|
|
|
551
591
|
const live = (await agentsAsync(cfg.coordDir)).filter((a) => a.liveness !== "dead" && (a.project === p.room || a.project === p.name));
|
|
552
592
|
return c.json({ ...fleetStatus(live), templates: FLEET_TEMPLATES, window: `fleet-${p.name}` });
|
|
553
593
|
});
|
|
594
|
+
const roleOfAgent = (id) => agents(cfg.coordDir).find((a) => a.agentId === id)?.role ?? null;
|
|
554
595
|
app.post("/actions/spawn-fleet", async (c) => {
|
|
555
596
|
if (!busWrites.enabled)
|
|
556
597
|
return c.json({ error: "bus writes are off (header toggle)" }, 403);
|
|
@@ -572,7 +613,7 @@ app.post("/actions/spawn-fleet", async (c) => {
|
|
|
572
613
|
for (const step of plan) {
|
|
573
614
|
const agentId = suggestId(p.name, step.role, [...ids(), ...out.map((o) => o.agentId)]);
|
|
574
615
|
log(`── ${step.role} → ${agentId} (${step.harness})`);
|
|
575
|
-
const res = await spawnAgent({ project: p.name, repoPath: p.path, room: p.room, role: step.role, harness: step.harness, agentId, lane: step.lane, waitSeconds: b.waitSeconds }, (id) => ids().includes(id), log, attached);
|
|
616
|
+
const res = await spawnAgent({ project: p.name, repoPath: p.path, room: p.room, role: step.role, harness: step.harness, agentId, lane: step.lane, waitSeconds: b.waitSeconds }, (id) => ids().includes(id), log, attached, roleOfAgent);
|
|
576
617
|
out.push({ agentId: res.agentId, pane: res.pane, registered: res.registered, attached: res.attached });
|
|
577
618
|
}
|
|
578
619
|
log(`fleet ${t.id}: ${out.filter((o) => o.registered).length}/${out.length} registered · ${out.filter((o) => o.attached).length}/${out.length} attached`);
|
|
@@ -597,7 +638,7 @@ app.post("/actions/spawn", async (c) => {
|
|
|
597
638
|
const ids = () => agents(cfg.coordDir).map((a) => a.agentId);
|
|
598
639
|
const attached = (id) => existsSync(path.join(cfg.coordDir, "transports", `${id}.json`));
|
|
599
640
|
const agentId = b.agentId?.trim() || suggestId(p.name, b.role, ids());
|
|
600
|
-
const r = runs.execAsync({ kind: "shell", label: `spawn ${agentId} (${b.role} · ${b.harness}) → fleet-${p.name}`, project: p.name, sideEffects: true, args: { role: b.role, harness: b.harness, agentId, lane: b.lane, model: b.model } }, (log) => spawnAgent({ project: p.name, repoPath: p.path, room: p.room, role: b.role, harness: b.harness, agentId, lane: b.lane, model: b.model, note: b.note, waitSeconds: b.waitSeconds }, (id) => ids().includes(id), log, attached));
|
|
641
|
+
const r = runs.execAsync({ kind: "shell", label: `spawn ${agentId} (${b.role} · ${b.harness}) → fleet-${p.name}`, project: p.name, sideEffects: true, args: { role: b.role, harness: b.harness, agentId, lane: b.lane, model: b.model } }, (log) => spawnAgent({ project: p.name, repoPath: p.path, room: p.room, role: b.role, harness: b.harness, agentId, lane: b.lane, model: b.model, note: b.note, waitSeconds: b.waitSeconds }, (id) => ids().includes(id), log, attached, roleOfAgent));
|
|
601
642
|
return c.json(r);
|
|
602
643
|
});
|
|
603
644
|
/* ---- Gated bus admin: prune stale, unregister one (writes; require switch + confirm) ---- */
|
|
@@ -662,6 +703,25 @@ app.post("/actions/phase/:name/:phase/:verb", async (c) => {
|
|
|
662
703
|
const r = await runs.exec({ kind: "bus", label: `${verb} ${phase} → ${target}`, project: p.name, sideEffects: true, args: { to: target, text: text.slice(0, 200) } }, (log) => busDavid.call("send_message", { to: target, text }, log));
|
|
663
704
|
return c.json(r, r.status === "ok" ? 200 : 403);
|
|
664
705
|
});
|
|
706
|
+
/* ---- Project-level onboarding actions (as david → the aide): create-prd conversation starter, /kickstart ---- */
|
|
707
|
+
app.post("/actions/project/:name/:verb", async (c) => {
|
|
708
|
+
const p = project(c.req.param("name"));
|
|
709
|
+
if (!p)
|
|
710
|
+
return c.json({ error: "unknown project" }, 404);
|
|
711
|
+
const verb = c.req.param("verb");
|
|
712
|
+
const b = (await c.req.json().catch(() => ({})));
|
|
713
|
+
const target = roleAgent(p.name, p.room, /aide|liaison|curator/i);
|
|
714
|
+
if (!target)
|
|
715
|
+
return c.json({ error: `no live aide for ${p.name}` }, 409);
|
|
716
|
+
const text = verb === "kickstart"
|
|
717
|
+
? `AGENT_ACTION: run /kickstart in ${p.name} (${p.path}) — PRD is in docs/PRD.md; produce the roadmap + docs scaffold (TECH_STACK, ARCHITECTURE_GUIDE, DECISIONS, PRODUCTION_ROADMAP, WORKSTREAMS.v1, QUEUE, DONE, FACTS) and report when the doctor is clean.${b.note ? " Note: " + b.note : ""}`
|
|
718
|
+
: verb === "create-prd" ? `AGENT_ACTION: let's write the PRD for ${p.name} (${p.path}) — run /create-prd with me; ask what you need to know, keep it to one pass, Status: Draft until I approve.${b.note ? " Note: " + b.note : ""}`
|
|
719
|
+
: null;
|
|
720
|
+
if (!text)
|
|
721
|
+
return c.json({ error: "verb must be kickstart | create-prd" }, 400);
|
|
722
|
+
const r = await runs.exec({ kind: "bus", label: `${verb} → ${target}`, project: p.name, sideEffects: true, args: { to: target, text: text.slice(0, 200) } }, (log) => busDavid.call("send_message", { to: target, text }, log));
|
|
723
|
+
return c.json(r, r.status === "ok" ? 200 : 403);
|
|
724
|
+
});
|
|
665
725
|
/* ---- Doctor → Fix: route a doctor finding to the role that owns it (live agent required); sent as david ---- */
|
|
666
726
|
const DOCTOR_OWNER = {
|
|
667
727
|
"orphan-wikilinks": { role: "aide", re: /aide|liaison|curator/i, how: "fix the orphan wikilinks (create the target doc or correct the link); docs are your lane" },
|
package/dist/skills.js
CHANGED
|
@@ -3,17 +3,22 @@ import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
|
3
3
|
import { createHash } from "node:crypto";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
6
|
+
import { packageRoot } from "./kitresolve.js";
|
|
6
7
|
const sha = (p) => (existsSync(p) ? createHash("sha1").update(readFileSync(p)).digest("hex").slice(0, 10) : null);
|
|
7
8
|
const dirs = (p) => (existsSync(p) ? readdirSync(p, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name) : []);
|
|
8
9
|
const front = (p) => { if (!existsSync(p))
|
|
9
10
|
return {}; const m = readFileSync(p, "utf8").match(/^---\n([\s\S]*?)\n---/); const o = {}; m?.[1].split("\n").forEach((l) => { const i = l.indexOf(":"); if (i > 0)
|
|
10
11
|
o[l.slice(0, i).trim()] = l.slice(i + 1).trim(); }); return o; };
|
|
11
12
|
export function groundworkSkills(kitPath, projectPath) {
|
|
12
|
-
const
|
|
13
|
-
const
|
|
13
|
+
const root = packageRoot(kitPath, "groundwork");
|
|
14
|
+
const versionMarker0 = existsSync(path.join(projectPath, "docs/.groundwork/VERSION")) ? readFileSync(path.join(projectPath, "docs/.groundwork/VERSION"), "utf8").trim() : null;
|
|
15
|
+
if (!root)
|
|
16
|
+
return { versionMarker: versionMarker0, kitVersion: null, driftedVersion: false, skills: [], error: "groundwork not found — npm i -g @davidbalzan/groundwork (or set kitPath to a kit checkout)" };
|
|
17
|
+
const payload = path.join(root, "payload/skills");
|
|
18
|
+
const pathsMjs = path.join(root, "src/lib/paths.mjs");
|
|
14
19
|
const minimal = existsSync(pathsMjs) ? [...(readFileSync(pathsMjs, "utf8").match(/MINIMAL_SKILLS = \[([\s\S]*?)\]/)?.[1] ?? "").matchAll(/"([^"]+)"/g)].map((m) => m[1]) : [];
|
|
15
20
|
const versionMarker = existsSync(path.join(projectPath, "docs/.groundwork/VERSION")) ? readFileSync(path.join(projectPath, "docs/.groundwork/VERSION"), "utf8").trim() : null;
|
|
16
|
-
const kitVersion = JSON.parse(readFileSync(path.join(
|
|
21
|
+
const kitVersion = JSON.parse(readFileSync(path.join(root, "package.json"), "utf8")).version;
|
|
17
22
|
return {
|
|
18
23
|
versionMarker, kitVersion, driftedVersion: !!versionMarker && versionMarker !== kitVersion,
|
|
19
24
|
skills: dirs(payload).map((name) => {
|
|
@@ -32,8 +37,11 @@ export function groundworkSkills(kitPath, projectPath) {
|
|
|
32
37
|
/* Global playbook cards live in more than one harness home: Claude → ~/.claude/skills, Grok → ~/.grok/skills (+ ~/.grok/groundwork-playbook). Measure each. */
|
|
33
38
|
const HARNESS_HOMES = [["claude", path.join(homedir(), ".claude/skills")], ["grok", path.join(homedir(), ".grok/skills")]];
|
|
34
39
|
export function playbookCards(kitPath) {
|
|
35
|
-
const
|
|
36
|
-
|
|
40
|
+
const root = packageRoot(kitPath, "playbook");
|
|
41
|
+
if (!root)
|
|
42
|
+
return { kitVersion: null, globalDir: HARNESS_HOMES[0][1], homes: [], cards: [], error: "agent-coordination not found — npm i -g agent-coordination (or set kitPath to a kit checkout)" };
|
|
43
|
+
const srcRoot = path.join(root, "skills");
|
|
44
|
+
const kitVersion = JSON.parse(readFileSync(path.join(root, "package.json"), "utf8")).version;
|
|
37
45
|
const homes = HARNESS_HOMES.filter(([, d]) => existsSync(d));
|
|
38
46
|
return {
|
|
39
47
|
kitVersion, globalDir: HARNESS_HOMES[0][1], homes: homes.map(([h, d]) => ({ harness: h, dir: d })),
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{m as e,t}from"./mermaid-parser.core-
|
|
1
|
+
import{m as e,t}from"./mermaid-parser.core-DG-hDTtF.js";import{n}from"./chunk-Y2CYZVJY-DsF7k-Jl.js";import{m as r}from"./src-CJc45UuW.js";import"./chunk-I66GZJ75-Bazokq-r.js";import{n as i,r as a,t as o}from"./chunk-6Q2QTUOP-N3v574-9.js";import{t as s}from"./chunk-JWPE2WC7-DVXcaiue.js";import"./mermaid.core-CxtgV-Z8.js";var c=e().RailroadAbnf.parser.LangiumParser,l=n(e=>{let t=e.alternatives.map(u);return t.length===1?t[0]:{type:`choice`,alternatives:t}},`transformAlternation`),u=n(e=>{let t=e.elements.map(f);return t.length===1?t[0]:{type:`sequence`,elements:t}},`transformConcatenation`),d=n(e=>{if(e.includes(`*`)){let[t,n]=e.split(`*`);return{min:t?parseInt(t,10):0,max:n?parseInt(n,10):1/0}}let t=parseInt(e,10);return{min:t,max:t}},`parseRepeat`),f=n(e=>{let t=p(e.primary);if(!e.repeat)return t;let{min:n,max:r}=d(e.repeat);return n===0&&r===1?{type:`optional`,element:t}:{type:`repetition`,element:t,min:n,max:r}},`transformElement`),p=n(e=>{switch(e.$type){case`AbnfStringLiteral`:return{type:`terminal`,value:e.value};case`AbnfNumVal`:return{type:`terminal`,value:e.value};case`AbnfRuleName`:return{type:`nonterminal`,name:e.name};case`AbnfGroup`:return l(e.element);case`AbnfOptionalGroup`:return{type:`optional`,element:l(e.element)};default:throw Error(`Unsupported ABNF primary node: ${e.$type}`)}},`transformPrimary`),m=n(e=>({name:e.name,definition:l(e.definition)}),`transformRule`),h=n(e=>{s(e,o),e.title&&o.setTitle(e.title),e.rules.map(e=>o.addRule(m(e)))},`populateDb`),g={parser:{parse:n(e=>{o.clear(),r.debug(`[ABNF Parser] Starting Langium parse`);let n=c.parse(e);if(n.lexerErrors.length>0||n.parserErrors.length>0)throw new t(n);let i=n.value;r.debug(`[ABNF Parser] Parsed rules:`,i.rules.length),h(i),r.debug(`[ABNF Parser] Parse complete`)},`parse`),parser:{yy:o}},db:o,renderer:a,styles:i};export{g as diagram};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{n as e,t}from"./path-fybaL0A-.js";import{a as n,c as r,d as i,f as a,i as o,l as s,m as c,n as l,o as u,p as d,r as f,u as p}from"./dist-
|
|
1
|
+
import{n as e,t}from"./path-fybaL0A-.js";import{a as n,c as r,d as i,f as a,i as o,l as s,m as c,n as l,o as u,p as d,r as f,u as p}from"./dist-BFMCu1a5.js";function m(e){return e.innerRadius}function h(e){return e.outerRadius}function g(e){return e.startAngle}function _(e){return e.endAngle}function v(e){return e&&e.padAngle}function y(e,t,n,r,i,a,o,s){var c=n-e,l=r-t,u=o-i,d=s-a,f=d*c-u*l;if(!(f*f<1e-12))return f=(u*(t-a)-d*(e-i))/f,[e+f*c,t+f*l]}function b(e,t,n,r,i,a,o){var c=e-n,l=t-r,u=(o?a:-a)/d(c*c+l*l),f=u*l,p=-u*c,m=e+f,h=t+p,g=n+f,_=r+p,v=(m+g)/2,y=(h+_)/2,b=g-m,x=_-h,S=b*b+x*x,C=i-a,w=m*_-g*h,T=(x<0?-1:1)*d(s(0,C*C*S-w*w)),E=(w*x-b*T)/S,D=(-w*b-x*T)/S,O=(w*x+b*T)/S,k=(-w*b+x*T)/S,A=E-v,j=D-y,M=O-v,N=k-y;return A*A+j*j>M*M+N*N&&(E=O,D=k),{cx:E,cy:D,x01:-f,y01:-p,x11:E*(i/C-1),y11:D*(i/C-1)}}function x(){var s=m,x=h,S=e(0),C=null,w=g,T=_,E=v,D=null,O=t(k);function k(){var e,t,m=+s.apply(this,arguments),h=+x.apply(this,arguments),g=w.apply(this,arguments)-r,_=T.apply(this,arguments)-r,v=l(_-g),k=_>g;if(D||=e=O(),h<m&&(t=h,h=m,m=t),!(h>1e-12))D.moveTo(0,0);else if(v>c-1e-12)D.moveTo(h*u(g),h*a(g)),D.arc(0,0,h,g,_,!k),m>1e-12&&(D.moveTo(m*u(_),m*a(_)),D.arc(0,0,m,_,g,k));else{var A=g,j=_,M=g,N=_,P=v,F=v,I=E.apply(this,arguments)/2,L=I>1e-12&&(C?+C.apply(this,arguments):d(m*m+h*h)),R=p(l(h-m)/2,+S.apply(this,arguments)),z=R,B=R,V,H;if(L>1e-12){var U=o(L/m*a(I)),W=o(L/h*a(I));(P-=U*2)>1e-12?(U*=k?1:-1,M+=U,N-=U):(P=0,M=N=(g+_)/2),(F-=W*2)>1e-12?(W*=k?1:-1,A+=W,j-=W):(F=0,A=j=(g+_)/2)}var G=h*u(A),K=h*a(A),q=m*u(N),J=m*a(N);if(R>1e-12){var Y=h*u(j),X=h*a(j),Z=m*u(M),Q=m*a(M),$;if(v<i){if($=y(G,K,Z,Q,Y,X,q,J)){var ee=G-$[0],te=K-$[1],ne=Y-$[0],re=X-$[1],ie=1/a(f((ee*ne+te*re)/(d(ee*ee+te*te)*d(ne*ne+re*re)))/2),ae=d($[0]*$[0]+$[1]*$[1]);z=p(R,(m-ae)/(ie-1)),B=p(R,(h-ae)/(ie+1))}else z=B=0}}F>1e-12?B>1e-12?(V=b(Z,Q,G,K,h,B,k),H=b(Y,X,q,J,h,B,k),D.moveTo(V.cx+V.x01,V.cy+V.y01),B<R?D.arc(V.cx,V.cy,B,n(V.y01,V.x01),n(H.y01,H.x01),!k):(D.arc(V.cx,V.cy,B,n(V.y01,V.x01),n(V.y11,V.x11),!k),D.arc(0,0,h,n(V.cy+V.y11,V.cx+V.x11),n(H.cy+H.y11,H.cx+H.x11),!k),D.arc(H.cx,H.cy,B,n(H.y11,H.x11),n(H.y01,H.x01),!k))):(D.moveTo(G,K),D.arc(0,0,h,A,j,!k)):D.moveTo(G,K),!(m>1e-12)||!(P>1e-12)?D.lineTo(q,J):z>1e-12?(V=b(q,J,Y,X,m,-z,k),H=b(G,K,Z,Q,m,-z,k),D.lineTo(V.cx+V.x01,V.cy+V.y01),z<R?D.arc(V.cx,V.cy,z,n(V.y01,V.x01),n(H.y01,H.x01),!k):(D.arc(V.cx,V.cy,z,n(V.y01,V.x01),n(V.y11,V.x11),!k),D.arc(0,0,m,n(V.cy+V.y11,V.cx+V.x11),n(H.cy+H.y11,H.cx+H.x11),k),D.arc(H.cx,H.cy,z,n(H.y11,H.x11),n(H.y01,H.x01),!k))):D.arc(0,0,m,N,M,k)}if(D.closePath(),e)return D=null,e+``||null}return k.centroid=function(){var e=(+s.apply(this,arguments)+ +x.apply(this,arguments))/2,t=(+w.apply(this,arguments)+ +T.apply(this,arguments))/2-i/2;return[u(t)*e,a(t)*e]},k.innerRadius=function(t){return arguments.length?(s=typeof t==`function`?t:e(+t),k):s},k.outerRadius=function(t){return arguments.length?(x=typeof t==`function`?t:e(+t),k):x},k.cornerRadius=function(t){return arguments.length?(S=typeof t==`function`?t:e(+t),k):S},k.padRadius=function(t){return arguments.length?(C=t==null?null:typeof t==`function`?t:e(+t),k):C},k.startAngle=function(t){return arguments.length?(w=typeof t==`function`?t:e(+t),k):w},k.endAngle=function(t){return arguments.length?(T=typeof t==`function`?t:e(+t),k):T},k.padAngle=function(t){return arguments.length?(E=typeof t==`function`?t:e(+t),k):E},k.context=function(e){return arguments.length?(D=e??null,k):D},k}export{x as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./chunk-KEIR6QF5-BICK3FdT.js";import{M as e}from"./mermaid-parser.core-DG-hDTtF.js";export{e as createArchitectureServices};
|