@diffohq/diffo 0.1.0 → 0.2.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 (102) hide show
  1. package/dist/cli.mjs +162 -32
  2. package/dist/client/assets/{abnfDiagram-VCTEODGH-DNZRM2Rn.js → abnfDiagram-VCTEODGH-BK74b0Uj.js} +1 -1
  3. package/dist/client/assets/{arc-s7daVIwl.js → arc-BZJjnFHJ.js} +1 -1
  4. package/dist/client/assets/architecture-7GRP2DOG-BMofp_0u.js +1 -0
  5. package/dist/client/assets/{architectureDiagram-5GKGNRK7-DsxTOcye.js → architectureDiagram-5GKGNRK7-BBjd1L77.js} +1 -1
  6. package/dist/client/assets/{blockDiagram-NRAW4CY4-DqGD_dcu.js → blockDiagram-I7D4REHJ-BWSjZ0YR.js} +2 -2
  7. package/dist/client/assets/{c4Diagram-UCG6FXSJ-CMuJod-h.js → c4Diagram-7LVT6UL2-z-xUGKCJ.js} +1 -1
  8. package/dist/client/assets/channel-CmX7fcUa.js +1 -0
  9. package/dist/client/assets/{chunk-TEH6E4GO-M22Cr-Sd.js → chunk-4HAMMTFA-CAN-T2o-.js} +1 -1
  10. package/dist/client/assets/{chunk-75Z2AOVW-HRLiZN7c.js → chunk-75Z2AOVW-B_bGdfJP.js} +1 -1
  11. package/dist/client/assets/{chunk-DU6HZSFF-D6gjp2l1.js → chunk-DU6HZSFF-wyqPpFTy.js} +1 -1
  12. package/dist/client/assets/{chunk-F27PBJKO-DRqkoKNg.js → chunk-F27PBJKO-C6X74WsU.js} +1 -1
  13. package/dist/client/assets/{chunk-GMAD6QVW-DG-OmEbV.js → chunk-GMAD6QVW-DBossm8a.js} +1 -1
  14. package/dist/client/assets/{chunk-OBVCFTLP-DOl2OrIN.js → chunk-GVQU2GXP-3NWb-Kjs.js} +1 -1
  15. package/dist/client/assets/{chunk-G27WJ6UU-QUFgr4za.js → chunk-IMKFNOWR-BCvjP66C.js} +1 -1
  16. package/dist/client/assets/{chunk-JQ64N6SF-CPdpoyCu.js → chunk-L3NEJ4N5-DppEbElX.js} +1 -1
  17. package/dist/client/assets/chunk-OSK3NFVY-BJ8i4cd-.js +10 -0
  18. package/dist/client/assets/{chunk-P2QGCYS3-CBWu7ViW.js → chunk-P2QGCYS3-fvNVbdI1.js} +1 -1
  19. package/dist/client/assets/{chunk-POPQ4Y6H-Dw_D6UWF.js → chunk-POPQ4Y6H-DKwUAqbe.js} +1 -1
  20. package/dist/client/assets/{chunk-PWAF6VOD-BHpo38Be.js → chunk-PWAF6VOD-BRN4CzQ0.js} +1 -1
  21. package/dist/client/assets/{chunk-RHFEMEQ7-BabrfzF-.js → chunk-SHT3W25Y-BfzjiqOe.js} +2 -2
  22. package/dist/client/assets/{chunk-SVP7TREG-CSTzyRmo.js → chunk-SVP7TREG-CsO-w42m.js} +1 -1
  23. package/dist/client/assets/{chunk-LCL6LL3I-DoHJ4n3V.js → chunk-TICWLB2K-DPzmokY4.js} +1 -1
  24. package/dist/client/assets/{chunk-XXDRQBXY-BHwLEQfa.js → chunk-XXDRQBXY-BikhrMBF.js} +1 -1
  25. package/dist/client/assets/classDiagram-ZZMXUADV-BSMgXeCj.js +1 -0
  26. package/dist/client/assets/classDiagram-v2-VYDZK3BY-BSMgXeCj.js +1 -0
  27. package/dist/client/assets/{core-B9wYK4yO.js → core-BJUznnCg.js} +1 -1
  28. package/dist/client/assets/{cose-bilkent-JH36ORCC-BL0lTP0k.js → cose-bilkent-JH36ORCC-CBu7-BXr.js} +1 -1
  29. package/dist/client/assets/{cynefin-OW5HDTMX-1zOzoEu-.js → cynefin-OW5HDTMX-iJEmw--0.js} +1 -1
  30. package/dist/client/assets/{cynefinDiagram-5FMLGOSQ-DST_IHb1.js → cynefinDiagram-5FMLGOSQ-B4JnaLZu.js} +1 -1
  31. package/dist/client/assets/{cytoscape.esm-C2x7inlt.js → cytoscape.esm-CGd-uY3x.js} +1 -1
  32. package/dist/client/assets/{dagre-3AP2YEHR-CoEaLaFu.js → dagre-GXQ25YYZ-bmDafESr.js} +1 -1
  33. package/dist/client/assets/{diagram-S7CK7UJ4-BwGXFSb-.js → diagram-S7CK7UJ4-C1jP2NX5.js} +1 -1
  34. package/dist/client/assets/{diagram-UQ7AKVKN-wJMwiRQW.js → diagram-UQ7AKVKN-DssGkq6D.js} +1 -1
  35. package/dist/client/assets/{diagram-VSXAHHWV-DRQH2hZ-.js → diagram-VSXAHHWV-BMNFierD.js} +1 -1
  36. package/dist/client/assets/{diagram-VX7I27RA-BWGYUOwn.js → diagram-VX7I27RA-BmHtC5T2.js} +1 -1
  37. package/dist/client/assets/{diagram-Z3DM3KII-C2ETOHnp.js → diagram-Z3DM3KII-CzCV6KsS.js} +1 -1
  38. package/dist/client/assets/dist-BT7WB1vp.js +142 -0
  39. package/dist/client/assets/{dist-C42IZvNn.js → dist-bii_eoxi.js} +1 -1
  40. package/dist/client/assets/{ebnfDiagram-PWID7BFC-CkcgAzr3.js → ebnfDiagram-PWID7BFC-DuVnjPWa.js} +1 -1
  41. package/dist/client/assets/{engine-oniguruma-D9HVl69n.js → engine-oniguruma-BlIEwGaN.js} +1 -1
  42. package/dist/client/assets/{erDiagram-SSCWMZ5O-Do6h2U1E.js → erDiagram-RLTQ6QDP-CtwYHyZu.js} +1 -1
  43. package/dist/client/assets/eventmodeling-NTZA5JFV-DBQsn9JF.js +1 -0
  44. package/dist/client/assets/flowDiagram-HODETNUW-C_zt1wER.js +1 -0
  45. package/dist/client/assets/{ganttDiagram-EL5Y4UJY-cC8gUuqf.js → ganttDiagram-EL5Y4UJY-CR8Sa06t.js} +1 -1
  46. package/dist/client/assets/{gitGraph-4MIJSDKK-Con-0IRc.js → gitGraph-4MIJSDKK-DoWc_DJ0.js} +1 -1
  47. package/dist/client/assets/{gitGraphDiagram-WWUBYQGX-Oj6POMRs.js → gitGraphDiagram-WWUBYQGX-C36JIyKh.js} +1 -1
  48. package/dist/client/assets/index-BUq9UmeC.js +91 -0
  49. package/dist/client/assets/{info-A6RAGUB7-Cl45HDP4.js → info-A6RAGUB7-DULcTga7.js} +1 -1
  50. package/dist/client/assets/{infoDiagram-RXCK75RN-DIVe-qvk.js → infoDiagram-27XIBGKW-eQbUxJ9T.js} +1 -1
  51. package/dist/client/assets/{ishikawaDiagram-5VMMS53U-BB3HsPau.js → ishikawaDiagram-5VMMS53U-CsPYEqSH.js} +1 -1
  52. package/dist/client/assets/{journeyDiagram-EYS64GPL-Cj8ezyBN.js → journeyDiagram-3NMN7TZE-DbHeKN0X.js} +2 -2
  53. package/dist/client/assets/{kanban-definition-3QL26DDD-D3dbth5Z.js → kanban-definition-UXKFOSKX-CbYeTY2r.js} +1 -1
  54. package/dist/client/assets/{line-tg3RVOrp.js → line-BeOqlVB1.js} +1 -1
  55. package/dist/client/assets/{linear-CzAWMsuG.js → linear-D5d3UlAm.js} +1 -1
  56. package/dist/client/assets/{mermaid-parser.core-DKC3l_TU.js → mermaid-parser.core-BLSiQUXp.js} +3 -3
  57. package/dist/client/assets/{mermaid.core-B0ktnMCY.js → mermaid.core-BoXUi08V.js} +5 -5
  58. package/dist/client/assets/{mindmap-definition-FBJOCRG2-CSUjR6w9.js → mindmap-definition-YA3MSWOX-o49TTsg4.js} +1 -1
  59. package/dist/client/assets/{packet-AYTQ26CC-NTsh29Fp.js → packet-AYTQ26CC-Blxpg-nI.js} +1 -1
  60. package/dist/client/assets/{pegDiagram-XKGWAZYB-BhLsv72R.js → pegDiagram-XKGWAZYB-CZxoewnI.js} +1 -1
  61. package/dist/client/assets/{pie-WAS4IAKB-DENes_Cx.js → pie-WAS4IAKB-D-fqeiW7.js} +1 -1
  62. package/dist/client/assets/{pieDiagram-E7YTZNPT-Ci9YzLDy.js → pieDiagram-E7YTZNPT-dAiwtigh.js} +1 -1
  63. package/dist/client/assets/{quadrantDiagram-AXDQQJYC-D7D5FwMe.js → quadrantDiagram-AXDQQJYC-mefTaSBl.js} +1 -1
  64. package/dist/client/assets/{radar-RG4KPBEZ-C3OVgpW0.js → radar-RG4KPBEZ-VaODqy4w.js} +1 -1
  65. package/dist/client/assets/{railroad-74A4TZTK-DHWDkPlt.js → railroad-74A4TZTK-BwoigfOV.js} +1 -1
  66. package/dist/client/assets/railroad-abnf-HS5TGJTU-BYYKbIBi.js +1 -0
  67. package/dist/client/assets/railroad-ebnf-LZEXJU2U-BFA23uel.js +1 -0
  68. package/dist/client/assets/railroad-peg-WCYAUIDC-Cq02Hm_P.js +1 -0
  69. package/dist/client/assets/{railroadDiagram-O6MQD6OU-ByO5lwcN.js → railroadDiagram-O6MQD6OU-DdhfhlaU.js} +1 -1
  70. package/dist/client/assets/{requirementDiagram-EFPCY7ZU-UAUf-JbW.js → requirementDiagram-BXWQKSXE-BVrRfVMy.js} +1 -1
  71. package/dist/client/assets/{sankeyDiagram-P5KCCOFB-D3dgv1AD.js → sankeyDiagram-P5KCCOFB-BalSSH4U.js} +1 -1
  72. package/dist/client/assets/{sequenceDiagram-WJ2MYXX4-DbGvVPa7.js → sequenceDiagram-WJ2MYXX4-B2KsE_Z_.js} +1 -1
  73. package/dist/client/assets/{src-DQRzrNpT.js → src-CfhDi0bm.js} +1 -1
  74. package/dist/client/assets/{stateDiagram-HBIQ2CUA-CJznFEjd.js → stateDiagram-D77RDMKH-CoH6bKG5.js} +1 -1
  75. package/dist/client/assets/stateDiagram-v2-MP3YSRHH-rXsredo-.js +1 -0
  76. package/dist/client/assets/{swimlanes-XN3QIQJK-Bp4PtrPm.js → swimlanes-42K2YHIH-3IkzoE1X.js} +1 -1
  77. package/dist/client/assets/swimlanesDiagram-VR7AAH4N-BeXrrDxh.js +8 -0
  78. package/dist/client/assets/{timeline-definition-24CTP7MA-jqaUS70q.js → timeline-definition-24CTP7MA-B2M9pP5h.js} +1 -1
  79. package/dist/client/assets/{treeView-Q6P3EWNA-4uMQTFyy.js → treeView-Q6P3EWNA-Brp55dHV.js} +1 -1
  80. package/dist/client/assets/{treemap-WGGIJYW6-BEvgQaxX.js → treemap-WGGIJYW6-zRe9RDJV.js} +1 -1
  81. package/dist/client/assets/{vennDiagram-4TSXK5OY-BzSNLT-7.js → vennDiagram-4TSXK5OY-PNF-rFET.js} +1 -1
  82. package/dist/client/assets/{wardley-WFR3VGLG-0Vpm3lxj.js → wardley-WFR3VGLG-BSDDZIR6.js} +1 -1
  83. package/dist/client/assets/{wardleyDiagram-VM6X3IG4-D9KaADBZ.js → wardleyDiagram-VM6X3IG4-CFaIuWQk.js} +1 -1
  84. package/dist/client/assets/{xychartDiagram-S5SC5T6Z-CnrJ3a0c.js → xychartDiagram-S5SC5T6Z-et5vk4QS.js} +1 -1
  85. package/dist/client/index.html +1 -1
  86. package/package.json +2 -1
  87. package/plugin.json +1 -1
  88. package/skills/diffo/SKILL.md +52 -124
  89. package/dist/client/assets/architecture-7GRP2DOG-DzM3CzOq.js +0 -1
  90. package/dist/client/assets/channel-Cftoq4wT.js +0 -1
  91. package/dist/client/assets/chunk-R7TYR2AO-DgC6z76h.js +0 -10
  92. package/dist/client/assets/classDiagram-DTDB5LWJ-uMtyQjAg.js +0 -1
  93. package/dist/client/assets/classDiagram-v2-JRS7N3AN-uMtyQjAg.js +0 -1
  94. package/dist/client/assets/eventmodeling-NTZA5JFV-LK0PWCmm.js +0 -1
  95. package/dist/client/assets/flowDiagram-A5DVABFB-W_CNjdk0.js +0 -1
  96. package/dist/client/assets/index-BPuQYkRJ.js +0 -90
  97. package/dist/client/assets/railroad-abnf-HS5TGJTU-XG9NyE2s.js +0 -1
  98. package/dist/client/assets/railroad-ebnf-LZEXJU2U-BePIURFQ.js +0 -1
  99. package/dist/client/assets/railroad-peg-WCYAUIDC-THfR3kY_.js +0 -1
  100. package/dist/client/assets/stateDiagram-v2-4QOOHH4V-o6PmCsJ0.js +0 -1
  101. package/dist/client/assets/swimlanesDiagram-VK2B7HYN-W1d9PL3X.js +0 -8
  102. /package/dist/client/assets/{sizeCapture-X5ZJPWSS-B0uUizjq.js → sizeCapture-INFHLROL-B0uUizjq.js} +0 -0
