@colbymchenry/codegraph-darwin-x64 0.9.6 → 0.9.8

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 (126) hide show
  1. package/lib/dist/bin/codegraph.js +28 -45
  2. package/lib/dist/bin/codegraph.js.map +1 -1
  3. package/lib/dist/context/formatter.d.ts.map +1 -1
  4. package/lib/dist/context/formatter.js +25 -6
  5. package/lib/dist/context/formatter.js.map +1 -1
  6. package/lib/dist/context/index.d.ts.map +1 -1
  7. package/lib/dist/context/index.js +31 -0
  8. package/lib/dist/context/index.js.map +1 -1
  9. package/lib/dist/db/queries.d.ts +74 -0
  10. package/lib/dist/db/queries.d.ts.map +1 -1
  11. package/lib/dist/db/queries.js +182 -0
  12. package/lib/dist/db/queries.js.map +1 -1
  13. package/lib/dist/extraction/generated-detection.d.ts +30 -0
  14. package/lib/dist/extraction/generated-detection.d.ts.map +1 -0
  15. package/lib/dist/extraction/generated-detection.js +80 -0
  16. package/lib/dist/extraction/generated-detection.js.map +1 -0
  17. package/lib/dist/extraction/grammars.d.ts +10 -0
  18. package/lib/dist/extraction/grammars.d.ts.map +1 -1
  19. package/lib/dist/extraction/grammars.js +13 -0
  20. package/lib/dist/extraction/grammars.js.map +1 -1
  21. package/lib/dist/extraction/index.d.ts.map +1 -1
  22. package/lib/dist/extraction/index.js +21 -6
  23. package/lib/dist/extraction/index.js.map +1 -1
  24. package/lib/dist/extraction/languages/java.d.ts.map +1 -1
  25. package/lib/dist/extraction/languages/java.js +6 -0
  26. package/lib/dist/extraction/languages/java.js.map +1 -1
  27. package/lib/dist/extraction/languages/kotlin.d.ts.map +1 -1
  28. package/lib/dist/extraction/languages/kotlin.js +6 -0
  29. package/lib/dist/extraction/languages/kotlin.js.map +1 -1
  30. package/lib/dist/extraction/tree-sitter-types.d.ts +10 -0
  31. package/lib/dist/extraction/tree-sitter-types.d.ts.map +1 -1
  32. package/lib/dist/extraction/tree-sitter.d.ts +25 -0
  33. package/lib/dist/extraction/tree-sitter.d.ts.map +1 -1
  34. package/lib/dist/extraction/tree-sitter.js +125 -1
  35. package/lib/dist/extraction/tree-sitter.js.map +1 -1
  36. package/lib/dist/extraction/wasm-runtime-flags.d.ts.map +1 -1
  37. package/lib/dist/extraction/wasm-runtime-flags.js +1 -0
  38. package/lib/dist/extraction/wasm-runtime-flags.js.map +1 -1
  39. package/lib/dist/index.d.ts +33 -1
  40. package/lib/dist/index.d.ts.map +1 -1
  41. package/lib/dist/index.js +37 -1
  42. package/lib/dist/index.js.map +1 -1
  43. package/lib/dist/installer/config-writer.d.ts +7 -8
  44. package/lib/dist/installer/config-writer.d.ts.map +1 -1
  45. package/lib/dist/installer/config-writer.js +7 -27
  46. package/lib/dist/installer/config-writer.js.map +1 -1
  47. package/lib/dist/installer/index.d.ts +2 -19
  48. package/lib/dist/installer/index.d.ts.map +1 -1
  49. package/lib/dist/installer/index.js +5 -36
  50. package/lib/dist/installer/index.js.map +1 -1
  51. package/lib/dist/installer/instructions-template.d.ts +11 -21
  52. package/lib/dist/installer/instructions-template.d.ts.map +1 -1
  53. package/lib/dist/installer/instructions-template.js +12 -56
  54. package/lib/dist/installer/instructions-template.js.map +1 -1
  55. package/lib/dist/installer/targets/antigravity.d.ts.map +1 -1
  56. package/lib/dist/installer/targets/antigravity.js +1 -0
  57. package/lib/dist/installer/targets/antigravity.js.map +1 -1
  58. package/lib/dist/installer/targets/claude.d.ts +10 -1
  59. package/lib/dist/installer/targets/claude.d.ts.map +1 -1
  60. package/lib/dist/installer/targets/claude.js +25 -40
  61. package/lib/dist/installer/targets/claude.js.map +1 -1
  62. package/lib/dist/installer/targets/codex.d.ts.map +1 -1
  63. package/lib/dist/installer/targets/codex.js +15 -13
  64. package/lib/dist/installer/targets/codex.js.map +1 -1
  65. package/lib/dist/installer/targets/cursor.d.ts.map +1 -1
  66. package/lib/dist/installer/targets/cursor.js +9 -38
  67. package/lib/dist/installer/targets/cursor.js.map +1 -1
  68. package/lib/dist/installer/targets/gemini.d.ts.map +1 -1
  69. package/lib/dist/installer/targets/gemini.js +15 -13
  70. package/lib/dist/installer/targets/gemini.js.map +1 -1
  71. package/lib/dist/installer/targets/kiro.d.ts.map +1 -1
  72. package/lib/dist/installer/targets/kiro.js +9 -27
  73. package/lib/dist/installer/targets/kiro.js.map +1 -1
  74. package/lib/dist/installer/targets/opencode.d.ts.map +1 -1
  75. package/lib/dist/installer/targets/opencode.js +15 -13
  76. package/lib/dist/installer/targets/opencode.js.map +1 -1
  77. package/lib/dist/installer/targets/types.d.ts +0 -15
  78. package/lib/dist/installer/targets/types.d.ts.map +1 -1
  79. package/lib/dist/mcp/engine.d.ts +6 -1
  80. package/lib/dist/mcp/engine.d.ts.map +1 -1
  81. package/lib/dist/mcp/engine.js +21 -42
  82. package/lib/dist/mcp/engine.js.map +1 -1
  83. package/lib/dist/mcp/index.d.ts +7 -4
  84. package/lib/dist/mcp/index.d.ts.map +1 -1
  85. package/lib/dist/mcp/index.js +46 -39
  86. package/lib/dist/mcp/index.js.map +1 -1
  87. package/lib/dist/mcp/proxy.d.ts +35 -0
  88. package/lib/dist/mcp/proxy.d.ts.map +1 -1
  89. package/lib/dist/mcp/proxy.js +223 -0
  90. package/lib/dist/mcp/proxy.js.map +1 -1
  91. package/lib/dist/mcp/server-instructions.d.ts +1 -1
  92. package/lib/dist/mcp/server-instructions.d.ts.map +1 -1
  93. package/lib/dist/mcp/server-instructions.js +2 -0
  94. package/lib/dist/mcp/server-instructions.js.map +1 -1
  95. package/lib/dist/mcp/session.d.ts +10 -0
  96. package/lib/dist/mcp/session.d.ts.map +1 -1
  97. package/lib/dist/mcp/session.js +7 -5
  98. package/lib/dist/mcp/session.js.map +1 -1
  99. package/lib/dist/mcp/tools.d.ts +39 -1
  100. package/lib/dist/mcp/tools.d.ts.map +1 -1
  101. package/lib/dist/mcp/tools.js +968 -96
  102. package/lib/dist/mcp/tools.js.map +1 -1
  103. package/lib/dist/resolution/callback-synthesizer.d.ts +2 -2
  104. package/lib/dist/resolution/callback-synthesizer.d.ts.map +1 -1
  105. package/lib/dist/resolution/callback-synthesizer.js +395 -29
  106. package/lib/dist/resolution/callback-synthesizer.js.map +1 -1
  107. package/lib/dist/resolution/import-resolver.d.ts +10 -0
  108. package/lib/dist/resolution/import-resolver.d.ts.map +1 -1
  109. package/lib/dist/resolution/import-resolver.js +34 -0
  110. package/lib/dist/resolution/import-resolver.js.map +1 -1
  111. package/lib/dist/resolution/index.d.ts.map +1 -1
  112. package/lib/dist/resolution/index.js +15 -0
  113. package/lib/dist/resolution/index.js.map +1 -1
  114. package/lib/dist/sync/git-hooks.d.ts.map +1 -1
  115. package/lib/dist/sync/git-hooks.js +2 -0
  116. package/lib/dist/sync/git-hooks.js.map +1 -1
  117. package/lib/dist/sync/worktree.d.ts.map +1 -1
  118. package/lib/dist/sync/worktree.js +1 -0
  119. package/lib/dist/sync/worktree.js.map +1 -1
  120. package/lib/node_modules/.package-lock.json +1 -1
  121. package/lib/package.json +1 -1
  122. package/package.json +1 -1
  123. package/lib/dist/installer/claude-md-template.d.ts +0 -14
  124. package/lib/dist/installer/claude-md-template.d.ts.map +0 -1
  125. package/lib/dist/installer/claude-md-template.js +0 -21
  126. package/lib/dist/installer/claude-md-template.js.map +0 -1
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.synthesizeCallbackEdges = synthesizeCallbackEdges;
4
+ const generated_detection_1 = require("../extraction/generated-detection");
5
+ const strip_comments_1 = require("./strip-comments");
4
6
  const REGISTRAR_NAME = /^(on[A-Z]\w*|subscribe|addListener|addEventListener|register|watch|listen|addCallback)$/;
