@davidbalzan/groundwork-console 0.1.7 → 0.1.9

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 (100) hide show
  1. package/README.md +1 -1
  2. package/dist/gitinfo.js +114 -0
  3. package/dist/kitresolve.js +11 -9
  4. package/dist/mcp.js +39 -1
  5. package/dist/server.js +119 -8
  6. package/package.json +1 -1
  7. package/public/assets/{abnfDiagram-N423BO3Z-B0nns4cA.js → abnfDiagram-N423BO3Z-BGsikmsF.js} +1 -1
  8. package/public/assets/{arc-DsDgWIU7.js → arc-B-4vSlyP.js} +1 -1
  9. package/public/assets/architecture-TIHT7OUA-CWSoiOz-.js +1 -0
  10. package/public/assets/{architectureDiagram-T3A2C74G-Bqo7zIaB.js → architectureDiagram-T3A2C74G-BSzvyipw.js} +1 -1
  11. package/public/assets/{blockDiagram-VBNYF7ZC-CvNPqlbt.js → blockDiagram-VBNYF7ZC-C5oWbld9.js} +1 -1
  12. package/public/assets/{c4Diagram-5PPSVZJV-BqjkNgv6.js → c4Diagram-5PPSVZJV-DCrbq1t_.js} +1 -1
  13. package/public/assets/channel-DjKps-UC.js +1 -0
  14. package/public/assets/{chunk-2GRJ4B5K-CWJJzbol.js → chunk-2GRJ4B5K-ne8hXJmp.js} +1 -1
  15. package/public/assets/{chunk-4I5QYGJK-CuHGN-lT.js → chunk-4I5QYGJK-BPNU3mTP.js} +1 -1
  16. package/public/assets/{chunk-5RXB4S5H-Cl92nmIs.js → chunk-5RXB4S5H-CqBlGmXs.js} +1 -1
  17. package/public/assets/{chunk-6Q2QTUOP-C3vYg7rX.js → chunk-6Q2QTUOP-CbJEu2Tv.js} +1 -1
  18. package/public/assets/{chunk-7Z6QIM7H-jkpbvAhi.js → chunk-7Z6QIM7H-N6BW9e3u.js} +1 -1
  19. package/public/assets/{chunk-GF5L2VYU-Bo-zB54d.js → chunk-GF5L2VYU-BO3eIRiS.js} +1 -1
  20. package/public/assets/{chunk-I66GZJ75-BD9h95Vq.js → chunk-I66GZJ75-Dqby7D_F.js} +1 -1
  21. package/public/assets/{chunk-JQJVKLGR-C6TRkyf7.js → chunk-JQJVKLGR-C-t0GbWv.js} +1 -1
  22. package/public/assets/{chunk-KBJHAD2P-RgoL4Nto.js → chunk-KBJHAD2P-DMPC8ktQ.js} +1 -1
  23. package/public/assets/{chunk-NSK5VX7P-C6TsGnGA.js → chunk-NSK5VX7P-DfB90W7I.js} +1 -1
  24. package/public/assets/{chunk-QR6OTTB3-Bq5akaxk.js → chunk-QR6OTTB3-PD4VlBbj.js} +1 -1
  25. package/public/assets/{chunk-RYQCIY6F-DtVCesJ4.js → chunk-RYQCIY6F-Cl0F6wPb.js} +1 -1
  26. package/public/assets/{chunk-UBXNYLIW-BjnaZZH0.js → chunk-UBXNYLIW-BhXlBYFb.js} +1 -1
  27. package/public/assets/{chunk-W5SLKNZC-D1KW-lQn.js → chunk-W5SLKNZC-LMuVsYgU.js} +1 -1
  28. package/public/assets/{chunk-WRU74C26-YZFoWy6X.js → chunk-WRU74C26-D-XSx_qA.js} +1 -1
  29. package/public/assets/{chunk-XXDRQBXY-CyrI_Mox.js → chunk-XXDRQBXY-y8l-lKuC.js} +1 -1
  30. package/public/assets/classDiagram-JCYQIIEL-DPUY4tj5.js +1 -0
  31. package/public/assets/classDiagram-v2-OCEON4UE-DPUY4tj5.js +1 -0
  32. package/public/assets/{cose-bilkent-JH36ORCC-v6lDOu7m.js → cose-bilkent-JH36ORCC-BjMU0NVP.js} +1 -1
  33. package/public/assets/{cynefin-VYW2F7L2-BtzRQs_J.js → cynefin-VYW2F7L2-B1PZgHyy.js} +1 -1
  34. package/public/assets/{cynefinDiagram-MW4NZA55--R8S0KoS.js → cynefinDiagram-MW4NZA55-D73WYrdw.js} +1 -1
  35. package/public/assets/{dagre-VZM6K2ZE-D6mRFOfB.js → dagre-VZM6K2ZE-K8m4zmsy.js} +1 -1
  36. package/public/assets/{diagram-7IWD3JNH-kfCoRMpW.js → diagram-7IWD3JNH-B33LU3jt.js} +1 -1
  37. package/public/assets/{diagram-B4RE2ZJO-sxUW6rzT.js → diagram-B4RE2ZJO-BKi9Ghjh.js} +1 -1
  38. package/public/assets/{diagram-LBJQPF4R-B926bgCA.js → diagram-LBJQPF4R-Ix7F1ppO.js} +1 -1
  39. package/public/assets/{diagram-Q27KOJAE-CcVi6njJ.js → diagram-Q27KOJAE-B2p07Tka.js} +1 -1
  40. package/public/assets/{diagram-UB23O5K3-BtW4XOuK.js → diagram-UB23O5K3-D1XH1Bhp.js} +1 -1
  41. package/public/assets/{dist-BnkmMC7M.js → dist-pnrLfy84.js} +1 -1
  42. package/public/assets/{ebnfDiagram-BXEA7PRR-BNGfWHYj.js → ebnfDiagram-BXEA7PRR-IkPfGSli.js} +1 -1
  43. package/public/assets/{erDiagram-JOGREHBK-BGrhe741.js → erDiagram-JOGREHBK-CAWgqEgt.js} +1 -1
  44. package/public/assets/eventmodeling-45OFAUF4-CcctuRbb.js +1 -0
  45. package/public/assets/flowDiagram-UKHOOZJN-zDkWvqJc.js +1 -0
  46. package/public/assets/{ganttDiagram-PKOTCBZU-CR5BNdXR.js → ganttDiagram-PKOTCBZU-COg5hBC2.js} +1 -1
  47. package/public/assets/{gitGraph-TEB2WS4Q-CfvC-iZD.js → gitGraph-TEB2WS4Q-DYuiIPVx.js} +1 -1
  48. package/public/assets/{gitGraphDiagram-DS77QQ5N-D45EWdll.js → gitGraphDiagram-DS77QQ5N-ZDuhxxyx.js} +1 -1
  49. package/public/assets/index-B0318bcJ.css +2 -0
  50. package/public/assets/index-j_HG7VFe.js +41 -0
  51. package/public/assets/{info-DKCQHKI2-stahmZKW.js → info-DKCQHKI2-ByhcH86q.js} +1 -1
  52. package/public/assets/{infoDiagram-6WML65LV-nWu8ujif.js → infoDiagram-6WML65LV-DItOCnkC.js} +1 -1
  53. package/public/assets/{ishikawaDiagram-WSZJBQD7-Ccwzv-xZ.js → ishikawaDiagram-WSZJBQD7-DoJV_iGr.js} +1 -1
  54. package/public/assets/{journeyDiagram-NVQOT4AX-R7gZw1Qw.js → journeyDiagram-NVQOT4AX-Dj2aplxz.js} +1 -1
  55. package/public/assets/{kanban-definition-27J2QSJJ-By8aTzkI.js → kanban-definition-27J2QSJJ-DXK1irL2.js} +1 -1
  56. package/public/assets/{line-zJXbSVxf.js → line-pHRnA6j_.js} +1 -1
  57. package/public/assets/{linear-B8U-jWnO.js → linear-iVFA7555.js} +1 -1
  58. package/public/assets/{mermaid-parser.core-B1iqIz20.js → mermaid-parser.core-D7AXmMRA.js} +3 -3
  59. package/public/assets/{mermaid.core-CuFK5CuK.js → mermaid.core-B20ztOv3.js} +4 -4
  60. package/public/assets/{mindmap-definition-FAOFIHXS-qJ2DuBku.js → mindmap-definition-FAOFIHXS-Bsyn0uqK.js} +1 -1
  61. package/public/assets/{packet-7NZHBO7P-CzQJdD0e.js → packet-7NZHBO7P-CEQknT-e.js} +1 -1
  62. package/public/assets/{pegDiagram-VL7TDLO6-CKmHw89b.js → pegDiagram-VL7TDLO6-DOtsTyuR.js} +1 -1
  63. package/public/assets/{pie-RZYD4A2V-Dt3CA16n.js → pie-RZYD4A2V-1iLUvJbV.js} +1 -1
  64. package/public/assets/{pieDiagram-7S7Q4E2Y-D9vwefN9.js → pieDiagram-7S7Q4E2Y-D4--2rpe.js} +1 -1
  65. package/public/assets/{quadrantDiagram-CIZ2JOQS-jtSAmOcp.js → quadrantDiagram-CIZ2JOQS-Br8f1-Gt.js} +1 -1
  66. package/public/assets/{radar-I7S5WNFK-BtDfUAqO.js → radar-I7S5WNFK-Br-8Crms.js} +1 -1
  67. package/public/assets/{railroad-3IZDKUUU-D5DNw8qB.js → railroad-3IZDKUUU-BEfff3Sh.js} +1 -1
  68. package/public/assets/railroad-abnf-AHOZXSZD-BqeIou7M.js +1 -0
  69. package/public/assets/railroad-ebnf-EBAXGLYW-D2cRF1np.js +1 -0
  70. package/public/assets/railroad-peg-LSFZ7HO6-DHsHFfwA.js +1 -0
  71. package/public/assets/{railroadDiagram-AXF67PYL-C-S9VT4Z.js → railroadDiagram-AXF67PYL-CVe-BvTz.js} +1 -1
  72. package/public/assets/{requirementDiagram-LRYGKXZP-Bmv6EbmT.js → requirementDiagram-LRYGKXZP-CWTLQb40.js} +1 -1
  73. package/public/assets/{sankeyDiagram-W5VNT64P-Cm2dLWH9.js → sankeyDiagram-W5VNT64P-BMG9HuBY.js} +1 -1
  74. package/public/assets/{sequenceDiagram-SI44F4Z6-DPslkAXf.js → sequenceDiagram-SI44F4Z6-Bp040MgC.js} +1 -1
  75. package/public/assets/{src-l8EnJwXz.js → src-0sS7-ICb.js} +1 -1
  76. package/public/assets/{stateDiagram-OKZ733FA-BBXMNQQ7.js → stateDiagram-OKZ733FA-DUQ9IHH3.js} +1 -1
  77. package/public/assets/stateDiagram-v2-UEYNNEHI-DZFTLoGK.js +1 -0
  78. package/public/assets/{swimlanes-SLNWSIFB-BeiwtQg-.js → swimlanes-SLNWSIFB-BUlgj2-U.js} +1 -1
  79. package/public/assets/swimlanesDiagram-ULZ7WXOC-BkedTpdm.js +8 -0
  80. package/public/assets/{timeline-definition-Z64GVDOM-Drd7Un_g.js → timeline-definition-Z64GVDOM-Dr9p5Uap.js} +1 -1
  81. package/public/assets/{treeView-QDETBFTQ-BT2KyIoB.js → treeView-QDETBFTQ-Cnle-Iuk.js} +1 -1
  82. package/public/assets/{treemap-6X3UGDF4-gdZw2F6X.js → treemap-6X3UGDF4-CE5Uf4v0.js} +1 -1
  83. package/public/assets/{vennDiagram-T6HMQDX7-Bbk8OMR0.js → vennDiagram-T6HMQDX7-HC8LLVRa.js} +1 -1
  84. package/public/assets/{wardley-OPB4EBWU-BI0xutNp.js → wardley-OPB4EBWU-Bv51cdvB.js} +1 -1
  85. package/public/assets/{wardleyDiagram-T6FBY63Y-DQvEcd8r.js → wardleyDiagram-T6FBY63Y-B-BXTw2T.js} +1 -1
  86. package/public/assets/{xychartDiagram-ELKLHX3M-CRWoUpzf.js → xychartDiagram-ELKLHX3M-BJbbcYRA.js} +1 -1
  87. package/public/index.html +2 -2
  88. package/public/assets/architecture-TIHT7OUA-okwBKksv.js +0 -1
  89. package/public/assets/channel-CI1-SNMv.js +0 -1
  90. package/public/assets/classDiagram-JCYQIIEL-zdEoGtpz.js +0 -1
  91. package/public/assets/classDiagram-v2-OCEON4UE-zdEoGtpz.js +0 -1
  92. package/public/assets/eventmodeling-45OFAUF4-Cn6RDMEG.js +0 -1
  93. package/public/assets/flowDiagram-UKHOOZJN-DOMsEKL1.js +0 -1
  94. package/public/assets/index-CxNe802C.css +0 -2
  95. package/public/assets/index-yHI4kvM-.js +0 -41
  96. package/public/assets/railroad-abnf-AHOZXSZD-DuJUfLMt.js +0 -1
  97. package/public/assets/railroad-ebnf-EBAXGLYW-CiLIN42p.js +0 -1
  98. package/public/assets/railroad-peg-LSFZ7HO6-DKtIAsvs.js +0 -1
  99. package/public/assets/stateDiagram-v2-UEYNNEHI-BxOobbWc.js +0 -1
  100. package/public/assets/swimlanesDiagram-ULZ7WXOC-BEW9yZ9H.js +0 -8