package/dist/cli.mjs CHANGED
@@ -225,6 +225,7 @@ function nextStepFor(kind, actionable) {
225
225
  }
226
226
  const ACK_NEXT_STEP = {
227
227
  reply: `When every thread is handled, run \`${CLI_COMMANDS.poll}\` again to keep listening (${POLL_STANCE}).`,
228
+ replyMore: "Interim reply posted — the reviewer still sees you working on this thread. Post the follow-up as a plain reply (no --more) BEFORE your next poll: re-polling closes the batch and counts the promise as never kept.",
228
229
  comment: `It's in the review as your comment, labeled as yours — the reviewer replies to take it up, or resolves it. Continue with the review threads, then run \`${CLI_COMMANDS.poll}\`.`,
229
230
  end: "Detached. Do not reopen or re-poll this review unless the user asks — deliver anything remaining directly in the conversation."
230
231
  };
@@ -271,6 +272,11 @@ const INTENT_CONTRACT = {
271
272
  fix: "- `issue` threads want a code change. Address each one, or push back in the thread with your reasoning."
272
273
  };
273
274
  const UNLABELED_CONTRACT = "- Unlabeled threads: judge from the text — a question wants an answer, not an edit.";
275
+ /** The agent spoke last: the thread is answered until the reviewer says more.
276
+ * A reply that promised a follow-up (`--more`) is not an answer yet. */
277
+ function answeredByAgent(thread) {
278
+ return thread.messages.at(-1)?.author === "agent" && thread.awaitingFollowUp !== true;
279
+ }
274
280
  /** The closing note is the reviewer summing up, so it earns an answer even when it
275
281
  * asks nothing — a note with no reply is the review's one dead end. */
