@colbymchenry/codegraph-win32-x64 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. package/lib/dist/bin/codegraph.d.ts +1 -1
  2. package/lib/dist/bin/codegraph.d.ts.map +1 -1
  3. package/lib/dist/bin/codegraph.js +232 -61
  4. package/lib/dist/bin/codegraph.js.map +1 -1
  5. package/lib/dist/bin/command-supervision.d.ts.map +1 -1
  6. package/lib/dist/bin/command-supervision.js +4 -1
  7. package/lib/dist/bin/command-supervision.js.map +1 -1
  8. package/lib/dist/db/migrations.d.ts +1 -1
  9. package/lib/dist/db/migrations.d.ts.map +1 -1
  10. package/lib/dist/db/migrations.js +19 -1
  11. package/lib/dist/db/migrations.js.map +1 -1
  12. package/lib/dist/db/queries.d.ts +51 -0
  13. package/lib/dist/db/queries.d.ts.map +1 -1
  14. package/lib/dist/db/queries.js +141 -0
  15. package/lib/dist/db/queries.js.map +1 -1
  16. package/lib/dist/db/schema.sql +19 -0
  17. package/lib/dist/directory.d.ts +9 -5
  18. package/lib/dist/directory.d.ts.map +1 -1
  19. package/lib/dist/directory.js +210 -19
  20. package/lib/dist/directory.js.map +1 -1
  21. package/lib/dist/extraction/cfml-extractor.d.ts +107 -0
  22. package/lib/dist/extraction/cfml-extractor.d.ts.map +1 -0
  23. package/lib/dist/extraction/cfml-extractor.js +494 -0
  24. package/lib/dist/extraction/cfml-extractor.js.map +1 -0
  25. package/lib/dist/extraction/grammars.d.ts +9 -0
  26. package/lib/dist/extraction/grammars.d.ts.map +1 -1
  27. package/lib/dist/extraction/grammars.js +115 -3
  28. package/lib/dist/extraction/grammars.js.map +1 -1
  29. package/lib/dist/extraction/index.d.ts +46 -1
  30. package/lib/dist/extraction/index.d.ts.map +1 -1
  31. package/lib/dist/extraction/index.js +254 -3
  32. package/lib/dist/extraction/index.js.map +1 -1
  33. package/lib/dist/extraction/languages/arkts.d.ts +3 -0
  34. package/lib/dist/extraction/languages/arkts.d.ts.map +1 -0
  35. package/lib/dist/extraction/languages/arkts.js +127 -0
  36. package/lib/dist/extraction/languages/arkts.js.map +1 -0
  37. package/lib/dist/extraction/languages/c-cpp.d.ts +42 -0
  38. package/lib/dist/extraction/languages/c-cpp.d.ts.map +1 -1
  39. package/lib/dist/extraction/languages/c-cpp.js +369 -4
  40. package/lib/dist/extraction/languages/c-cpp.js.map +1 -1
  41. package/lib/dist/extraction/languages/cfquery.d.ts +12 -0
  42. package/lib/dist/extraction/languages/cfquery.d.ts.map +1 -0
  43. package/lib/dist/extraction/languages/cfquery.js +28 -0
  44. package/lib/dist/extraction/languages/cfquery.js.map +1 -0
  45. package/lib/dist/extraction/languages/cfscript.d.ts +3 -0
  46. package/lib/dist/extraction/languages/cfscript.d.ts.map +1 -0
  47. package/lib/dist/extraction/languages/cfscript.js +73 -0
  48. package/lib/dist/extraction/languages/cfscript.js.map +1 -0
  49. package/lib/dist/extraction/languages/cobol.d.ts +33 -0
  50. package/lib/dist/extraction/languages/cobol.d.ts.map +1 -0
  51. package/lib/dist/extraction/languages/cobol.js +499 -0
  52. package/lib/dist/extraction/languages/cobol.js.map +1 -0
  53. package/lib/dist/extraction/languages/erlang.d.ts +3 -0
  54. package/lib/dist/extraction/languages/erlang.d.ts.map +1 -0
  55. package/lib/dist/extraction/languages/erlang.js +350 -0
  56. package/lib/dist/extraction/languages/erlang.js.map +1 -0
  57. package/lib/dist/extraction/languages/index.d.ts.map +1 -1
  58. package/lib/dist/extraction/languages/index.js +18 -0
  59. package/lib/dist/extraction/languages/index.js.map +1 -1
  60. package/lib/dist/extraction/languages/nix.d.ts +3 -0
  61. package/lib/dist/extraction/languages/nix.d.ts.map +1 -0
  62. package/lib/dist/extraction/languages/nix.js +294 -0
  63. package/lib/dist/extraction/languages/nix.js.map +1 -0
  64. package/lib/dist/extraction/languages/solidity.d.ts +3 -0
  65. package/lib/dist/extraction/languages/solidity.d.ts.map +1 -0
  66. package/lib/dist/extraction/languages/solidity.js +293 -0
  67. package/lib/dist/extraction/languages/solidity.js.map +1 -0
  68. package/lib/dist/extraction/languages/terraform.d.ts +3 -0
  69. package/lib/dist/extraction/languages/terraform.d.ts.map +1 -0
  70. package/lib/dist/extraction/languages/terraform.js +641 -0
  71. package/lib/dist/extraction/languages/terraform.js.map +1 -0
  72. package/lib/dist/extraction/languages/vbnet.d.ts +11 -0
  73. package/lib/dist/extraction/languages/vbnet.d.ts.map +1 -0
  74. package/lib/dist/extraction/languages/vbnet.js +141 -0
  75. package/lib/dist/extraction/languages/vbnet.js.map +1 -0
  76. package/lib/dist/extraction/mybatis-extractor.d.ts +30 -10
  77. package/lib/dist/extraction/mybatis-extractor.d.ts.map +1 -1
  78. package/lib/dist/extraction/mybatis-extractor.js +140 -38
  79. package/lib/dist/extraction/mybatis-extractor.js.map +1 -1
  80. package/lib/dist/extraction/tree-sitter-helpers.d.ts.map +1 -1
  81. package/lib/dist/extraction/tree-sitter-helpers.js +1 -0
  82. package/lib/dist/extraction/tree-sitter-helpers.js.map +1 -1
  83. package/lib/dist/extraction/tree-sitter-types.d.ts +3 -1
  84. package/lib/dist/extraction/tree-sitter-types.d.ts.map +1 -1
  85. package/lib/dist/extraction/tree-sitter.d.ts +38 -0
  86. package/lib/dist/extraction/tree-sitter.d.ts.map +1 -1
  87. package/lib/dist/extraction/tree-sitter.js +753 -10
  88. package/lib/dist/extraction/tree-sitter.js.map +1 -1
  89. package/lib/dist/extraction/wasm/tree-sitter-arkts.wasm +0 -0
  90. package/lib/dist/extraction/wasm/tree-sitter-cfml.wasm +0 -0
  91. package/lib/dist/extraction/wasm/tree-sitter-cfquery.wasm +0 -0
  92. package/lib/dist/extraction/wasm/tree-sitter-cfscript.wasm +0 -0
  93. package/lib/dist/extraction/wasm/tree-sitter-cobol.wasm +0 -0
  94. package/lib/dist/extraction/wasm/tree-sitter-erlang.wasm +0 -0
  95. package/lib/dist/extraction/wasm/tree-sitter-nix.wasm +0 -0
  96. package/lib/dist/extraction/wasm/tree-sitter-terraform.wasm +0 -0
  97. package/lib/dist/extraction/wasm/tree-sitter-vbnet.wasm +0 -0
  98. package/lib/dist/index.d.ts +63 -1
  99. package/lib/dist/index.d.ts.map +1 -1
  100. package/lib/dist/index.js +306 -2
  101. package/lib/dist/index.js.map +1 -1
  102. package/lib/dist/installer/index.d.ts.map +1 -1
  103. package/lib/dist/installer/index.js +3 -1
  104. package/lib/dist/installer/index.js.map +1 -1
  105. package/lib/dist/mcp/daemon.d.ts +25 -3
  106. package/lib/dist/mcp/daemon.d.ts.map +1 -1
  107. package/lib/dist/mcp/daemon.js +39 -7
  108. package/lib/dist/mcp/daemon.js.map +1 -1
  109. package/lib/dist/mcp/dynamic-boundaries.d.ts.map +1 -1
  110. package/lib/dist/mcp/dynamic-boundaries.js +2 -1
  111. package/lib/dist/mcp/dynamic-boundaries.js.map +1 -1
  112. package/lib/dist/mcp/early-ppid.d.ts +26 -0
  113. package/lib/dist/mcp/early-ppid.d.ts.map +1 -0
  114. package/lib/dist/mcp/early-ppid.js +29 -0
  115. package/lib/dist/mcp/early-ppid.js.map +1 -0
  116. package/lib/dist/mcp/index.d.ts.map +1 -1
  117. package/lib/dist/mcp/index.js +21 -4
  118. package/lib/dist/mcp/index.js.map +1 -1
  119. package/lib/dist/mcp/proxy.d.ts.map +1 -1
  120. package/lib/dist/mcp/proxy.js +21 -3
  121. package/lib/dist/mcp/proxy.js.map +1 -1
  122. package/lib/dist/mcp/startup-handshake.d.ts +44 -0
  123. package/lib/dist/mcp/startup-handshake.d.ts.map +1 -0
  124. package/lib/dist/mcp/startup-handshake.js +73 -0
  125. package/lib/dist/mcp/startup-handshake.js.map +1 -0
  126. package/lib/dist/mcp/tools.d.ts +22 -0
  127. package/lib/dist/mcp/tools.d.ts.map +1 -1
  128. package/lib/dist/mcp/tools.js +74 -9
  129. package/lib/dist/mcp/tools.js.map +1 -1
  130. package/lib/dist/project-config.d.ts +38 -0
  131. package/lib/dist/project-config.d.ts.map +1 -1
  132. package/lib/dist/project-config.js +101 -2
  133. package/lib/dist/project-config.js.map +1 -1
  134. package/lib/dist/resolution/callback-synthesizer.d.ts.map +1 -1
  135. package/lib/dist/resolution/callback-synthesizer.js +672 -0
  136. package/lib/dist/resolution/callback-synthesizer.js.map +1 -1
  137. package/lib/dist/resolution/frameworks/cics.d.ts +20 -0
  138. package/lib/dist/resolution/frameworks/cics.d.ts.map +1 -0
  139. package/lib/dist/resolution/frameworks/cics.js +90 -0
  140. package/lib/dist/resolution/frameworks/cics.js.map +1 -0
  141. package/lib/dist/resolution/frameworks/index.d.ts.map +1 -1
  142. package/lib/dist/resolution/frameworks/index.js +6 -0
  143. package/lib/dist/resolution/frameworks/index.js.map +1 -1
  144. package/lib/dist/resolution/frameworks/java.d.ts.map +1 -1
  145. package/lib/dist/resolution/frameworks/java.js +14 -6
  146. package/lib/dist/resolution/frameworks/java.js.map +1 -1
  147. package/lib/dist/resolution/frameworks/terraform.d.ts +38 -0
  148. package/lib/dist/resolution/frameworks/terraform.d.ts.map +1 -0
  149. package/lib/dist/resolution/frameworks/terraform.js +277 -0
  150. package/lib/dist/resolution/frameworks/terraform.js.map +1 -0
  151. package/lib/dist/resolution/import-resolver.d.ts +7 -0
  152. package/lib/dist/resolution/import-resolver.d.ts.map +1 -1
  153. package/lib/dist/resolution/import-resolver.js +129 -4
  154. package/lib/dist/resolution/import-resolver.js.map +1 -1
  155. package/lib/dist/resolution/index.d.ts +39 -6
  156. package/lib/dist/resolution/index.d.ts.map +1 -1
  157. package/lib/dist/resolution/index.js +287 -50
  158. package/lib/dist/resolution/index.js.map +1 -1
  159. package/lib/dist/resolution/name-matcher.d.ts +0 -3
  160. package/lib/dist/resolution/name-matcher.d.ts.map +1 -1
  161. package/lib/dist/resolution/name-matcher.js +225 -33
  162. package/lib/dist/resolution/name-matcher.js.map +1 -1
  163. package/lib/dist/resolution/strip-comments.d.ts +1 -1
  164. package/lib/dist/resolution/strip-comments.d.ts.map +1 -1
  165. package/lib/dist/resolution/strip-comments.js +49 -0
  166. package/lib/dist/resolution/strip-comments.js.map +1 -1
  167. package/lib/dist/resolution/types.d.ts +20 -0
  168. package/lib/dist/resolution/types.d.ts.map +1 -1
  169. package/lib/dist/resolution/workspace-packages.d.ts +10 -0
  170. package/lib/dist/resolution/workspace-packages.d.ts.map +1 -1
  171. package/lib/dist/resolution/workspace-packages.js +142 -4
  172. package/lib/dist/resolution/workspace-packages.js.map +1 -1
  173. package/lib/dist/search/identifier-segments.d.ts +60 -0
  174. package/lib/dist/search/identifier-segments.d.ts.map +1 -0
  175. package/lib/dist/search/identifier-segments.js +176 -0
  176. package/lib/dist/search/identifier-segments.js.map +1 -0
  177. package/lib/dist/sync/git-hooks.d.ts.map +1 -1
  178. package/lib/dist/sync/git-hooks.js +2 -0
  179. package/lib/dist/sync/git-hooks.js.map +1 -1
  180. package/lib/dist/sync/watcher.d.ts +10 -5
  181. package/lib/dist/sync/watcher.d.ts.map +1 -1
  182. package/lib/dist/sync/watcher.js +51 -14
  183. package/lib/dist/sync/watcher.js.map +1 -1
  184. package/lib/dist/sync/worktree.d.ts.map +1 -1
  185. package/lib/dist/sync/worktree.js +5 -0
  186. package/lib/dist/sync/worktree.js.map +1 -1
  187. package/lib/dist/types.d.ts +19 -1
  188. package/lib/dist/types.d.ts.map +1 -1
  189. package/lib/dist/types.js +10 -0
  190. package/lib/dist/types.js.map +1 -1
  191. package/lib/node_modules/.package-lock.json +1 -1
  192. package/lib/package.json +1 -1
  193. package/package.json +1 -1
  194. package/lib/dist/reasoning/config.d.ts +0 -45
  195. package/lib/dist/reasoning/config.d.ts.map +0 -1
  196. package/lib/dist/reasoning/config.js +0 -171
  197. package/lib/dist/reasoning/config.js.map +0 -1
  198. package/lib/dist/reasoning/credentials.d.ts +0 -5
  199. package/lib/dist/reasoning/credentials.d.ts.map +0 -1
  200. package/lib/dist/reasoning/credentials.js +0 -83
  201. package/lib/dist/reasoning/credentials.js.map +0 -1
  202. package/lib/dist/reasoning/login.d.ts +0 -21
  203. package/lib/dist/reasoning/login.d.ts.map +0 -1
  204. package/lib/dist/reasoning/login.js +0 -85
  205. package/lib/dist/reasoning/login.js.map +0 -1
  206. package/lib/dist/reasoning/reasoner.d.ts +0 -43
  207. package/lib/dist/reasoning/reasoner.d.ts.map +0 -1
  208. package/lib/dist/reasoning/reasoner.js +0 -308
  209. package/lib/dist/reasoning/reasoner.js.map +0 -1
