@shiyuphan/follow-me 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/README.md +44 -55
  2. package/dist/src/cli.js +566 -188
  3. package/dist/src/comments.js +66 -5
  4. package/dist/src/git.js +12 -0
  5. package/dist/src/host.js +19 -8
  6. package/dist/src/install.js +21 -0
  7. package/dist/src/plan.js +1 -1
  8. package/dist/src/prompt.js +343 -39
  9. package/dist/src/server.js +176 -67
  10. package/dist/src/session.js +13 -11
  11. package/dist/ui/dist/assets/abnfDiagram-WEYOIA3C-DcmzRkVZ.js +1 -0
  12. package/dist/ui/dist/assets/arc-T5QB0T1C.js +1 -0
  13. package/dist/ui/dist/assets/architecture-WOLXFQ4H-DUIkQTeE.js +1 -0
  14. package/dist/ui/dist/assets/architectureDiagram-ZHVYKT53-BUYi4-bA.js +36 -0
  15. package/dist/ui/dist/assets/array-BifhSqXX.js +1 -0
  16. package/dist/ui/dist/assets/blockDiagram-RJAE7QA3-dyJxtT9e.js +140 -0
  17. package/dist/ui/dist/assets/c4Diagram-XDPPPE54-CrAgiaZR.js +38 -0
  18. package/dist/ui/dist/assets/channel-wW7ebm0i.js +1 -0
  19. package/dist/ui/dist/assets/chunk-2E7WHVMD-C6fAf4Vj.js +88 -0
  20. package/dist/ui/dist/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
  21. package/dist/ui/dist/assets/chunk-4EMNKR2T-BLZjwZ4Q.js +1 -0
  22. package/dist/ui/dist/assets/chunk-55XCU76K-DGLK2Q-L.js +1 -0
  23. package/dist/ui/dist/assets/chunk-5VM5RSS4-ZNzvKenW.js +15 -0
  24. package/dist/ui/dist/assets/chunk-7S6XSTFO-BablszTX.js +2 -0
  25. package/dist/ui/dist/assets/chunk-AQDTSKZB-Cuk3B_vb.js +72 -0
  26. package/dist/ui/dist/assets/chunk-DSNLSHJZ-DiC78RqE.js +127 -0
  27. package/dist/ui/dist/assets/chunk-DUW6YSOI-Dgx8z5s3.js +1 -0
  28. package/dist/ui/dist/assets/chunk-HAGFOTED-X_RbvixX.js +62 -0
  29. package/dist/ui/dist/assets/chunk-HNE4Q36P-C14_x5ck.js +1 -0
  30. package/dist/ui/dist/assets/chunk-J5ZVWO5B-oDCd-gWO.js +1 -0
  31. package/dist/ui/dist/assets/chunk-JWPE2WC7-DVXcaiue.js +1 -0
  32. package/dist/ui/dist/assets/chunk-NGNAAXSQ-C2Wy9Xmz.js +136 -0
  33. package/dist/ui/dist/assets/chunk-NQOKRVIF-Dxd0dtYR.js +213 -0
  34. package/dist/ui/dist/assets/chunk-UHW7PFBP-BIxePNUS.js +1 -0
  35. package/dist/ui/dist/assets/chunk-VQ5W62QI-CKSKLbj4.js +1 -0
  36. package/dist/ui/dist/assets/chunk-WQQL4CDK-CWNmZGlR.js +10 -0
  37. package/dist/ui/dist/assets/chunk-XXDRQBXY-D-I5QHgG.js +1 -0
  38. package/dist/ui/dist/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +1 -0
  39. package/dist/ui/dist/assets/chunk-YZO67HFG-DFP68CFd.js +1 -0
  40. package/dist/ui/dist/assets/chunk-ZAUX2LTX-DY90d4QG.js +1 -0
  41. package/dist/ui/dist/assets/chunk-ZCHMQSOR-Dlpr-5UC.js +2 -0
  42. package/dist/ui/dist/assets/classDiagram-v2-4MYWR3UA-CH9uhoLJ.js +217 -0
  43. package/dist/ui/dist/assets/cose-bilkent-JH36ORCC-DtYHrp8D.js +1 -0
  44. package/dist/ui/dist/assets/cynefin-EF2NZ3EQ-BtslbtrY.js +1 -0
  45. package/dist/ui/dist/assets/cynefinDiagram-WF24UDRP-DUU1Jcc7.js +62 -0
  46. package/dist/ui/dist/assets/cytoscape.esm-BegBNzOV.js +321 -0
  47. package/dist/ui/dist/assets/dagre-NK4GIZVX-CDlP-dRW.js +4 -0
  48. package/dist/ui/dist/assets/defaultLocale-BFoDCU3G.js +1 -0
  49. package/dist/ui/dist/assets/diagram-CBK3MWTC-DiV2fn-r.js +24 -0
  50. package/dist/ui/dist/assets/diagram-I5XIIV2D-Bg5RehGi.js +24 -0
  51. package/dist/ui/dist/assets/diagram-ODIL2UCW-CFmazz3f.js +30 -0
  52. package/dist/ui/dist/assets/diagram-PFBEJUXE-uw3ZBqDp.js +3 -0
  53. package/dist/ui/dist/assets/diagram-RJA3I4VV-uEOYYRYa.js +41 -0
  54. package/dist/ui/dist/assets/diagram-TYUNFSNR-BN58LF_3.js +200 -0
  55. package/dist/ui/dist/assets/dist-tWS_lXDS.js +1 -0
  56. package/dist/ui/dist/assets/ebnfDiagram-V4P3TTPT-B68Tz-w8.js +1 -0
  57. package/dist/ui/dist/assets/elk-QONTBCTQ-DZZohNea.js +27 -0
  58. package/dist/ui/dist/assets/erDiagram-KYQIGQAA-BH_m74pv.js +99 -0
  59. package/dist/ui/dist/assets/eventmodeling-K75KTNOO-CTp5FHos.js +1 -0
  60. package/dist/ui/dist/assets/flowDiagram-IUGLLWBU-CsCvexry.js +1 -0
  61. package/dist/ui/dist/assets/ganttDiagram-R7GPNTJY-BDau7_0f.js +292 -0
  62. package/dist/ui/dist/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  63. package/dist/ui/dist/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  64. package/dist/ui/dist/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  65. package/dist/ui/dist/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  66. package/dist/ui/dist/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  67. package/dist/ui/dist/assets/gitGraph-VSP46ZUC-Bv84IoCm.js +1 -0
  68. package/dist/ui/dist/assets/gitGraphDiagram-PDYL7XHY-DzXcMlm8.js +106 -0
  69. package/dist/ui/dist/assets/graphlib-DS17s2tU.js +1 -0
  70. package/dist/ui/dist/assets/index-BTJoqVVN.js +133 -0
  71. package/dist/ui/dist/assets/index-D60U3smc.css +1 -0
  72. package/dist/ui/dist/assets/info-OHQRW6UA-222J0O_j.js +1 -0
  73. package/dist/ui/dist/assets/infoDiagram-MVDRKAX5-DE2sa3Vz.js +2 -0
  74. package/dist/ui/dist/assets/init-C-OQMol4.js +1 -0
  75. package/dist/ui/dist/assets/ishikawaDiagram-HPEHV2GE-BJDk0Zcj.js +70 -0
  76. package/dist/ui/dist/assets/journeyDiagram-BOSHW7O7-JSUXX5Hb.js +139 -0
  77. package/dist/ui/dist/assets/kanban-definition-FNSA4HC5-Bg79b49M.js +89 -0
  78. package/dist/ui/dist/assets/katex-B_JcIwqj.js +257 -0
  79. package/dist/ui/dist/assets/line-Bk5qQSIa.js +1 -0
  80. package/dist/ui/dist/assets/linear-PFnVMru5.js +1 -0
  81. package/dist/ui/dist/assets/mermaid-parser.core-Dcxlzc-S.js +7 -0
  82. package/dist/ui/dist/assets/mermaid.core-TeOHQJ00.js +44 -0
  83. package/dist/ui/dist/assets/mindmap-definition-6OY57UG3-IaNk8NnW.js +98 -0
  84. package/dist/ui/dist/assets/ordinal-BDEzSJ7C.js +1 -0
  85. package/dist/ui/dist/assets/packet-JDAUHWVQ-DMff5cVK.js +1 -0
  86. package/dist/ui/dist/assets/path-fybaL0A-.js +1 -0
  87. package/dist/ui/dist/assets/pegDiagram-ECIUJNBX-66Xu4YRc.js +1 -0
  88. package/dist/ui/dist/assets/pie-XZMESJXO-GEtzLCIM.js +1 -0
  89. package/dist/ui/dist/assets/pieDiagram-GNMPY74T-7jMBi-kU.js +39 -0
  90. package/dist/ui/dist/assets/prism-bash-D6zCJ74D.js +1 -0
  91. package/dist/ui/dist/assets/prism-c-04YixN25.js +1 -0
  92. package/dist/ui/dist/assets/prism-clike-DapgWyxx.js +1 -0
  93. package/dist/ui/dist/assets/prism-core-FDHlWQ43.js +1 -0
  94. package/dist/ui/dist/assets/prism-cpp-C-lJbC-6.js +1 -0
  95. package/dist/ui/dist/assets/prism-css-CPgOzxXp.js +1 -0
  96. package/dist/ui/dist/assets/prism-go-C_8qAH5n.js +1 -0
  97. package/dist/ui/dist/assets/prism-java-6wtusvIl.js +1 -0
  98. package/dist/ui/dist/assets/prism-javascript-BnO-swvr.js +1 -0
  99. package/dist/ui/dist/assets/prism-json-Dp_-W-Hv.js +1 -0
  100. package/dist/ui/dist/assets/prism-jsx-D-D0NWtC.js +1 -0
  101. package/dist/ui/dist/assets/prism-kotlin-BUlJgC-o.js +1 -0
  102. package/dist/ui/dist/assets/prism-markdown-Bc9-36OC.js +1 -0
  103. package/dist/ui/dist/assets/prism-markup-Cp04rIa1.js +1 -0
  104. package/dist/ui/dist/assets/prism-markup-templating-KkSMBj6Y.js +1 -0
  105. package/dist/ui/dist/assets/prism-php-Co4fM40K.js +1 -0
  106. package/dist/ui/dist/assets/prism-python-CfiSVtM6.js +1 -0
  107. package/dist/ui/dist/assets/prism-ruby-DFCASkuD.js +1 -0
  108. package/dist/ui/dist/assets/prism-rust-Bu9N8X3N.js +1 -0
  109. package/dist/ui/dist/assets/prism-sql-CYhVDMEE.js +1 -0
  110. package/dist/ui/dist/assets/prism-swift-B0kWc-ne.js +1 -0
  111. package/dist/ui/dist/assets/prism-toml-DKb54GAT.js +1 -0
  112. package/dist/ui/dist/assets/prism-tsx-p6J2Kc_4.js +1 -0
  113. package/dist/ui/dist/assets/prism-typescript-BMqONzxf.js +1 -0
  114. package/dist/ui/dist/assets/prism-yaml-C0JHu8gr.js +1 -0
  115. package/dist/ui/dist/assets/quadrantDiagram-D57K4MC3-8hIWayC2.js +7 -0
  116. package/dist/ui/dist/assets/radar-ABXABTNO-BTdwUOOF.js +1 -0
  117. package/dist/ui/dist/assets/railroad-I3PHUGI6-DcMbDqBL.js +1 -0
  118. package/dist/ui/dist/assets/railroad-abnf-LNEFI6M7-gTzUTWeI.js +1 -0
  119. package/dist/ui/dist/assets/railroad-ebnf-DWYD2UWJ-CNtdUPtk.js +1 -0
  120. package/dist/ui/dist/assets/railroad-peg-JR7BVNR7-DOiExAtE.js +1 -0
  121. package/dist/ui/dist/assets/railroadDiagram-Y72NRBL7-2kE_ygvM.js +1 -0
  122. package/dist/ui/dist/assets/requirementDiagram-EQYSW5RA-CoIAHRq2.js +84 -0
  123. package/dist/ui/dist/assets/rough.esm-Dy-Kn_BL.js +1 -0
  124. package/dist/ui/dist/assets/sankeyDiagram-SY24JHO5-7p8D3wim.js +40 -0
  125. package/dist/ui/dist/assets/sequenceDiagram-BLEHNHQU-C6QFnMvn.js +169 -0
  126. package/dist/ui/dist/assets/sizeCapture-INFHLROL-3uHXrFqA.js +1 -0
  127. package/dist/ui/dist/assets/src-FgijoBVs.js +1 -0
  128. package/dist/ui/dist/assets/stateDiagram-v2-A6IESKWZ-n9eAb_bT.js +291 -0
  129. package/dist/ui/dist/assets/swimlanes-B3ENRKFQ-U18mtUzX.js +1 -0
  130. package/dist/ui/dist/assets/swimlanesDiagram-AYQRV4AK-DX07QVy9.js +8 -0
  131. package/dist/ui/dist/assets/timeline-definition-JXYKGLGB-CD2Z8QGP.js +120 -0
  132. package/dist/ui/dist/assets/treeView-D4JQ5SDB-sqUjH5gB.js +1 -0
  133. package/dist/ui/dist/assets/treemap-SAJKECNS-DvZ38FP7.js +1 -0
  134. package/dist/ui/dist/assets/usecaseDiagram-WPOLBNNX-CqOGidaZ.js +328 -0
  135. package/dist/ui/dist/assets/vennDiagram-Q3Y3IDCW-D1SXSBQ5.js +34 -0
  136. package/dist/ui/dist/assets/wardley-7MLQ67FV-Bpwio-Dx.js +1 -0
  137. package/dist/ui/dist/assets/wardleyDiagram-IVAIDKWA-JSUXHMnk.js +78 -0
  138. package/dist/ui/dist/assets/xychartDiagram-4URNHDU4-B4IFqQTN.js +7 -0
  139. package/dist/ui/dist/index.html +15 -0
  140. package/dist/ui/dist/style.css +121 -0
  141. package/package.json +31 -10
  142. package/skills/follow-me/SKILL.md +15 -89
  143. package/dist/src/anthropic.js +0 -133
  144. package/dist/ui/app.js +0 -2201
  145. package/dist/ui/concerns.js +0 -165
  146. package/dist/ui/highlight.js +0 -221
  147. package/dist/ui/index.html +0 -94
  148. package/dist/ui/live.js +0 -184
  149. package/dist/ui/playback.js +0 -579
  150. package/dist/ui/questions.js +0 -189
  151. package/dist/ui/style.css +0 -1331
  152. package/dist/ui/summary.js +0 -45