276
282
  const CLOSING_CONTRACT = "- The closing note speaks for the whole review: read it first, and reply to it — briefly if it only sums up, in full if it asks something.";
@@ -281,9 +287,34 @@ function intentContract(threads) {
281
287
  if (threads.some((t) => t.closingNote)) lines.push(CLOSING_CONTRACT);
282
288
  return lines;
283
289
  }
284
- function replyProtocol(threads) {
290
+ /**
291
+ * The short form, for a session that already received the full protocol this
292
+ * attachment (tracked per session pid by the DeliveryQueue). The full text is
293
+ * ~580 tokens and re-shipping it with every delivery was the loop's largest
294
+ * recurring cost; the compact form keeps only the per-batch contract — intent
295
+ * rules, the reply command, the re-poll — plus the pointer that reprints the rest.
296
+ */
297
+ function compactProtocol(threads) {
298
+ return `## How to respond
299
+
300
+ Same protocol as your earlier deliveries (\`${CLI} help agent\` reprints it in full):
301
+
302
+ 1. Act on each thread.
303
+ ${intentContract(threads).map((line) => ` ${line}`).join("\n")}
304
+ 2. Reply per thread as soon as it is handled — what changed and where (\`file:line\`), verify it the cheapest honest way first:
305
+
306
+ ${CLI_COMMANDS.reply}
307
+
308
+ 3. When every thread is handled, run \`${CLI_COMMANDS.poll}\` again to keep listening.
309
+
310
+ Change only what these threads ask about — the reviewer is mid-read. Re-read
311
+ the current file before editing; the code may have moved. Resolving a thread
312
+ is the reviewer's call, never yours.`;
313
+ }
314
+ function replyProtocol(threads, mode = "full") {
315
+ if (mode === "compact") return compactProtocol(threads);
285
316
  const batchNote = threads.length > 1 ? "\nRead every thread before you start editing — threads can touch the same\ncode, and an edit for one can move what another is anchored to.\n" : "";
286
- const answeredNote = threads.some((t) => t.messages.at(-1)?.author === "agent") ? [" - Some threads end with your own earlier reply — if nothing new was asked since, don't reply to them again."] : [];
317
+ const answeredNote = threads.some((t) => answeredByAgent(t)) ? [" - Some threads end with your own earlier reply — if nothing new was asked since, don't reply to them again."] : [];
287
318
  return `## How to respond
288
319
 
289
320
  Diffo is running locally; talk to it through its CLI.
@@ -303,6 +334,10 @@ ${[...intentContract(threads).map((line) => ` ${line}`), ...answeredNote].join
303
334
  is fixed, verify it the cheapest honest way (run the relevant test, re-read
304
335
  the change) and mention what you checked.
305
336
  Reply as soon as a thread is handled; don't save replies for the end.
337
+ A reply that only promises a follow-up ("I'll investigate and report
338
+ back") is not an answer — post it with \`--more\` so the reviewer keeps
339
+ seeing you at work on that thread, then post the real answer as a plain
340
+ reply (no \`--more\`) before your next poll.
306
341
  Replies and comment threads render GitHub-flavored markdown, and a
307
342
  \`\`\`mermaid fence renders as a diagram — use one when a flow, sequence,
308
343
  or state picture explains the change better than prose. Keep it small
@@ -418,6 +453,7 @@ function snapshotBlock(thread) {
418
453
  }
419
454
  const lines = codeContext.split("\n");
420
455
  const where = thread.anchor.kind === "changeset" ? "the file" : `\`${thread.anchor.path}\``;
456
+ if (thread.deliveredThrough) return [`(diff snapshot delivered to you earlier — read the current ${where} instead)`];
421
457
  if (!anchored) return [
422
458
  "The commented change:",
423
459
  "```diff",
@@ -457,7 +493,7 @@ function buildThreadPrompt(thread, ctx) {
457
493
  threadBlock(thread, 0),
458
494
  "",
459
495
  ...siblings ? [siblings, ""] : [],
460
- replyProtocol([thread]),
496
+ replyProtocol([thread], ctx.protocol),
461
497
  ""
462
498
  ].join("\n");
463
499
  }
@@ -469,7 +505,7 @@ function buildCoalescedPrompt(threads, ctx) {
469
505
  ...ctx.changeset ? [specLine(ctx.changeset), ""] : [],
470
506
  ...threads.map((t, i) => `${threadBlock(t, i)}\n`),
471
507
  ...siblings ? [siblings, ""] : [],
472
- replyProtocol(threads),
508
+ replyProtocol(threads, ctx.protocol),
473
509
  ""
474
510
  ].join("\n");
475
511
  }
@@ -494,7 +530,9 @@ function buildFinishPrompt(threads, ctx, coverage) {
494
530
  const repo = ctx.repo;
495
531
  const sent = threads.filter((t) => t.state === "sent");
496
532
  const closing = sent.find((t) => t.closingNote);
497
- const actionable = closing ? [closing, ...sent.filter((t) => t !== closing)] : sent;
533
+ const ordered = closing ? [closing, ...sent.filter((t) => t !== closing)] : sent;
534
+ const answered = ordered.filter((t) => t !== closing && answeredByAgent(t));
535
+ const actionable = ordered.filter((t) => !answered.includes(t));
498
536
  const changed = coverage.changedFiles ?? [];
499
537
  const commented = coverage.commentedUnread ?? [];
500
538
  const filtered = coverage.filteredOut ?? [];
@@ -523,11 +561,12 @@ function buildFinishPrompt(threads, ctx, coverage) {
523
561
  "",
524
562
  ...owedLines
525
563
  ];
564
+ if (answered.length > 0) parts.push(`${answered.length} thread${answered.length === 1 ? "" : "s"} you already answered, with nothing new since — no reply owed, not repeated here:`, ...answered.map((t) => `- ${t.id} — ${describeAnchor(t.anchor)}`), "");
526
565
  if (coverage.skippedFiles.length > 0) parts.push("The reviewer left the not-marked-read files unread. If any of them hide", "a risky or subtle change you made, say so in a comment thread", `(\`${CLI_COMMANDS.comment}\`) — the reviewer replies to take it up, or resolves it.`, "");
527
566
  if (actionable.length === 0) {
528
567
  const fullRead = coverage.totalHunks > 0 && coverage.viewedHunks >= coverage.totalHunks && changed.length === 0 && commented.length === 0 && filtered.length === 0 && coverage.skippedFiles.length === 0;
529
568
  parts.push(fullRead && coverage.note === void 0 ? "They read everything and left nothing to address — take it as a green light to proceed." : "No open review threads — nothing to act on.", `Run \`${CLI_COMMANDS.poll}\` again to keep listening (${POLL_STANCE});`, "the reviewer may follow up.", "");
530
- } else parts.push(`${actionable.length} review thread${actionable.length === 1 ? "" : "s"} to act on:`, "", ...actionable.map((t, i) => `${threadBlock(t, i)}\n`), replyProtocol(actionable), "");
569
+ } else parts.push(`${actionable.length} review thread${actionable.length === 1 ? "" : "s"} to act on:`, "", ...actionable.map((t, i) => `${threadBlock(t, i)}\n`), replyProtocol(actionable, ctx.protocol), "");
531
570
  return parts.join("\n");
532
571
  }
