@shiyuphan/follow-me 0.5.1 → 0.7.1

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 (102) hide show
  1. package/README.md +9 -3
  2. package/dist/src/cli.js +112 -19
  3. package/dist/src/comments.js +6 -1
  4. package/dist/src/coverage.js +397 -0
  5. package/dist/src/definition.js +226 -0
  6. package/dist/src/git.js +87 -13
  7. package/dist/src/host.js +4 -0
  8. package/dist/src/plan.js +12 -1
  9. package/dist/src/prompt.js +80 -23
  10. package/dist/src/server.js +65 -8
  11. package/dist/src/session.js +33 -0
  12. package/dist/src/types.js +4 -0
  13. package/dist/ui/dist/assets/{abnfDiagram-WEYOIA3C-8UsLPznO.js → abnfDiagram-WEYOIA3C-cHEJvcBn.js} +1 -1
  14. package/dist/ui/dist/assets/{arc-vovjGalz.js → arc-xolFyc0v.js} +1 -1
  15. package/dist/ui/dist/assets/{architecture-WOLXFQ4H-BBMK-PUu.js → architecture-WOLXFQ4H-B3hvvNV2.js} +1 -1
  16. package/dist/ui/dist/assets/{architectureDiagram-ZHVYKT53-Bw_ehaQk.js → architectureDiagram-ZHVYKT53-BNTMbPJW.js} +1 -1
  17. package/dist/ui/dist/assets/{blockDiagram-RJAE7QA3-CAmD0wKf.js → blockDiagram-RJAE7QA3-NYY6qeuY.js} +1 -1
  18. package/dist/ui/dist/assets/{c4Diagram-XDPPPE54-DCP-3zgt.js → c4Diagram-XDPPPE54-B_bNpXHQ.js} +1 -1
  19. package/dist/ui/dist/assets/channel-DPDY0XJP.js +1 -0
  20. package/dist/ui/dist/assets/{chunk-2E7WHVMD-BiHSeJMu.js → chunk-2E7WHVMD-BTet9STn.js} +1 -1
  21. package/dist/ui/dist/assets/{chunk-4EMNKR2T-CR3lHKf_.js → chunk-4EMNKR2T-qoQt4Fpx.js} +1 -1
  22. package/dist/ui/dist/assets/{chunk-55XCU76K-CtaoloTT.js → chunk-55XCU76K-C3I6EB-H.js} +1 -1
  23. package/dist/ui/dist/assets/{chunk-7S6XSTFO-eABdc7fu.js → chunk-7S6XSTFO-CL-9s_8p.js} +1 -1
  24. package/dist/ui/dist/assets/{chunk-AQDTSKZB-CPlbJWi-.js → chunk-AQDTSKZB-DHoGnGyP.js} +1 -1
  25. package/dist/ui/dist/assets/{chunk-DSNLSHJZ-YiMuxsb-.js → chunk-DSNLSHJZ-Bk8WO5JF.js} +1 -1
  26. package/dist/ui/dist/assets/{chunk-HAGFOTED-1VcdL78a.js → chunk-HAGFOTED-CwAC85lg.js} +1 -1
  27. package/dist/ui/dist/assets/{chunk-HNE4Q36P-u-sr_0Qu.js → chunk-HNE4Q36P-CIllywWr.js} +1 -1
  28. package/dist/ui/dist/assets/{chunk-NQOKRVIF-C_QlWSze.js → chunk-NQOKRVIF-DDlpyYiH.js} +1 -1
  29. package/dist/ui/dist/assets/{chunk-UHW7PFBP-CNuZhsbU.js → chunk-UHW7PFBP-Cxdndcon.js} +1 -1
  30. package/dist/ui/dist/assets/{chunk-VQ5W62QI-DAHWbLqU.js → chunk-VQ5W62QI-CL9d9_iJ.js} +1 -1
  31. package/dist/ui/dist/assets/{chunk-WQQL4CDK-DWQnxFCd.js → chunk-WQQL4CDK-C4kzN8QU.js} +1 -1
  32. package/dist/ui/dist/assets/{chunk-XXDRQBXY-B0lGgSOR.js → chunk-XXDRQBXY-Cv_2FSiY.js} +1 -1
  33. package/dist/ui/dist/assets/{chunk-YZO67HFG-BDw7efkN.js → chunk-YZO67HFG-oHiuYaCJ.js} +1 -1
  34. package/dist/ui/dist/assets/{chunk-ZAUX2LTX-B5wfHsZv.js → chunk-ZAUX2LTX-Cy_8d0XT.js} +1 -1
  35. package/dist/ui/dist/assets/{chunk-ZCHMQSOR--rWtYtAG.js → chunk-ZCHMQSOR-piIpIcQi.js} +1 -1
  36. package/dist/ui/dist/assets/{classDiagram-v2-4MYWR3UA-BKuQ_xoS.js → classDiagram-v2-4MYWR3UA-DNKRIujO.js} +1 -1
  37. package/dist/ui/dist/assets/{cose-bilkent-JH36ORCC-Dk5fIpU0.js → cose-bilkent-JH36ORCC-BWKTGCHY.js} +1 -1
  38. package/dist/ui/dist/assets/{cynefin-EF2NZ3EQ-DIykJb2g.js → cynefin-EF2NZ3EQ-yx7KKkAk.js} +1 -1
  39. package/dist/ui/dist/assets/{cynefinDiagram-WF24UDRP-Cg8tBqUb.js → cynefinDiagram-WF24UDRP-BTnZ7QVL.js} +1 -1
  40. package/dist/ui/dist/assets/{dagre-NK4GIZVX-BC_tnnjz.js → dagre-NK4GIZVX-CPh5Zn5I.js} +1 -1
  41. package/dist/ui/dist/assets/{diagram-CBK3MWTC-B_MQGixU.js → diagram-CBK3MWTC-pI6NFdrD.js} +1 -1
  42. package/dist/ui/dist/assets/{diagram-I5XIIV2D-DO681pZ5.js → diagram-I5XIIV2D-DUIfARoS.js} +1 -1
  43. package/dist/ui/dist/assets/{diagram-ODIL2UCW-D14jLzU6.js → diagram-ODIL2UCW-lafSPvAw.js} +1 -1
  44. package/dist/ui/dist/assets/{diagram-PFBEJUXE-cML48gUS.js → diagram-PFBEJUXE-u1lZas3a.js} +1 -1
  45. package/dist/ui/dist/assets/{diagram-RJA3I4VV-DSoq8jFk.js → diagram-RJA3I4VV-mc0GLMHo.js} +1 -1
  46. package/dist/ui/dist/assets/{diagram-TYUNFSNR-CaJ4pvnl.js → diagram-TYUNFSNR-1NRbBrZW.js} +1 -1
  47. package/dist/ui/dist/assets/{dist-D3j-DODP.js → dist-CHZ9mrl_.js} +1 -1
  48. package/dist/ui/dist/assets/{ebnfDiagram-V4P3TTPT-CyRpjSix.js → ebnfDiagram-V4P3TTPT-DdStjTDc.js} +1 -1
  49. package/dist/ui/dist/assets/{elk-QONTBCTQ-ICJdzVFL.js → elk-QONTBCTQ-BzRoFO45.js} +1 -1
  50. package/dist/ui/dist/assets/{erDiagram-KYQIGQAA-B57hNTfw.js → erDiagram-KYQIGQAA-DlMkgQxj.js} +1 -1
  51. package/dist/ui/dist/assets/{eventmodeling-K75KTNOO-BG8tUVIQ.js → eventmodeling-K75KTNOO-BRYIOrod.js} +1 -1
  52. package/dist/ui/dist/assets/flowDiagram-IUGLLWBU-C1IBh_GZ.js +1 -0
  53. package/dist/ui/dist/assets/{ganttDiagram-R7GPNTJY-Czt6Klik.js → ganttDiagram-R7GPNTJY-D8fQZ7Ah.js} +1 -1
  54. package/dist/ui/dist/assets/{gitGraph-VSP46ZUC-De_ZWJLj.js → gitGraph-VSP46ZUC-C7ZydyWX.js} +1 -1
  55. package/dist/ui/dist/assets/{gitGraphDiagram-PDYL7XHY-BMCJoOnT.js → gitGraphDiagram-PDYL7XHY-DCItam0v.js} +1 -1
  56. package/dist/ui/dist/assets/index-8BehwQ7E.js +134 -0
  57. package/dist/ui/dist/assets/index-mC2OW2Av.css +1 -0
  58. package/dist/ui/dist/assets/{info-OHQRW6UA-D75H9kUq.js → info-OHQRW6UA-C3KneBKT.js} +1 -1
  59. package/dist/ui/dist/assets/{infoDiagram-MVDRKAX5-Bsm_I9dt.js → infoDiagram-MVDRKAX5-6wK09D32.js} +1 -1
  60. package/dist/ui/dist/assets/{ishikawaDiagram-HPEHV2GE-98xZAJ9y.js → ishikawaDiagram-HPEHV2GE-DTP5C8VX.js} +1 -1
  61. package/dist/ui/dist/assets/{journeyDiagram-BOSHW7O7-BWVGuc0P.js → journeyDiagram-BOSHW7O7-B--UIMtq.js} +1 -1
  62. package/dist/ui/dist/assets/{kanban-definition-FNSA4HC5-DgUE_KDd.js → kanban-definition-FNSA4HC5-Ku4N9QGt.js} +1 -1
  63. package/dist/ui/dist/assets/{line-i__8Rvas.js → line-C4-H5h1-.js} +1 -1
  64. package/dist/ui/dist/assets/{linear-BXKj0Fip.js → linear-DhAID3hm.js} +1 -1
  65. package/dist/ui/dist/assets/{mermaid-parser.core-DxvaorTa.js → mermaid-parser.core-CMPNc-u6.js} +3 -3
  66. package/dist/ui/dist/assets/{mermaid.core-ujYTsRZz.js → mermaid.core-Nd_xOg2y.js} +4 -4
  67. package/dist/ui/dist/assets/{mindmap-definition-6OY57UG3-B7QD6sjk.js → mindmap-definition-6OY57UG3-BlVsym5h.js} +1 -1
  68. package/dist/ui/dist/assets/{packet-JDAUHWVQ-B6aIwfD1.js → packet-JDAUHWVQ-rsk7SM7S.js} +1 -1
  69. package/dist/ui/dist/assets/{pegDiagram-ECIUJNBX-_RStHj-a.js → pegDiagram-ECIUJNBX-C0L20M8z.js} +1 -1
  70. package/dist/ui/dist/assets/{pie-XZMESJXO-CNLC-pTe.js → pie-XZMESJXO-B23-iZSN.js} +1 -1
  71. package/dist/ui/dist/assets/{pieDiagram-GNMPY74T-0uT7MT-u.js → pieDiagram-GNMPY74T-BqFhe4Oh.js} +1 -1
  72. package/dist/ui/dist/assets/{prism-core-DtxziK1i.js → prism-core-DYsiqJeR.js} +1 -1
  73. package/dist/ui/dist/assets/{quadrantDiagram-D57K4MC3-CTMKb5hl.js → quadrantDiagram-D57K4MC3-BKP75B-m.js} +1 -1
  74. package/dist/ui/dist/assets/{radar-ABXABTNO-D53h6I0N.js → radar-ABXABTNO-BrEk2mQ7.js} +1 -1
  75. package/dist/ui/dist/assets/{railroad-I3PHUGI6-CwjhW4mc.js → railroad-I3PHUGI6-wWO4Eapc.js} +1 -1
  76. package/dist/ui/dist/assets/{railroad-abnf-LNEFI6M7-DDbQ3RmB.js → railroad-abnf-LNEFI6M7-DfY-IPvE.js} +1 -1
  77. package/dist/ui/dist/assets/{railroad-ebnf-DWYD2UWJ-Am7YJAqO.js → railroad-ebnf-DWYD2UWJ-19wedWGk.js} +1 -1
  78. package/dist/ui/dist/assets/{railroad-peg-JR7BVNR7-Ci32KoVW.js → railroad-peg-JR7BVNR7-9vIslnoJ.js} +1 -1
  79. package/dist/ui/dist/assets/{railroadDiagram-Y72NRBL7-BPU5A6XW.js → railroadDiagram-Y72NRBL7-DVnmXs8w.js} +1 -1
  80. package/dist/ui/dist/assets/{requirementDiagram-EQYSW5RA-_0Pq4QdV.js → requirementDiagram-EQYSW5RA-BEoMdlzR.js} +1 -1
  81. package/dist/ui/dist/assets/{sankeyDiagram-SY24JHO5-c9h-7uf8.js → sankeyDiagram-SY24JHO5-D7Tn2Gbs.js} +1 -1
  82. package/dist/ui/dist/assets/{sequenceDiagram-BLEHNHQU-B9-S4Rc9.js → sequenceDiagram-BLEHNHQU-CyjUDBaQ.js} +1 -1
  83. package/dist/ui/dist/assets/{src-BZ3kG7lO.js → src-BiC9XNcI.js} +1 -1
  84. package/dist/ui/dist/assets/{stateDiagram-v2-A6IESKWZ-py1smSen.js → stateDiagram-v2-A6IESKWZ-CMp1VCO9.js} +1 -1
  85. package/dist/ui/dist/assets/{swimlanes-B3ENRKFQ-C1E90aH4.js → swimlanes-B3ENRKFQ-KEciLd6r.js} +1 -1
  86. package/dist/ui/dist/assets/swimlanesDiagram-AYQRV4AK-DzestpDE.js +8 -0
  87. package/dist/ui/dist/assets/{timeline-definition-JXYKGLGB-DNe1To6B.js → timeline-definition-JXYKGLGB-C8YQtv7M.js} +1 -1
  88. package/dist/ui/dist/assets/{treeView-D4JQ5SDB-D6MG89i6.js → treeView-D4JQ5SDB-DgAzPaOO.js} +1 -1
  89. package/dist/ui/dist/assets/{treemap-SAJKECNS-CZpt7Mlh.js → treemap-SAJKECNS-eCRqZMYc.js} +1 -1
  90. package/dist/ui/dist/assets/{usecaseDiagram-WPOLBNNX-CKsv-03M.js → usecaseDiagram-WPOLBNNX-Dirryw9G.js} +1 -1
  91. package/dist/ui/dist/assets/{vennDiagram-Q3Y3IDCW-DGJ1qlyJ.js → vennDiagram-Q3Y3IDCW-BjxQbAUx.js} +1 -1
  92. package/dist/ui/dist/assets/{wardley-7MLQ67FV-ClOzBemC.js → wardley-7MLQ67FV-CqHX5RUz.js} +1 -1
  93. package/dist/ui/dist/assets/{wardleyDiagram-IVAIDKWA-C68ahPyF.js → wardleyDiagram-IVAIDKWA-Ds1HoEUY.js} +1 -1
  94. package/dist/ui/dist/assets/{xychartDiagram-4URNHDU4-DWDamCoH.js → xychartDiagram-4URNHDU4-kodzDb12.js} +1 -1
  95. package/dist/ui/dist/index.html +2 -2
  96. package/package.json +1 -1
  97. package/skills/follow-me/SKILL.md +5 -1
  98. package/dist/ui/dist/assets/channel-B7CT_oIp.js +0 -1
  99. package/dist/ui/dist/assets/flowDiagram-IUGLLWBU--HPNoVQJ.js +0 -1
  100. package/dist/ui/dist/assets/index-B8TYcxF8.css +0 -1
  101. package/dist/ui/dist/assets/index-CYKbk4QS.js +0 -134
  102. package/dist/ui/dist/assets/swimlanesDiagram-AYQRV4AK-I98Q8AqP.js +0 -8