@@ -430,6 +430,270 @@ function flutterBuildEdges(queries, ctx) {
430
430
  }
431
431
  return edges;
432
432
  }
433
+ /**
434
+ * Reactive ArkUI property decorators: assigning a property carrying one of
435
+ * these re-runs the owning struct's `build()`. Covers both state models —
436
+ * V1 (`@Component`: State/Prop/Link/Provide/Consume/Storage*) and V2
437
+ * (`@ComponentV2`: Local/Provider/Consumer; `@Param` is read-only in V2 so
438
+ * the assignment gate never fires on it, and `@Trace` lives on `@ObservedV2`
439
+ * data classes, not struct properties).
440
+ */
441
+ const ARKUI_REACTIVE_DECORATORS = new Set([
442
+ 'State', 'Prop', 'Link', 'Provide', 'Consume', 'StorageLink', 'StorageProp',
443
+ 'LocalStorageLink', 'LocalStorageProp', 'ObjectLink',
444
+ 'Local', 'Provider', 'Consumer',
445
+ ]);
446
+ /** ArkUI-observed array mutators — `this.todos.push(x)` re-renders like an assignment. */
447
+ const ARKUI_ARRAY_MUTATORS = 'push|pop|shift|unshift|splice|sort|reverse|fill';
448
+ /**
449
+ * Phase 4b-ets: ArkUI state → build (the ArkTS analog of react-render /
450
+ * flutter-build). Assigning a reactive-decorated property (`@State count`,
451
+ * `@Link selected`, …) re-runs the `@Component struct`'s `build()`, but that
452
+ * hop is framework-internal — no static edge — so "onClick → markAllDone →
453
+ * this.todos = […] → rebuilt list" dead-ends at the assignment. Bridge it:
454
+ * for each arkts struct with a `build()` method and at least one reactive
455
+ * property, link every sibling method whose body ASSIGNS (or array-mutates)
456
+ * one of those properties → `build`. Assignment-gated on the struct's OWN
457
+ * reactive property names — a method that merely reads state, or a struct
458
+ * with no reactive properties, gets nothing (this is the precision line the
459
+ * all-sibling-methods design would erase).
460
+ */
461
+ function arkuiStateBuildEdges(queries, ctx) {
462
+ const edges = [];
463
+ const seen = new Set();
464
+ for (const struct of queries.getNodesByKind('struct')) {
465
+ if (struct.language !== 'arkts')
466
+ continue;
467
+ const children = queries.getOutgoingEdges(struct.id, ['contains'])
468
+ .map((e) => queries.getNodeById(e.target))
469
+ .filter((n) => !!n);
470
+ const build = children.find((n) => n.kind === 'method' && n.name === 'build');
471
+ if (!build)
472
+ continue;
473
+ const reactiveProps = children.filter((n) => n.kind === 'property' && (n.decorators ?? []).some((d) => ARKUI_REACTIVE_DECORATORS.has(d)));
474
+ if (reactiveProps.length === 0)
475
+ continue;
476
+ const propAlternation = reactiveProps
477
+ .map((p) => p.name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
478
+ .join('|');
479
+ // `this.count = …` / `+=` / `++` / `--` / `this.todos.push(…)`. The
480
+ // `=(?!=)` keeps `this.done == x` comparisons out.
481
+ const mutationRe = new RegExp(`this\\.(?:${propAlternation})\\s*(?:=(?!=)|\\+\\+|--|[+\\-*/%&|^]=|\\.(?:${ARKUI_ARRAY_MUTATORS})\\s*\\()`);
482
+ let added = 0;
483
+ for (const m of children) {
484
+ if (added >= MAX_CALLBACKS_PER_CHANNEL)
485
+ break;
486
+ if (m.kind !== 'method' || m.id === build.id)
487
+ continue;
488
+ const content = ctx.readFile(m.filePath);
489
+ const src = content && sliceLines(content, m.startLine, m.endLine);
490
+ if (!src || !mutationRe.test((0, strip_comments_1.stripCommentsForRegex)(src, 'typescript')))
491
+ continue;
492
+ const key = `${m.id}>${build.id}`;
493
+ if (seen.has(key))
494
+ continue;
495
+ seen.add(key);
496
+ edges.push({
497
+ source: m.id, target: build.id, kind: 'calls', line: m.startLine,
498
+ provenance: 'heuristic',
499
+ metadata: { synthesizedBy: 'arkui-state', via: 'state assignment', registeredAt: `${build.filePath}:${build.startLine}` },
500
+ });
501
+ added++;
502
+ }
503
+ }
504
+ return edges;
505
+ }
506
+ /** Emit/subscribe call sites of HarmonyOS's `@ohos.events.emitter` bus. */
507
+ const ARKUI_EMITTER_CALL_RE = /\bemitter\s*\.\s*(emit|on|once)\s*\(\s*([A-Za-z_$][\w$.]*|\{[^)]{0,120}?\beventId\s*:\s*[^,}]+[^)]*?\})/g;
508
+ /** Cap per event bucket — a generic key with many parties is dynamic routing, not a static pair. */
509
+ const ARKUI_EMITTER_FANOUT_CAP = 8;
510
+ /**
511
+ * Phase 4b-ets2: HarmonyOS `@ohos.events.emitter` bridge. The cross-component
512
+ * bus — `emitter.emit(eventId)` fires `emitter.on(eventId, cb)` — is
513
+ * framework-internal, so an order flow riding it (OrangeShopping's
514
+ * add-to-cart) dead-ends at the emit. Link emit-site enclosing
515
+ * function/method → on/once-site enclosing function/method when both
516
+ * reference the SAME statically-recoverable event key.
517
+ *
518
+ * Key recovery, per call site (comment-stripped enclosing-file source): the
519
+ * first argument is an `{ eventId: K }` literal, a `Names.Dotted` constant, or
520
+ * a local whose same-file declaration is `new EventsId(K)` / `= K` — chase one
521
+ * level. Precision scoping learned from the samples monorepo (thousands of
522
+ * unrelated samples, most using eventId 1): NUMERIC keys pair within the same
523
+ * FILE only; NAMED keys pair within the same workspace module directory (or
524
+ * the whole project when it declares no modules — the single-app case), both
525
+ * behind a fan-out cap. Inline `on(id, (e) => {…})` arrows need no special
526
+ * handling — their bodies' calls already attribute to the registering method,
527
+ * so targeting that method keeps the chain connected.
528
+ */
529
+ function arkuiEmitterEdges(ctx) {
530
+ // bucket key -> emit sites / handler sites
531
+ const emits = new Map();
532
+ const handlers = new Map();
533
+ const moduleDirs = (() => {
534
+ const ws = ctx.getWorkspacePackages?.();
535
+ return ws ? [...new Set(ws.byName.values())].sort((a, b) => b.length - a.length) : [];
536
+ })();
537
+ const moduleScopeOf = (file) => {
538
+ for (const dir of moduleDirs) {
539
+ if (file === dir || file.startsWith(dir + '/'))
540
+ return dir;
541
+ }
542
+ return '';
543
+ };
544
+ for (const file of ctx.getAllFiles()) {
545
+ if (!file.endsWith('.ets'))
546
+ continue;
547
+ const content = ctx.readFile(file);
548
+ if (!content || !content.includes('emitter.'))
549
+ continue;
550
+ const safe = (0, strip_comments_1.stripCommentsForRegex)(content, 'typescript');
551
+ const nodes = ctx.getNodesInFile(file)
552
+ .filter((n) => n.kind === 'method' || n.kind === 'function');
553
+ ARKUI_EMITTER_CALL_RE.lastIndex = 0;
554
+ let m;
555
+ while ((m = ARKUI_EMITTER_CALL_RE.exec(safe))) {
556
+ const verb = m[1];
557
+ const arg = m[2].trim();
558
+ const line = safe.slice(0, m.index).split('\n').length;
559
+ const encl = nodes
560
+ .filter((n) => n.startLine <= line && n.endLine >= line)
561
+ .sort((a, b) => (a.endLine - a.startLine) - (b.endLine - b.startLine))[0];
562
+ if (!encl)
563
+ continue;
564
+ // Recover the event key from the first argument.
565
+ let key = null;
566
+ const idLit = arg.startsWith('{') ? arg.match(/\beventId\s*:\s*([\w$.]+)/)?.[1] : undefined;
567
+ const token = idLit ?? arg;
568
+ if (token !== undefined) {
569
+ if (/^\d+$/.test(token)) {
570
+ key = `num:${file}:${token}`; // numeric: same-file only
571
+ }
572
+ else if (token.includes('.')) {
573
+ key = `name:${moduleScopeOf(file)}:${token}`;
574
+ }
575
+ else {
576
+ // Local variable — chase its same-file declaration one level:
577
+ // `let x = new EventsId(K)` / `const x = K`.
578
+ const declRe = new RegExp(`\\b${token.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b\\s*(?::[^=\\n]+)?=\\s*(?:new\\s+[\\w$.]+\\(\\s*([^)\\n]+?)\\s*\\)|([\\w$.]+))`);
579
+ const decl = safe.match(declRe);
580
+ const inner = (decl?.[1] ?? decl?.[2])?.trim();
581
+ if (inner && /^\d+$/.test(inner))
582
+ key = `num:${file}:${inner}`;
583
+ else if (inner && /^[\w$.]+$/.test(inner))
584
+ key = `name:${moduleScopeOf(file)}:${inner}`;
585
+ }
586
+ }
587
+ if (!key)
588
+ continue;
589
+ const site = { nodeId: encl.id, file, line };
590
+ if (verb === 'emit') {
591
+ (emits.get(key) ?? emits.set(key, []).get(key)).push(site);
592
+ }
593
+ else {
594
+ (handlers.get(key) ?? handlers.set(key, []).get(key)).push(site);
595
+ }
596
+ }
597
+ }
598
+ const edges = [];
599
+ const seen = new Set();
600
+ for (const [key, emitSites] of emits) {
601
+ const handlerSites = handlers.get(key);
602
+ if (!handlerSites)
603
+ continue;
604
+ if (emitSites.length > ARKUI_EMITTER_FANOUT_CAP || handlerSites.length > ARKUI_EMITTER_FANOUT_CAP)
605
+ continue;
606
+ const eventLabel = key.slice(key.lastIndexOf(':') + 1);
607
+ for (const e of emitSites)
608
+ for (const h of handlerSites) {
609
+ if (e.nodeId === h.nodeId)
610
+ continue;
611
+ const dedupe = `${e.nodeId}>${h.nodeId}`;
612
+ if (seen.has(dedupe))
613
+ continue;
614
+ seen.add(dedupe);
615
+ edges.push({
616
+ source: e.nodeId, target: h.nodeId, kind: 'calls', line: e.line,
617
+ provenance: 'heuristic',
618
+ metadata: { synthesizedBy: 'arkui-emitter', event: eventLabel, registeredAt: `${h.file}:${h.line}` },
619
+ });
620
+ }
621
+ }
622
+ return edges;
623
+ }
624
+ /** `router.pushUrl({ url: 'pages/Detail' })` / replaceUrl — literal urls only. */
625
+ const ARKUI_ROUTER_RE = /\brouter\s*\.\s*(?:pushUrl|replaceUrl)\s*\(\s*\{[^)]{0,200}?\burl\s*:\s*['"]([\w\-./]+)['"]/g;
626
+ /**
627
+ * Phase 4b-ets3: HarmonyOS page navigation. `router.pushUrl({ url:
628
+ * 'pages/Detail' })` reaches the `@Entry struct` of
629
+ * `<module>/src/main/ets/pages/Detail.ets`, but the hop is a string — no
630
+ * static edge — so "tap → openDetail → ???" ends at the router call. Bridge
631
+ * literal urls to the page struct: the url resolves against the standard
632
+ * `src/main/ets/` layout (what main_pages.json entries name); candidates
633
+ * prefer the caller's own workspace module (routes are module-scoped), and
634
+ * anything still ambiguous is dropped rather than guessed. Only `@Entry`
635
+ * structs qualify as targets — the decorator is what makes a file a page.
636
+ */
637
+ function arkuiRouterEdges(ctx) {
638
+ const edges = [];
639
+ const seen = new Set();
640
+ const allFiles = ctx.getAllFiles();
641
+ const moduleDirs = (() => {
642
+ const ws = ctx.getWorkspacePackages?.();
643
+ return ws ? [...new Set(ws.byName.values())].sort((a, b) => b.length - a.length) : [];
644
+ })();
645
+ const moduleScopeOf = (file) => {
646
+ for (const dir of moduleDirs) {
647
+ if (file === dir || file.startsWith(dir + '/'))
648
+ return dir;
649
+ }
650
+ return '';
651
+ };
652
+ for (const file of allFiles) {
653
+ if (!file.endsWith('.ets'))
654
+ continue;
655
+ const content = ctx.readFile(file);
656
+ if (!content || !content.includes('router.'))
657
+ continue;
658
+ const safe = (0, strip_comments_1.stripCommentsForRegex)(content, 'typescript');
659
+ const nodes = ctx.getNodesInFile(file)
660
+ .filter((n) => n.kind === 'method' || n.kind === 'function');
661
+ ARKUI_ROUTER_RE.lastIndex = 0;
662
+ let m;
663
+ while ((m = ARKUI_ROUTER_RE.exec(safe))) {
664
+ const url = m[1];
665
+ const line = safe.slice(0, m.index).split('\n').length;
666
+ const encl = nodes
667
+ .filter((n) => n.startLine <= line && n.endLine >= line)
668
+ .sort((a, b) => (a.endLine - a.startLine) - (b.endLine - b.startLine))[0];
669
+ if (!encl)
670
+ continue;
671
+ const suffix = `/src/main/ets/${url}.ets`;
672
+ let candidates = allFiles.filter((f) => f.endsWith(suffix));
673
+ if (candidates.length > 1) {
674
+ const scope = moduleScopeOf(file);
675
+ const sameModule = candidates.filter((f) => moduleScopeOf(f) === scope);
676
+ if (sameModule.length > 0)
677
+ candidates = sameModule;
678
+ }
679
+ if (candidates.length !== 1)
680
+ continue; // ambiguous or unresolved — never guess
681
+ const page = ctx.getNodesInFile(candidates[0]).find((n) => n.kind === 'struct' && (n.decorators ?? []).includes('Entry'));
682
+ if (!page)
683
+ continue;
684
+ const key = `${encl.id}>${page.id}`;
685
+ if (seen.has(key))
686
+ continue;
687
+ seen.add(key);
688
+ edges.push({
689
+ source: encl.id, target: page.id, kind: 'calls', line,
690
+ provenance: 'heuristic',
691
+ metadata: { synthesizedBy: 'arkui-route', event: url, registeredAt: `${candidates[0]}:${page.startLine}` },
692
+ });
693
+ }
694
+ }
695
+ return edges;
696
+ }
433
697
  /**
434
698
  * Phase 4c: C++ virtual override. A call through a base/interface pointer
435
699
  * (`db->Get(...)`, `iter->Next()`) dispatches at runtime to a subclass override,
@@ -501,6 +765,7 @@ function cppOverrideEdges(queries) {
501
765
  // or an `object` (Scala) so the loop also iterates those kinds.
502
766
  const IFACE_OVERRIDE_LANGS = new Set([
503
767
  'java', 'kotlin', 'csharp', 'typescript', 'javascript', 'swift', 'scala', 'go', 'rust',
768
+ 'arkts',
504
769
  ]);
505
770
  /**
506
771
  * Go implicit interface satisfaction (#584). Go has no `implements` keyword — a
@@ -2692,6 +2957,398 @@ function sidekiqDispatchEdges(ctx) {
2692
2957
  }
2693
2958
  return edges;
2694
2959
  }
2960
+ // ── Erlang behaviour-callback dispatch ────────────────────────────────────────
2961
+ // An Erlang behaviour is a compile-checked callback contract: the behaviour
2962
+ // module declares `-callback init(...) -> ...`, implementers declare
2963
+ // `-behaviour(B)` and export the callbacks, and the framework side dispatches
2964
+ // through a VARIABLE module — cowboy's `Handler:init(Req, Opts)` and
2965
+ // `Middleware:execute(Req, Env)` folds, ejabberd's `Mod:start/2`. Extraction
2966
+ // deliberately leaves var-module calls silent (no static target), so the flow
2967
+ // breaks at exactly the hop agents ask about (request → handler init). Bridge:
2968
+ //
2969
+ // dispatch site `Var:fn(args…)` → every in-repo implementer of the behaviour
2970
+ // declaring `fn` with the SITE's arity — provided exactly ONE in-repo
2971
+ // behaviour declares (fn, arity); a name+arity collision across behaviours
2972
+ // bails (silent beats wrong) — and the implementer defines and exports `fn`.
2973
+ //
2974
+ // Behaviours are discovered by scanning every Erlang file for `-callback`
2975
+ // declarations (not just `implements` targets), so a behaviour with zero
2976
+ // implementers still participates in the ambiguity gate. Fan-out control: a
2977
+ // mega-behaviour (ejabberd's gen_mod, ~200 mod_* implementers) would mint
2978
+ // hundreds of edges per site that READ as complete coverage while being
2979
+ // arbitrary — above the cap the site is skipped entirely and the boundary
2980
+ // stays visibly dynamic (explore's boundary announcer covers it) instead of
2981
+ // silently truncated.
2982
+ const ERLANG_EXT = /\.(?:erl|hrl)$/;
2983
+ // `Var:fn(` — variable (capitalized) module, lowercase function, immediate
2984
+ // open-paren. The leading char class rejects `?MODULE:fn(` (macro), `a:b(`
2985
+ // (static remote call, already linked), and mid-word matches.
2986
+ const ERLANG_DISPATCH_RE = /(^|[^?\w@'])([A-Z][A-Za-z0-9_@]*):([a-z][A-Za-z0-9_@]*)\(/g;
2987
+ const ERLANG_CALLBACK_DECL_RE = /(^|\n)\s*-callback\s+('[^'\n]+'|[a-z][A-Za-z0-9_@]*)\s*\(/g;
2988
+ const ERLANG_BEHAVIOUR_FANOUT_CAP = 24;
2989
+ /**
2990
+ * Argument count of the call/declaration whose `(` sits at `openIdx` —
2991
+ * top-level commas + 1, `()` → 0, unbalanced/oversized → -1. Skips nested
2992
+ * (), [], {}, <<>> content, `"strings"`, `'atoms'`, and `$c` char literals,
2993
+ * so `-callback init(fun((a, b) -> ok), #{k => v}) -> ok.` counts 2.
2994
+ */
2995
+ function erlangArityAt(src, openIdx) {
2996
+ let depth = 1;
2997
+ let commas = 0;
2998
+ let sawArg = false;
2999
+ const limit = Math.min(src.length, openIdx + 4000);
3000
+ for (let i = openIdx + 1; i < limit; i++) {
3001
+ const ch = src[i];
3002
+ if (ch === '"' || ch === "'") {
3003
+ i++;
3004
+ while (i < limit && src[i] !== ch) {
3005
+ if (src[i] === '\\')
3006
+ i++;
3007
+ i++;
3008
+ }
3009
+ sawArg = true;
3010
+ continue;
3011
+ }
3012
+ if (ch === '$') {
3013
+ i++;
3014
+ if (src[i] === '\\')
3015
+ i++;
3016
+ sawArg = true;
3017
+ continue;
3018
+ }
3019
+ if (ch === '(' || ch === '[' || ch === '{') {
3020
+ depth++;
3021
+ sawArg = true;
3022
+ continue;
3023
+ }
3024
+ if (ch === ')' || ch === ']' || ch === '}') {
3025
+ depth--;
3026
+ if (depth === 0)
3027
+ return sawArg ? commas + 1 : 0;
3028
+ continue;
3029
+ }
3030
+ if (ch === ',' && depth === 1) {
3031
+ commas++;
3032
+ continue;
3033
+ }
3034
+ if (!/\s/.test(ch))
3035
+ sawArg = true;
3036
+ }
3037
+ return -1;
3038
+ }
3039
+ /**
3040
+ * Nix module-system option wiring. A NixOS/home-manager/nix-darwin option is
3041
+ * DECLARED in one module (`options.launchd.user.agents = mkOption { ... }`)
3042
+ * and SET in others (`launchd.user.agents.yabai = { ... }` inside a module's
3043
+ * config) — the connection happens by option-path unification inside the
3044
+ * module-system evaluator, so there is no static call/import edge to follow
3045
+ * and flow questions ("how does services.yabai.enable become a launchd
3046
+ * service?") go dark at the module boundary.
3047
+ *
3048
+ * This pass links each config-write binding to the option declaration whose
3049
+ * path is the longest static-segment prefix of the write path. Precision gates:
3050
+ * - only STATIC segments participate: plain identifiers, plus quoted segments
3051
+ * (`"git/config"`, `"com.apple.dock"`) as opaque verbatim tokens that match
3052
+ * only quote-exactly; an interpolated (`${name}`) segment ends the prefix,
3053
+ * so dynamic paths never match beyond their static head;
3054
+ * - matched prefixes must be ≥2 segments: 1-segment paths would wrongly link
3055
+ * every package's `meta = { ... }` attrset to nixos's `options.meta`;
3056
+ * - a prefix declared in more than one file is ambiguous → no edge (a wrong
3057
+ * edge is worse than none);
3058
+ * - writes physically inside an options block are declaration internals
3059
+ * (types, defaults, examples), never config writes → excluded.
3060
+ * Both declaration spellings register: flat (`options.a.b = ...`) by name, and
3061
+ * nested (`options = { a.b = ...; }`) by line-span containment.
3062
+ */
3063
+ function nixLeadingPlainSegments(name) {
3064
+ const segs = [];
3065
+ let i = 0;
3066
+ const n = name.length;
3067
+ while (i < n) {
3068
+ if (name[i] === '"') {
3069
+ // Quoted segment — an opaque verbatim token (quotes kept, so it can
3070
+ // never collide with a plain identifier). `NSGlobalDomain."com.apple.
3071
+ // mouse.tapBehavior"` must match ITS OWN quoted declaration, not
3072
+ // whichever sibling registered the shared plain prefix first.
3073
+ let j = i + 1;
3074
+ while (j < n && name[j] !== '"') {
3075
+ if (name[j] === '\\')
3076
+ j++;
3077
+ j++;
3078
+ }
3079
+ if (j >= n)
3080
+ return segs; // unterminated — stop at the static head
3081
+ const tok = name.slice(i, j + 1);
3082
+ if (tok.includes('${'))
3083
+ return segs; // interpolated → dynamic → stop
3084
+ segs.push(tok);
3085
+ i = j + 1;
3086
+ if (i >= n)
3087
+ break;
3088
+ if (name[i] !== '.')
3089
+ return segs;
3090
+ i++;
3091
+ continue;
3092
+ }
3093
+ let j = i;
3094
+ while (j < n && name[j] !== '.') {
3095
+ if (name[j] === '"' || (name[j] === '$' && name[j + 1] === '{'))
3096
+ return segs;
3097
+ j++;
3098
+ }
3099
+ const seg = name.slice(i, j);
3100
+ if (!/^[A-Za-z_][A-Za-z0-9_'-]*$/.test(seg))
3101
+ return segs;
3102
+ segs.push(seg);
3103
+ i = j + 1;
3104
+ }
3105
+ return segs;
3106
+ }
3107
+ async function nixOptionPathEdges(queries, onYield) {
3108
+ // One streaming pass over nix bindings (variables + the odd function-valued
3109
+ // option); memory stays O(bindings-kept), not O(all nodes) (#610).
3110
+ const byFile = new Map();
3111
+ let scanned = 0;
3112
+ for (const kind of ['variable', 'function']) {
3113
+ for (const node of queries.iterateNodesByKind(kind)) {
3114
+ if ((++scanned & 0x3fff) === 0 && onYield)
3115
+ await onYield();
3116
+ if (node.language !== 'nix')
3117
+ continue;
3118
+ const segs = nixLeadingPlainSegments(node.name);
3119
+ if (segs.length === 0)
3120
+ continue;
3121
+ const rec = {
3122
+ id: node.id,
3123
+ filePath: node.filePath,
3124
+ startLine: node.startLine,
3125
+ endLine: node.endLine,
3126
+ segs,
3127
+ };
3128
+ const arr = byFile.get(node.filePath);
3129
+ if (arr)
3130
+ arr.push(rec);
3131
+ else
3132
+ byFile.set(node.filePath, [rec]);
3133
+ }
3134
+ }
3135
+ // Per file: walk bindings outermost-first with a stack of active option
3136
+ // spans, composing nested declaration paths (`options = { services.foo = {
3137
+ // enable = mkOption ...; }; }` registers services.foo AND services.foo.enable).
3138
+ // An `options` binding nested inside another option span is a SUBMODULE's
3139
+ // own namespace (`attrsOf (submodule { options = ...; })`) — its internals
3140
+ // are not globally addressable, so the sentinel blocks registration below it
3141
+ // while still excluding the region from write candidates.
3142
+ const SUBMODULE = 'submodule';
3143
+ const decls = new Map();
3144
+ const writes = [];
3145
+ const register = (path, rec) => {
3146
+ if (path.length < 2 || path.includes(SUBMODULE))
3147
+ return;
3148
+ const key = path.join('.');
3149
+ const arr = decls.get(key);
3150
+ if (arr)
3151
+ arr.push(rec);
3152
+ else
3153
+ decls.set(key, [rec]);
3154
+ };
3155
+ for (const recs of byFile.values()) {
3156
+ recs.sort((a, b) => a.startLine - b.startLine || b.endLine - a.endLine);
3157
+ const stack = [];
3158
+ for (const rec of recs) {
3159
+ while (stack.length > 0 && stack[stack.length - 1].end < rec.startLine)
3160
+ stack.pop();
3161
+ // Strict containment at line granularity: a one-line nested binding is
3162
+ // indistinguishable from its container, so it stays unclassified (rare
3163
+ // in module code, where option blocks are multi-line).
3164
+ const enclosing = stack.length > 0 &&
3165
+ rec.startLine >= stack[stack.length - 1].start &&
3166
+ rec.endLine <= stack[stack.length - 1].end &&
3167
+ !(rec.startLine === stack[stack.length - 1].start && rec.endLine === stack[stack.length - 1].end)
3168
+ ? stack[stack.length - 1]
3169
+ : null;
3170
+ if (rec.segs[0] === 'options') {
3171
+ const ownPath = rec.segs.slice(1); // [] for the bare `options = { ... }` spelling
3172
+ const prefix = enclosing ? [SUBMODULE] : ownPath;
3173
+ register(prefix, rec);
3174
+ stack.push({ start: rec.startLine, end: rec.endLine, prefix });
3175
+ continue;
3176
+ }
3177
+ if (enclosing) {
3178
+ const composed = [...enclosing.prefix, ...rec.segs];
3179
+ register(composed, rec);
3180
+ stack.push({ start: rec.startLine, end: rec.endLine, prefix: composed });
3181
+ continue;
3182
+ }
3183
+ if (rec.segs.length >= 2) {
3184
+ writes.push(rec);
3185
+ }
3186
+ }
3187
+ }
3188
+ if (decls.size === 0 || writes.length === 0)
3189
+ return [];
3190
+ const edges = [];
3191
+ for (const w of writes) {
3192
+ // `config.services.x = ...` spells the same write with an explicit prefix.
3193
+ const segs = w.segs[0] === 'config' ? w.segs.slice(1) : w.segs;
3194
+ if (segs.length < 2)
3195
+ continue;
3196
+ // Longest prefix wins; an ambiguous longest match does NOT fall back to a
3197
+ // shorter one (that would link `services.nginx.virtualHosts.x` to
3198
+ // `options.services.nginx` when virtualHosts is the contested path).
3199
+ for (let len = Math.min(segs.length, 6); len >= 2; len--) {
3200
+ const candidates = decls.get(segs.slice(0, len).join('.'));
3201
+ if (!candidates || candidates.length === 0)
3202
+ continue;
3203
+ const files = new Set(candidates.map((c) => c.filePath));
3204
+ if (files.size === 1) {
3205
+ const target = candidates[0];
3206
+ if (target.id !== w.id) {
3207
+ edges.push({
3208
+ source: w.id,
3209
+ target: target.id,
3210
+ kind: 'references',
3211
+ line: w.startLine,
3212
+ provenance: 'heuristic',
3213
+ metadata: {
3214
+ synthesizedBy: 'nix-option-path',
3215
+ optionPath: segs.slice(0, len).join('.'),
3216
+ registeredAt: `${target.filePath}:${target.startLine}`,
3217
+ },
3218
+ });
3219
+ }
3220
+ }
3221
+ break; // longest hit decides, matched or ambiguous
3222
+ }
3223
+ }
3224
+ return edges;
3225
+ }
3226
+ function erlangBehaviourDispatchEdges(queries, ctx) {
3227
+ // Cheap language gate: no Erlang modules → no cost beyond one kind query.
3228
+ const erlangModules = queries.getNodesByKind('namespace').filter((n) => n.language === 'erlang');
3229
+ if (erlangModules.length === 0)
3230
+ return [];
3231
+ // Pass 1 — scan every Erlang file with `-callback` decls: behaviour module →
3232
+ // its (name, arity) callback set, and the global `name/arity` → declaring
3233
+ // behaviours map that drives the ambiguity gate.
3234
+ const moduleByFile = new Map();
3235
+ for (const ns of erlangModules) {
3236
+ if (!moduleByFile.has(ns.filePath))
3237
+ moduleByFile.set(ns.filePath, ns);
3238
+ }
3239
+ const declaringBehaviours = new Map(); // `fn/arity` → behaviour namespaces
3240
+ const callbackNames = new Set();
3241
+ for (const file of ctx.getAllFiles()) {
3242
+ if (!ERLANG_EXT.test(file))
3243
+ continue;
3244
+ const behaviour = moduleByFile.get(file);
3245
+ if (!behaviour)
3246
+ continue; // a .hrl or module-less file can't be a behaviour
3247
+ const content = ctx.readFile(file);
3248
+ if (!content || !content.includes('-callback'))
3249
+ continue;
3250
+ const safe = (0, strip_comments_1.stripCommentsForRegex)(content, 'erlang');
3251
+ ERLANG_CALLBACK_DECL_RE.lastIndex = 0;
3252
+ let m;
3253
+ while ((m = ERLANG_CALLBACK_DECL_RE.exec(safe))) {
3254
+ const name = m[2].replace(/^'|'$/g, '');
3255
+ const arity = erlangArityAt(safe, m.index + m[0].length - 1);
3256
+ if (arity < 0)
3257
+ continue;
3258
+ const key = `${name}/${arity}`;
3259
+ const arr = declaringBehaviours.get(key);
3260
+ if (arr) {
3261
+ if (!arr.some((b) => b.id === behaviour.id))
3262
+ arr.push(behaviour);
3263
+ }
3264
+ else {
3265
+ declaringBehaviours.set(key, [behaviour]);
3266
+ }
3267
+ callbackNames.add(name);
3268
+ }
3269
+ }
3270
+ if (declaringBehaviours.size === 0)
3271
+ return [];
3272
+ // Implementer target lookup, lazy per (behaviour, fn): implementers come
3273
+ // from the `implements` edges extraction resolved, and the target is the
3274
+ // implementer module's own exported `fn` function node.
3275
+ const targetCache = new Map();
3276
+ const targetsOf = (behaviour, fn) => {
3277
+ const cacheKey = `${behaviour.id}#${fn}`;
3278
+ let targets = targetCache.get(cacheKey);
3279
+ if (targets)
3280
+ return targets;
3281
+ targets = [];
3282
+ for (const e of queries.getIncomingEdges(behaviour.id, ['implements'])) {
3283
+ const impl = queries.getNodeById(e.source);
3284
+ if (!impl || impl.language !== 'erlang' || impl.kind !== 'namespace')
3285
+ continue;
3286
+ const fnNode = ctx
3287
+ .getNodesInFile(impl.filePath)
3288
+ .find((n) => n.kind === 'function' && n.name === fn && n.isExported !== false);
3289
+ if (fnNode)
3290
+ targets.push(fnNode);
3291
+ }
3292
+ targetCache.set(cacheKey, targets);
3293
+ return targets;
3294
+ };
3295
+ // Pass 2 — dispatch sites. Only files containing a var-module call shape are
3296
+ // scanned in full.
3297
+ const edges = [];
3298
+ const seen = new Set();
3299
+ for (const file of ctx.getAllFiles()) {
3300
+ if (!ERLANG_EXT.test(file))
3301
+ continue;
3302
+ const content = ctx.readFile(file);
3303
+ if (!content || !/[A-Z][A-Za-z0-9_@]*:[a-z]/.test(content))
3304
+ continue;
3305
+ const safe = (0, strip_comments_1.stripCommentsForRegex)(content, 'erlang');
3306
+ const nodesInFile = ctx.getNodesInFile(file);
3307
+ ERLANG_DISPATCH_RE.lastIndex = 0;
3308
+ let m;
3309
+ while ((m = ERLANG_DISPATCH_RE.exec(safe))) {
3310
+ const fn = m[3];
3311
+ if (!callbackNames.has(fn))
3312
+ continue;
3313
+ const openIdx = m.index + m[0].length - 1;
3314
+ const arity = erlangArityAt(safe, openIdx);
3315
+ if (arity < 0)
3316
+ continue;
3317
+ const behaviours = declaringBehaviours.get(`${fn}/${arity}`);
3318
+ if (!behaviours || behaviours.length !== 1)
3319
+ continue; // unknown or ambiguous
3320
+ const behaviour = behaviours[0];
3321
+ const targets = targetsOf(behaviour, fn);
3322
+ if (targets.length === 0 || targets.length > ERLANG_BEHAVIOUR_FANOUT_CAP)
3323
+ continue;
3324
+ const line = safe.slice(0, m.index).split('\n').length;
3325
+ const disp = enclosingFn(nodesInFile, line);
3326
+ if (!disp)
3327
+ continue;
3328
+ for (const target of targets) {
3329
+ if (target.id === disp.id)
3330
+ continue;
3331
+ const key = `${disp.id}>${target.id}`;
3332
+ if (seen.has(key))
3333
+ continue;
3334
+ seen.add(key);
3335
+ edges.push({
3336
+ source: disp.id,
3337
+ target: target.id,
3338
+ kind: 'calls',
3339
+ line,
3340
+ provenance: 'heuristic',
3341
+ metadata: {
3342
+ synthesizedBy: 'erlang-behaviour',
3343
+ via: `${behaviour.name}:${fn}/${arity}`,
3344
+ registeredAt: `${file}:${line}`,
3345
+ },
3346
+ });
3347
+ }
3348
+ }
3349
+ }
3350
+ return edges;
3351
+ }
2695
3352
  // ── Laravel events (PHP) ──────────────────────────────────────────────────────
2696
3353
  // Laravel decouples an event dispatch from its listener(s), linked by the EVENT CLASS:
2697
3354
  // // app/Events/PlaybackStarted.php + app/Listeners/UpdateLastfmNowPlaying.php
@@ -2894,6 +3551,12 @@ async function synthesizeCallbackEdges(queries, ctx) {
2894
3551
  await yieldToLoop();
2895
3552
  const flutterEdges = flutterBuildEdges(queries, ctx);
2896
3553
  await yieldToLoop();
3554
+ const arkuiStateEdges = arkuiStateBuildEdges(queries, ctx);
3555
+ await yieldToLoop();
3556
+ const arkuiEmitter = arkuiEmitterEdges(ctx);
3557
+ await yieldToLoop();
3558
+ const arkuiRoutes = arkuiRouterEdges(ctx);
3559
+ await yieldToLoop();
2897
3560
  const cppEdges = cppOverrideEdges(queries);
2898
3561
  await yieldToLoop();
2899
3562
  const ifaceEdges = interfaceOverrideEdges(queries);
@@ -2932,12 +3595,16 @@ async function synthesizeCallbackEdges(queries, ctx) {
2932
3595
  await yieldToLoop();
2933
3596
  const sidekiqEdges = sidekiqDispatchEdges(ctx);
2934
3597
  await yieldToLoop();
3598
+ const erlangBehaviourEdges = erlangBehaviourDispatchEdges(queries, ctx);
3599
+ await yieldToLoop();
2935
3600
  const laravelEdges = laravelEventEdges(ctx);
2936
3601
  await yieldToLoop();
2937
3602
  const cFnPtrEdges = (0, c_fnptr_synthesizer_1.cFnPointerDispatchEdges)(queries, ctx);
2938
3603
  await yieldToLoop();
2939
3604
  const goframeEdges = (0, goframe_synthesizer_1.goframeRouteEdges)(ctx);
2940
3605
  await yieldToLoop();
3606
+ const nixOptionEdges = await nixOptionPathEdges(queries, yieldToLoop);
3607
+ await yieldToLoop();
2941
3608
  const merged = [];
2942
3609
  const seen = new Set();
2943
3610
  for (const e of [
@@ -2950,6 +3617,9 @@ async function synthesizeCallbackEdges(queries, ctx) {
2950
3617
  ...svelteKitEdges,
2951
3618
  ...pascalEdges,
2952
3619
  ...flutterEdges,
3620
+ ...arkuiStateEdges,
3621
+ ...arkuiEmitter,
3622
+ ...arkuiRoutes,
2953
3623
  ...cppEdges,
2954
3624
  ...ifaceEdges,
2955
3625
  ...kotlinExpectActual,
@@ -2969,9 +3639,11 @@ async function synthesizeCallbackEdges(queries, ctx) {
2969
3639
  ...springEdges,
2970
3640
  ...mediatrEdges,
2971
3641
  ...sidekiqEdges,
3642
+ ...erlangBehaviourEdges,
2972
3643
  ...laravelEdges,
2973
3644
  ...cFnPtrEdges,
2974
3645
  ...goframeEdges,
3646
+ ...nixOptionEdges,
2975
3647
  ]) {
2976
3648
  const key = `${e.source}>${e.target}`;
2977
3649
  if (seen.has(key))