package/README.md CHANGED
@@ -18,7 +18,7 @@ groundwork-console token # print the token
18
18
  groundwork-console config # print the config path
19
19
  ```
20
20
 
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`.
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.24.0` · groundwork `^0.3.0` · agent-coordination `^0.5.0`.
22
22
 
23
23
  ## From another machine
24
24
 
package/dist/gitinfo.js CHANGED
@@ -1,6 +1,8 @@
1
1
  /* Read-only git views for a project's docs: uncommitted hunks (working tree vs HEAD, staged included), last commit touching the file + its hunks, and a docs/ status map. */
2
2
  import { execFile } from "node:child_process";
3
3
  import { promisify } from "node:util";
4
+ import { existsSync } from "node:fs";
5
+ import path from "node:path";
4
6
  const run = promisify(execFile);
5
7
  async function git(cwd, args) { try {
6
8
  const r = await run("git", args, { cwd, maxBuffer: 8 << 20 });
@@ -67,3 +69,115 @@ export async function lastDocsCommit(projectPath) {
67
69
  const files = ((await git(projectPath, ["show", "--format=", "--name-only", short, "--", "docs"])) ?? "").split("\n").filter(Boolean).map((f) => f.replace(/^docs\//, ""));
68
70
  return { short, subject, date, files };
69
71
  }
72
+ const GIT_CACHE_MS = 3_000;
73
+ const STALE_DAYS = 14;
74
+ const GRAPH_MAX = 500;
75
+ const cache = new Map();
76
+ function cached(key, fn) {
77
+ const hit = cache.get(key);
78
+ if (hit && Date.now() - hit.at < GIT_CACHE_MS)
79
+ return hit.value;
80
+ const value = fn();
81
+ cache.set(key, { at: Date.now(), value });
82
+ value.catch(() => cache.delete(key));
83
+ return value;
84
+ }
85
+ const splitRecords = (out) => (out ?? "").split("\n").filter(Boolean).map((l) => l.split("\x1f"));
86
+ export function gitGraph(projectPath, limit = 200) {
87
+ const n = Math.max(1, Math.min(GRAPH_MAX, Math.floor(limit) || 200));
88
+ return cached(`graph:${projectPath}:${n}`, async () => {
89
+ const head = (await git(projectPath, ["rev-parse", "HEAD"]))?.trim() ?? "";
90
+ const rows = splitRecords(await git(projectPath, ["log", "--all", "--date-order", `-n${n}`, "--format=%H%x1f%h%x1f%P%x1f%s%x1f%an%x1f%aI%x1f%D"]));
91
+ const commits = rows.map(([sha, short, parents, subject, author, date, refs]) => ({ sha, short, parents: (parents ?? "").split(" ").filter(Boolean), subject: subject ?? "", author: author ?? "", date: date ?? "", refs: (refs ?? "").split(",").map((r) => r.trim().replace(/^HEAD -> /, "HEAD -> ")).filter(Boolean) }));
92
+ return { head, commits };
93
+ });
94
+ }
95
+ /** `git worktree list --porcelain` → entries; dirty = `git status --porcelain` non-empty (skipped for prunable/missing paths); agent from the fleet sibling dir name `<repo>-<agentId>[-<slice>]`, else board owner whose Branch·Worktree column names the branch/path. */
96
+ export function gitWorktrees(projectPath, hints = {}) {
97
+ return cached(`worktrees:${projectPath}:${(hints.agentIds ?? []).join(",")}`, async () => {
98
+ const out = (await git(projectPath, ["worktree", "list", "--porcelain"])) ?? "";
99
+ const blocks = out.split(/\n\s*\n/).map((b) => b.split("\n").filter(Boolean)).filter((b) => b.length);
100
+ const repoBase = path.basename(projectPath.replace(/\/+$/, ""));
101
+ const agentIds = [...(hints.agentIds ?? [])].sort((a, b) => b.length - a.length); // longest first: "<project>-worker-1" before "<project>"
102
+ const worktrees = await Promise.all(blocks.map(async (b, i) => {
103
+ const get = (k) => b.find((l) => l === k || l.startsWith(k + " "))?.slice(k.length).trim();
104
+ const wt = get("worktree") ?? "";
105
+ const sha = get("HEAD") ?? "";
106
+ const branchRef = get("branch");
107
+ const prunable = get("prunable") !== undefined;
108
+ const detached = b.includes("detached") || !branchRef;
109
+ const branch = branchRef ? branchRef.replace(/^refs\/heads\//, "") : null;
110
+ const dirty = !prunable && wt && existsSync(wt) ? ((await git(wt, ["status", "--porcelain"])) ?? "").trim().length > 0 : false;
111
+ const dir = path.basename(wt);
112
+ let agent;
113
+ if (i > 0 && dir.startsWith(repoBase + "-")) {
114
+ const suffix = dir.slice(repoBase.length + 1);
115
+ agent = agentIds.find((id) => suffix === id || suffix.startsWith(id + "-"));
116
+ }
117
+ if (!agent) {
118
+ const row = hints.boardRows?.find((r) => r.branchWorktree && ((branch && r.branchWorktree.includes(branch)) || r.branchWorktree.includes(dir)));
119
+ if (row?.owner)
120
+ agent = row.owner;
121
+ }
122
+ return { path: wt, branch, sha, short: sha.slice(0, 7), detached, dirty, prunable, ...(agent ? { agent } : {}), isMain: i === 0 };
123
+ }));
124
+ return { worktrees };
125
+ });
126
+ }
127
+ /** gh (optional): open/merged/closed PRs by head branch. Missing/unauthenticated gh → null (envelope `ghAvailable:false`). */
128
+ async function ghPrs(projectPath) {
129
+ try {
130
+ const r = await run("gh", ["pr", "list", "--state", "all", "--limit", "50", "--json", "number,url,state,title,headRefName"], { cwd: projectPath, maxBuffer: 8 << 20, timeout: 15_000 });
131
+ const list = JSON.parse(r.stdout);
132
+ const m = new Map();
133
+ for (const p of list)
134
+ if (!m.has(p.headRefName))
135
+ m.set(p.headRefName, { number: p.number, url: p.url, state: p.state, title: p.title });
136
+ return m;
137
+ }
138
+ catch {
139
+ return null;
140
+ }
141
+ }
142
+ export function gitBranches(projectPath, hints = {}) {
143
+ return cached(`branches:${projectPath}:${(hints.agentIds ?? []).join(",")}`, async () => {
144
+ const defaultBranch = "main";
145
+ const base = `origin/${defaultBranch}`;
146
+ const hasBase = (await git(projectPath, ["rev-parse", "--verify", "-q", `${base}^{commit}`]))?.trim() ? true : false;
147
+ const rows = splitRecords(await git(projectPath, ["for-each-ref", "--sort=-committerdate", "--format=%(refname)%1f%(objectname)%1f%(objectname:short)%1f%(subject)%1f%(committerdate:iso-strict)", "refs/heads", "refs/remotes"]))
148
+ .filter(([ref]) => !/^refs\/remotes\/[^/]+\/HEAD$/.test(ref));
149
+ const [{ worktrees }, prs] = await Promise.all([gitWorktrees(projectPath, hints), ghPrs(projectPath)]);
150
+ const qaOf = (n) => { const row = hints.openPrs?.find((r) => Object.values(r).some((v) => new RegExp(`(^|[^0-9])#${n}([^0-9]|$)`).test(v))); if (!row)
151
+ return undefined; const qa = row.mergeGate ?? row.status ?? row.state; return qa ? String(qa) : undefined; };
152
+ const now = Date.now();
153
+ const branches = await Promise.all(rows.map(async ([ref, sha, short, subject, date]) => {
154
+ const remote = ref.startsWith("refs/remotes/");
155
+ const name = ref.replace(/^refs\/(heads|remotes)\//, "");
156
+ let ahead = null, behind = null, merged = false;
157
+ if (hasBase) {
158
+ const lr = (await git(projectPath, ["rev-list", "--left-right", "--count", `${base}...${ref}`]))?.trim().split(/\s+/);
159
+ if (lr && lr.length === 2) {
160
+ behind = +lr[0];
161
+ ahead = +lr[1];
162
+ }
163
+ merged = (await gitOk(projectPath, ["merge-base", "--is-ancestor", ref, base]));
164
+ }
165
+ const stale = date ? now - Date.parse(date) > STALE_DAYS * 86_400_000 : false;
166
+ const localName = remote ? name.replace(/^[^/]+\//, "") : name;
167
+ const wt = remote ? undefined : worktrees.find((w) => w.branch === name);
168
+ const boardRow = hints.boardRows?.find((r) => r.branchWorktree && r.branchWorktree.includes(localName));
169
+ const owner = boardRow?.owner || wt?.agent;
170
+ const pr = prs?.get(localName);
171
+ const prOut = pr ? { ...pr, ...(qaOf(pr.number) ? { qa: qaOf(pr.number) } : {}) } : undefined;
172
+ return { name, remote, sha, short, subject: subject ?? "", date: date ?? "", ahead, behind, merged, stale, ...(wt ? { worktree: wt.path } : {}), ...(owner ? { owner } : {}), ...(prOut ? { pr: prOut } : {}) };
173
+ }));
174
+ return { defaultBranch, base, baseFound: hasBase, ghAvailable: prs !== null, branches };
175
+ });
176
+ }
177
+ async function gitOk(cwd, args) { try {
178
+ await run("git", args, { cwd });
179
+ return true;
180
+ }
181
+ catch {
182
+ return false;
183
+ } }
@@ -2,8 +2,9 @@ import { execFileSync } from "node:child_process";
2
2
  import { existsSync, readFileSync, realpathSync } from "node:fs";