package/dist/src/git.js CHANGED
@@ -1,13 +1,16 @@
1
1
  import { execFile } from "node:child_process";
2
2
  import { link, mkdir, mkdtemp, readdir, rm } from "node:fs/promises";
3
3
  import { join } from "node:path";
4
- import { isSha, neededBlobs, parseDiff } from "./diff.js";
4
+ import { isRepoPath, isSha, neededBlobs, parseDiff } from "./diff.js";
5
5
  export class GitError extends Error {
6
6
  name = "GitError";
7
7
  args;
8
- constructor(args, stderr) {
8
+ /** git's exit status, or null when it did not run or was killed. */
9
+ status;
10
+ constructor(args, stderr, status = null) {
9
11
  super(`git ${args.join(" ")}: ${stderr}`);
10
12
  this.args = args;
13
+ this.status = status;
11
14
  }
12
15
  }
13
16
  function git(repo, args, opts = {}) {
@@ -18,8 +21,10 @@ function git(repo, args, opts = {}) {
18
21
  maxBuffer: 1 << 30,
19
22
  env: { ...process.env, ...opts.env },
20
23
  }, (error, stdout, stderr) => {
21
- if (error)
22
- reject(new GitError(args, stderr.toString("utf8").trim() || error.message));
24
+ if (error) {
25
+ const status = typeof error.code === "number" ? error.code : null;
26
+ reject(new GitError(args, stderr.toString("utf8").trim() || error.message, status));
27
+ }
23
28
  else
24
29
  resolve(stdout);
25
30
  });
@@ -97,25 +102,94 @@ export async function defaultBaseRef(repo) {
97
102
  }
98
103
  throw new Error("neither main nor master exists; pass --base <ref>");
99
104
  }