@@ -6,7 +6,7 @@ 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
8
  import { isConcernId, isPlanVersion } from "./plan.js";
9
- import { renderReport } from "./prompt.js";
9
+ import { renderReport, reportItems } from "./prompt.js";
10
10
  import { isLockToken } from "./session.js";
11
11
  /** The requested port is taken; host.ts decides whether that is our server or a stranger. */
12
12
  export class PortInUseError extends Error {
@@ -23,19 +23,16 @@ export class RegistrationError extends Error {
23
23
  }
24
24
  /** GET /api/health answers with this, so a CLI can tell our server from another program. */
25
25
  export const HEALTH_APP = "follow-me";
26
- const UI_DIR = new URL("../ui/", import.meta.url);
27
- /** Every module and the stylesheet in ui/ is served flat, so a new module needs no route. */
28
- const STATIC_FILE = /^\/([a-z][a-z-]*\.(?:js|css))$/;
26
+ /** The page Vite builds from ui/ into ui/dist; `npm run build` makes it. */
27
+ const UI_DIR = new URL("../ui/dist/", import.meta.url);
28
+ /** Hashed bundles under /assets/ and the few flat files of ui/public at the root. */
29
+ const STATIC_FILE = /^\/((?:assets\/)?[\w-]+(?:\.[\w-]+)*\.(js|css|woff2|svg))$/;
29
30
  const STATIC_TYPES = new Map([
30
31
  ["js", "text/javascript; charset=utf-8"],
31
32
  ["css", "text/css; charset=utf-8"],
33
+ ["woff2", "font/woff2"],
34
+ ["svg", "image/svg+xml"],
32
35
  ]);
33
- /** Mermaid's ESM entry imports its chunks by relative path, so they are served beside it. */
34
- const MERMAID_ENTRY = new URL(import.meta.resolve("mermaid/dist/mermaid.esm.min.mjs"));
35
- const MERMAID_CHUNK = /^\/vendor\/chunks\/mermaid\.esm\.min\/([\w-]+\.mjs)$/;
36
- /** Prism is a plain script per component, loaded one at a time by the page as languages need. */
37
- const PRISM_CORE = new URL(import.meta.resolve("prismjs/components/prism-core.min.js"));
38
- const PRISM_PATH = /^\/vendor\/prism\/([a-z][a-z-]*)\.js$/;
39
36
  const MAX_BODY_BYTES = 1_000_000;
40
37
  const BASE32 = "abcdefghijklmnopqrstuvwxyz234567";
41
38
  const COMMENT_ID = /^c_[a-z2-7]{6}$/;
@@ -85,7 +82,7 @@ export async function loadDraft(reviewsDir, version) {
85
82
  }
86
83
  catch (error) {
87
84
  if (isRecord(error) && error["code"] === "ENOENT") {
88
- return { comments: [], concerns: [], resolved: [] };
85
+ return { comments: [], concerns: [], resolved: [], excluded: { comments: [], concerns: [] } };
89
86
  }
90
87
  throw error;
91
88
  }
@@ -196,7 +193,7 @@ function indexPage(rows, now) {
196
193
  const body = items.length === 0
197
194
  ? ` <section class="empty">
198
195
  <h1>No sessions yet</h1>
199
- <p>Run <code>follow-me review</code> to review a plan, or <code>follow-me walk</code> to read a branch someone else wrote. Each one opens here.</p>
196
+ <p>Ask your coding agent to review a branch with follow-me. Each review opens here.</p>
200
197
  <p><a href="https://github.com/phanshiyu/follow-me#readme">How follow-me works</a></p>
201
198
  </section>`
202
199
  : ` <h1>Sessions</h1>
@@ -221,44 +218,18 @@ ${body}
221
218
  </html>
222
219
  `;
223
220
  }
224
- /** The file behind a /vendor/ path, or null. Chunk names are checked, so no path escapes. */
225
- function vendorFile(path) {
226
- const part = PRISM_PATH.exec(path)?.[1];
227
- if (part !== undefined) {
228
- return part === "core" ? PRISM_CORE : new URL(`prism-${part}.min.js`, PRISM_CORE);
229
- }
230
- if (path === "/vendor/mermaid.mjs")
231
- return MERMAID_ENTRY;
232
- const chunk = MERMAID_CHUNK.exec(path)?.[1];
233
- return chunk === undefined ? null : new URL(`chunks/mermaid.esm.min/${chunk}`, MERMAID_ENTRY);
234
- }
235
- async function readUi(name) {
236
- try {
237
- return await readFile(fileURLToPath(new URL(name, UI_DIR)));
238
- }
239
- catch (error) {
240
- if (isRecord(error) && error["code"] === "ENOENT")
241
- throw new HttpError(404, "not found");
242
- throw error;
243
- }
244
- }
245
- async function sendVendor(res, file) {
246
- let bytes;
221
+ async function readUi(dir, name) {
247
222
  try {
248
- bytes = await readFile(fileURLToPath(file));
223
+ return await readFile(fileURLToPath(new URL(name, dir)));
249
224
  }
250
225
  catch (error) {
251
226
  if (isRecord(error) && error["code"] === "ENOENT")
252
- throw new HttpError(404, "not found");
227
+ return null;
253
228
  throw error;
254
229
  }
255
- // The bundle only changes with the installed package, and it is several megabytes.
256
- res.writeHead(200, {
257
- "content-type": "text/javascript; charset=utf-8",
258
- "cache-control": "max-age=86400",
259
- });
260
- res.end(bytes);
261
230
  }
231
+ const NOT_BUILT = `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="color-scheme" content="light dark" /><title>follow-me</title></head><body style="font-family: system-ui; max-width: 40rem; margin: 4rem auto; padding: 0 1rem"><h1>The review page is not built</h1><p>This follow-me runs from a checkout without <code>ui/dist</code>. Run <code>npm run build</code> (or <code>npm run ui:build</code>) in the checkout, then reload.</p></body></html>
232
+ `;
262
233
  function viewOf(registration, loaded, draft) {
263
234
  const { state, reviewsDir } = loaded;
264
235
  return {
@@ -267,7 +238,10 @@ function viewOf(registration, loaded, draft) {
267
238
  comments: new Map(state.comments.map((c) => [c.id, c])),
268
239
  decisions: new Map(draft.concerns.map((d) => [d.concern, d])),
269
240
  resolved: new Set(draft.resolved),
241
+ excludedComments: new Set(draft.excluded.comments),
242
+ excludedConcerns: new Set(draft.excluded.concerns),
270
243
  unitIds: new Set(state.units.map((u) => u.id)),
244
+ hunkRows: new Map(state.units.flatMap((u) => u.kind === "hunk" ? [[u.id, u.body.length]] : [])),
271
245
  concernIds: new Set(state.commits.flatMap((c) => (c.concerns ?? []).map((k) => k.id))),
272
246
  draftFile: draftPath(reviewsDir, state.version),
273
247
  reviewFile: join(reviewsDir, `v${state.version}.json`),
@@ -286,6 +260,38 @@ function mintReplyId(taken) {
286
260
  return id;
287
261
  }
288
262
  }
263
+ /**
264
+ * What the agent may see of the log: a pending flag is held back with everything said about it,
265
+ * and reaches the agent as an ordinary "comment" event when the reviewer submits.
266
+ */
267
+ function forAgent(log) {
268
+ const held = new Set();
269
+ const out = [];
270
+ for (const event of log) {
271
+ if (event.type === "comment" || event.type === "comment-updated") {
272
+ const { comment } = event;
273
+ if (comment.pending === true) {
274
+ held.add(comment.id);
275
+ continue;
276
+ }
277
+ if (held.delete(comment.id))
278
+ out.push({ ...event, type: "comment" });
279
+ else
280
+ out.push(event);
281
+ }
282
+ else if (event.type === "comment-deleted") {
283
+ if (!held.delete(event.id))
284
+ out.push(event);
285
+ }
286
+ else if (event.type === "reply") {
287
+ if (!held.has(event.commentId))
288
+ out.push(event);
289
+ }
290
+ else
291
+ out.push(event);
292
+ }
293
+ return out;
294
+ }
289
295
  const DEFAULT_POLL_MS = 25_000;
290
296
  const MAX_POLL_MS = 60_000;
291
297
  const STREAM_PING_MS = 15_000;
@@ -308,17 +314,25 @@ function hostSession(id, registration, loaded, draft, load) {
308
314
  for (const listener of [...listeners])
309
315
  listener();
310
316
  };
311
- const waitForEvents = (since, timeoutMs, signal) => new Promise((resolve) => {
312
- const answer = () => resolve({ seq: events.length, events: events.slice(since) });
313
- if (events.length > since || timeoutMs === 0 || signal.aborted)
317
+ const seen = (agent) => (agent ? forAgent(events) : events);
318
+ const waitForEvents = (since, timeoutMs, signal, agent) => new Promise((resolve) => {
319
+ const news = () => seen(agent).filter((e) => e.seq > since);
320
+ const answer = () => resolve({ seq: events.length, events: news() });
321
+ if (news().length > 0 || timeoutMs === 0 || signal.aborted)
314
322
  return answer();
315
323
  const wake = () => {
324
+ if (news().length === 0 && !signal.aborted)
325
+ return;
316
326
  clearTimeout(timer);
317
327
  waiters.delete(wake);
318
328
  signal.removeEventListener("abort", wake);
319
329
  answer();
320
330
  };
321
- const timer = setTimeout(wake, timeoutMs);
331
+ const timer = setTimeout(() => {
332
+ waiters.delete(wake);
333
+ signal.removeEventListener("abort", wake);
334
+ answer();
335
+ }, timeoutMs);
322
336
  waiters.add(wake);
323
337
  signal.addEventListener("abort", wake);
324
338
  });
@@ -330,6 +344,10 @@ function hostSession(id, registration, loaded, draft, load) {
330
344
  if (swapping)
331
345
  throw new HttpError(409, "the session is switching plan versions; try again");
332
346
  };
347
+ /** The exclusions as stored, or null when nothing is excluded. */
348
+ const excludedOf = () => view.excludedComments.size + view.excludedConcerns.size === 0
349
+ ? null
350
+ : { comments: [...view.excludedComments], concerns: [...view.excludedConcerns] };
333
351
  // Writes are chained so two rapid edits never race on the same file.
334
352
  let writes = Promise.resolve();
335
353
  const saveDraft = () => {
@@ -337,12 +355,14 @@ function hostSession(id, registration, loaded, draft, load) {
337
355
  const file = view.draftFile;
338
356
  const concerns = [...view.decisions.values()];
339
357
  const resolved = [...view.resolved];
358
+ const excluded = excludedOf();
340
359
  const snapshot = JSON.stringify({
341
360
  schema: "follow-me/review-draft@1",
342
361
  planVersion: state.version,
343
362
  comments: [...view.comments.values()],
344
363
  ...(concerns.length === 0 ? {} : { concerns }),
345
364
  ...(resolved.length === 0 ? {} : { resolved }),
365
+ ...(excluded === null ? {} : { excluded }),
346
366
  }, null, 2);
347
367
  const dir = view.loaded.reviewsDir;
348
368
  writes = writes.then(async () => {
@@ -363,20 +383,43 @@ function hostSession(id, registration, loaded, draft, load) {
363
383
  if (anchor.kind === "hunk" && !view.unitIds.has(anchor.unit)) {
364
384
  throw new HttpError(400, "anchor names an unknown unit");
365
385
  }
386
+ if (anchor.kind === "hunk" && anchor.offsets !== undefined) {
387
+ const rows = view.hunkRows.get(anchor.unit);
388
+ if (rows === undefined)
389
+ throw new HttpError(400, "anchor.offsets names a unit without rows");
390
+ if (anchor.offsets.some((o) => o >= rows)) {
391
+ throw new HttpError(400, `anchor.offsets is out of range, the unit has ${rows} rows`);
392
+ }
393
+ }
366
394
  return parsed;
367
395
  };
396
+ /** Clears `pending` on every held flag at once and tells the agent what it missed. */
397
+ const release = () => {
398
+ const held = [...view.comments.values()].filter((c) => c.pending === true);
399
+ if (held.length === 0)
400
+ return 0;
401
+ emit({ type: "submitted" });
402
+ for (const comment of held) {
403
+ const { pending: _held, ...open } = comment;
404
+ view.comments.set(comment.id, open);
405
+ emit({ type: "comment", comment: open });
406
+ }
407
+ return held.length;
408
+ };
368
409
  const review = () => {
369
410
  const concerns = [...view.decisions.values()];
370
411
  const resolved = [...view.resolved];
412
+ const excluded = excludedOf();
371
413
  return {
372
414
  schema: "follow-me/review@1",
373
415
  mode: view.loaded.state.mode,
374
416
  planVersion: view.loaded.state.version,
375
417
  planHash: view.loaded.state.planHash,
376
418
  endedAt: new Date().toISOString(),
377
- comments: [...view.comments.values()],
419
+ comments: [...view.comments.values()].filter((c) => c.pending !== true),
378
420
  ...(concerns.length === 0 ? {} : { concerns }),
379
421
  ...(resolved.length === 0 ? {} : { resolved }),
422
+ ...(excluded === null ? {} : { excluded }),
380
423
  };
381
424
  };
382
425
  const report = (finished) => {
@@ -412,24 +455,26 @@ function hostSession(id, registration, loaded, draft, load) {
412
455
  mode: view.loaded.state.mode,
413
456
  version: view.loaded.state.version,
414
457
  comments: view.comments.size,
458
+ openedAt: new Date(openedAt).toISOString(),
415
459
  }),
416
460
  snapshot: () => ({
417
461
  ...view.loaded.state,
418
462
  comments: [...view.comments.values()],
419
463
  concerns: [...view.decisions.values()],
420
464
  resolved: [...view.resolved],
465
+ excluded: { comments: [...view.excludedComments], concerns: [...view.excludedConcerns] },
421
466
  seq: events.length,
422
467
  previous: view.loaded.previous,
423
468
  }),
424
469
  /** Events after `since`, now and as they happen, until unfollowed or the review ends. */
425
470
  latest: () => events.length,
426
- follow: (since, send, stop) => {
471
+ follow: (since, send, stop, agent) => {
427
472
  if (since > events.length) {
428
473
  throw new HttpError(409, `since ${since} is ahead of this session's log (${events.length})`);
429
474
  }
430
475
  let last = since;
431
476
  const flush = () => {
432
- for (const event of events.slice(last)) {
477
+ for (const event of seen(agent).filter((e) => e.seq > last)) {
433
478
  send(event);
434
479
  last = event.seq;
435
480
  if (event.type === "ended") {
@@ -443,13 +488,21 @@ function hostSession(id, registration, loaded, draft, load) {
443
488
  flush();
444
489
  return () => listeners.delete(flush);
445
490
  },
446
- poll: (since, timeoutMs, signal) => {
491
+ poll: (since, timeoutMs, signal, agent) => {
447
492
  if (since > events.length) {
448
493
  throw new HttpError(409, `since ${since} is ahead of this session's log (${events.length})`);
449
494
  }
450
- return waitForEvents(since, timeoutMs, signal);
495
+ return waitForEvents(since, timeoutMs, signal, agent);
451
496
  },
452
497
  report: () => report(review()),
498
+ reportItems: () => {
499
+ const { state } = view.loaded;
500
+ const units = new Map(state.units.map((u) => [u.id, u]));
501
+ return reportItems(review(), state.commits, units, {
502
+ branch: state.anchor.branch,
503
+ responses: [],
504
+ });
505
+ },
453
506
  add: async (raw) => {
454
507
  open();
455
508
  if (!isRecord(raw))
@@ -479,6 +532,7 @@ function hostSession(id, registration, loaded, draft, load) {
479
532
  if (gone === undefined)
480
533
  throw new HttpError(404, "no such comment");
481
534
  view.comments.delete(target);
535
+ view.excludedComments.delete(gone.id);
482
536
  emit({ type: "comment-deleted", id: gone.id });
483
537
  await saveDraft();
484
538
  },
@@ -506,15 +560,52 @@ function hostSession(id, registration, loaded, draft, load) {
506
560
  if (!isConcernId(target) || !view.concernIds.has(target)) {
507
561
  throw new HttpError(404, "no such concern in this plan");
508
562
  }
509
- const decision = parseNewDecision(target, raw);
510
- if (typeof decision === "string")
511
- throw new HttpError(400, `malformed decision: ${decision}`);
563
+ const parsed = parseNewDecision(target, raw);
564
+ if (typeof parsed === "string")
565
+ throw new HttpError(400, `malformed decision: ${parsed}`);
566
+ const decision = { ...parsed, decidedAt: new Date().toISOString() };
512
567
  view.decisions.set(target, decision);
513
568
  const { concern: concernId, ...rest } = decision;
514
569
  emit({ type: "concern", concernId, ...rest });
515
570
  await saveDraft();
516
571
  return decision;
517
572
  },
573
+ /** Leaves a comment or an agent concern out of the hand-off, or puts it back. */
574
+ exclude: async (raw) => {
575
+ open();
576
+ const body = isRecord(raw) ? raw : {};
577
+ const { comment, concern, excluded } = body;
578
+ if (typeof excluded !== "boolean" || (comment === undefined) === (concern === undefined)) {
579
+ throw new HttpError(400, "expected { comment } or { concern }, and { excluded: boolean }");
580
+ }
581
+ if (comment !== undefined) {
582
+ if (!isCommentId(comment) || !view.comments.has(comment)) {
583
+ throw new HttpError(404, "no such comment");
584
+ }
585
+ if (excluded)
586
+ view.excludedComments.add(comment);
587
+ else
588
+ view.excludedComments.delete(comment);
589
+ }
590
+ else {
591
+ if (!isConcernId(concern) || !view.concernIds.has(concern)) {
592
+ throw new HttpError(404, "no such concern in this plan");
593
+ }
594
+ if (excluded)
595
+ view.excludedConcerns.add(concern);
596
+ else
597
+ view.excludedConcerns.delete(concern);
598
+ }
599
+ await saveDraft();
600
+ return { comments: [...view.excludedComments], concerns: [...view.excludedConcerns] };
601
+ },
602
+ /** Releases every held flag in one go; the reviewer pressed Submit. */
603
+ submit: async () => {
604
+ open();
605
+ const released = release();
606
+ await saveDraft();
607
+ return { released };
608
+ },
518
609
  resolve: async (raw) => {
519
610
  open();
520
611
  const body = isRecord(raw) ? raw : {};
@@ -551,6 +642,7 @@ function hostSession(id, registration, loaded, draft, load) {
551
642
  try {
552
643
  await writes;
553
644
  const next = await load({ ...view.registration, version, hash, lockToken: null }, view.loaded.release);
645
+ release();
554
646
  if (hasWork())
555
647
  await writeReview(review());
556
648
  else
@@ -566,6 +658,7 @@ function hostSession(id, registration, loaded, draft, load) {
566
658
  end: async () => {
567
659
  open();
568
660
  ending = true;
661
+ release();
569
662
  const finished = review();
570
663
  const file = await writeReview(finished);
571
664
  view.loaded.release();
@@ -588,6 +681,7 @@ function sameRegistration(a, b) {
588
681
  * itself once it has had no session for idleMs.
589
682
  */
590
683
  export function startServer(opts) {
684
+ const uiDir = opts.uiDir ?? UI_DIR;
591
685
  const registry = new Map();
592
686
  const pending = new Map();
593
687
  let boundPort = 0;
@@ -671,17 +765,21 @@ export function startServer(opts) {
671
765
  const method = req.method ?? "GET";
672
766
  const url = new URL(req.url ?? "/", "http://localhost");
673
767
  const path = url.pathname;
674
- const asset = method === "GET" ? STATIC_FILE.exec(path)?.[1] : undefined;
675
- if (asset !== undefined) {
676
- const bytes = await readUi(asset);
677
- const type = STATIC_TYPES.get(asset.slice(asset.lastIndexOf(".") + 1));
678
- res.writeHead(200, { "content-type": type ?? "text/plain", "cache-control": "no-store" });
768
+ const asset = method === "GET" ? STATIC_FILE.exec(path) : null;
769
+ if (asset !== null) {
770
+ const [, name = "", ext = ""] = asset;
771
+ const bytes = await readUi(uiDir, name);
772
+ if (bytes === null)
773
+ throw new HttpError(404, "not found");
774
+ // Vite hashes every name under assets/, so those never change under the same URL.
775
+ const cache = name.startsWith("assets/") ? "max-age=31536000, immutable" : "no-store";
776
+ res.writeHead(200, {
777
+ "content-type": STATIC_TYPES.get(ext) ?? "text/plain",
778
+ "cache-control": cache,
779
+ });
679
780
  res.end(bytes);
680
781
  return;
681
782
  }
682
- const vendor = method === "GET" ? vendorFile(path) : null;
683
- if (vendor)
684
- return sendVendor(res, vendor);
685
783
  if (method === "GET" && path === "/") {
686
784
  const rows = [...registry.values()].map((h) => ({
687
785
  summary: h.summary(),
@@ -711,11 +809,12 @@ export function startServer(opts) {
711
809
  res.end();
712
810
  return;
713
811
  }
714
- if (method === "GET" && rest === "/") {
812
+ if (method === "GET" && (rest === "/" || rest === "/report")) {
715
813
  if (!isSessionId(rawId) || !registry.has(rawId)) {
716
814
  return sendHtml(res, 404, '<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="color-scheme" content="light dark" /><title>follow-me</title><link rel="stylesheet" href="/style.css" /></head><body class="index"><header class="topbar"><strong>follow-me</strong></header><main class="index-main"><section class="empty"><h1>This session has ended</h1><p>It finished, or it never existed. <a href="/">All sessions</a></p></section></main></body></html>\n');
717
815
  }
718
- return sendHtml(res, 200, await readFile(fileURLToPath(new URL("index.html", UI_DIR)), "utf8"));
816
+ const page = await readUi(uiDir, "index.html");
817
+ return page === null ? sendHtml(res, 503, NOT_BUILT) : sendHtml(res, 200, page.toString());
719
818
  }
720
819
  const session = hosted(rawId);
721
820
  if (method === "GET" && rest === "/api/state")
@@ -733,7 +832,7 @@ export function startServer(opts) {
733
832
  });
734
833
  res.write(": follow-me\n\n");
735
834
  const ping = setInterval(() => res.write(": ping\n\n"), STREAM_PING_MS);
736
- const unfollow = session.follow(since, (event) => res.write(`id: ${event.seq}\ndata: ${JSON.stringify(event)}\n\n`), () => res.end());
835
+ const unfollow = session.follow(since, (event) => res.write(`id: ${event.seq}\ndata: ${JSON.stringify(event)}\n\n`), () => res.end(), url.searchParams.get("audience") === "agent");
737
836
  res.on("close", () => {
738
837
  clearInterval(ping);
739
838
  unfollow();
@@ -745,7 +844,11 @@ export function startServer(opts) {
745
844
  res.on("close", () => abort.abort());
746
845
  const since = queryInt(url, "since", 0, Number.MAX_SAFE_INTEGER, 0);
747
846
  const timeout = queryInt(url, "timeout", 0, MAX_POLL_MS, DEFAULT_POLL_MS);
748
- return sendJson(res, 200, await session.poll(since, timeout, abort.signal));
847
+ const agent = url.searchParams.get("audience") === "agent";
848
+ return sendJson(res, 200, await session.poll(since, timeout, abort.signal, agent));
849
+ }
850
+ if (method === "GET" && rest === "/api/report" && url.searchParams.get("format") === "json") {
851
+ return sendJson(res, 200, session.reportItems());
749
852
  }
750
853
  if (method === "GET" && rest === "/api/report") {
751
854
  res.writeHead(200, { "content-type": "text/markdown; charset=utf-8" });
@@ -762,6 +865,12 @@ export function startServer(opts) {
762
865
  await session.swap(version, hash);
763
866
  return sendJson(res, 200, session.summary());
764
867
  }
868
+ if (method === "POST" && rest === "/api/submit") {
869
+ return sendJson(res, 200, await session.submit());
870
+ }
871
+ if (method === "POST" && rest === "/api/excluded") {
872
+ return sendJson(res, 200, await session.exclude(await readJson(req)));
873
+ }
765
874
  if (method === "POST" && rest === "/api/resolved") {
766
875
  return sendJson(res, 200, { resolved: await session.resolve(await readJson(req)) });
767
876
  }
@@ -202,8 +202,9 @@ function parseApplied(v) {
202
202
  function makeSession(root, dir, anchor, diff) {
203
203
  const plansDir = join(dir, "plans");
204
204
  const splitReviews = join(dir, "reviews");
205
- const walkReviewsDir = join(splitReviews, "walk");
206
- const reviewsDir = (mode) => mode === "walk" ? walkReviewsDir : splitReviews;
205
+ // Older builds wrote reviewer reviews to reviews/walk/; they stay readable, nothing new goes there.
206
+ const legacyWalkDir = join(splitReviews, "walk");
207
+ const reviewsDir = () => splitReviews;
207
208
  const draftPath = join(dir, "draft.json");
208
209
  const promptPath = join(dir, "prompt.md");
209
210
  const appliedPath = join(dir, "applied.json");
@@ -216,16 +217,16 @@ function makeSession(root, dir, anchor, diff) {
216
217
  }
217
218
  return { version, file, hash: planHash(file) };
218
219
  });
219
- const reviewsIn = (mode) => versions(reviewsDir(mode)).map(({ version, path }) => {
220
+ const reviewsIn = (dir) => versions(dir).map(({ version, path }) => {
220
221
  const review = parseReview(readJson(path));
221
222
  if (typeof review === "string")
222
223
  throw new SessionFileError(`${path}: ${review}`);
223
224
  return { version, review };
224
225
  });
225
- const reviews = () => reviewsIn("split");
226
- const walkReviews = () => [...reviews().filter((r) => r.review.mode === "walk"), ...reviewsIn("walk")].sort((a, b) => a.version - b.version);
227
- const draftVersions = (mode) => {
228
- const dir = reviewsDir(mode);
226
+ const reviews = () => reviewsIn(splitReviews);
227
+ const walkReviews = () => [...reviews().filter((r) => r.review.mode === "walk"), ...reviewsIn(legacyWalkDir)].sort((a, b) => a.version - b.version);
228
+ const draftVersions = () => {
229
+ const dir = reviewsDir();
229
230
  if (!existsSync(dir))
230
231
  return [];
231
232
  return readdirSync(dir).flatMap((name) => {
@@ -234,11 +235,11 @@ function makeSession(root, dir, anchor, diff) {
234
235
  });
235
236
  };
236
237
  const draftReview = (mode, version) => {
237
- const path = join(reviewsDir(mode), `v${version}.draft.json`);
238
+ const path = join(reviewsDir(), `v${version}.draft.json`);
238
239
  const draft = parseDraft(readJson(path));
239
240
  if (typeof draft === "string")
240
241
  throw new SessionFileError(`${path}: ${draft}`);
241
- const { comments, concerns, resolved } = draft;
242
+ const { comments, concerns, resolved, excluded } = draft;
242
243
  return {
243
244
  schema: "follow-me/review@1",
244
245
  mode,
@@ -248,6 +249,7 @@ function makeSession(root, dir, anchor, diff) {
248
249
  comments,
249
250
  ...(concerns.length === 0 ? {} : { concerns }),
250
251
  ...(resolved.length === 0 ? {} : { resolved }),
252
+ ...(excluded.comments.length + excluded.concerns.length === 0 ? {} : { excluded }),
251
253
  };
252
254
  };
253
255
  const priorReview = (mode, before) => {
@@ -255,7 +257,7 @@ function makeSession(root, dir, anchor, diff) {
255
257
  const done = (mode === "walk" ? walkReviews() : reviews().filter((r) => r.review.mode === mode))
256
258
  .filter((r) => below(r.version))
257
259
  .at(-1);
258
- const unfinished = draftVersions(mode)
260
+ const unfinished = draftVersions()
259
261
  .filter((v) => below(v) && v > (done?.version ?? 0))
260
262
  .sort((a, b) => a - b)
261
263
  .at(-1);
@@ -283,7 +285,7 @@ function makeSession(root, dir, anchor, diff) {
283
285
  writeDraft: (draft) => writeJson(draftPath, draft),
284
286
  writePrompt: (text) => writeText(promptPath, text),
285
287
  writeReview: (review) => {
286
- const path = join(reviewsDir(review.mode), `v${review.planVersion}.json`);
288
+ const path = join(reviewsDir(), `v${review.planVersion}.json`);
287
289
  if (existsSync(path))
288
290
  throw new SessionFileError(`${path} is already written`);
289
291
  writeJson(path, review);
@@ -0,0 +1 @@
1
+ import{m as e,t}from"./mermaid-parser.core-Dcxlzc-S.js";import{n}from"./chunk-Y2CYZVJY-DsF7k-Jl.js";import{m as r}from"./src-FgijoBVs.js";import"./chunk-DSNLSHJZ-DiC78RqE.js";import{n as i,r as a,t as o}from"./chunk-2E7WHVMD-C6fAf4Vj.js";import{t as s}from"./chunk-JWPE2WC7-DVXcaiue.js";import"./mermaid.core-TeOHQJ00.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};
@@ -0,0 +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-tWS_lXDS.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-NGNAAXSQ-C2Wy9Xmz.js";export{M as createArchitectureServices}from"./mermaid-parser.core-Dcxlzc-S.js";