3
3
  import path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
- // TODO(kit publish): bump mcp to the version that ships send_message.inReplyTo (kit main 18ffbf4 is still tagged 0.23.0) — until then the capability is detected at runtime (BusClient.supports) and surfaced in /health.requires.mcp.capabilities.
6
- export const REQUIRES = { seam: "^0.1.0", mcp: "^0.23.0", groundwork: "^0.3.0", playbook: "^0.5.0" };
5
+ // mcp ^0.24.0 ships send_message.inReplyTo (npm 2026-08-19) — that is the floor. send_command's allowlist (/reload-skills since 0.25.0) is NOT a floor:
6
+ // it is feature-detected per connection (BusClient.sendCommands /health.requires.mcp.capabilities.sendCommand) and the console degrades gracefully without it.
7
+ export const REQUIRES = { seam: "^0.1.0", mcp: "^0.24.0", groundwork: "^0.3.0", playbook: "^0.5.0" };
7
8
  const which = (bin) => { try {
8
9
  return execFileSync("which", [bin], { encoding: "utf8" }).trim() || null;
9
10
  }
@@ -57,25 +58,26 @@ export function installedVersions(kitPath) {
57
58
  const gw = groundworkCli(kitPath), pb = playbookCli(kitPath), mcp = mcpServer(kitPath);
58
59
  return { seam, groundwork: pkgVersionNear(gw), playbook: pkgVersionNear(pb), mcp: pkgVersionNear(mcp), paths: { groundwork: gw, playbook: pb, mcp, kitCheckout: hasKitCheckout(kitPath) ? kitPath : null } };
59
60
  }
60
- /** Minimal caret-range check (^a.b.c): same major (or same minor when major is 0), and >= min. */
61
+ /** Floor check: the console's requirement is a MINIMUM within the same major (kit packages bump minors often; 0.25.1 must satisfy "^0.24.0").
62
+ Written as ^a.b.c for familiarity, evaluated as >= a.b.c and < (a+1).0.0 — NOT strict caret semantics for 0.x. */
61
63
  export function satisfies(v, range) {
62
64
  if (!v)
63
65
  return null;
64
66
  const m = range.match(/^\^(\d+)\.(\d+)\.(\d+)$/);
65
67
  if (!m)
66
68
  return null;
67
- const [a, b, c] = v.split(".").map(Number);
69
+ const [a, b, c] = v.split(".").map((n) => parseInt(n, 10));
68
70
  const [x, y, z] = m.slice(1).map(Number);
71
+ if (![a, b, c].every(Number.isFinite))
72
+ return null;
69
73
  if (a !== x)
70
- return false;
71
- if (x === 0 && b !== y)
72
- return false;
73
- return a > x || b > y || (b === y && c >= z);
74
+ return false; // different major: not satisfied (breaking either way)
75
+ return b > y || (b === y && c >= z);
74
76
  }
75
77
  export function requiresReport(kitPath, caps) {
76
78
  const inst = installedVersions(kitPath);
77
79
  const check = (k) => ({ required: REQUIRES[k], installed: inst[k], ok: satisfies(inst[k], REQUIRES[k]) });
78
- const r = { seam: check("seam"), mcp: { ...check("mcp"), capabilities: caps ?? { inReplyTo: null }, warnings: caps?.inReplyTo === false ? ["inReplyTo missing — decision replies will not be linked/cleared; upgrade agent-coord-mcp"] : [] }, groundwork: check("groundwork"), playbook: check("playbook") };
80
+ const r = { seam: check("seam"), mcp: { ...check("mcp"), capabilities: caps ?? { inReplyTo: null, sendCommand: null }, warnings: caps?.inReplyTo === false ? ["inReplyTo missing — decision replies will not be linked/cleared; upgrade agent-coord-mcp"] : [] }, groundwork: check("groundwork"), playbook: check("playbook") };
79
81
  return { ...r, paths: inst.paths, ok: Object.values(r).every((x) => x.ok !== false) };
80
82
  }
81
83
  /** 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. */
package/dist/mcp.js CHANGED
@@ -74,12 +74,17 @@ export class BusClient {
74
74
  const reg = await client.callTool({ name: "register", arguments: { agentId: id.agentId, project: id.project, role: id.role, force: true } });
75
75
  log?.(`register → ${JSON.stringify(reg.structuredContent ?? textOf(reg)).slice(0, 160)}`);
76
76
  // Feature detection: which args each tool accepts (e.g. send_message.inReplyTo landed in kit 18ffbf4; the live bin may predate it).
77
+ let sendCommandDesc = "";
77
78
  try {
78
79
  const tools = await client.listTools();
79
- for (const t of tools.tools)
80
+ for (const t of tools.tools) {
80
81
  this.toolArgs.set(t.name, new Set(Object.keys(t.inputSchema?.properties ?? {})));
82
+ if (t.name === "send_command")
83
+ sendCommandDesc = t.description ?? "";
84
+ }
81
85
  }
82
86
  catch { /* older server: no detection, pass nothing extra */ }
87
+ this.commands = await detectCommands(client, sendCommandDesc, log);
83
88
  this.client = client;
84
89
  return client;
85
90
  })();