100
- async function catBlobs(repo, shas) {
101
- const blobs = new Map();
102
- if (shas.length === 0)
103
- return blobs;
104
- const out = await git(repo, ["cat-file", "--batch"], { input: `${shas.join("\n")}\n` });
105
+ /** The blob named by each object spec, in order, as latin1; throws when one is not a blob. */
106
+ async function catBatch(repo, specs) {
107
+ if (specs.length === 0)
108
+ return [];
109
+ const input = Buffer.from(`${specs.join("\n")}\n`, "latin1");
110
+ const out = await git(repo, ["cat-file", "--batch"], { input });
111
+ const blobs = [];
105
112
  let at = 0;
106
- for (const sha of shas) {
113
+ for (const spec of specs) {
107
114
  const nl = out.indexOf(0x0a, at);
108
115
  const header = out.subarray(at, nl).toString("latin1");
109
116
  const m = /^([0-9a-f]+) blob (\d+)$/.exec(header);
110
- if (nl === -1 || m === null || m[1] !== sha) {
111
- throw new Error(`cat-file returned "${header}" for ${sha}`);
117
+ if (nl === -1 || m === null || (isSha(spec) && m[1] !== spec)) {
118
+ throw new Error(`cat-file returned "${header}" for ${spec}`);
112
119
  }
113
120
  const size = Number(m[2]);
114
- blobs.set(sha, out.subarray(nl + 1, nl + 1 + size).toString("latin1"));
121
+ blobs.push(out.subarray(nl + 1, nl + 1 + size).toString("latin1"));
115
122
  at = nl + 1 + size + 1;
116
123
  }
117
124
  return blobs;
118
125
  }
126
+ async function catBlobs(repo, shas) {
127
+ const blobs = await catBatch(repo, shas);
128
+ return new Map(shas.map((sha, i) => [sha, blobs[i] ?? ""]));
129
+ }
130
+ /** Each path's bytes at the commit, as latin1. */
131
+ export async function blobsAt(repo, commit, paths) {
132
+ const blobs = await catBatch(repo, paths.map((p) => `${commit}:${p}`));
133
+ return new Map(paths.map((p, i) => [p, blobs[i] ?? ""]));
134
+ }
135
+ function pathList(out) {
136
+ return new Set(out
137
+ .toString("latin1")
138
+ .split("\0")
139
+ .filter((p) => isRepoPath(p)));
140
+ }
141
+ /** Every file path in the commit's tree. */
142
+ export async function pathsAt(repo, commit) {
143
+ return pathList(await git(repo, ["ls-tree", "-r", "-z", "--name-only", commit]));
144
+ }
145
+ /**
146
+ * Paths whose content differs between the commit and `to`: another commit, or with null the
147
+ * working tree and index. Untracked files are not listed.
148
+ */
149
+ export async function pathsChanged(repo, from, to) {
150
+ const target = to === null ? [] : [to];
151
+ return pathList(await git(repo, ["diff", "--name-only", "-z", ...DIFF_FLAGS, from, ...target, "--"]));
152
+ }
153
+ /**
154
+ * Lines of the commit's tree, not the working tree, that match any of the POSIX extended
155
+ * patterns, in files matching the pathspecs. Binary files are skipped.
156
+ */
157
+ export async function grepTip(repo, commit, patterns, pathspecs) {
158
+ const args = [
159
+ "grep",
160
+ "-n",
161
+ "-E",
162
+ "-I",
163
+ "-z",
164
+ "--no-color",
165
+ "--no-column",
166
+ ...patterns.flatMap((p) => ["-e", p]),
167
+ commit,
168
+ "--",
169
+ ...pathspecs,
170
+ ];
171
+ let out;
172
+ try {
173
+ out = await git(repo, args);
174
+ }
175
+ catch (error) {
176
+ // grep exits 1 when nothing matched.
177
+ if (error instanceof GitError && error.status === 1)
178
+ return [];
179
+ throw error;
180
+ }
181
+ const prefix = `${commit}:`;
182
+ return out
183
+ .toString("latin1")
184
+ .split("\n")
185
+ .flatMap((record) => {
186
+ const [name = "", line = "", ...text] = record.split("\0");
187
+ const path = name.slice(prefix.length);
188
+ if (!name.startsWith(prefix) || !isRepoPath(path) || !/^\d+$/.test(line))
189
+ return [];
190
+ return [{ path, line: Number(line), text: text.join("\0") }];
191
+ });
192
+ }
119
193
  const DIFF_FLAGS = ["--no-renames", "--full-index", "--no-color", "--no-ext-diff", "--no-textconv"];
120
194
  export async function loadDiff(repo, baseRef, tipRef) {
121
195
  const [tip, baseCommit] = await Promise.all([
package/dist/src/host.js CHANGED
@@ -373,6 +373,10 @@ export async function postComment(port, summary, comment) {
373
373
  }
374
374
  return id;
375
375
  }
376
+ /** Tells the hosted page that coverage.json changed, so it reloads its marks. */
377
+ export async function postCoverage(port, summary) {
378
+ await call(port, "POST", `${summary.url}api/coverage`);
379
+ }
376
380
  /** The consolidated report of the hosted session as it stands. */
377
381
  export async function fetchReport(port, summary, format = "markdown") {
378
382
  const res = await fetch(`${sessionUrl(port, summary)}api/report?format=${format}`);
package/dist/src/plan.js CHANGED
@@ -17,6 +17,10 @@ const STATUSES = ["addressed", "declined", "deferred", "answered"];
17
17
  function isResponseStatus(s) {
18
18
  return STATUSES.some((x) => x === s);
19
19
  }
20
+ const SECTIONS = ["story", "steps", "details", "wiring"];
21
+ function isCommitSection(s) {
22
+ return SECTIONS.some((x) => x === s);
23
+ }
20
24
  const SEVERITIES = ["blocker", "major", "minor"];
21
25
  const CONCERN_KINDS = ["bug", "missing-test", "naming", "risk"];
22
26
  export function isSeverity(s) {
@@ -223,6 +227,10 @@ export function parsePlanFile(draft) {
223
227
  }
224
228
  if (typeof c.message !== "string")
225
229
  bad(`${at}.message`, "expected a string");
230
+ const section = c.section ?? null;
231
+ if (section !== null && !isCommitSection(section)) {
232
+ bad(`${at}.section`, `expected one of ${SECTIONS.join(", ")}`);
233
+ }
226
234
  const extras = parseExtras(c, false);
227
235
  if ("error" in extras)
228
236
  bad(`${at}.${extras.field}`, extras.error);
@@ -239,7 +247,8 @@ export function parsePlanFile(draft) {
239
247
  }
240
248
  }
241
249
  if (typeof c.message === "string" && !("error" in extras)) {
242
- commits.push({ message: c.message, ...extras, units });
250
+ const placed = isCommitSection(section) ? { section } : {};
251
+ commits.push({ message: c.message, ...placed, ...extras, units });
243
252
  }
244
253
  }
245
254
  }
@@ -287,6 +296,7 @@ export function serializePlanFile(file) {
287
296
  tip: file.tip,
288
297
  commits: file.commits.map((c) => ({
289
298
  message: c.message,
299
+ ...(c.section === undefined ? {} : { section: c.section }),
290
300
  ...serializeExtras(c),
291
301
  units: c.units.map((ref) => ref.narration === undefined && ref.diagram === undefined && ref.concerns === undefined
292
302
  ? formatUnitRef(ref)
@@ -578,6 +588,7 @@ function project(diff, file, claims, issues) {
578
588
  return {
579
589
  index: k,
580
590
  message: commit.message,
591
+ ...(commit.section === undefined ? {} : { section: commit.section }),
581
592
  ...withText(commit.narration ?? null, commit.diagram ?? null),
582
593
  ...(mine.length === 0 ? {} : { concerns: mine }),
583
594
  refs,
@@ -1,3 +1,4 @@
1
+ import { changedLines } from "./coverage.js";
1
2
  import { tipStartOf } from "./diff.js";
2
3
  import { anchorOn, concernOn } from "./evolve.js";
3
4
  /** Consecutive runs collapse: [0,1,2,3,7] becomes "0-3,7". */
@@ -581,14 +582,14 @@ function itemHeading(item, level) {
581
582
  parts.push(kept === null ? "raised by the reviewer" : "raised by the agent, kept by the reviewer");
582
583
  return `${"#".repeat(level)} ${parts.join(" · ")}`;
583
584
  }
584
- function renderItems(items, units, level) {
585
+ function renderItems(items, units, level, tallies) {
585
586
  const out = [];
586
587
  const sorted = sortItems(items, units);
587
588
  const paths = [...new Set(sorted.flatMap((i) => (i.where.path === null ? [] : [i.where.path])))];
588
- const group = (title, members) => {
589
+ const group = (title, members, lead = []) => {
589
590
  if (members.length === 0)
590
591
  return;
591
- out.push(`${"#".repeat(level)} ${title}`, "");
592
+ out.push(`${"#".repeat(level)} ${title}`, "", ...lead);
592
593
  for (const item of members) {
593
594
  out.push(itemHeading(item, level + 1), "", ...quoteBlock(item.where.quote));
594
595
  if (item.where.quote.length > 0)
@@ -596,8 +597,12 @@ function renderItems(items, units, level) {
596
597
  out.push(...voices(item), "");
597
598
  }
598
599
  };
599
- for (const path of paths)
600
- group(path, sorted.filter((i) => i.where.path === path));
600
+ for (const path of paths) {
601
+ const tally = tallies.get(path);
602
+ group(path, sorted.filter((i) => i.where.path === path), tally === undefined
603
+ ? []
604
+ : [`coverage: ${tally.covered} of ${tally.executable} changed lines`, ""]);
605
+ }
601
606
  group("General", sorted.filter((i) => i.where.path === null));
602
607
  return out;
603
608
  }
@@ -719,24 +724,43 @@ function spanOf(rows) {
719
724
  startOldLine: range && first.op !== "+" ? first.base : null,
720
725
  };
721
726
  }
722
- function commentSpan(ctx, c) {
727
+ function commentRows(ctx, c) {
723
728
  const { anchor } = c;
724
729
  if (anchor.kind !== "hunk")
725
- return NO_SPAN;
730
+ return null;
726
731
  const hunk = ctx.units.get(anchor.unit);
727
732
  if (hunk?.kind !== "hunk")
728
- return NO_SPAN;
733
+ return null;
729
734
  const { offsets, changes } = anchor;
730
- if (offsets !== undefined)
731
- return spanOf(numberedRows(hunk, ctx.units, (i) => offsets.includes(i)));
732
- return spanOf(numberedRows(hunk, ctx.units, (_, n) => n !== null && (changes === null || changes.includes(n))));
735
+ const want = offsets !== undefined
736
+ ? (i) => offsets.includes(i)
737
+ : (_, n) => n !== null && (changes === null || changes.includes(n));
738
+ return { path: hunk.path, rows: numberedRows(hunk, ctx.units, want) };
733
739
  }
734
- function concernSpan(ctx, concern) {
740
+ function concernRows(ctx, concern) {
735
741
  const hunk = concern.unit === undefined ? undefined : ctx.units.get(concern.unit);
736
742
  if (hunk?.kind !== "hunk")
737
- return NO_SPAN;
743
+ return null;
738
744
  const lines = concern.lines;
739
- return spanOf(numberedRows(hunk, ctx.units, (_, n) => n !== null && (lines === undefined || lines.includes(n))));
745
+ const want = (_, n) => n !== null && (lines === undefined || lines.includes(n));
746
+ return { path: hunk.path, rows: numberedRows(hunk, ctx.units, want) };
747
+ }
748
+ function spanAt(at) {
749
+ return at === null ? NO_SPAN : spanOf(at.rows);
750
+ }
751
+ /**
752
+ * Whether the test run ran every executable added row the item points at. Absent without a run
753
+ * that still fits the file, when it picks no such row, or when its code changed after it was said.
754
+ */
755
+ function coveredOf(ctx, id, at) {
756
+ const file = at === null ? undefined : ctx.coverage?.get(at.path);
757
+ if (at === null || file === undefined || ctx.marks?.has(id))
758
+ return {};
759
+ const hits = at.rows.flatMap((r) => {
760
+ const n = r.op === "+" ? file.lines.get(r.tip) : undefined;
761
+ return n === undefined ? [] : [n];
762
+ });
763
+ return hits.length === 0 ? {} : { covered: hits.every((n) => n > 0) };
740
764
  }
741
765
  function threadOf(c) {
742
766
  return (c.replies ?? []).map((r) => ({ author: r.author, body: r.body }));
@@ -761,6 +785,7 @@ function collectItems(review, ctx, responses) {
761
785
  const { kept } = it;
762
786
  const first = it.comments[0];
763
787
  if (kept !== null) {
788
+ const at = concernRows(ctx, kept.concern);
764
789
  const note = kept.note === null ? "" : `\n\nReviewer: ${kept.note}`;
765
790
  const thread = it.comments.flatMap((c) => [
766
791
  { author: "reviewer", body: c.body },
@@ -770,7 +795,7 @@ function collectItems(review, ctx, responses) {
770
795
  where: it.where,
771
796
  item: {
772
797
  id: kept.concern.id,
773
- ...concernSpan(ctx, kept.concern),
798
+ ...spanAt(at),
774
799
  path: it.where.path,
775
800
  oldPath: it.where.path,
776
801
  severity: it.severity,
@@ -780,16 +805,18 @@ function collectItems(review, ctx, responses) {
780
805
  resolved: false,
781
806
  excluded: noConcerns.has(kept.concern.id),
782
807
  ...changedOf(ctx, kept.concern.id),
808
+ ...coveredOf(ctx, kept.concern.id, at),
783
809
  },
784
810
  };
785
811
  }
786
812
  if (first === undefined)
787
813
  throw new Error("an item with neither a concern nor a comment");
814
+ const at = commentRows(ctx, first);
788
815
  return {
789
816
  where: it.where,
790
817
  item: {
791
818
  id: first.id,
792
- ...commentSpan(ctx, first),
819
+ ...spanAt(at),
793
820
  path: it.where.path,
794
821
  oldPath: it.where.path,
795
822
  severity: it.severity,
@@ -799,6 +826,7 @@ function collectItems(review, ctx, responses) {
799
826
  resolved: resolved.has(first.id),
800
827
  excluded: noComments.has(first.id),
801
828
  ...changedOf(ctx, first.id),
829
+ ...coveredOf(ctx, first.id, at),
802
830
  },
803
831
  };
804
832
  });
@@ -812,7 +840,7 @@ function collectItems(review, ctx, responses) {
812
840
  where,
813
841
  item: {
814
842
  id: q.id,
815
- ...commentSpan(ctx, q),
843
+ ...spanAt(commentRows(ctx, q)),
816
844
  path: where.path,
817
845
  oldPath: where.path,
818
846
  severity: q.severity ?? null,
@@ -832,7 +860,7 @@ function collectItems(review, ctx, responses) {
832
860
  * Excluded ones are present and marked; whoever posts them skips those.
833
861
  */
834
862
  export function reportItems(review, commits, units, opts) {
835
- const session = folded(review, commits, units, opts.earlier);
863
+ const session = folded(review, commits, units, opts);
836
864
  return collectItems(session.review, session.ctx, opts.responses).map((c) => c.item);
837
865
  }
838
866
  /** A short list of what the reviewer left out, so nothing vanishes without a trace. */
@@ -855,9 +883,11 @@ function renderExcluded(collected) {
855
883
  * taking their unit, and a mark on each whose code changed. Each id appears once; this
856
884
  * version's own copy wins.
857
885
  */
858
- function folded(review, commits, units, earlier) {
886
+ function folded(review, commits, units, opts) {
887
+ const { earlier } = opts;
888
+ const coverage = opts.coverage === undefined ? {} : { coverage: opts.coverage };
859
889
  if (earlier === undefined)
860
- return { review, ctx: { commits, units } };
890
+ return { review, ctx: { commits, units, ...coverage } };
861
891
  const marks = new Map();
862
892
  const mark = (id, at) => {
863
893
  if (at.since !== null)
@@ -895,6 +925,7 @@ function folded(review, commits, units, earlier) {
895
925
  ctx: {
896
926
  units,
897
927
  marks,
928
+ ...coverage,
898
929
  commits: commits.map((commit, k) => {
899
930
  const mine = lost.filter((c) => owner(c.unit, c.commit) === k);
900
931
  if (mine.length === 0)
@@ -915,7 +946,7 @@ function folded(review, commits, units, earlier) {
915
946
  * Concerns the reviewer has not decided are left out, as are those dismissed.
916
947
  */
917
948
  export function renderReport(full, commits, units, opts) {
918
- const session = folded(full, commits, units, opts.earlier);
949
+ const session = folded(full, commits, units, opts);
919
950
  const { ctx } = session;
920
951
  const excluded = session.review.excluded;
921
952
  if (excluded === undefined)
@@ -942,12 +973,14 @@ function renderIncluded(review, ctx, opts) {
942
973
  const issues = walk ? rest : rest.filter((c) => c.severity !== undefined);
943
974
  const feedback = walk ? [] : rest.filter((c) => c.severity === undefined);
944
975
  const items = buildItems(ctx, keptConcerns(review, commits), issues);
976
+ // Keyed by path as the items name it, so a file's heading finds its count.
977
+ const tallies = new Map(ctx.coverage === undefined ? [] : changedLines(units, ctx.coverage));
945
978
  const out = [];
946
979
  if (walk) {
947
980
  out.push(`# Code review: ${opts.branch} (${commits.length === 1 ? "1 commit" : `${commits.length} commits`}, ${summarizeIssues(items)})`, "");
948
981
  if (items.length === 0 && questions.length === 0)
949
982
  out.push("The reviewer raised no issues.", "");
950
- out.push(...renderItems(items, units, 2));
983
+ out.push(...renderItems(items, units, 2, tallies));
951
984
  }
952
985
  else {
953
986
  out.push(`# Review of plan v${review.planVersion}`, "");
@@ -960,7 +993,7 @@ function renderIncluded(review, ctx, opts) {
960
993
  }
961
994
  out.push(...renderPlanFeedback(ctx, feedback));
962
995
  if (items.length > 0) {
963
- out.push(`## Code issues (${summarizeIssues(items)})`, "", ...renderItems(items, units, 3));
996
+ out.push(`## Code issues (${summarizeIssues(items)})`, "", ...renderItems(items, units, 3, tallies));
964
997
  }
965
998
  }
966
999
  if (questions.length > 0)
@@ -1016,6 +1049,8 @@ function renderEvent(event, ctx) {
1016
1049
  return [`- ${event.commentId} ${event.resolved ? "resolved" : "reopened"}`];
1017
1050
  case "version":
1018
1051
  return [`- plan v${event.version} is open`];
1052
+ case "coverage":
1053
+ return [`- the page shows coverage from ${event.tip.slice(0, 12)}`];
1019
1054
  case "submitted":
1020
1055
  return ["- the reviewer submitted the review"];
1021
1056
  case "ended":
@@ -1065,3 +1100,25 @@ export function renderShownConcern(concern, commit, ctx) {
1065
1100
  "",
1066
1101
  ].join("\n");
1067
1102
  }
1103
+ function utf8(latin1) {
1104
+ return Buffer.from(latin1, "latin1").toString("utf8");
1105
+ }
1106
+ /** Where a name is defined, each with the commit it lands in and its first lines at the tip. */
1107
+ export function renderShownDefinitions(name, candidates) {
1108
+ if (candidates.length === 0)
1109
+ return `no definition of ${name} at the tip\n`;
1110
+ return `${candidates
1111
+ .flatMap((c) => {
1112
+ const where = c.commit !== null
1113
+ ? `in commit ${c.commit + 1}`
1114
+ : c.unit !== null
1115
+ ? `in ${c.unit}, which no commit of the plan takes`
1116
+ : "unchanged by the branch";
1117
+ const width = String(c.newLine + c.preview.length - 1).length;
1118
+ return [
1119
+ `${utf8(c.path)}:${c.newLine} ${c.kind}, ${where}`,
1120
+ ...c.preview.map((l, i) => ` ${String(c.newLine + i).padStart(width)} ${utf8(l)}`),
1121
+ ];
1122
+ })
1123
+ .join("\n")}\n`;
1124
+ }
@@ -5,10 +5,13 @@ import { homedir } from "node:os";
5
5
  import { basename, isAbsolute, join } from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import { isReplyId, parseComment, parseDraft, parseNewDecision, parseNewReply, } from "./comments.js";
8
+ import { serializeCoverage, staleFiles } from "./coverage.js";
9
+ import { definitionIndex, isIdentifier } from "./definition.js";
10
+ import { isRepoPath } from "./diff.js";
8
11
  import { carryConcerns, carrySession } from "./evolve.js";
9
12
  import { isConcernId, isPlanVersion } from "./plan.js";
10
13
  import { renderReport, reportItems } from "./prompt.js";
11
- import { isLockToken } from "./session.js";
14
+ import { coverageAt, isLockToken, readCoverageIn, SessionFileError } from "./session.js";
12
15
  /** The requested port is taken; host.ts decides whether that is our server or a stranger. */
13
16
  export class PortInUseError extends Error {
14
17
  name = "PortInUseError";
@@ -255,6 +258,7 @@ function viewOf(registration, loaded, draft, earlier = loaded.earlier) {
255
258
  unitIds: new Set(state.units.map((u) => u.id)),
256
259
  hunkRows: new Map(state.units.flatMap((u) => u.kind === "hunk" ? [[u.id, u.body.length]] : [])),
257
260
  concernIds: new Set(state.commits.flatMap((c) => (c.concerns ?? []).map((k) => k.id))),
261
+ definitions: definitionIndex(loaded.repo, state.anchor.tip, new Map(state.units.map((u) => [u.id, u])), state.commits),
258
262
  draftFile: draftPath(reviewsDir, state.version),
259
263
  reviewFile: join(reviewsDir, `v${state.version}.json`),
260
264
  };
@@ -468,25 +472,44 @@ function hostSession(id, registration, loaded, draft, load) {
468
472
  ...carriedRefs(view),
469
473
  };
470
474
  };
471
- const reportOptions = () => {
475
+ const reportOptions = async () => {
472
476
  const comments = sessionOf(view);
477
+ const { repo, state } = view.loaded;
478
+ const coverage = await coverageAt(repo, view.registration.dir, state.anchor.tip);
473
479
  return {
474
- branch: view.loaded.state.anchor.branch,
480
+ branch: state.anchor.branch,
475
481
  responses: [],
476
482
  ...(comments === null ? {} : { earlier: { comments, concerns: view.earlierConcerns } }),
483
+ ...(coverage === null ? {} : { coverage }),
477
484
  };
478
485
  };
479
486
  const unitsOf = () => new Map(view.loaded.state.units.map((u) => [u.id, u]));
480
- const report = (finished) => renderReport(finished, view.loaded.state.commits, unitsOf(), reportOptions());
487
+ const report = async (finished) => renderReport(finished, view.loaded.state.commits, unitsOf(), await reportOptions());
481
488
  /** Writes reviews/vN.json and its report once, and removes the autosave. */
482
489
  const writeReview = async (finished) => {
483
490
  await writes;
484
491
  await mkdir(view.loaded.reviewsDir, { recursive: true });
485
492
  await writeAtomic(view.reviewFile, `${JSON.stringify(finished, null, 2)}\n`);
486
- await writeAtomic(view.reviewFile.replace(/\.json$/, ".md"), report(finished));
493
+ await writeAtomic(view.reviewFile.replace(/\.json$/, ".md"), await report(finished));
487
494
  await rm(view.draftFile, { force: true });
488
495
  return view.reviewFile;
489
496
  };
497
+ /** coverage.json as the agent last wrote it, or a 404 before its first run. */
498
+ const coverageFile = () => {
499
+ let coverage;
500
+ try {
501
+ coverage = readCoverageIn(view.registration.dir);
502
+ }
503
+ catch (error) {
504
+ if (error instanceof SessionFileError)
505
+ throw new HttpError(500, error.message);
506
+ throw error;
507
+ }
508
+ if (coverage === null) {
509
+ throw new HttpError(404, "no coverage yet; the agent records it with follow-me coverage");
510
+ }
511
+ return coverage;
512
+ };
490
513
  const hasWork = () => view.comments.size > 0 || view.decisions.size > 0 || view.earlierReplies.size > 0;
491
514
  return {
492
515
  id,
@@ -544,7 +567,28 @@ function hostSession(id, registration, loaded, draft, load) {
544
567
  return waitForEvents(since, timeoutMs, signal);
545
568
  },
546
569
  report: () => report(review()),
547
- reportItems: () => reportItems(review(), view.loaded.state.commits, unitsOf(), reportOptions()),
570
+ /**
571
+ * The agent's last test run, read from disk so a restarted server serves it too, with the
572
+ * covered files whose content changed since its tip. A new plan for the same tip stales nothing.
573
+ */
574
+ coverage: async () => {
575
+ const coverage = coverageFile();
576
+ const stale = await staleFiles(view.loaded.repo, coverage, view.loaded.state.anchor.tip);
577
+ return { ...serializeCoverage(coverage), stale };
578
+ },
579
+ /** The agent wrote coverage.json; the page hears it as one event and reloads its marks. */
580
+ coverageTaken: () => {
581
+ open();
582
+ const { tip, takenAt } = coverageFile();
583
+ emit({ type: "coverage", tip, takenAt });
584
+ return { tip, takenAt };
585
+ },
586
+ /** The page reads a definition the plan shows from its hunk, so only others carry a preview. */
587
+ define: async (name, from) => {
588
+ const found = await view.definitions.lookup(name, from !== null && isRepoPath(from) ? from : null);
589
+ return found.map((c) => (c.unit === null ? c : { ...c, preview: [] }));
590
+ },
591
+ reportItems: async () => reportItems(review(), view.loaded.state.commits, unitsOf(), await reportOptions()),
548
592
  add: async (raw) => {
549
593
  open();
550
594
  if (!isRecord(raw))
@@ -943,12 +987,25 @@ export function startServer(opts) {
943
987
  const timeout = queryInt(url, "timeout", 0, MAX_POLL_MS, DEFAULT_POLL_MS);
944
988
  return sendJson(res, 200, await session.poll(since, timeout, abort.signal));
945
989
  }
990
+ if (method === "GET" && rest === "/api/definition") {
991
+ const name = url.searchParams.get("name") ?? "";
992
+ if (!isIdentifier(name))
993
+ throw new HttpError(400, "name must be an identifier");
994
+ const candidates = await session.define(name, url.searchParams.get("from"));
995
+ return sendJson(res, 200, { candidates });
996
+ }
997
+ if (method === "GET" && rest === "/api/coverage") {
998
+ return sendJson(res, 200, await session.coverage());
999
+ }
1000
+ if (method === "POST" && rest === "/api/coverage") {
1001
+ return sendJson(res, 200, session.coverageTaken());
1002
+ }
946
1003
  if (method === "GET" && rest === "/api/report" && url.searchParams.get("format") === "json") {
947
- return sendJson(res, 200, session.reportItems());
1004
+ return sendJson(res, 200, await session.reportItems());
948
1005
  }
949
1006
  if (method === "GET" && rest === "/api/report") {
950
1007
  res.writeHead(200, { "content-type": "text/markdown; charset=utf-8" });
951
- res.end(session.report());
1008
+ res.end(await session.report());
952
1009
  return;
953
1010
  }
954
1011
  if (method === "POST" && rest === "/api/version") {
@@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto";
2
2
  import { existsSync, linkSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync, } from "node:fs";
3
3
  import { basename, dirname, join } from "node:path";
4
4
  import { parseDraft, parseReview } from "./comments.js";
5
+ import { freshFiles, parseCoverage, serializeCoverage, staleFiles } from "./coverage.js";
5
6
  import { isSha } from "./diff.js";
6
7
  import { evolvePlan } from "./evolve.js";
7
8
  import { branchExists, currentBranch, defaultBaseRef, GitError, loadDiff, objectsDir, QUARANTINE_PREFIX, repoRoot, resolveCommit, } from "./git.js";
@@ -200,6 +201,36 @@ function parseApplied(v) {
200
201
  return null;
201
202
  return { schema, version, branch, head, hash, signed };
202
203
  }
204
+ function coveragePath(dir) {
205
+ return join(dir, "coverage.json");
206
+ }
207
+ /** The coverage.json of the session in `dir`, or null when there is none. */
208
+ export function readCoverageIn(dir) {
209
+ const path = coveragePath(dir);
210
+ const raw = readJson(path);
211
+ if (raw === undefined)
212
+ return null;
213
+ const coverage = parseCoverage(raw);
214
+ if (typeof coverage === "string")
215
+ throw new SessionFileError(`${path}: ${coverage}`);
216
+ return coverage;
217
+ }
218
+ /**
219
+ * What the report counts: the files of the session's test run that still describe `tip`. Null
220
+ * without a run, and when coverage.json cannot be read, so a report never fails on it.
221
+ */
222
+ export async function coverageAt(repo, dir, tip) {
223
+ let coverage;
224
+ try {
225
+ coverage = readCoverageIn(dir);
226
+ }
227
+ catch (error) {
228
+ if (error instanceof SessionFileError)
229
+ return null;
230
+ throw error;
231
+ }
232
+ return coverage === null ? null : freshFiles(coverage, await staleFiles(repo, coverage, tip));
233
+ }
203
234
  function makeSession(root, dir, anchor, diff) {
204
235
  const plansDir = join(dir, "plans");
205
236
  const splitReviews = join(dir, "reviews");
@@ -310,6 +341,8 @@ function makeSession(root, dir, anchor, diff) {
310
341
  writeJson(join(plansDir, `v${version}.json`), serializePlanFile(plan.file));
311
342
  return version;
312
343
  },
344
+ coverage: () => readCoverageIn(dir),
345
+ writeCoverage: (coverage) => writeJson(coveragePath(dir), serializeCoverage(coverage)),
313
346
  lock: (command) => acquireLock(dir, anchor.branch, command),
314
347
  adoptLock: (from, command) => adoptLock(dir, from, command),
315
348
  lockState: () => readLock(join(dir, "lock")),
package/dist/src/types.js CHANGED
@@ -10,6 +10,10 @@
10
10
  * session.ts the .follow-me/<slug>/ layout: session anchor, plans, reviews, draft.
11
11
  * comments.ts the wire formats of review state: comments, replies, concern decisions, the
12
12
  * review file, the autosaved draft and the server's events. Pure parsers.
13
+ * definition.ts where an identifier is defined at the branch tip, and where that lands in the
14
+ * plan: per-language patterns, ranking, placement and a per-tip cache.
15
+ * coverage.ts lcov from the agent's test run: parse, place at the tip, merge runs, count the
16
+ * changed lines covered, and which files went stale since.
13
17
  * prompt.ts the planner prompt for an agent, the feedback and the consolidated report.
14
18
  * server.ts the review server: a registry of hosted sessions, their JSON API, and ui/.
15
19
  * host.ts the one server per user: its record in the state dir, attach, start, stop.
@@ -1 +1 @@
1
- import{m as e,t}from"./mermaid-parser.core-DxvaorTa.js";import{n}from"./chunk-Y2CYZVJY-DsF7k-Jl.js";import{m as r}from"./src-BZ3kG7lO.js";import"./chunk-DSNLSHJZ-YiMuxsb-.js";import{n as i,r as a,t as o}from"./chunk-2E7WHVMD-BiHSeJMu.js";import{t as s}from"./chunk-JWPE2WC7-DVXcaiue.js";import"./mermaid.core-ujYTsRZz.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-CMPNc-u6.js";import{n}from"./chunk-Y2CYZVJY-DsF7k-Jl.js";import{m as r}from"./src-BiC9XNcI.js";import"./chunk-DSNLSHJZ-Bk8WO5JF.js";import{n as i,r as a,t as o}from"./chunk-2E7WHVMD-BTet9STn.js";import{t as s}from"./chunk-JWPE2WC7-DVXcaiue.js";import"./mermaid.core-Nd_xOg2y.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-D3j-DODP.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-CHZ9mrl_.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};