533
572
  //#endregion
@@ -590,11 +629,15 @@ The loop:
590
629
  away, leaving a background server watching the working tree. Never open a
591
630
  browser at the reviewer — end your message with the printed URL instead,
592
631
  and keep ending every message with it while you stay attached.
593
- 2. Guide (only when the changeset needs it — ${GUIDE.when}): before sharing
594
- the URL, post ONE comment on the whole changeset (\`diffo comment -m "…"\`,
595
- no file): ${GUIDE.what}.
596
- ${GUIDE.stance}. If the changeset later shifts under the guide,
597
- ${GUIDE.update}.
632
+ Attached without ever seeing the URL (a takeover, a fresh session)?
633
+ \`diffo status\` prints it.
634
+ 2. Guide — post one only when the changeset needs orientation:
635
+ ${GUIDE.when}.
636
+ Before sharing the URL, post ONE comment on the whole changeset
637
+ (\`diffo comment -m "…"\`, no file), containing:
638
+ ${GUIDE.what}.
639
+ ${GUIDE.stance}.
640
+ If the changeset later shifts under the guide, ${GUIDE.update}.
598
641
  3. Listen: run \`diffo poll\` — it blocks until the reviewer acts, then prints
599
642
  one JSON payload naming the threads to act on. Run it attended:
600
643
  ${POLL_STANCE}.
@@ -604,7 +647,10 @@ The loop:
604
647
  answer in the reply and no edit. Your edits reach the reviewer live.
605
648
  5. Reply: \`diffo reply <threadId> --message "<text>"\` (pipe long replies on
606
649
  stdin) — concise, addressed to the reviewer. Markdown renders; a
607
- \`\`\`mermaid fence draws a diagram.
650
+ \`\`\`mermaid fence draws a diagram. A reply that only promises a
651
+ follow-up ("I'll investigate and report back") goes out with \`--more\` —
652
+ the reviewer keeps seeing you at work — and the real answer follows as a
653
+ plain reply before the next poll.
608
654
  6. Comment (sparingly): \`diffo comment [<file>] [--line <n>] -m "<text>"\`
609
655
  starts a thread in your voice — a concern, or context that helps the read.
610
656
  7. Poll again only when the whole batch is handled — a new poll tells the
@@ -615,7 +661,9 @@ Rules:
615
661
  - Change only what the threads ask about — the reviewer is mid-read, and an
616
662
  unrelated edit moves the diff under them.
617
663
  - One attached agent at a time: the newest poll carries the review; don't
618
- re-poll to take it back from another session — tell the user instead.
664
+ re-poll to take it back from another session — tell the user instead. When
665
+ a poll says it took the review over, or returns status "superseded", say so
666
+ to the user: their feedback moved with it.
619
667
  - Resolving a thread is the reviewer's call, never yours.`,
620
668
  poll: `diffo poll — wait for the reviewer's feedback
621
669
 
@@ -640,6 +688,10 @@ Usage: diffo reply <threadId> --message "<text>"
640
688
 
641
689
  Thread ids arrive in poll payloads. Each run posts one message, so don't
642
690
  re-run a reply that succeeded.
691
+ --more marks the reply as interim: you are still working, and a follow-up
692
+ reply on this thread is promised. The reviewer keeps seeing the thread as
693
+ "with the agent" until your next plain reply; going quiet instead marks it
694
+ unanswered.
643
695
  Messages render GitHub-flavored markdown; a \`\`\`mermaid fence renders as a
644
696
  diagram in the review.
645
697
 
@@ -878,6 +930,7 @@ function parseVerb(verb, rest) {
878
930
  short: "m"
879
931
  },
880
932
  line: { type: "string" },
933
+ more: { type: "boolean" },
881
934
  json: { type: "boolean" },
882
935
  help: {
883
936
  type: "boolean",
@@ -898,6 +951,10 @@ function parseVerb(verb, rest) {
898
951
  kind: "error",
899
952
  message: `'${verb}' takes no --json`
900
953
  };
954
+ if (values.more && verb !== "reply") return {
955
+ kind: "error",
956
+ message: `'${verb}' takes no --more`
957
+ };
901
958
  if (verb === "poll" || verb === "end" || verb === "setup" || verb === "status" || verb === "stop") {
902
959
  if (positionals.length > 0) return {
903
960
  kind: "error",
@@ -930,7 +987,8 @@ function parseVerb(verb, rest) {
930
987
  return {
931
988
  kind: "reply",
932
989
  threadId,
933
- message: values.message ?? null
990
+ message: values.message ?? null,
991
+ more: values.more === true
934
992
  };
935
993
  }
936
994
  const file = positionals[0] ?? null;
@@ -1531,7 +1589,8 @@ var DiffoDb = class {
1531
1589
  const known = /* @__PURE__ */ new Set([
1532
1590
  "reviews",
1533
1591
  "servers",
1534
- "repo_ports"
1592
+ "repo_ports",
1593
+ "ui_settings"
1535
1594
  ]);
1536
1595
  const tables = this.db.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%'").all();
1537
1596
  for (const { name } of tables) if (!known.has(name)) this.db.exec(`DROP TABLE IF EXISTS "${name.replaceAll("\"", "\"\"")}"`);