@@ -91,6 +96,11 @@ export class BusClient {
91
96
  }
92
97
  }
93
98
  toolArgs = new Map();
99
+ commands = null;
100
+ /** send_command allowlist detected on the connected server (e.g. ["/clear","/compact"] on 0.24.0, +"/reload-skills" on 0.25.0); null = not connected / undetectable. */
101
+ get sendCommands() { return this.client ? this.commands : null; }
102
+ /** True when the connected server accepts this raw control command. */
103
+ acceptsCommand(cmd) { return this.sendCommands?.includes(cmd) ?? false; }
94
104
  /** True when the connected server's tool schema lists this argument (false when unknown or not connected yet). */
95
105
  supports(tool, arg) { return this.toolArgs.get(tool)?.has(arg) ?? false; }
96
106
  get connected() { return !!this.client; }
@@ -109,6 +119,34 @@ export class BusClient {
109
119
  }
110
120
  async close() { await this.client?.close(); this.client = null; }
111
121
  }
122
+ /* send_command allowlist detection. The MCP does not expose the allowlist in the tool schema (command is a plain string), so:
123
+ 1. probe once per connection with a command that can never be valid — the server rejects it BEFORE looking at to/room or touching any pane
124
+ (transport.ts sendCommandTool: normalizeControlCommand → `unsupported command '…'. Allowed: /clear, /compact[, /reload-skills]`), so this is
125
+ side-effect-free and needs no bus-writes gate; 2. fall back to the tool description ("allowlist is locked to /clear, /compact and /reload-skills"). */
126
+ const PROBE_COMMAND = "/__groundwork-console-probe__";
127
+ async function detectCommands(client, description, log) {
128
+ const fromText = (t) => { const m = t.match(/Allowed:\s*([^\n]+)/i) ?? t.match(/locked to ([^.;\n]+)/i); return m ? [...m[1].matchAll(/\/[a-z][\w-]*/g)].map((x) => x[0]) : []; };
129
+ try {
130
+ const res = await client.callTool({ name: "send_command", arguments: { from: "groundwork-console", command: PROBE_COMMAND } });
131
+ const out = (res.structuredContent ?? parseText(textOf(res)));
132
+ const list = fromText(typeof out === "string" ? out : out?.error ?? textOf(res));
133
+ if (list.length) {
134
+ log?.(`send_command allowlist (probe): ${list.join(" ")}`);
135
+ return list;
136
+ }
137
+ }
138
+ catch (e) {
139
+ const list = fromText(e.message ?? "");
140
+ if (list.length)
141
+ return list;
142
+ }
143
+ const list = fromText(description);
144
+ if (list.length) {
145
+ log?.(`send_command allowlist (description): ${list.join(" ")}`);
146
+ return list;
147
+ }
148
+ return null;
149
+ }
112
150
  function textOf(r) { return (r.content ?? []).map((c) => c.text ?? "").join("\n"); }
