@imenam/mcp-documentor 1.0.70 → 1.0.72

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 (73) hide show
  1. package/dist/src/cli/setup-mcp.js +2 -2
  2. package/dist/src/core/conflict-inspector.d.ts +50 -0
  3. package/dist/src/core/conflict-inspector.js +140 -0
  4. package/dist/src/core/git-sync.d.ts +102 -4
  5. package/dist/src/core/git-sync.js +265 -25
  6. package/dist/src/core/merge-drivers/merge-config.d.ts +1 -0
  7. package/dist/src/core/merge-drivers/merge-config.js +68 -0
  8. package/dist/src/core/merge-drivers/merge-doc.d.ts +1 -0
  9. package/dist/src/core/merge-drivers/merge-doc.js +105 -0
  10. package/dist/src/core/merge-drivers/text-merge.d.ts +15 -0
  11. package/dist/src/core/merge-drivers/text-merge.js +53 -0
  12. package/dist/src/core/merge-drivers/three-way.d.ts +34 -0
  13. package/dist/src/core/merge-drivers/three-way.js +86 -0
  14. package/dist/src/gui-worker.js +21 -0
  15. package/dist/src/index.js +146 -9
  16. package/dist/src/tools/conflicts.d.ts +12 -0
  17. package/dist/src/tools/conflicts.js +145 -0
  18. package/dist/src/tools/sync.js +43 -6
  19. package/dist/src/version.d.ts +1 -1
  20. package/dist/src/version.js +1 -1
  21. package/gui/dist/assets/{arc-Carsq0k3.js → arc-KWKRz6LO.js} +1 -1
  22. package/gui/dist/assets/{architectureDiagram-3BPJPVTR-caqcwdTw.js → architectureDiagram-3BPJPVTR-Cjfl98ZS.js} +1 -1
  23. package/gui/dist/assets/{blockDiagram-GPEHLZMM-0wUO8k2I.js → blockDiagram-GPEHLZMM-CtPUrnse.js} +1 -1
  24. package/gui/dist/assets/{c4Diagram-AAUBKEIU-DS6q7fMy.js → c4Diagram-AAUBKEIU-Dh5_7IZy.js} +1 -1
  25. package/gui/dist/assets/channel-JejxY436.js +1 -0
  26. package/gui/dist/assets/{chunk-2J33WTMH-CrJc-G6D.js → chunk-2J33WTMH-44T5JbXy.js} +1 -1
  27. package/gui/dist/assets/{chunk-4BX2VUAB-C5vtPFbB.js → chunk-4BX2VUAB-D1fEQ0Ng.js} +1 -1
  28. package/gui/dist/assets/{chunk-55IACEB6-BchyQUFM.js → chunk-55IACEB6-M2lEB2Vn.js} +1 -1
  29. package/gui/dist/assets/{chunk-727SXJPM-BCIaWtKq.js → chunk-727SXJPM-B3A_Fp4f.js} +1 -1
  30. package/gui/dist/assets/{chunk-AQP2D5EJ-BEQJDJt5.js → chunk-AQP2D5EJ-CqkJXv3c.js} +1 -1
  31. package/gui/dist/assets/{chunk-FMBD7UC4-IfQ3QtrC.js → chunk-FMBD7UC4-Iyj_Ky8e.js} +1 -1
  32. package/gui/dist/assets/{chunk-ND2GUHAM-4Du33w7t.js → chunk-ND2GUHAM-DE5R-C9U.js} +1 -1
  33. package/gui/dist/assets/{chunk-QZHKN3VN-HR0CP6fa.js → chunk-QZHKN3VN-BekBU-YP.js} +1 -1
  34. package/gui/dist/assets/classDiagram-4FO5ZUOK-CdAqI_QA.js +1 -0
  35. package/gui/dist/assets/classDiagram-v2-Q7XG4LA2-CdAqI_QA.js +1 -0
  36. package/gui/dist/assets/{cose-bilkent-S5V4N54A-C9Re3ENx.js → cose-bilkent-S5V4N54A-Dw2zOlxr.js} +1 -1
  37. package/gui/dist/assets/{dagre-BM42HDAG-DZmcvMMp.js → dagre-BM42HDAG-ebVZNyxI.js} +1 -1
  38. package/gui/dist/assets/{diagram-2AECGRRQ-EyJrAGht.js → diagram-2AECGRRQ-Bh5FUKy0.js} +1 -1
  39. package/gui/dist/assets/{diagram-5GNKFQAL-CHd3T9_b.js → diagram-5GNKFQAL-YJJTJy-Q.js} +1 -1
  40. package/gui/dist/assets/{diagram-KO2AKTUF-CIyFrZSR.js → diagram-KO2AKTUF-D161kBvB.js} +1 -1
  41. package/gui/dist/assets/{diagram-LMA3HP47-BlkJALyz.js → diagram-LMA3HP47-Drcmx9J8.js} +1 -1
  42. package/gui/dist/assets/{diagram-OG6HWLK6-P8LA7IZE.js → diagram-OG6HWLK6-DRrxnW_H.js} +1 -1
  43. package/gui/dist/assets/{erDiagram-TEJ5UH35-BPywgWN1.js → erDiagram-TEJ5UH35-FrH4iEa3.js} +1 -1
  44. package/gui/dist/assets/{flowDiagram-I6XJVG4X-ByL0kGiV.js → flowDiagram-I6XJVG4X-BMxSElvH.js} +1 -1
  45. package/gui/dist/assets/{ganttDiagram-6RSMTGT7-_pnaYCU4.js → ganttDiagram-6RSMTGT7-H09FGGUr.js} +1 -1
  46. package/gui/dist/assets/{gitGraphDiagram-PVQCEYII-DN67CDGs.js → gitGraphDiagram-PVQCEYII-DLhPfXNp.js} +1 -1
  47. package/gui/dist/assets/{index-Bjc5MOe1.js → index-CdGsMXYw.js} +53 -53
  48. package/gui/dist/assets/index-DAzSbGj_.css +1 -0
  49. package/gui/dist/assets/{infoDiagram-5YYISTIA-Bm5LEpzH.js → infoDiagram-5YYISTIA-CV8ctX8t.js} +1 -1
  50. package/gui/dist/assets/{ishikawaDiagram-YF4QCWOH-DUYSULkT.js → ishikawaDiagram-YF4QCWOH-BeuHUSPO.js} +1 -1
  51. package/gui/dist/assets/{journeyDiagram-JHISSGLW-BvJ1_f0L.js → journeyDiagram-JHISSGLW-CfAHwYXQ.js} +1 -1
  52. package/gui/dist/assets/{kanban-definition-UN3LZRKU-DDYKg6RY.js → kanban-definition-UN3LZRKU-CFF4KWbn.js} +1 -1
  53. package/gui/dist/assets/{linear-HlpG9u6P.js → linear-DwZpGeRp.js} +1 -1
  54. package/gui/dist/assets/{mindmap-definition-RKZ34NQL-CPa_wo_s.js → mindmap-definition-RKZ34NQL-BW0yW4ap.js} +1 -1
  55. package/gui/dist/assets/{pieDiagram-4H26LBE5-BWo0c4DP.js → pieDiagram-4H26LBE5-plBCN_c7.js} +1 -1
  56. package/gui/dist/assets/{quadrantDiagram-W4KKPZXB-CLahU-6B.js → quadrantDiagram-W4KKPZXB-Dmp9sOq_.js} +1 -1
  57. package/gui/dist/assets/{requirementDiagram-4Y6WPE33-BoYbgzQ7.js → requirementDiagram-4Y6WPE33-CthKC15J.js} +1 -1
  58. package/gui/dist/assets/{sankeyDiagram-5OEKKPKP-CGm79aMo.js → sankeyDiagram-5OEKKPKP-BCAzzfPU.js} +1 -1
  59. package/gui/dist/assets/{sequenceDiagram-3UESZ5HK-BkknTD52.js → sequenceDiagram-3UESZ5HK-DgwOe82k.js} +1 -1
  60. package/gui/dist/assets/{stateDiagram-AJRCARHV-DSBurgwC.js → stateDiagram-AJRCARHV-B6k-x1TC.js} +1 -1
  61. package/gui/dist/assets/stateDiagram-v2-BHNVJYJU-bfw3SR0t.js +1 -0
  62. package/gui/dist/assets/{timeline-definition-PNZ67QCA-CBHyrGuz.js → timeline-definition-PNZ67QCA-CqwqJkZw.js} +1 -1
  63. package/gui/dist/assets/{vennDiagram-CIIHVFJN-Dn9fr_bR.js → vennDiagram-CIIHVFJN-REFDrI5t.js} +1 -1
  64. package/gui/dist/assets/{wardley-L42UT6IY-nMuySeAb.js → wardley-L42UT6IY-53gLZWfR.js} +1 -1
  65. package/gui/dist/assets/{wardleyDiagram-YWT4CUSO-Byn2STLI.js → wardleyDiagram-YWT4CUSO-OPfKpNx-.js} +1 -1
  66. package/gui/dist/assets/{xychartDiagram-2RQKCTM6-Bi-Y9wp5.js → xychartDiagram-2RQKCTM6-HWGySgFg.js} +1 -1
  67. package/gui/dist/index.html +2 -2
  68. package/package.json +1 -1
  69. package/gui/dist/assets/channel-D51HexOc.js +0 -1
  70. package/gui/dist/assets/classDiagram-4FO5ZUOK-CU0dQ3zy.js +0 -1
  71. package/gui/dist/assets/classDiagram-v2-Q7XG4LA2-CU0dQ3zy.js +0 -1
  72. package/gui/dist/assets/index-hjkEDRmc.css +0 -1
  73. package/gui/dist/assets/stateDiagram-v2-BHNVJYJU-DfKn22XF.js +0 -1
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Generic 3-way merge primitives shared by the git merge drivers.
3
+ *
4
+ * All helpers follow git's contract: `base` is the common ancestor, `ours` is
5
+ * the side being merged into, `theirs` is the incoming side. A value that only
6
+ * one side changed is taken from that side; a value both sides changed to the
7
+ * same thing is not a conflict.
8
+ */
9
+ function eq(a, b) {
10
+ return JSON.stringify(a ?? null) === JSON.stringify(b ?? null);
11
+ }
12
+ /** Classic 3-way merge of a single value. Conflicts only when both sides diverged differently. */
13
+ export function mergeScalar(base, ours, theirs) {
14
+ if (eq(ours, theirs))
15
+ return { conflict: false, value: ours };
16
+ if (eq(ours, base))
17
+ return { conflict: false, value: theirs };
18
+ if (eq(theirs, base))
19
+ return { conflict: false, value: ours };
20
+ return { conflict: true, ours, theirs };
21
+ }
22
+ /**
23
+ * Set merge that honours deletions: an entry disappears only if a side that had
24
+ * it in `base` removed it. Plain union would resurrect deleted entries forever.
25
+ * Order is stable — base order first, then additions from ours, then theirs.
26
+ */
27
+ export function mergeSet(base = [], ours = [], theirs = []) {
28
+ const baseSet = new Set(base);
29
+ const ourSet = new Set(ours);
30
+ const theirSet = new Set(theirs);
31
+ const result = [];
32
+ for (const item of base) {
33
+ const removed = !ourSet.has(item) || !theirSet.has(item);
34
+ if (!removed)
35
+ result.push(item);
36
+ }
37
+ for (const item of [...ours, ...theirs]) {
38
+ if (!baseSet.has(item) && !result.includes(item))
39
+ result.push(item);
40
+ }
41
+ return result;
42
+ }
43
+ /**
44
+ * Set merge over objects identified by `keyOf`. Entries present on both sides
45
+ * with diverging content are resolved by `pick` rather than reported as a
46
+ * conflict — callers supply a domain rule (e.g. keep the freshest snapshot).
47
+ */
48
+ export function mergeKeyedList(base = [], ours = [], theirs = [], keyOf, pick) {
49
+ const index = (list) => new Map(list.map((item) => [keyOf(item), item]));
50
+ const baseMap = index(base);
51
+ const ourMap = index(ours);
52
+ const theirMap = index(theirs);
53
+ const keys = mergeSet([...baseMap.keys()], [...ourMap.keys()], [...theirMap.keys()]);
54
+ const result = [];
55
+ for (const key of keys) {
56
+ const o = ourMap.get(key);
57
+ const t = theirMap.get(key);
58
+ if (o && t)
59
+ result.push(eq(o, t) ? o : pick(o, t));
60
+ else if (o)
61
+ result.push(o);
62
+ else if (t)
63
+ result.push(t);
64
+ }
65
+ return result;
66
+ }
67
+ /** Later of two date-ish strings. Falls back to lexicographic order for unparseable values. */
68
+ export function laterDate(a, b) {
69
+ if (!a)
70
+ return b;
71
+ if (!b)
72
+ return a;
73
+ const da = Date.parse(a);
74
+ const db = Date.parse(b);
75
+ if (Number.isNaN(da) || Number.isNaN(db))
76
+ return a > b ? a : b;
77
+ return da >= db ? a : b;
78
+ }
79
+ /** Earlier of two date-ish strings. Falls back to lexicographic order. */
80
+ export function earlierDate(a, b) {
81
+ if (!a)
82
+ return b;
83
+ if (!b)
84
+ return a;
85
+ return laterDate(a, b) === a ? b : a;
86
+ }
@@ -192,6 +192,7 @@ const READ_ONLY_WRITE_ROUTES = [
192
192
  { method: "PUT", prefix: "/api/doc/" },
193
193
  { method: "POST", prefix: "/api/images" },
194
194
  { method: "POST", prefix: "/api/configurations/apply" },
195
+ { method: "POST", prefix: "/api/sync" },
195
196
  ];