5
7
  const DISPATCHER_NAME = /(emit|trigger|notify|dispatch|fire|publish|flush)/i;
6
8
  const MAX_CALLBACKS_PER_CHANNEL = 40;
@@ -19,6 +21,18 @@ const VUE_HANDLER_RE = /(?:@|v-on:)([a-zA-Z][\w-]*)(?:\.[\w]+)*\s*=\s*"([^"]+)"/
19
21
  // Composable/hook destructure: `const { close: closeSidebar } = useSidebarControl()`.
20
22
  // Captures the destructure body + the called composable; only `use*` calls qualify.
21
23
  const VUE_DESTRUCTURE_RE = /(?:const|let|var)\s*\{([^}]+)\}\s*=\s*(\w+)\s*\(/g;
24
+ // Closure-collection dynamic dispatch (language-agnostic, Swift-first). A method
25
+ // appends a closure to a collection property; another method iterates that
26
+ // property *invoking each element* (`coll.forEach { $0() }` / `{ it() }`). The
27
+ // element-invoke (`$0(` / `it(`) PROVES the collection holds closures, so pairing
28
+ // a dispatcher to same-named registrars (`.append`/`.add`/`.push`/`.insert`,
29
+ // incl. Swift `prop.write { $0.append }`) is high-precision. Cross-file/class by
30
+ // design: Alamofire appends in `DataRequest.validate` but iterates in the base
31
+ // `Request.didCompleteTask` — neither same-file nor same-class pairing reaches it.
32
+ const CC_DISPATCH_RE = /(\w+)\.forEach\s*\{\s*(?:\$0|it)\s*\(/g;
33
+ const CC_APPEND_WRITE_RE = /(\w+)\.write\s*\{\s*\$0(?:\.(\w+))?\.(?:append|add|push|insert)\s*\(/g;
34
+ const CC_APPEND_DIRECT_RE = /(\w+)\.(?:append|add|push|insert)\s*\(/g;
35
+ const CC_FANOUT_CAP = 8; // skip a field name with more dispatchers/registrars than this (too generic to pair confidently)
22
36
  function kebabToPascal(s) {
23
37
  return s.split('-').map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join('');
24
38
  }
@@ -127,6 +141,88 @@ function fieldChannelEdges(queries, ctx) {
127
141
  }
128
142
  return edges;
129
143
  }
144
+ /**
145
+ * Closure-collection dispatch: dispatcher iterates a closure-collection property
146
+ * invoking each element; registrar appends a closure to the same-named property.
147
+ * Emits dispatcher → registrar so a flow reaches the registration site (where the
148
+ * appended closure's body — and its callers — live). High-precision: the
149
+ * dispatcher's element-invoke is the gate (a `.forEach` that does NOT invoke its
150
+ * element is ignored), so a repo with no closure-collection dispatch yields zero
151
+ * edges regardless of how many `.append`/`.push` sites it has.
152
+ *
153
+ * Pairs globally by field name (cross-file/class is required — see Alamofire's
154
+ * base-class `Request.didCompleteTask` iterating `validators` appended by the
155
+ * subclass `DataRequest.validate`), bounded by a fan-out cap so a generic field
156
+ * name shared across unrelated classes can't fan out into noise.
157
+ */
158
+ function closureCollectionEdges(queries, ctx) {
159
+ const candidates = [...queries.getNodesByKind('method'), ...queries.getNodesByKind('function')];
160
+ const dispatchers = new Map(); // field → dispatcher methods + forEach line
161
+ const registrars = new Map(); // field → registrar methods + append line
162
+ const addReg = (field, node, absLine) => {
163
+ if (!field || /^\d+$/.test(field))
164
+ return; // `$0.append` mis-captures the `0`; the write-RE owns that field
165
+ const arr = registrars.get(field) ?? [];
166
+ if (!arr.some((r) => r.node.id === node.id))
167
+ arr.push({ node, line: absLine });
168
+ registrars.set(field, arr);
169
+ };
170
+ for (const m of candidates) {
171
+ const content = ctx.readFile(m.filePath);
172
+ const src = content && sliceLines(content, m.startLine, m.endLine);
173
+ if (!src)
174
+ continue;
175
+ const hasForEach = src.includes('.forEach');
176
+ const hasAppend = src.includes('.append(') || src.includes('.add(') || src.includes('.push(') || src.includes('.insert(');
177
+ if (!hasForEach && !hasAppend)
178
+ continue;
179
+ const lineAt = (idx) => (m.startLine ?? 1) + src.slice(0, idx).split('\n').length - 1;
180
+ if (hasForEach) {
181
+ CC_DISPATCH_RE.lastIndex = 0;
182
+ let d;
183
+ while ((d = CC_DISPATCH_RE.exec(src))) {
184
+ const arr = dispatchers.get(d[1]) ?? [];
185
+ if (!arr.some((n) => n.node.id === m.id))
186
+ arr.push({ node: m, line: lineAt(d.index) });
187
+ dispatchers.set(d[1], arr);
188
+ }
189
+ }
190
+ if (hasAppend) {
191
+ CC_APPEND_WRITE_RE.lastIndex = 0;
192
+ let w;
193
+ while ((w = CC_APPEND_WRITE_RE.exec(src)))
194
+ addReg(w[2] || w[1], m, lineAt(w.index)); // nested `$0.streams` else the `.write` receiver
195
+ CC_APPEND_DIRECT_RE.lastIndex = 0;
196
+ let a;
197
+ while ((a = CC_APPEND_DIRECT_RE.exec(src)))
198
+ addReg(a[1], m, lineAt(a.index));
199
+ }
200
+ }
201
+ const edges = [];
202
+ const seen = new Set();
203
+ for (const [field, disps] of dispatchers) {
204
+ const regs = registrars.get(field);
205
+ if (!regs || regs.length === 0)
206
+ continue;
207
+ if (disps.length > CC_FANOUT_CAP || regs.length > CC_FANOUT_CAP)
208
+ continue; // generic field — can't pair confidently
209
+ for (const disp of disps)
210
+ for (const reg of regs) {
211
+ if (disp.node.id === reg.node.id)
212
+ continue;
213
+ const key = `${disp.node.id}>${reg.node.id}`;
214
+ if (seen.has(key))
215
+ continue;
216
+ seen.add(key);
217
+ edges.push({
218
+ source: disp.node.id, target: reg.node.id, kind: 'calls', line: disp.line,
219
+ provenance: 'heuristic',
220
+ metadata: { synthesizedBy: 'closure-collection', field, registeredAt: `${reg.node.filePath}:${reg.line}` },
221
+ });
222
+ }
223
+ }
224
+ return edges;
225
+ }
130
226
  /** Phase 2: string-keyed EventEmitter channels (on('e', fn) ↔ emit('e')). */
131
227
  function eventEmitterEdges(ctx) {
132
228
  const emitsByEvent = new Map(); // event → dispatcher node ids
@@ -342,7 +438,16 @@ function cppOverrideEdges(queries) {
342
438
  * trace/callees reach the implementation. Over-approximation accepted
343
439
  * (reachability-correct); capped per class, gated to JVM languages.
344
440
  */
345
- const IFACE_OVERRIDE_LANGS = new Set(['java', 'kotlin']);
441
+ // Languages whose static `implements`/`extends` edges should bridge an
442
+ // interface (or abstract base) method to the matching concrete-class method.
443
+ // The set is "languages with explicit nominal subtyping and a single class
444
+ // kind that holds methods" — i.e. the shape this loop expects. Swift and
445
+ // Scala fit shape-wise (Swift `protocol`/`class`, Scala `trait`/`class`)
446
+ // and are added below; their concrete-side nodes can be a `struct` (Swift)
447
+ // or an `object` (Scala) so the loop also iterates those kinds.
448
+ const IFACE_OVERRIDE_LANGS = new Set([
449
+ 'java', 'kotlin', 'csharp', 'typescript', 'javascript', 'swift', 'scala',
450
+ ]);
346
451
  function interfaceOverrideEdges(queries) {
347
452
  const edges = [];
348
453
  const seen = new Set();
@@ -350,46 +455,163 @@ function interfaceOverrideEdges(queries) {
350
455
  .getOutgoingEdges(classId, ['contains'])
351
456
  .map((e) => queries.getNodeById(e.target))
352
457
  .filter((n) => !!n && n.kind === 'method');
353
- for (const cls of queries.getNodesByKind('class')) {
354
- const implMethods = methodsOf(cls.id).filter((n) => IFACE_OVERRIDE_LANGS.has(n.language));
355
- if (implMethods.length === 0)
356
- continue;
357
- for (const sup of queries.getOutgoingEdges(cls.id, ['implements', 'extends'])) {
358
- const base = queries.getNodeById(sup.target);
359
- if (!base || !IFACE_OVERRIDE_LANGS.has(base.language) || base.id === cls.id)
458
+ // Concrete-side kinds vary by language: `class` covers Java / Kotlin /
459
+ // C# / TS / Swift-classes / Scala-classes; `struct` covers Swift value
460
+ // types that conform to protocols. Iterate both.
461
+ const concreteKinds = ['class', 'struct'];
462
+ for (const kind of concreteKinds) {
463
+ for (const cls of queries.getNodesByKind(kind)) {
464
+ const implMethods = methodsOf(cls.id).filter((n) => IFACE_OVERRIDE_LANGS.has(n.language));
465
+ if (implMethods.length === 0)
360
466
  continue;
361
- // Group impl methods by name to handle OVERLOADS: an interface `list()` and
362
- // `list(params)` are distinct nodes and a call may resolve to either, so
363
- // link every base overload every same-name impl overload (keying by name
364
- // alone would drop all but one and miss the resolved overload).
365
- const implByName = new Map();
366
- for (const m of implMethods) {
367
- const arr = implByName.get(m.name);
368
- if (arr)
369
- arr.push(m);
370
- else
371
- implByName.set(m.name, [m]);
467
+ for (const sup of queries.getOutgoingEdges(cls.id, ['implements', 'extends'])) {
468
+ const base = queries.getNodeById(sup.target);
469
+ if (!base || !IFACE_OVERRIDE_LANGS.has(base.language) || base.id === cls.id)
470
+ continue;
471
+ // Group impl methods by name to handle OVERLOADS: an interface `list()` and
472
+ // `list(params)` are distinct nodes and a call may resolve to either, so
473
+ // link every base overload → every same-name impl overload (keying by name
474
+ // alone would drop all but one and miss the resolved overload).
475
+ const implByName = new Map();
476
+ for (const m of implMethods) {
477
+ const arr = implByName.get(m.name);
478
+ if (arr)
479
+ arr.push(m);
480
+ else
481
+ implByName.set(m.name, [m]);
482
+ }
483
+ let added = 0;
484
+ for (const bm of methodsOf(base.id)) {
485
+ if (added >= MAX_CALLBACKS_PER_CHANNEL)
486
+ break;
487
+ for (const m of implByName.get(bm.name) ?? []) {
488
+ if (added >= MAX_CALLBACKS_PER_CHANNEL)
489
+ break;
490
+ if (bm.id === m.id)
491
+ continue;
492
+ const key = `${bm.id}>${m.id}`;
493
+ if (seen.has(key))
494
+ continue;
495
+ seen.add(key);
496
+ edges.push({
497
+ source: bm.id,
498
+ target: m.id,
499
+ kind: 'calls',
500
+ line: bm.startLine,
501
+ provenance: 'heuristic',
502
+ metadata: { synthesizedBy: 'interface-impl', via: m.name, registeredAt: `${m.filePath}:${m.startLine}` },
503
+ });
504
+ added++;
505
+ }
506
+ }
372
507
  }
508
+ }
509
+ }
510
+ return edges;
511
+ }
512
+ /**
513
+ * Go gRPC stub → impl bridge. The protoc-gen-go-grpc codegen emits an
514
+ * `UnimplementedXxxServer` struct in `*_grpc.pb.go` carrying one method
515
+ * per service RPC; the real handler is a hand-written struct in another
516
+ * file (`x/bank/keeper/msg_server.go::msgServer.Send` in cosmos-sdk).
517
+ * Go's structural typing means no `implements` edge exists for our
518
+ * resolver to follow, so `trace("Send","SendCoins")` lands on the
519
+ * empty stub and reports "no path" (validated empirically — the cosmos
520
+ * Q1 r1 trace failure that drove this work).
521
+ *
522
+ * Bridge: for each `UnimplementedXxxServer` whose RPC-method names are
523
+ * a SUBSET of some other Go struct's method names, emit `calls` edges
524
+ * `stub.method → impl.method` (paired by name). Excludes the gRPC
525
+ * internal markers `mustEmbedUnimplementedXxxServer` and
526
+ * `testEmbeddedByValue`, and skips candidate impls that themselves
527
+ * live in a generated file (their `xxxClient` / sibling stubs would
528
+ * otherwise look like impls).
529
+ *
530
+ * Multiple candidates is allowed and capped at MAX_CALLBACKS_PER_CHANNEL —
531
+ * a service often has both a production impl and one or more test
532
+ * mocks; linking to all preserves trace utility without false-favoring.
533
+ *
534
+ * Provenance: `heuristic`, `synthesizedBy: 'go-grpc-stub-impl'`. The
535
+ * stub's source line is the wiring site shown in the trace trail.
536
+ */
537
+ function goGrpcStubImplEdges(queries) {
538
+ const edges = [];
539
+ const seen = new Set();
540
+ const STUB_RE = /^Unimplemented.*Server$/;
541
+ // gRPC internal-helper methods that appear on every Unimplemented*Server;
542
+ // not part of the service contract, so exclude when computing the RPC-method
543
+ // signature used to match impls.
544
+ const isInternalMarker = (n) => n.startsWith('mustEmbed') || n === 'testEmbeddedByValue';
545
+ // Methods directly contained by each Go struct, name-only. Built once.
546
+ const methodNamesByStruct = new Map();
547
+ const methodNodesByStruct = new Map();
548
+ const goStructs = [];
549
+ for (const s of queries.getNodesByKind('struct')) {
550
+ if (s.language !== 'go')
551
+ continue;
552
+ goStructs.push(s);
553
+ const ms = queries
554
+ .getOutgoingEdges(s.id, ['contains'])
555
+ .map((e) => queries.getNodeById(e.target))
556
+ .filter((n) => !!n && n.kind === 'method');
557
+ methodNodesByStruct.set(s.id, ms);
558
+ methodNamesByStruct.set(s.id, new Set(ms.map((m) => m.name)));
559
+ }
560
+ for (const stub of goStructs) {
561
+ if (!STUB_RE.test(stub.name))
562
+ continue;
563
+ // The stub MUST live in a generated file — that's what tells us this is
564
+ // a protoc-emitted scaffold rather than someone naming a struct
565
+ // `UnimplementedXxxServer` by hand. Without this gate we'd also bridge
566
+ // such hand-written structs and create misleading edges.
567
+ if (!(0, generated_detection_1.isGeneratedFile)(stub.filePath))
568
+ continue;
569
+ const stubMethods = (methodNodesByStruct.get(stub.id) ?? []).filter((m) => !isInternalMarker(m.name));
570
+ if (stubMethods.length === 0)
571
+ continue;
572
+ const stubMethodNames = stubMethods.map((m) => m.name);
573
+ for (const cand of goStructs) {
574
+ if (cand.id === stub.id)
575
+ continue;
576
+ // Skip generated-file candidates — they're siblings (msgClient,
577
+ // UnsafeMsgServer, …) whose method sets coincidentally match.
578
+ if ((0, generated_detection_1.isGeneratedFile)(cand.filePath))
579
+ continue;
580
+ const candNames = methodNamesByStruct.get(cand.id);
581
+ if (!candNames)
582
+ continue;
583
+ // Subset: every RPC method must exist on the candidate by name.
584
+ // Signature-level match would tighten this further, but name-match
585
+ // alone already gives one-to-one pairing in real codebases because
586
+ // gRPC method-name sets are highly distinctive (Send + MultiSend +
587
+ // UpdateParams + SetSendEnabled is unique to bank's MsgServer).
588
+ if (!stubMethodNames.every((n) => candNames.has(n)))
589
+ continue;
590
+ const candMethods = methodNodesByStruct.get(cand.id) ?? [];
373
591
  let added = 0;
374
- for (const bm of methodsOf(base.id)) {
592
+ for (const sm of stubMethods) {
375
593
  if (added >= MAX_CALLBACKS_PER_CHANNEL)
376
594
  break;
377
- for (const m of implByName.get(bm.name) ?? []) {
595
+ for (const cm of candMethods) {
378
596
  if (added >= MAX_CALLBACKS_PER_CHANNEL)
379
597
  break;
380
- if (bm.id === m.id)
598
+ if (cm.name !== sm.name)
381
599
  continue;
382
- const key = `${bm.id}>${m.id}`;
600
+ const key = `${sm.id}>${cm.id}`;
383
601
  if (seen.has(key))
384
602
  continue;
385
603
  seen.add(key);
386
604
  edges.push({
387
- source: bm.id,
388
- target: m.id,
605
+ source: sm.id,
606
+ target: cm.id,
389
607
  kind: 'calls',
390
- line: bm.startLine,
608
+ line: sm.startLine,
391
609
  provenance: 'heuristic',
392
- metadata: { synthesizedBy: 'interface-impl', via: m.name, registeredAt: `${m.filePath}:${m.startLine}` },
610
+ metadata: {
611
+ synthesizedBy: 'go-grpc-stub-impl',
612
+ via: cm.name,
613
+ registeredAt: `${cm.filePath}:${cm.startLine}`,
614
+ },
393
615
  });
394
616
  added++;
395
617
  }
@@ -878,14 +1100,153 @@ function mybatisJavaXmlEdges(queries) {
878
1100
  }
879
1101
  return edges;
880
1102
  }
1103
+ /**
1104
+ * Gin middleware chain. Gin runs its entire handler chain through one dynamic
1105
+ * line in `(*Context).Next`:
1106
+ * for c.index < len(c.handlers) { c.handlers[c.index](c); c.index++ }
1107
+ * `c.handlers` is a `HandlersChain` (`[]HandlerFunc`) assembled at registration
1108
+ * time by `combineHandlers` from the funcs passed to `r.Use(...)` /
1109
+ * `r.GET("/path", h...)` / `r.Handle(...)`. Because the call is a computed index
1110
+ * into a runtime-built slice, tree-sitter resolves `c.handlers[c.index](c)` to
1111
+ * NOTHING — so `callees(Next)` is just the `len()` helper and the flow
1112
+ * `ServeHTTP → handleHTTPRequest → Next` dead-ends at the exact symbol the
1113
+ * "how do requests flow through the middleware chain" question is about. The
1114
+ * agent then re-queries Next and falls back to Read/grep (validated: the gin
1115
+ * WITH-arm rabbit-holed on precisely this dead-end).
1116
+ *
1117
+ * Bridge it: find the chain DISPATCHER (a Go method whose body invokes a
1118
+ * `handlers` slice by index) and link it → every HandlerFunc registered via a
1119
+ * gin registration call, so `callees(Next)` and `trace(ServeHTTP, <handler>)`
1120
+ * connect end-to-end. Named handlers only (`gin.Logger()` → `Logger`,
1121
+ * `authMiddleware`); inline closures are anonymous and skipped. Like
1122
+ * react-render / interface-impl this is a deliberate over-approximation —
1123
+ * reachability-correct (any registered handler CAN run for some route), capped,
1124
+ * and gated on the dispatcher existing so it never runs on non-gin Go repos.
1125
+ * Provenance `heuristic`, `synthesizedBy:'gin-middleware-chain'`; `registeredAt`
1126
+ * is the `.Use`/`.GET` site an agent would otherwise grep for.
1127
+ */
1128
+ const GIN_DISPATCH_RE = /\.handlers\s*\[[^\]]*\]\s*\(/; // c.handlers[c.index](c)
1129
+ const GIN_REG_RE = /\.(?:Use|GET|POST|PUT|PATCH|DELETE|OPTIONS|HEAD|Any|Handle)\s*\(/g;
1130
+ /** Balanced `(...)` body starting at the '(' index; null if unbalanced. */
1131
+ function goBalancedArgs(s, openIdx) {
1132
+ let depth = 0;
1133
+ for (let i = openIdx; i < s.length; i++) {
1134
+ const c = s[i];
1135
+ if (c === '(')
1136
+ depth++;
1137
+ else if (c === ')') {
1138
+ depth--;
1139
+ if (depth === 0)
1140
+ return s.slice(openIdx + 1, i);
1141
+ }
1142
+ }
1143
+ return null;
1144
+ }
1145
+ /** Split a top-level comma list, respecting nested () [] {}. */
1146
+ function goSplitArgs(args) {
1147
+ const out = [];
1148
+ let depth = 0, cur = '';
1149
+ for (const c of args) {
1150
+ if (c === '(' || c === '[' || c === '{') {
1151
+ depth++;
1152
+ cur += c;
1153
+ }
1154
+ else if (c === ')' || c === ']' || c === '}') {
1155
+ depth--;
1156
+ cur += c;
1157
+ }
1158
+ else if (c === ',' && depth === 0) {
1159
+ out.push(cur);
1160
+ cur = '';
1161
+ }
1162
+ else
1163
+ cur += c;
1164
+ }
1165
+ if (cur.trim())
1166
+ out.push(cur);
1167
+ return out;
1168
+ }
1169
+ /** Tail ident of a handler arg: `gin.Logger()`→`Logger`, `mw`→`mw`; null for string paths / closures. */
1170
+ function goHandlerIdent(expr) {
1171
+ const cleaned = expr.trim().replace(/\(\s*\)$/, ''); // drop a trailing call ()
1172
+ if (!cleaned || cleaned.startsWith('"') || cleaned.startsWith('`') || cleaned.startsWith('func'))
1173
+ return null;
1174
+ const m = cleaned.match(/(?:\.|^)([A-Za-z_]\w*)$/);
1175
+ return m ? m[1] : null;
1176
+ }
1177
+ function ginMiddlewareChainEdges(queries, ctx) {
1178
+ // 1. Find the chain dispatcher(s): a Go method that invokes a `handlers` slice by index.
1179
+ const dispatchers = queries.getNodesByKind('method').filter((n) => {
1180
+ if (n.language !== 'go')
1181
+ return false;
1182
+ const content = ctx.readFile(n.filePath);
1183
+ const src = content && sliceLines(content, n.startLine, n.endLine);
1184
+ return !!src && GIN_DISPATCH_RE.test(src);
1185
+ });
1186
+ if (dispatchers.length === 0)
1187
+ return []; // not a gin repo — bail
1188
+ // 2. Collect handler identifiers registered via gin registration calls
1189
+ // (.Use / .GET / … / .Handle). String args (paths/methods) and inline
1190
+ // closures are dropped by goHandlerIdent; the rest are HandlerFuncs.
1191
+ const registered = new Map(); // name → registeredAt (file:line)
1192
+ for (const file of ctx.getAllFiles()) {
1193
+ if (!file.endsWith('.go'))
1194
+ continue;
1195
+ const content = ctx.readFile(file);
1196
+ if (!content || (!content.includes('.Use(') && !/\.(?:GET|POST|PUT|PATCH|DELETE|OPTIONS|HEAD|Any|Handle)\(/.test(content)))
1197
+ continue;
1198
+ const safe = (0, strip_comments_1.stripCommentsForRegex)(content, 'go');
1199
+ GIN_REG_RE.lastIndex = 0;
1200
+ let m;
1201
+ while ((m = GIN_REG_RE.exec(safe))) {
1202
+ const parenIdx = m.index + m[0].length - 1;
1203
+ const argStr = goBalancedArgs(safe, parenIdx);
1204
+ if (!argStr)
1205
+ continue;
1206
+ const line = safe.slice(0, m.index).split('\n').length;
1207
+ for (const arg of goSplitArgs(argStr)) {
1208
+ const name = goHandlerIdent(arg);
1209
+ if (name && !registered.has(name))
1210
+ registered.set(name, `${file}:${line}`);
1211
+ }
1212
+ }
1213
+ }
1214
+ if (registered.size === 0)
1215
+ return [];
1216
+ // 3. Link each dispatcher → each registered handler node (dedup, capped).
1217
+ const edges = [];
1218
+ const seen = new Set();
1219
+ for (const disp of dispatchers) {
1220
+ let added = 0;
1221
+ for (const [name, registeredAt] of registered) {
1222
+ if (added >= MAX_CALLBACKS_PER_CHANNEL)
1223
+ break;
1224
+ const handler = ctx.getNodesByName(name).find((n) => (n.kind === 'function' || n.kind === 'method') && n.language === 'go');
1225
+ if (!handler || handler.id === disp.id)
1226
+ continue;
1227
+ const key = `${disp.id}>${handler.id}`;
1228
+ if (seen.has(key))
1229
+ continue;
1230
+ seen.add(key);
1231
+ edges.push({
1232
+ source: disp.id, target: handler.id, kind: 'calls', line: disp.startLine,
1233
+ provenance: 'heuristic',
1234
+ metadata: { synthesizedBy: 'gin-middleware-chain', via: name, registeredAt },
1235
+ });
1236
+ added++;
1237
+ }
1238
+ }
1239
+ return edges;
1240
+ }
881
1241
  /**
882
1242
  * Synthesize dispatcher→callback edges (field observers + EventEmitters +
883
1243
  * React re-render + JSX children + Vue templates + RN event channel +
884
- * Fabric native-impl + MyBatis Java↔XML). Returns the count added. Never
885
- * throws into indexing — callers wrap in try/catch.
1244
+ * Fabric native-impl + MyBatis Java↔XML + Gin middleware chain). Returns the
1245
+ * count added. Never throws into indexing — callers wrap in try/catch.
886
1246
  */
887
1247
  function synthesizeCallbackEdges(queries, ctx) {
888
1248
  const fieldEdges = fieldChannelEdges(queries, ctx);
1249
+ const closureCollEdges = closureCollectionEdges(queries, ctx);
889
1250
  const emitterEdges = eventEmitterEdges(ctx);
890
1251
  const renderEdges = reactRenderEdges(queries, ctx);
891
1252
  const jsxEdges = reactJsxChildEdges(ctx);
@@ -893,13 +1254,16 @@ function synthesizeCallbackEdges(queries, ctx) {
893
1254
  const flutterEdges = flutterBuildEdges(queries, ctx);
894
1255
  const cppEdges = cppOverrideEdges(queries);
895
1256
  const ifaceEdges = interfaceOverrideEdges(queries);
1257
+ const goGrpcEdges = goGrpcStubImplEdges(queries);
896
1258
  const rnEventEdgesList = rnEventEdges(ctx);
897
1259
  const fabricNativeEdges = fabricNativeImplEdges(ctx);
898
1260
  const mybatisEdges = mybatisJavaXmlEdges(queries);
1261
+ const ginEdges = ginMiddlewareChainEdges(queries, ctx);
899
1262
  const merged = [];
900
1263
  const seen = new Set();
901
1264
  for (const e of [
902
1265
  ...fieldEdges,
1266
+ ...closureCollEdges,
903
1267
  ...emitterEdges,
904
1268
  ...renderEdges,
905
1269
  ...jsxEdges,
@@ -907,9 +1271,11 @@ function synthesizeCallbackEdges(queries, ctx) {
907
1271
  ...flutterEdges,
908
1272
  ...cppEdges,
909
1273
  ...ifaceEdges,
1274
+ ...goGrpcEdges,
910
1275
  ...rnEventEdgesList,
911
1276
  ...fabricNativeEdges,
912
1277
  ...mybatisEdges,
1278
+ ...ginEdges,
913
1279
  ]) {
914
1280
  const key = `${e.source}>${e.target}`;
915
1281
  if (seen.has(key))