113
151
  function parseText(t) { try {
114
152
  return JSON.parse(t);
package/dist/server.js CHANGED
@@ -20,7 +20,7 @@ import { detectedHarnesses, groundworkSkills, playbookCards } from "./skills.js"
20
20
  import { extensionsOf } from "./extensions.js";
21
21
  import { parseWorkDoc, workstreamsExtensionsOf } from "@davidbalzan/groundwork-seam";
22
22
  import { EventEngine } from "./events.js";
23
- import { docGit, docsStatus, lastDocsCommit } from "./gitinfo.js";
23
+ import { docGit, docsStatus, lastDocsCommit, gitGraph, gitBranches, gitWorktrees } from "./gitinfo.js";
24
24
  import { Notifier } from "./notify.js";
25
25
  import { Runs } from "./runs.js";
26
26
  import { BusClient, AGENT_ID, busWrites, seamWrites, saveSwitches, SWITCHES_FILE } from "./mcp.js";
@@ -46,7 +46,9 @@ const notifier = new Notifier(cfg, runs, events);
46
46
  const busDavid = new BusClient(cfg.kitPath, cfg.coordDir, "david", process.env.AGENT_COORD_MCP);
47
47
  const clientFor = (id) => (id === "david" ? busDavid : bus);
48
48
  /** Capabilities of the connected MCP (null until a client has connected and listed tools). */
49
- const mcpCaps = () => ({ inReplyTo: bus.connected ? bus.supports("send_message", "inReplyTo") : busDavid.connected ? busDavid.supports("send_message", "inReplyTo") : null });
49
+ const mcpCaps = () => { const cl = bus.connected ? bus : busDavid.connected ? busDavid : null; return { inReplyTo: cl ? cl.supports("send_message", "inReplyTo") : null, sendCommand: cl ? cl.sendCommands : null }; };
50
+ /* Live tmux-attached agents — the only valid send_command targets (the MCP hard-gates to tmux-push/-remote). */
51
+ const liveAttached = () => agents(cfg.coordDir).filter((a) => a.liveness === "live" && (a.transport === "tmux-push" || a.transport === "tmux-push-remote")).map((a) => a.agentId);
50
52
  const PUBLIC_DIR = path.resolve(HERE, "../public");
51
53
  const root = new Hono();
52
54
  const app = new Hono().basePath("/api");
@@ -113,13 +115,24 @@ app.post("/actions/send", async (c) => {
113
115
  return c.json({ error: "text required" }, 400);
114
116
  if (!b.room && !b.to)
115
117
  return c.json({ error: "room or to required" }, 400);
116
- // Raw control commands: exactly `/clear` or `/compact` (no args) to ONE agent go through MCP send_command — the only path that types a slash command
117
- // into a live CLI (allowlist locked to these two in the MCP; widening it /reload-skills etc. is a kit decision, not done here). Everything else is a text DM.
118
- const RAW_COMMANDS = new Set(["/clear", "/compact"]);
118
+ // Raw control commands: a bare `/<cmd>` (no args) to ONE agent goes through MCP send_command — the only path that types a slash command into a live CLI.
119
+ // The allowlist is the CONNECTED server's (feature-detected: 0.24.0 = /clear /compact; 0.25.0 adds /reload-skills — BusClient.sendCommands). A bare slash
120
+ // command the server does not accept degrades to the text wrapper `AGENT_ACTION: run /<cmd>` (never a 4xx; the agent runs it from the request, #14).
119
121
  const cmd = b.text.trim();
120
- if (RAW_COMMANDS.has(cmd)) {
121
- if (!b.to)
122
- return c.json({ error: `${cmd} is unicast — pick an agent (rooms get text only)` }, 400);
122
+ let degraded;
123
+ if (/^\/[a-z][\w-]*$/i.test(cmd) && b.to) {
124
+ const cl = clientFor(who);
125
+ try {
126
+ await cl.connect();
127
+ }
128
+ catch { /* not connected → no raw path; fall through to the text DM */ }
129
+ if (!cl.acceptsCommand(cmd)) {
130
+ const have = cl.sendCommands;
131
+ degraded = `${cmd} is not in the connected agent-coord-mcp's send_command allowlist (${have ? have.join(" ") : "unknown"})${cmd === "/reload-skills" ? " — needs agent-coord-mcp ≥0.25.0 + MCP session restart" : ""}; sent as a text request instead`;
132
+ b.text = `AGENT_ACTION: run ${cmd}`;
133
+ }
134
+ }
135
+ if (/^\/[a-z][\w-]*$/i.test(cmd) && !degraded && (b.to ? clientFor(who).acceptsCommand(cmd) : false)) {
123
136
  const r = await runs.exec({ kind: "bus", label: `command ${cmd} → ${b.to}`, sideEffects: true, args: { as: who, to: b.to, command: cmd } }, async (log) => {
124
137
  const out = (await clientFor(who).call("send_command", { to: b.to, command: cmd, waitForDelivery: true, deliveryTimeoutMs: 15_000 }, log));
125
138
  log(`delivery: ${out.delivery ?? "?"}${out.reason ? ` · ${out.reason}` : ""}${out.note ? ` · ${out.note}` : ""}`);
@@ -130,6 +143,8 @@ app.post("/actions/send", async (c) => {
130
143
  const out = (r.result ?? {});
131
144
  return c.json({ ...r, raw: true, delivery: out.delivery ?? null, warning: out.delivery === "pending" ? `pusher has not confirmed typing ${cmd} yet${out.reason ? ` — ${out.reason}` : ""}` : undefined }, r.status === "ok" ? 200 : 403);
132
145
  }
146
+ if (/^\/[a-z][\w-]*$/i.test(cmd) && b.room && !b.to)
147
+ return c.json({ error: `${cmd} is unicast — pick an agent (rooms get text only)` }, 400);
133
148
  const PREFIX_RE = /^(BLOCKER|RISK|DONE|FYI|AGENT_ACTION|GO|SCOPE CHANGE|VERDICT|DAVID_DECISION)\b/;
134
149
  if (b.room && !PREFIX_RE.test(b.text))
135
150
  return c.json({ error: "room posts need a prefix (FYI / AGENT_ACTION / DAVID_DECISION / BLOCKER / RISK / DONE / GO / SCOPE CHANGE / VERDICT)" }, 400);
@@ -137,6 +152,8 @@ app.post("/actions/send", async (c) => {
137
152
  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) => {
138
153
  const cl = clientFor(who);
139
154
  await cl.connect(log);
155
+ if (degraded)
156
+ log(`⚠ ${degraded}`);
140
157
  // Reply linkage = bus inReplyTo only (console-owned answered.json retired 2026-08-19). Parent: explicit from the UI (answers[0] / inReplyTo);
141
158
  // for a DM as David with no explicit parent, the latest still-open DAVID_DECISION DM from that agent — so "reply to the agent" clears its packet.
142
159
  let parent = b.inReplyTo ?? b.answers?.[0];
@@ -164,6 +181,56 @@ app.post("/actions/send", async (c) => {
164
181
  }
165
182
  return out;
166
183
  });
184
+ return c.json({ ...r, ...(degraded ? { raw: false, degraded: true, warning: degraded } : {}) }, r.status === "ok" ? 200 : 403);
185
+ });
186
+ /* Ops › Skills: after a playbook update, push `/reload-skills` RAW to every live tmux-attached agent (send_command, bus-writes gated, only when the connected
187
+ MCP's allowlist carries it — 0.25.0+). Per-agent delivery confirmed|pending in the Run. Targets may be narrowed with `agentIds`. */
188
+ app.get("/actions/reload-skills", async (c) => {
189
+ const cl = bus;
190
+ try {
191
+ await cl.connect();
192
+ }
193
+ catch { /* report what we know */ }
194
+ return c.json({ supported: cl.acceptsCommand("/reload-skills"), allowlist: cl.sendCommands, agents: liveAttached(), hint: cl.acceptsCommand("/reload-skills") ? null : "needs agent-coord-mcp ≥0.25.0 (send_command allowlist with /reload-skills) + a restart of the console's MCP session and the fleet's" });
195
+ });
196
+ app.post("/actions/reload-skills", async (c) => {
197
+ const b = (await c.req.json().catch(() => ({})));
198
+ if (b.confirm !== "RELOAD")
199
+ return c.json({ error: "confirm must be RELOAD" }, 400);
200
+ if (!busWrites.enabled)
201
+ return c.json({ error: "bus writes are off — switch them on in the header (WRITES) first" }, 403);
202
+ const cl = bus;
203
+ try {
204
+ await cl.connect();
205
+ }
206
+ catch (e) {
207
+ return c.json({ error: e.message }, 500);
208
+ }
209
+ if (!cl.acceptsCommand("/reload-skills"))
210
+ return c.json({ error: `connected agent-coord-mcp does not accept /reload-skills (allowlist ${cl.sendCommands?.join(" ") ?? "unknown"}) — needs ≥0.25.0 + MCP session restart` }, 409);
211
+ const live = liveAttached();
212
+ const targets = (b.agentIds?.length ? b.agentIds.filter((id) => live.includes(id)) : live);
213
+ if (!targets.length)
214
+ return c.json({ error: "no live tmux-attached agents to reload" }, 400);
215
+ const r = await runs.exec({ kind: "bus", label: `reload skills → ${targets.length} session${targets.length === 1 ? "" : "s"}`, sideEffects: true, args: { agentIds: targets } }, async (log) => {
216
+ const results = [];
217
+ for (const to of targets) {
218
+ try {
219
+ const out = (await cl.call("send_command", { to, command: "/reload-skills", waitForDelivery: true, deliveryTimeoutMs: 15_000 }, log));
220
+ const delivery = out.error ? `error: ${out.error}` : out.delivery ?? "?";
221
+ results.push({ agentId: to, delivery, ...(out.reason ? { reason: out.reason } : {}) });
222
+ log(`${to}: ${delivery}${out.reason ? ` · ${out.reason}` : ""}`);
223
+ }
224
+ catch (e) {
225
+ results.push({ agentId: to, delivery: `error: ${e.message}` });
226
+ log(`${to}: error ${e.message}`);
227
+ }
228
+ }
229
+ const confirmed = results.filter((x) => x.delivery === "confirmed").length, pending = results.filter((x) => x.delivery === "pending").length;
230
+ if (confirmed + pending === 0)
231
+ throw new Error(`no session accepted /reload-skills (${results.map((x) => `${x.agentId}: ${x.delivery}`).join("; ")})`); // an all-error push is a failed Run, never ok
232
+ return { results, confirmed, pending };
233
+ });
167
234
  return c.json(r, r.status === "ok" ? 200 : 403);
168
235
  });
169
236
  /* Uploads — pasted images for DMs. Path + served URL both go into the message text as `[image: <path> · <url>]`.
@@ -307,6 +374,50 @@ app.get("/projects/:name/status", async (c) => {
307
374
  return c.json({ error: "unknown project" }, 404);
308
375
  return c.json(await status(cfg.kitPath, p.path));
309
376
  });
377
+ /* ---- Git branches screen v1 (docs/contracts/git-screen-v1.md): read-only git/gh, 3 s cache, errors → { error } ---- */
378
+ const gitHints = (p) => {
379
+ const src = existsSync(path.join(p.path, "docs/WORKSTREAMS.md")) ? readFileSync(path.join(p.path, "docs/WORKSTREAMS.md"), "utf8") : null;
380
+ const rows = boardOf(p.path).workstreams.rows.flatMap((r) => ("branchWorktree" in r ? [{ owner: r.owner, branchWorktree: r.branchWorktree }] : []));
381
+ let openPrs = [];
382
+ try {
383
+ openPrs = src ? workstreamsExtensionsOf(parseWorkDoc(src)).openPrs : [];
384
+ }
385
+ catch { /* board unparsable → no qa column */ }
386
+ return { agentIds: agents(cfg.coordDir).map((a) => a.agentId), boardRows: rows, openPrs };
387
+ };
388
+ app.get("/projects/:name/git/graph", async (c) => {
389
+ const p = project(c.req.param("name"));
390
+ if (!p)
391
+ return c.json({ error: "unknown project" }, 404);
392
+ try {
393
+ return c.json(await gitGraph(p.path, Number(c.req.query("limit") ?? 200)));
394
+ }
395
+ catch (e) {
396
+ return c.json({ error: e.message }, 500);
397
+ }
398
+ });
399
+ app.get("/projects/:name/git/branches", async (c) => {
400
+ const p = project(c.req.param("name"));
401
+ if (!p)
402
+ return c.json({ error: "unknown project" }, 404);
403
+ try {
404
+ return c.json(await gitBranches(p.path, gitHints(p)));
405
+ }
406
+ catch (e) {
407
+ return c.json({ error: e.message }, 500);
408
+ }
409
+ });
410
+ app.get("/projects/:name/git/worktrees", async (c) => {
411
+ const p = project(c.req.param("name"));
412
+ if (!p)
413
+ return c.json({ error: "unknown project" }, 404);
414
+ try {
415
+ return c.json(await gitWorktrees(p.path, gitHints(p)));
416
+ }
417
+ catch (e) {
418
+ return c.json({ error: e.message }, 500);
419
+ }
420
+ });
310
421
  app.get("/projects/:name/skills", async (c) => {
311
422
  const p = project(c.req.param("name"));
312
423
  if (!p)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@davidbalzan/groundwork-console",
3
3
  "private": false,
4
- "version": "0.1.7",
4
+ "version": "0.1.9",
5
5
  "type": "module",
6
6
  "dependencies": {
7
7
  "@davidbalzan/groundwork-seam": "^0.1.0",
@@ -1 +1 @@
1
- import{m as e,t}from"./mermaid-parser.core-B1iqIz20.js";import{n}from"./chunk-Y2CYZVJY-DsF7k-Jl.js";import{m as r}from"./src-l8EnJwXz.js";import"./chunk-I66GZJ75-BD9h95Vq.js";import{n as i,r as a,t as o}from"./chunk-6Q2QTUOP-C3vYg7rX.js";import{t as s}from"./chunk-JWPE2WC7-DVXcaiue.js";import"./mermaid.core-CuFK5CuK.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
+ import{m as e,t}from"./mermaid-parser.core-D7AXmMRA.js";import{n}from"./chunk-Y2CYZVJY-DsF7k-Jl.js";import{m as r}from"./src-0sS7-ICb.js";import"./chunk-I66GZJ75-Dqby7D_F.js";import{n as i,r as a,t as o}from"./chunk-6Q2QTUOP-CbJEu2Tv.js";import{t as s}from"./chunk-JWPE2WC7-DVXcaiue.js";import"./mermaid.core-B20ztOv3.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-BnkmMC7M.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};
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-pnrLfy84.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-D7AXmMRA.js";export{e as createArchitectureServices};