@@ -1556,6 +1615,10 @@ var DiffoDb = class {
1556
1615
  repo_path TEXT PRIMARY KEY,
1557
1616
  port INTEGER NOT NULL
1558
1617
  );
1618
+ CREATE TABLE IF NOT EXISTS ui_settings (
1619
+ key TEXT PRIMARY KEY,
1620
+ value TEXT NOT NULL
1621
+ );
1559
1622
  PRAGMA user_version = ${SCHEMA_VERSION};
1560
1623
  `);
1561
1624
  this.pruneVanishedRepos();
@@ -1631,6 +1694,16 @@ var DiffoDb = class {
1631
1694
  this.db.prepare(`INSERT INTO repo_ports (repo_path, port) VALUES (?, ?)
1632
1695
  ON CONFLICT(repo_path) DO UPDATE SET port = excluded.port`).run(repoPath, port);
1633
1696
  }
1697
+ /** Reviewer preferences that must outlive one server's origin — every repo is
1698
+ * served from its own port, so localStorage alone can't hold a choice like
1699
+ * the theme. Keyed blobs, no scope: these are per-human, not per-repo. */
1700
+ getUiSetting(key) {
1701
+ return this.db.prepare("SELECT value FROM ui_settings WHERE key = ?").get(key)?.value ?? null;
1702
+ }
1703
+ setUiSetting(key, value) {
1704
+ this.db.prepare(`INSERT INTO ui_settings (key, value) VALUES (?, ?)
1705
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value`).run(key, value);
1706
+ }
1634
1707
  getServer(repoPath) {
1635
1708
  const row = this.db.prepare("SELECT port, pid, started_at FROM servers WHERE repo_path = ?").get(repoPath);
1636
1709
  return row ? {
@@ -1710,6 +1783,11 @@ var DeliveryQueue = class DeliveryQueue {
1710
1783
  reason: "no-agent"
1711
1784
  };
1712
1785
  owner = null;
1786
+ /** The session pid that last received the full reply protocol. Later
1787
+ * deliveries to the same session carry the compact form — the full text is
1788
+ * the loop's largest recurring token cost. A takeover (new pid) or an
1789
+ * anonymous session (null pid) always gets the full form again. */
1790
+ protocolPid = null;
1713
1791
  batch = null;
1714
1792
  batchWatch = null;
1715
1793
  waiterWatch = null;
@@ -1770,6 +1848,12 @@ var DeliveryQueue = class DeliveryQueue {
1770
1848
  ownerPid() {
1771
1849
  return this.ownerConnected() ? this.owner : null;
1772
1850
  }
1851
+ needsFullProtocol() {
1852
+ return this.owner === null || this.owner !== this.protocolPid;
1853
+ }
1854
+ markProtocolSent() {
1855
+ this.protocolPid = this.owner;
1856
+ }
1773
1857
  /**
1774
1858
  * A poll attaches for its session; the newest poll always carries the review.
1775
1859
  * Returns the pid of a still-live session it took the review from, so both
@@ -2045,10 +2129,19 @@ var DeliveryQueue = class DeliveryQueue {
2045
2129
  this.notify();
2046
2130
  }
2047
2131
  /** The agent replied on a thread. Returns how long since that thread's feedback
2048
- * was delivered, or null when the reply wasn't an answer to a delivery. */
2049
- agentReplied(threadId) {
2132
+ * was delivered, or null when the reply wasn't an answer to a delivery.
2133
+ * `interim` (a `--more` reply) keeps the thread on the clock: a follow-up is
2134
+ * still owed, so the spinner stays and a batch that closes without the
2135
+ * follow-up counts the thread unanswered. */
2136
+ agentReplied(threadId, interim = false) {
2050
2137
  const at = this.deliveredAt.get(threadId);
2051
- this.deliveredAt.delete(threadId);
2138
+ if (!interim) this.deliveredAt.delete(threadId);
2139
+ else if (at === void 0 && this.batch) {
2140
+ this.deliveredAt.set(threadId, Date.now());
2141
+ if (!this.batch.threadIds.includes(threadId)) this.batch.threadIds.push(threadId);
2142
+ this.awaitingReply = true;
2143
+ this.clearGrace();
2144
+ }
2052
2145
  if (this.batch) this.batch.sawReply = true;
2053
2146
  if (this.deliveredAt.size > 0) {
2054
2147
  if (this.awaitingReply) {
@@ -2328,7 +2421,7 @@ var ReviewStore = class {
2328
2421
  * the reviewer raced in since — appended at the end, it would render as an
2329
2422
  * answer to a comment it never saw, and the raced comment would stop reading
2330
2423
  * as "waiting on the agent" even though it still is. */
2331
- addMessage(threadId, author, text, withheld = false, seenThroughMs) {
2424
+ addMessage(threadId, author, text, withheld = false, seenThroughMs, followUp = false) {
2332
2425
  return this.update(threadId, ({ unanswered: _answered, ...thread }) => {
2333
2426
  const message = {
2334
2427
  id: randomUUID(),
@@ -2336,9 +2429,11 @@ var ReviewStore = class {
2336
2429
  text,
2337
2430
  at: (/* @__PURE__ */ new Date()).toISOString()
2338
2431
  };
2339
- const raced = author === "agent" && seenThroughMs !== void 0 ? thread.messages.findIndex((m) => Date.parse(m.at) > seenThroughMs) : -1;
2432
+ const raced = author === "agent" && seenThroughMs !== void 0 ? thread.messages.findIndex((m) => m.author === "reviewer" && Date.parse(m.at) > seenThroughMs) : -1;
2433
+ const { awaitingFollowUp: _promised, ...settled } = thread;
2340
2434
  return {
2341
- ...thread,
2435
+ ...author === "agent" ? settled : thread,
2436
+ ...author === "agent" && followUp ? { awaitingFollowUp: true } : {},
2342
2437
  ...withheld && author === "reviewer" ? { withheld: true } : {},
2343
2438
  messages: raced === -1 ? [...thread.messages, message] : [
2344
2439
  ...thread.messages.slice(0, raced),
@@ -2387,7 +2482,7 @@ var ReviewStore = class {
2387
2482
  const threads = this.state.threads.map((thread) => {
2388
2483
  if (!wanted.has(thread.id) || !applies(thread)) return thread;
2389
2484
  changed = true;
2390
- const { unanswered: _was, ...rest } = thread;
2485
+ const { unanswered: _was, awaitingFollowUp: _promised, ...rest } = thread;
2391
2486
  return thread.unanswered ? rest : {
2392
2487
  ...rest,
2393
2488
  unanswered: true
@@ -2401,8 +2496,9 @@ var ReviewStore = class {
2401
2496
  this.commit();
2402
2497
  }
2403
2498
  setState(threadId, state) {
2404
- return this.update(threadId, (thread) => ({
2499
+ return this.update(threadId, ({ awaitingFollowUp: promised, ...thread }) => ({
2405
2500
  ...thread,
2501
+ ...promised && state !== "resolved" ? { awaitingFollowUp: true } : {},
2406
2502
  state,
2407
2503
  ...state === "sent" && !thread.sentAt ? { sentAt: (/* @__PURE__ */ new Date()).toISOString() } : {},
2408
2504
  ...state === "resolved" ? { codeContext: null } : {}
@@ -2746,6 +2842,7 @@ function normalizeThread(value, now) {
2746
2842
  ...anchored ? { anchored } : {},
2747
2843
  codeChanged: t.codeChanged === true,
2748
2844
  ...t.unanswered === true ? { unanswered: true } : {},
2845
+ ...t.awaitingFollowUp === true && t.unanswered !== true ? { awaitingFollowUp: true } : {},
2749
2846
  ...t.closingNote === true ? { closingNote: true } : {},
2750
2847
  ...typeof t.sentAt === "string" ? { sentAt: t.sentAt } : {},
2751
2848
  messages,
@@ -3231,6 +3328,22 @@ function createApp(ctx, store, review, queue) {
3231
3328
  note: "shutting down"
3232
3329
  });
3233
3330
  });
3331
+ const THEME_VALUES = /* @__PURE__ */ new Set([
3332
+ "system",
3333
+ "light",
3334
+ "dark"
3335
+ ]);
3336
+ app.get("/api/settings", (c) => {
3337
+ const theme = ctx.uiSettings?.get("theme") ?? null;
3338
+ return c.json({ theme: theme !== null && THEME_VALUES.has(theme) ? theme : null });
3339
+ });
3340
+ app.put("/api/settings", async (c) => {
3341
+ if (!ctx.uiSettings) return c.json({ error: "settings unavailable" }, 503);
3342
+ const theme = (await c.req.json().catch(() => null))?.theme;
3343
+ if (typeof theme !== "string" || !THEME_VALUES.has(theme)) return c.json({ error: "need {theme: \"system\" | \"light\" | \"dark\"}" }, 400);
3344
+ ctx.uiSettings.set("theme", theme);
3345
+ return c.json({ ok: true });
3346
+ });
3234
3347
  const repoInfo = () => store?.get().repo ?? {
3235
3348
  path: resolve(ctx.root),
3236
3349
  name: "",
@@ -3298,9 +3411,10 @@ function createApp(ctx, store, review, queue) {
3298
3411
  const id = c.req.param("id");
3299
3412
  if (!review.get().threads.some((t) => t.id === id)) return c.json({ error: "no such thread" }, 404);
3300
3413
  queue?.noteSession(parseSessionPid(c.req.header("x-diffo-session-pid")));
3301
- const waitedMs = queue?.agentReplied(id) ?? null;
3414
+ const more = body?.more === true;
3415
+ const waitedMs = queue?.agentReplied(id, more) ?? null;
3302
3416
  const seenThroughMs = waitedMs === null ? void 0 : Date.now() - waitedMs;
3303
- const thread = review.addMessage(id, "agent", text, false, seenThroughMs);
3417
+ const thread = review.addMessage(id, "agent", text, false, seenThroughMs, more);
3304
3418
  if (!thread) return c.json({ error: "no such thread" }, 404);
3305
3419
  if (waitedMs !== null) review.annotateAgentReplies([thread.id], waitedMs);
3306
3420
  return c.json({
@@ -3479,6 +3593,7 @@ function createApp(ctx, store, review, queue) {
3479
3593
  });
3480
3594
  const pollPayload = (snapshot) => {
3481
3595
  const threads = review?.get().threads ?? [];
3596
+ const protocol = queue?.needsFullProtocol() === false ? "compact" : void 0;
3482
3597
  if (snapshot.kind === "cleared") return {
3483
3598
  status: "feedback",
3484
3599
  kind: "cleared",
@@ -3493,15 +3608,22 @@ function createApp(ctx, store, review, queue) {
3493
3608
  return {
3494
3609
  status: "feedback",
3495
3610
  kind: "finish",
3496
- threadIds: batch.filter((t) => t.state === "sent" && t.messages.at(-1)?.author === "reviewer").map((t) => t.id),
3611
+ threadIds: batch.filter((t) => t.state === "sent" && !answeredByAgent(t)).map((t) => t.id),
3497
3612
  prompt: buildFinishPrompt(batch, {
3498
3613
  repo: repoInfo(),
3499
- changeset: store?.get() ?? null
3614
+ changeset: store?.get() ?? null,
3615
+ protocol
3500
3616
  }, snapshot.coverage)
3501
3617
  };
3502
3618
  }
3503
3619
  const delivered = threads.filter((t) => snapshot.threadIds.includes(t.id));
3504
- const prompt = delivered.length === 1 ? buildThreadPrompt(delivered[0], promptCtx(delivered[0].id)) : buildCoalescedPrompt(delivered, promptCtx(...snapshot.threadIds));
3620
+ const prompt = delivered.length === 1 ? buildThreadPrompt(delivered[0], {
3621
+ ...promptCtx(delivered[0].id),
3622
+ protocol
3623
+ }) : buildCoalescedPrompt(delivered, {
3624
+ ...promptCtx(...snapshot.threadIds),
3625
+ protocol
3626
+ });
3505
3627
  return {
3506
3628
  status: "feedback",
3507
3629
  kind: "threads",
@@ -3579,6 +3701,7 @@ function createApp(ctx, store, review, queue) {
3579
3701
  await s.write(JSON.stringify(payload));
3580
3702
  if (aborted) return;
3581
3703
  queue.confirm(snapshot, payload.threadIds);
3704
+ if (payload.threadIds.length > 0) queue.markProtocolSent();
3582
3705
  review.markDelivered(payload.threadIds);
3583
3706
  review.clearUnanswered(payload.threadIds);
3584
3707
  if (snapshot.kind === "finish") review.markFinishCollected();
@@ -3737,6 +3860,8 @@ function markDevIndex(html, isDev) {
3737
3860
  return html.replace(/<title>[^<]*<\/title>/, "<title>diffo-dev</title>").replace(/<\/head>/, " <meta name=\"diffo-env\" content=\"development\" />\n </head>");
3738
3861
  }
3739
3862
  function rehydrateQueue(review, queue) {
3863
+ const promised = review.get().threads.filter((t) => t.awaitingFollowUp === true).map((t) => t.id);
3864
+ if (promised.length > 0) review.markUnanswered(promised);
3740
3865
  const state = review.get();
3741
3866
  queue.enqueueThreads(undeliveredThreadIds(state.threads));
3742
3867
  const finish = state.lastFinish;
@@ -3808,7 +3933,11 @@ function startServer(options) {
3808
3933
  const app = createApp({
3809
3934
  ...options,
3810
3935
  idle,
3811
- onShutdownRequest: options.onShutdownRequest ?? (() => process.exit(0))
3936
+ onShutdownRequest: options.onShutdownRequest ?? (() => process.exit(0)),
3937
+ uiSettings: {
3938
+ get: (key) => db.getUiSetting(key),
3939
+ set: (key, value) => db.setUiSetting(key, value)
3940
+ }
3812
3941
  }, store, review, queue);
3813
3942
  const server = serve({
3814
3943
  fetch: app.fetch,
@@ -4631,7 +4760,7 @@ if (command.kind === "stop") {
4631
4760
  }
4632
4761
  if (command.kind === "poll") {
4633
4762
  const port = await requireServer();
4634
- process.stderr.write("diffo: waiting for the reviewer — keep this process attended: a tracked\nbackground task or the foreground, never detached.\nIf it is killed or times out, just re-run it: what the reviewer sent is\nheld in the review, so it outlives this poll and the server both.\n");
4763
+ process.stderr.write("diffo: waiting for the reviewer — keep this process attended: a tracked\nbackground task or the foreground, never detached. If it dies, just\nre-run it — feedback is held in the review and survives.\n");
4635
4764
  try {
4636
4765
  const res = await fetch(apiUrl(port, "/api/agent/poll"), { headers: sessionHeaders() });
4637
4766
  if (!res.ok) fail(`poll failed (${res.status})`);
@@ -4654,7 +4783,8 @@ if (command.kind === "reply") {
4654
4783
  if (!message) fail("reply needs a message — pass --message \"<text>\" or pipe it on stdin");
4655
4784
  const { status, body } = await postJson(await requireServer(), `/api/review/threads/${encodeURIComponent(command.threadId)}/messages`, {
4656
4785
  author: "agent",
4657
- text: message
4786
+ text: message,
4787
+ ...command.more ? { more: true } : {}
4658
4788
  });
4659
4789
  if (status === 404) fail(`no thread with id '${command.threadId}'`);
4660
4790
  if (status !== 200) fail(`reply failed (${status})`);
@@ -4663,7 +4793,7 @@ if (command.kind === "reply") {
4663
4793
  ok: true,
4664
4794
  threadId: thread.id,
4665
4795
  state: thread.state,
4666
- next_step: ACK_NEXT_STEP.reply
4796
+ next_step: command.more ? ACK_NEXT_STEP.replyMore : ACK_NEXT_STEP.reply
4667
4797
  }));
4668
4798
  process.exit(0);
4669
4799
  }
@@ -1 +1 @@
1
- import{m as e,t}from"./mermaid-parser.core-DKC3l_TU.js";import{n}from"./chunk-Y2CYZVJY-DsF7k-Jl.js";import{m as r}from"./src-DQRzrNpT.js";import"./chunk-DU6HZSFF-D6gjp2l1.js";import{n as i,r as a,t as o}from"./chunk-SVP7TREG-CSTzyRmo.js";import{t as s}from"./chunk-JWPE2WC7-DVXcaiue.js";import"./mermaid.core-B0ktnMCY.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-BLSiQUXp.js";import{n}from"./chunk-Y2CYZVJY-DsF7k-Jl.js";import{m as r}from"./src-CfhDi0bm.js";import"./chunk-DU6HZSFF-wyqPpFTy.js";import{n as i,r as a,t as o}from"./chunk-SVP7TREG-CsO-w42m.js";import{t as s}from"./chunk-JWPE2WC7-DVXcaiue.js";import"./mermaid.core-BoXUi08V.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-C42IZvNn.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-bii_eoxi.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-FOHPRMQF-DHwB1DNv.js";import{M as e}from"./mermaid-parser.core-BLSiQUXp.js";export{e as createArchitectureServices};