196
197
  app.use("*", async (c, next) => {
197
198
  if (isReadOnly()) {
@@ -314,6 +315,26 @@ app.get("/api/config", async (c) => {
314
315
  return c.json({ error: result.error }, 500);
315
316
  return c.json(result.data);
316
317
  });
318
+ app.get("/api/sync/status", async (c) => {
319
+ const result = await ipcRequest("SYNC_STATUS", {}, 20000);
320
+ if (result.error)
321
+ return c.json({ error: result.error }, 500);
322
+ return c.json(result.data);
323
+ });
324
+ app.post("/api/sync", async (c) => {
325
+ let message;
326
+ try {
327
+ message = (await c.req.json())?.message;
328
+ }
329
+ catch {
330
+ // Body is optional — the commit message falls back to an auto-generated one.
331
+ }
332
+ // Network round-trips (fetch + push) plus a possible embedding reindex.
333
+ const result = await ipcRequest("SYNC_DOCS", { message }, 180000);
334
+ if (result.error)
335
+ return c.json({ error: result.error }, 500);
336
+ return c.json(result.data);
337
+ });
317
338
  app.get("/api/projects", async (c) => {
318
339
  const result = await ipcRequest("LIST_PROJECTS");
319
340
  if (result.error)
package/dist/src/index.js CHANGED
@@ -43,6 +43,7 @@ import { uploadImage } from "./tools/upload-image.js";
43
43
  import { getImage } from "./tools/get-image.js";
44
44
  import { SyncEngine } from "./core/git-sync.js";
45
45
  import { syncStatus, pullDocs, pushDocs, syncDocs, resolveSyncConflict } from "./tools/sync.js";
46
+ import { listConflicts, getConflictHunks, resolveConflictHunks } from "./tools/conflicts.js";
46
47
  import { isReadOnly, WRITE_TOOLS } from "../shared/read-only.js";
47
48
  const __filename = fileURLToPath(import.meta.url);
48
49
  const __dirname = path.dirname(__filename);
@@ -167,6 +168,12 @@ function handleIPCMessage(msg) {
167
168
  else if (msg.type === "GET_FILE_SECTION") {
168
169
  handleGetFileSection(msg);
169
170
  }
171
+ else if (msg.type === "SYNC_STATUS") {
172
+ handleSyncStatusIPC(msg);
173
+ }
174
+ else if (msg.type === "SYNC_DOCS") {
175
+ handleSyncDocsIPC(msg);
176
+ }
170
177
  else {
171
178
  logger.debug(`Unhandled message from GUI: ${msg.type}`);
172
179
  }
@@ -197,7 +204,14 @@ async function handleGetInitialState(msg) {
197
204
  broadcastToGUI({
198
205
  type: "INITIAL_STATE",
199
206
  correlationId: msg.correlationId,
200
- data: { config, docs, projects: config.projects, bundles, readOnly: isReadOnly() },
207
+ data: {
208
+ config,
209
+ docs,
210
+ projects: config.projects,
211
+ bundles,
212
+ readOnly: isReadOnly(),
213
+ syncEnabled: syncEngine.isConfigured(),
214
+ },
201
215
  timestamp: new Date().toISOString(),
202
216
  });
203
217
  }
@@ -210,7 +224,7 @@ async function handleGetConfig(msg) {
210
224
  broadcastToGUI({
211
225
  type: "CONFIG_RESPONSE",
212
226
  correlationId: msg.correlationId,
213
- data: { ...config, version: getConfig().version, readOnly: isReadOnly() },
227
+ data: { ...config, version: getConfig().version, readOnly: isReadOnly(), syncEnabled: syncEngine.isConfigured() },
214
228
  timestamp: new Date().toISOString(),
215
229
  });
216
230
  }
@@ -636,6 +650,81 @@ async function handleGitDiff(msg) {
636
650
  });
637
651
  }
638
652
  }
653
+ /**
654
+ * Sync state for the GUI button. `configured` is false when the environment has
655
+ * no MCP_DOCUMENTOR_SYNC_REMOTE — the GUI then hides the button entirely.
656
+ */
657
+ async function handleSyncStatusIPC(msg) {
658
+ try {
659
+ if (!syncEngine.isConfigured()) {
660
+ broadcastToGUI({
661
+ type: "SYNC_STATUS_RESPONSE",
662
+ correlationId: msg.correlationId,
663
+ data: { configured: false },
664
+ timestamp: new Date().toISOString(),
665
+ });
666
+ return;
667
+ }
668
+ const s = await syncEngine.status();
669
+ broadcastToGUI({
670
+ type: "SYNC_STATUS_RESPONSE",
671
+ correlationId: msg.correlationId,
672
+ data: { configured: true, ...s },
673
+ timestamp: new Date().toISOString(),
674
+ });
675
+ }
676
+ catch (err) {
677
+ broadcastToGUI({
678
+ type: "SYNC_STATUS_RESPONSE",
679
+ correlationId: msg.correlationId,
680
+ error: err.message,
681
+ timestamp: new Date().toISOString(),
682
+ });
683
+ }
684
+ }
685
+ async function handleSyncDocsIPC(msg) {
686
+ if (isReadOnly()) {
687
+ broadcastToGUI({
688
+ type: "SYNC_DOCS_RESPONSE",
689
+ correlationId: msg.correlationId,
690
+ error: "READ_ONLY mode: sync is disabled",
691
+ timestamp: new Date().toISOString(),
692
+ });
693
+ return;
694
+ }
695
+ try {
696
+ // Reuse the tool so the GUI and the agent get identical pull/push semantics.
697
+ const result = await syncDocs(syncEngine, storageEngine, embeddingEngine, embeddingStore, {
698
+ message: msg.data?.message,
699
+ });
700
+ const text = result.content.map((c) => c.text).join("\n");
701
+ if (result.isError) {
702
+ broadcastToGUI({
703
+ type: "SYNC_DOCS_RESPONSE",
704
+ correlationId: msg.correlationId,
705
+ error: text,
706
+ timestamp: new Date().toISOString(),
707
+ });
708
+ return;
709
+ }
710
+ broadcastToGUI({
711
+ type: "SYNC_DOCS_RESPONSE",
712
+ correlationId: msg.correlationId,
713
+ data: { message: text },
714
+ timestamp: new Date().toISOString(),
715
+ });
716
+ // A pull can add/remove docs — push a fresh tree to every connected GUI.
717
+ await handleGetInitialState({});
718
+ }
719
+ catch (err) {
720
+ broadcastToGUI({
721
+ type: "SYNC_DOCS_RESPONSE",
722
+ correlationId: msg.correlationId,
723
+ error: err.message,
724
+ timestamp: new Date().toISOString(),
725
+ });
726
+ }
727
+ }
639
728
  async function handleUpdateConfig(msg) {
640
729
  if (isReadOnly()) {
641
730
  broadcastToGUI({
@@ -1201,17 +1290,17 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
1201
1290
  },
1202
1291
  {
1203
1292
  name: "sync_status",
1204
- description: "Show the synchronization status: current branch, how many commits the local repo is ahead/behind the remote, conflicted files, and uncommitted changes. The sync target (repository, branch, token) is fixed by the server administrator via environment variables and cannot be changed from here.",
1293
+ description: "Show the synchronization status: current branch, whether a merge or rebase is in progress, how many commits the local repo is ahead/behind the remote, conflicted files, and uncommitted changes. The sync target (repository, branch, token) is fixed by the server administrator via environment variables and cannot be changed from here.",
1205
1294
  inputSchema: { type: "object", properties: {} },
1206
1295
  },
1207
1296
  {
1208
1297
  name: "pull_docs",
1209
- description: "Pull documentation updates from the GitHub remote. Defensive: only fast-forwards a clean working tree, never auto-merges a divergent history (local data is never overwritten). Reindexes embeddings for new documents after a successful update.",
1298
+ description: "Pull documentation updates from the GitHub remote. Defensive: fast-forwards when purely behind, and rebases local commits on top of the remote when the history diverged. Never touches an uncommitted local change. A conflict stops the rebase with both versions preserved - resolve it with resolve_sync_conflict. Reindexes embeddings for new documents after a successful update.",
1210
1299
  inputSchema: { type: "object", properties: {} },
1211
1300
  },
1212
1301
  {
1213
1302
  name: "push_docs",
1214
- description: "Commit local documentation changes (markdown + assets + project list) and push them to the GitHub remote.",
1303
+ description: "Commit local documentation changes (markdown + assets + project list) and push them to the GitHub remote. Refuses to run while a conflict is unresolved, so conflict markers are never committed as documentation content.",
1215
1304
  inputSchema: {
1216
1305
  type: "object",
1217
1306
  properties: {
@@ -1221,7 +1310,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
1221
1310
  },
1222
1311
  {
1223
1312
  name: "sync_docs",
1224
- description: "Full synchronization: pull (defensive) then push. If the pull reveals a divergence or conflict, the push is skipped so it can be resolved first.",
1313
+ description: "Full synchronization: pull (fast-forward or rebase) then push. If the pull reveals a divergence or conflict, the push is skipped so it can be resolved first with resolve_sync_conflict.",
1225
1314
  inputSchema: {
1226
1315
  type: "object",
1227
1316
  properties: {
@@ -1231,14 +1320,56 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
1231
1320
  },
1232
1321
  {
1233
1322
  name: "resolve_sync_conflict",
1234
- description: "Bulk-resolve sync merge conflicts by keeping one side for the conflicted files, then stage them. For nuanced resolution, edit the conflicted markdown files directly instead. After resolving, run push_docs.",
1323
+ description: "Bulk-resolve sync conflicts by keeping one side for the conflicted files, stage them, and carry the merge or rebase to completion when nothing is left conflicted. Sides are absolute ('local' vs 'remote'), not git's ours/theirs, which swap meaning between a merge and a rebase. For nuanced resolution, edit the conflicted markdown files directly instead. After resolving, run push_docs.",
1235
1324
  inputSchema: {
1236
1325
  type: "object",
1237
1326
  properties: {
1238
- strategy: { type: "string", enum: ["mine", "theirs"], description: "'mine' keeps the local version, 'theirs' keeps the incoming remote version" },
1327
+ side: { type: "string", enum: ["local", "remote"], description: "'local' keeps this machine's version, 'remote' keeps the incoming version from the other machine" },
1239
1328
  paths: { type: "array", items: { type: "string" }, description: "Specific conflicted file paths to resolve (defaults to all conflicted files)" },
1240
1329
  },
1241
- required: ["strategy"],
1330
+ required: ["side"],
1331
+ },
1332
+ },
1333
+ {
1334
+ name: "list_conflicts",
1335
+ description: "List every file left conflicted by a sync, without opening any of them: which project and document each one is, whether it is a content / delete-modify / add-add conflict, how many hunks it has and in which markdown sections, and who last changed it on each side. Sides are absolute: 'local' is this machine, 'remote' is the other machine. Start here when a pull reports a conflict.",
1336
+ inputSchema: { type: "object", properties: {} },
1337
+ },
1338
+ {
1339
+ name: "get_conflict_hunks",
1340
+ description: "Return only the passages two machines disagree on in one conflicted document, as numbered hunks: the section each falls in, the common ancestor, and both sides. Output is capped per side so a large document never has to be read in full. Use this instead of get_doc when resolving a sync conflict.",
1341
+ inputSchema: {
1342
+ type: "object",
1343
+ properties: {
1344
+ path: { type: "string", description: "Conflicted file path as reported by list_conflicts (e.g. 'projects/alpha/api.md')" },
1345
+ hunks: { type: "array", items: { type: "number" }, description: "Specific hunk numbers to return (defaults to all)" },
1346
+ max_chars_per_side: { type: "number", description: "Character budget per side, per hunk (default 1200). Anything beyond it is withheld and counted." },
1347
+ },
1348
+ required: ["path"],
1349
+ },
1350
+ },
1351
+ {
1352
+ name: "resolve_conflict_hunks",
1353
+ description: "Resolve a conflicted document hunk by hunk. Each decision keeps the local side, the remote side, both (local first), neither, or your own replacement text. The file is only staged once every hunk has been decided, so conflict markers can never be committed as documentation content. When the file is the last one conflicted, the merge or rebase is carried to completion.",
1354
+ inputSchema: {
1355
+ type: "object",
1356
+ properties: {
1357
+ path: { type: "string", description: "Conflicted file path as reported by list_conflicts" },
1358
+ decisions: {
1359
+ type: "array",
1360
+ description: "One entry per hunk you are deciding. Undecided hunks keep their markers.",
1361
+ items: {
1362
+ type: "object",
1363
+ properties: {
1364
+ hunk: { type: "number", description: "Hunk number from get_conflict_hunks" },
1365
+ take: { type: "string", enum: ["local", "remote", "both", "none", "custom"], description: "'local' this machine, 'remote' the other machine, 'both' concatenates local then remote, 'none' drops the passage, 'custom' uses the provided content" },
1366
+ content: { type: "string", description: "Replacement text, required when take is 'custom'" },
1367
+ },
1368
+ required: ["hunk", "take"],
1369
+ },
1370
+ },
1371
+ },
1372
+ required: ["path", "decisions"],
1242
1373
  },
1243
1374
  },
1244
1375
  {
@@ -1342,6 +1473,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1342
1473
  return await syncDocs(syncEngine, storageEngine, embeddingEngine, embeddingStore, args);
1343
1474
  case "resolve_sync_conflict":
1344
1475
  return await resolveSyncConflict(syncEngine, args);
1476
+ case "list_conflicts":
1477
+ return await listConflicts(syncEngine);
1478
+ case "get_conflict_hunks":
1479
+ return await getConflictHunks(syncEngine, args);
1480
+ case "resolve_conflict_hunks":
1481
+ return await resolveConflictHunks(syncEngine, args);
1345
1482
  case "reconnect_gui":
1346
1483
  return await reconnectGui(args);
1347
1484
  default:
@@ -0,0 +1,12 @@
1
+ import type { SyncEngine } from "../core/git-sync.js";
2
+ type ToolResult = {
3
+ content: {
4
+ type: "text";
5
+ text: string;
6
+ }[];
7
+ isError?: boolean;
8
+ };
9
+ export declare function listConflicts(syncEngine: SyncEngine): Promise<ToolResult>;
10
+ export declare function getConflictHunks(syncEngine: SyncEngine, args: unknown): Promise<ToolResult>;
11
+ export declare function resolveConflictHunks(syncEngine: SyncEngine, args: unknown): Promise<ToolResult>;
12
+ export {};
@@ -0,0 +1,145 @@
1
+ import { z } from "zod";
2
+ import { parseConflicts, applyResolutions, preview, hasConflictMarkers } from "../core/conflict-inspector.js";
3
+ const ok = (text) => ({ content: [{ type: "text", text }] });
4
+ const err = (text) => ({ content: [{ type: "text", text }], isError: true });
5
+ const NOT_CONFIGURED = "Sync is not configured. Set MCP_DOCUMENTOR_SYNC_REMOTE (and MCP_DOCUMENTOR_SYNC_TOKEN) in the server environment.";
6
+ /** Documents live at projects/<project>/<id>.md — recover both for reporting. */
7
+ function describeTarget(filePath) {
8
+ const match = filePath.match(/^projects\/([^/]+)\/(.+)\.md$/);
9
+ return match ? `project "${match[1]}", doc "${match[2]}"` : filePath;
10
+ }
11
+ function describeSide(label, side) {
12
+ if (!side)
13
+ return ` ${label}: (no commit recorded)`;
14
+ return ` ${label}: ${side.author} · ${side.date} · "${side.subject}"`;
15
+ }
16
+ export async function listConflicts(syncEngine) {
17
+ try {
18
+ if (!syncEngine.isConfigured())
19
+ return err(NOT_CONFIGURED);
20
+ const { operation, files } = await syncEngine.listConflicts();
21
+ if (files.length === 0)
22
+ return ok("No conflicts. Nothing to resolve.");
23
+ const lines = [
24
+ `${files.length} conflicted file(s) during a ${operation}.`,
25
+ `"local" is this machine's version, "remote" is the other machine's.`,
26
+ "",
27
+ ];
28
+ for (const file of files) {
29
+ lines.push(`${file.path} (${describeTarget(file.path)})`);
30
+ if (file.kind === "delete-modify") {
31
+ lines.push(` Kind: delete/modify — the ${file.deletedOn} side deleted this document while the other edited it.`);
32
+ }
33
+ else if (file.kind === "add-add") {
34
+ lines.push(` Kind: add/add — both sides created this document independently; there is no common ancestor.`);
35
+ }
36
+ else {
37
+ // Counting hunks needs the working file, which only exists for content
38
+ // conflicts (a deleted side leaves nothing on disk).
39
+ try {
40
+ const { hunks } = parseConflicts(syncEngine.readWorkingFile(file.path), operation);
41
+ const sections = [...new Set(hunks.map((h) => h.section))];
42
+ lines.push(` Kind: content — ${hunks.length} conflicting hunk(s) in: ${sections.join(", ")}`);
43
+ }
44
+ catch {
45
+ lines.push(` Kind: content`);
46
+ }
47
+ }
48
+ lines.push(describeSide("local ", file.local), describeSide("remote", file.remote), "");
49
+ }
50
+ lines.push(`Next: get_conflict_hunks to see just the disagreeing passages, resolve_conflict_hunks to decide hunk by hunk,`, `or resolve_sync_conflict to keep one whole side for every file at once.`);
51
+ return ok(lines.join("\n"));
52
+ }
53
+ catch (e) {
54
+ return err(`Error listing conflicts: ${e instanceof Error ? e.message : String(e)}`);
55
+ }
56
+ }
57
+ const GetHunksArgs = z.object({
58
+ path: z.string(),
59
+ hunks: z.array(z.number().int().positive()).optional(),
60
+ max_chars_per_side: z.number().int().positive().max(20000).default(1200),
61
+ });
62
+ export async function getConflictHunks(syncEngine, args) {
63
+ try {
64
+ if (!syncEngine.isConfigured())
65
+ return err(NOT_CONFIGURED);
66
+ const v = GetHunksArgs.parse(args);
67
+ const operation = await syncEngine.operationInProgress();
68
+ const { hunks } = parseConflicts(syncEngine.readWorkingFile(v.path), operation);
69
+ if (hunks.length === 0)
70
+ return ok(`No conflict markers in "${v.path}".`);
71
+ const wanted = v.hunks?.length ? hunks.filter((h) => v.hunks.includes(h.index)) : hunks;
72
+ if (wanted.length === 0)
73
+ return ok(`None of the requested hunks exist in "${v.path}" (it has ${hunks.length}).`);
74
+ const lines = [`${v.path} — ${wanted.length} of ${hunks.length} hunk(s), during a ${operation}.`, ""];
75
+ for (const hunk of wanted) {
76
+ lines.push(`Hunk ${hunk.index} — section "${hunk.section}" (line ${hunk.line})`);
77
+ if (hunk.base)
78
+ lines.push(` [base — what both sides started from]`, preview(hunk.base, v.max_chars_per_side));
79
+ lines.push(` [local — this machine]`, preview(hunk.local, v.max_chars_per_side));
80
+ lines.push(` [remote — the other machine]`, preview(hunk.remote, v.max_chars_per_side));
81
+ lines.push("");
82
+ }
83
+ lines.push(`Resolve with resolve_conflict_hunks: take "local", "remote", "both", "none" (drop it), or "custom" with your own text.`);
84
+ return ok(lines.join("\n"));
85
+ }
86
+ catch (e) {
87
+ return err(`Error reading conflict hunks: ${e instanceof Error ? e.message : String(e)}`);
88
+ }
89
+ }
90
+ const ResolveHunksArgs = z.object({
91
+ path: z.string(),
92
+ decisions: z
93
+ .array(z.object({
94
+ hunk: z.number().int().positive(),
95
+ take: z.enum(["local", "remote", "both", "none", "custom"]),
96
+ content: z.string().optional(),
97
+ }))
98
+ .min(1),
99
+ });
100
+ export async function resolveConflictHunks(syncEngine, args) {
101
+ try {
102
+ if (!syncEngine.isConfigured())
103
+ return err(NOT_CONFIGURED);
104
+ const v = ResolveHunksArgs.parse(args);
105
+ const missingContent = v.decisions.filter((d) => d.take === "custom" && d.content === undefined);
106
+ if (missingContent.length > 0) {
107
+ return err(`Hunk(s) ${missingContent.map((d) => d.hunk).join(", ")} use take "custom" but no content was provided.`);
108
+ }
109
+ const operation = await syncEngine.operationInProgress();
110
+ const decisions = v.decisions.map((d) => d.take === "custom" ? { index: d.hunk, take: "custom", content: d.content } : { index: d.hunk, take: d.take });
111
+ const original = syncEngine.readWorkingFile(v.path);
112
+ if (!hasConflictMarkers(original)) {
113
+ return err(`"${v.path}" has no conflict markers — there is nothing to resolve hunk by hunk.`);
114
+ }
115
+ const result = applyResolutions(original, operation, decisions);
116
+ // The invariant is checked against the text itself rather than the hunk
117
+ // count: staging a file that still holds markers tells git the conflict is
118
+ // settled and lets the markers be committed as documentation content.
119
+ const settled = !hasConflictMarkers(result.text);
120
+ await syncEngine.writeResolvedFile(v.path, result.text, settled);
121
+ const lines = [`Resolved ${result.applied.length} hunk(s) in "${v.path}".`];
122
+ if (result.unknown.length > 0) {
123
+ lines.push(`Ignored — no such hunk: ${result.unknown.join(", ")}.`);
124
+ }
125
+ if (!settled) {
126
+ lines.push(`${result.remaining} hunk(s) still conflicted; the file is left unstaged until every hunk is decided.`, `Run get_conflict_hunks again — the remaining hunks have been renumbered.`);
127
+ return ok(lines.join("\n"));
128
+ }
129
+ lines.push(`File fully resolved and staged.`);
130
+ const finish = await syncEngine.continueOperation();
131
+ if (finish.remaining.length > 0) {
132
+ lines.push(``, `Still conflicted elsewhere (${finish.remaining.length}):`, ` - ${finish.remaining.join("\n - ")}`);
133
+ }
134
+ else if (finish.done && finish.operation !== "none") {
135
+ lines.push(``, `The ${finish.operation} is complete. Run push_docs to publish.`);
136
+ }
137
+ else if (finish.error) {
138
+ lines.push(``, `The ${finish.operation} could not be completed: ${finish.error}`);
139
+ }
140
+ return ok(lines.join("\n"));
141
+ }
142
+ catch (e) {
143
+ return err(`Error resolving hunks: ${e instanceof Error ? e.message : String(e)}`);
144
+ }
145
+ }
@@ -7,6 +7,15 @@ function err(text) {
7
7
  return { content: [{ type: "text", text }], isError: true };
8
8
  }
9
9
  const NOT_CONFIGURED = "Sync is not configured. Set MCP_DOCUMENTOR_SYNC_REMOTE (and MCP_DOCUMENTOR_SYNC_TOKEN) in the server environment.";
10
+ /**
11
+ * Names the sides in absolute terms. Git's own "ours"/"theirs" swap meaning
12
+ * between a merge and a rebase, so they are never surfaced to the agent.
13
+ */
14
+ function describeOperation(operation) {
15
+ return operation === "rebase"
16
+ ? "A rebase is in progress: your local commits are being replayed on top of the remote."
17
+ : "A merge is in progress: the remote commits are being merged into your branch.";
18
+ }
10
19
  export async function syncStatus(syncEngine) {
11
20
  try {
12
21
  if (!syncEngine.isConfigured())
@@ -14,6 +23,7 @@ export async function syncStatus(syncEngine) {
14
23
  const s = await syncEngine.status();
15
24
  const lines = [
16
25
  `Branch: ${s.branch}`,
26
+ s.operation === "none" ? "No merge or rebase in progress." : describeOperation(s.operation),
17
27
  `Ahead: ${s.ahead} commit(s) · Behind: ${s.behind} commit(s)`,
18
28
  s.conflicted.length > 0 ? `Conflicted files (${s.conflicted.length}):\n - ${s.conflicted.join("\n - ")}` : "No conflicts.",
19
29
  s.dirty.length > 0 ? `Uncommitted changes (${s.dirty.length}):\n - ${s.dirty.join("\n - ")}` : "Working tree clean.",
@@ -31,9 +41,9 @@ function describePull(res) {
31
41
  case "updated":
32
42
  return `Pulled ${res.changed.length} changed file(s):\n - ${res.changed.join("\n - ")}`;
33
43
  case "diverged":
34
- return `You have uncommitted local changes while the remote has ${res.behind} new commit(s). No changes applied — your local data is intact. Run push_docs to commit and publish your changes, then pull again to merge.`;
44
+ return `You have uncommitted local changes while the remote has ${res.behind} new commit(s). No changes applied — your local data is intact. Run push_docs to commit and publish your changes, then pull again.`;
35
45
  case "conflict":
36
- return `Merge conflict in ${res.conflicted.length} file(s):\n - ${res.conflicted.join("\n - ")}\nEdit these markdown files to resolve, or use resolve_sync_conflict, then push_docs.`;
46
+ return `Conflict in ${res.conflicted.length} file(s):\n - ${res.conflicted.join("\n - ")}\nResolve with resolve_sync_conflict (side "local" keeps this machine version, "remote" keeps the other machine version), or edit these markdown files directly. Then run push_docs.`;
37
47
  }
38
48
  }
39
49
  /** Reindex embeddings for new docs after a successful merge. Best-effort. */
@@ -93,19 +103,46 @@ export async function syncDocs(syncEngine, storageEngine, embeddingEngine, embed
93
103
  return err(`Error syncing docs: ${e instanceof Error ? e.message : String(e)}`);
94
104
  }
95
105
  }
96
- const ResolveConflictArgs = z.object({
97
- strategy: z.enum(["mine", "theirs"]),
106
+ const ResolveConflictArgs = z
107
+ .object({
108
+ side: z.enum(["local", "remote"]).optional(),
109
+ // Legacy spelling. "mine"/"theirs" mirrored git's flags, which invert under
110
+ // a rebase - accepted for compatibility, mapped onto the absolute names.
111
+ strategy: z.enum(["local", "remote", "mine", "theirs"]).optional(),
98
112
  paths: z.array(z.string()).optional(),
113
+ })
114
+ .refine((v) => v.side !== undefined || v.strategy !== undefined, {
115
+ message: 'Provide "side": either "local" or "remote".',
99
116
  });
117
+ function normalizeSide(v) {
118
+ if (v.side)
119
+ return v.side;
120
+ return v.strategy === "remote" || v.strategy === "theirs" ? "remote" : "local";
121
+ }
100
122
  export async function resolveSyncConflict(syncEngine, args) {
101
123
  try {
102
124
  if (!syncEngine.isConfigured())
103
125
  return err(NOT_CONFIGURED);
104
126
  const v = ResolveConflictArgs.parse(args);
105
- const resolved = await syncEngine.resolveConflict(v.strategy, v.paths);
127
+ const side = normalizeSide(v);
128
+ const { resolved, operation } = await syncEngine.resolveConflict(side, v.paths);
106
129
  if (resolved.length === 0)
107
130
  return ok("No conflicted files to resolve.");
108
- return ok(`Resolved ${resolved.length} file(s) keeping "${v.strategy}":\n - ${resolved.join("\n - ")}\nReview and run push_docs to finalize.`);
131
+ const kept = side === "local" ? "the local version (this machine)" : "the incoming remote version";
132
+ const lines = [`Resolved ${resolved.length} file(s) keeping ${kept}:`, ` - ${resolved.join("\n - ")}`];
133
+ // Everything staged means the merge or rebase can be carried to completion
134
+ // right away; leaving it half-finished would block the next sync.
135
+ const finish = await syncEngine.continueOperation();
136
+ if (finish.remaining.length > 0) {
137
+ lines.push(``, `Still conflicted (${finish.remaining.length}):`, ` - ${finish.remaining.join("\n - ")}`, `Resolve these too, then run resolve_sync_conflict or push_docs again.`);
138
+ }
139
+ else if (finish.done && finish.operation !== "none") {
140
+ lines.push(``, `The ${finish.operation} is complete. Run push_docs to publish.`);
141
+ }
142
+ else if (finish.error) {
143
+ lines.push(``, `The ${finish.operation} could not be completed: ${finish.error}`);
144
+ }
145
+ return ok(lines.join("\n"));
109
146
  }
110
147
  catch (e) {
111
148
  return err(`Error resolving conflict: ${e instanceof Error ? e.message : String(e)}`);
@@ -1 +1 @@
1
- export declare const APP_VERSION = "1.0.70";
1
+ export declare const APP_VERSION = "1.0.72";
@@ -1,2 +1,2 @@
1
1
  // Auto-generated version file - do not edit manually
2
- export const APP_VERSION = '1.0.70';
2
+ export const APP_VERSION = '1.0.72';
@@ -1 +1 @@
1
- import{a0 as ln,a1 as an,a2 as y,a3 as tn,a4 as H,a5 as q,a6 as _,a7 as un,a8 as rn,a9 as L,aa as o,ab as B,ac as sn,ad as on,ae as fn}from"./index-Bjc5MOe1.js";function cn(l){return l.innerRadius}function yn(l){return l.outerRadius}function gn(l){return l.startAngle}function dn(l){return l.endAngle}function mn(l){return l&&l.padAngle}function pn(l,h,I,D,v,A,C,a){var O=I-l,i=D-h,n=C-v,d=a-A,u=d*O-n*i;if(!(u*u<y))return u=(n*(h-A)-d*(l-v))/u,[l+u*O,h+u*i]}function W(l,h,I,D,v,A,C){var a=l-I,O=h-D,i=(C?A:-A)/L(a*a+O*O),n=i*O,d=-i*a,u=l+n,s=h+d,f=I+n,c=D+d,F=(u+f)/2,t=(s+c)/2,m=f-u,g=c-s,R=m*m+g*g,T=v-A,P=u*c-f*s,S=(g<0?-1:1)*L(fn(0,T*T*R-P*P)),j=(P*g-m*S)/R,z=(-P*m-g*S)/R,w=(P*g+m*S)/R,p=(-P*m+g*S)/R,x=j-F,e=z-t,r=w-F,G=p-t;return x*x+e*e>r*r+G*G&&(j=w,z=p),{cx:j,cy:z,x01:-n,y01:-d,x11:j*(v/T-1),y11:z*(v/T-1)}}function hn(){var l=cn,h=yn,I=B(0),D=null,v=gn,A=dn,C=mn,a=null,O=ln(i);function i(){var n,d,u=+l.apply(this,arguments),s=+h.apply(this,arguments),f=v.apply(this,arguments)-an,c=A.apply(this,arguments)-an,F=un(c-f),t=c>f;if(a||(a=n=O()),s<u&&(d=s,s=u,u=d),!(s>y))a.moveTo(0,0);else if(F>tn-y)a.moveTo(s*H(f),s*q(f)),a.arc(0,0,s,f,c,!t),u>y&&(a.moveTo(u*H(c),u*q(c)),a.arc(0,0,u,c,f,t));else{var m=f,g=c,R=f,T=c,P=F,S=F,j=C.apply(this,arguments)/2,z=j>y&&(D?+D.apply(this,arguments):L(u*u+s*s)),w=_(un(s-u)/2,+I.apply(this,arguments)),p=w,x=w,e,r;if(z>y){var G=sn(z/u*q(j)),M=sn(z/s*q(j));(P-=G*2)>y?(G*=t?1:-1,R+=G,T-=G):(P=0,R=T=(f+c)/2),(S-=M*2)>y?(M*=t?1:-1,m+=M,g-=M):(S=0,m=g=(f+c)/2)}var J=s*H(m),K=s*q(m),N=u*H(T),Q=u*q(T);if(w>y){var U=s*H(g),V=s*q(g),X=u*H(R),Y=u*q(R),E;if(F<rn)if(E=pn(J,K,X,Y,U,V,N,Q)){var Z=J-E[0],$=K-E[1],b=U-E[0],k=V-E[1],nn=1/q(on((Z*b+$*k)/(L(Z*Z+$*$)*L(b*b+k*k)))/2),en=L(E[0]*E[0]+E[1]*E[1]);p=_(w,(u-en)/(nn-1)),x=_(w,(s-en)/(nn+1))}else p=x=0}S>y?x>y?(e=W(X,Y,J,K,s,x,t),r=W(U,V,N,Q,s,x,t),a.moveTo(e.cx+e.x01,e.cy+e.y01),x<w?a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,s,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),!t),a.arc(r.cx,r.cy,x,o(r.y11,r.x11),o(r.y01,r.x01),!t))):(a.moveTo(J,K),a.arc(0,0,s,m,g,!t)):a.moveTo(J,K),!(u>y)||!(P>y)?a.lineTo(N,Q):p>y?(e=W(N,Q,U,V,u,-p,t),r=W(J,K,X,Y,u,-p,t),a.lineTo(e.cx+e.x01,e.cy+e.y01),p<w?a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,u,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),t),a.arc(r.cx,r.cy,p,o(r.y11,r.x11),o(r.y01,r.x01),!t))):a.arc(0,0,u,T,R,t)}if(a.closePath(),n)return a=null,n+""||null}return i.centroid=function(){var n=(+l.apply(this,arguments)+ +h.apply(this,arguments))/2,d=(+v.apply(this,arguments)+ +A.apply(this,arguments))/2-rn/2;return[H(d)*n,q(d)*n]},i.innerRadius=function(n){return arguments.length?(l=typeof n=="function"?n:B(+n),i):l},i.outerRadius=function(n){return arguments.length?(h=typeof n=="function"?n:B(+n),i):h},i.cornerRadius=function(n){return arguments.length?(I=typeof n=="function"?n:B(+n),i):I},i.padRadius=function(n){return arguments.length?(D=n==null?null:typeof n=="function"?n:B(+n),i):D},i.startAngle=function(n){return arguments.length?(v=typeof n=="function"?n:B(+n),i):v},i.endAngle=function(n){return arguments.length?(A=typeof n=="function"?n:B(+n),i):A},i.padAngle=function(n){return arguments.length?(C=typeof n=="function"?n:B(+n),i):C},i.context=function(n){return arguments.length?(a=n??null,i):a},i}export{hn as d};
1
+ import{a0 as ln,a1 as an,a2 as y,a3 as tn,a4 as H,a5 as q,a6 as _,a7 as un,a8 as rn,a9 as L,aa as o,ab as B,ac as sn,ad as on,ae as fn}from"./index-CdGsMXYw.js";function cn(l){return l.innerRadius}function yn(l){return l.outerRadius}function gn(l){return l.startAngle}function dn(l){return l.endAngle}function mn(l){return l&&l.padAngle}function pn(l,h,I,D,v,A,C,a){var O=I-l,i=D-h,n=C-v,d=a-A,u=d*O-n*i;if(!(u*u<y))return u=(n*(h-A)-d*(l-v))/u,[l+u*O,h+u*i]}function W(l,h,I,D,v,A,C){var a=l-I,O=h-D,i=(C?A:-A)/L(a*a+O*O),n=i*O,d=-i*a,u=l+n,s=h+d,f=I+n,c=D+d,F=(u+f)/2,t=(s+c)/2,m=f-u,g=c-s,R=m*m+g*g,T=v-A,P=u*c-f*s,S=(g<0?-1:1)*L(fn(0,T*T*R-P*P)),j=(P*g-m*S)/R,z=(-P*m-g*S)/R,w=(P*g+m*S)/R,p=(-P*m+g*S)/R,x=j-F,e=z-t,r=w-F,G=p-t;return x*x+e*e>r*r+G*G&&(j=w,z=p),{cx:j,cy:z,x01:-n,y01:-d,x11:j*(v/T-1),y11:z*(v/T-1)}}function hn(){var l=cn,h=yn,I=B(0),D=null,v=gn,A=dn,C=mn,a=null,O=ln(i);function i(){var n,d,u=+l.apply(this,arguments),s=+h.apply(this,arguments),f=v.apply(this,arguments)-an,c=A.apply(this,arguments)-an,F=un(c-f),t=c>f;if(a||(a=n=O()),s<u&&(d=s,s=u,u=d),!(s>y))a.moveTo(0,0);else if(F>tn-y)a.moveTo(s*H(f),s*q(f)),a.arc(0,0,s,f,c,!t),u>y&&(a.moveTo(u*H(c),u*q(c)),a.arc(0,0,u,c,f,t));else{var m=f,g=c,R=f,T=c,P=F,S=F,j=C.apply(this,arguments)/2,z=j>y&&(D?+D.apply(this,arguments):L(u*u+s*s)),w=_(un(s-u)/2,+I.apply(this,arguments)),p=w,x=w,e,r;if(z>y){var G=sn(z/u*q(j)),M=sn(z/s*q(j));(P-=G*2)>y?(G*=t?1:-1,R+=G,T-=G):(P=0,R=T=(f+c)/2),(S-=M*2)>y?(M*=t?1:-1,m+=M,g-=M):(S=0,m=g=(f+c)/2)}var J=s*H(m),K=s*q(m),N=u*H(T),Q=u*q(T);if(w>y){var U=s*H(g),V=s*q(g),X=u*H(R),Y=u*q(R),E;if(F<rn)if(E=pn(J,K,X,Y,U,V,N,Q)){var Z=J-E[0],$=K-E[1],b=U-E[0],k=V-E[1],nn=1/q(on((Z*b+$*k)/(L(Z*Z+$*$)*L(b*b+k*k)))/2),en=L(E[0]*E[0]+E[1]*E[1]);p=_(w,(u-en)/(nn-1)),x=_(w,(s-en)/(nn+1))}else p=x=0}S>y?x>y?(e=W(X,Y,J,K,s,x,t),r=W(U,V,N,Q,s,x,t),a.moveTo(e.cx+e.x01,e.cy+e.y01),x<w?a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,s,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),!t),a.arc(r.cx,r.cy,x,o(r.y11,r.x11),o(r.y01,r.x01),!t))):(a.moveTo(J,K),a.arc(0,0,s,m,g,!t)):a.moveTo(J,K),!(u>y)||!(P>y)?a.lineTo(N,Q):p>y?(e=W(N,Q,U,V,u,-p,t),r=W(J,K,X,Y,u,-p,t),a.lineTo(e.cx+e.x01,e.cy+e.y01),p<w?a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,u,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),t),a.arc(r.cx,r.cy,p,o(r.y11,r.x11),o(r.y01,r.x01),!t))):a.arc(0,0,u,T,R,t)}if(a.closePath(),n)return a=null,n+""||null}return i.centroid=function(){var n=(+l.apply(this,arguments)+ +h.apply(this,arguments))/2,d=(+v.apply(this,arguments)+ +A.apply(this,arguments))/2-rn/2;return[H(d)*n,q(d)*n]},i.innerRadius=function(n){return arguments.length?(l=typeof n=="function"?n:B(+n),i):l},i.outerRadius=function(n){return arguments.length?(h=typeof n=="function"?n:B(+n),i):h},i.cornerRadius=function(n){return arguments.length?(I=typeof n=="function"?n:B(+n),i):I},i.padRadius=function(n){return arguments.length?(D=n==null?null:typeof n=="function"?n:B(+n),i):D},i.startAngle=function(n){return arguments.length?(v=typeof n=="function"?n:B(+n),i):v},i.endAngle=function(n){return arguments.length?(A=typeof n=="function"?n:B(+n),i):A},i.padAngle=function(n){return arguments.length?(C=typeof n=="function"?n:B(+n),i):C},i.context=function(n){return arguments.length?(a=n??null,i):a},i}export{hn as d};