@nanonets/graft 0.10.0 → 0.12.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 (238) hide show
  1. package/README.md +34 -13
  2. package/dist/ai/crux.d.ts.map +1 -1
  3. package/dist/ai/crux.js +7 -6
  4. package/dist/ai/crux.js.map +1 -1
  5. package/dist/ai/failure.d.ts +49 -0
  6. package/dist/ai/failure.d.ts.map +1 -0
  7. package/dist/ai/failure.js +74 -0
  8. package/dist/ai/failure.js.map +1 -0
  9. package/dist/ai/llm/anthropic.d.ts.map +1 -1
  10. package/dist/ai/llm/anthropic.js +4 -1
  11. package/dist/ai/llm/anthropic.js.map +1 -1
  12. package/dist/ai/llm/claude-cli.d.ts +60 -0
  13. package/dist/ai/llm/claude-cli.d.ts.map +1 -0
  14. package/dist/ai/llm/claude-cli.js +417 -0
  15. package/dist/ai/llm/claude-cli.js.map +1 -0
  16. package/dist/ai/llm/openai.d.ts.map +1 -1
  17. package/dist/ai/llm/openai.js +2 -0
  18. package/dist/ai/llm/openai.js.map +1 -1
  19. package/dist/ai/llm/types.d.ts +9 -0
  20. package/dist/ai/llm/types.d.ts.map +1 -1
  21. package/dist/ai/llm/types.js +12 -1
  22. package/dist/ai/llm/types.js.map +1 -1
  23. package/dist/ask/ask.d.ts.map +1 -1
  24. package/dist/ask/ask.js +39 -45
  25. package/dist/ask/ask.js.map +1 -1
  26. package/dist/ask/fuse.d.ts +83 -46
  27. package/dist/ask/fuse.d.ts.map +1 -1
  28. package/dist/ask/fuse.js +165 -67
  29. package/dist/ask/fuse.js.map +1 -1
  30. package/dist/blast/blast-cli.d.ts +30 -0
  31. package/dist/blast/blast-cli.d.ts.map +1 -0
  32. package/dist/blast/blast-cli.js +163 -0
  33. package/dist/blast/blast-cli.js.map +1 -0
  34. package/dist/blast/blast.d.ts +119 -0
  35. package/dist/blast/blast.d.ts.map +1 -0
  36. package/dist/blast/blast.js +383 -0
  37. package/dist/blast/blast.js.map +1 -0
  38. package/dist/blast/diff.d.ts +53 -0
  39. package/dist/blast/diff.d.ts.map +1 -0
  40. package/dist/blast/diff.js +193 -0
  41. package/dist/blast/diff.js.map +1 -0
  42. package/dist/blast/modules.d.ts +33 -0
  43. package/dist/blast/modules.d.ts.map +1 -0
  44. package/dist/blast/modules.js +83 -0
  45. package/dist/blast/modules.js.map +1 -0
  46. package/dist/blast/name.d.ts +61 -0
  47. package/dist/blast/name.d.ts.map +1 -0
  48. package/dist/blast/name.js +233 -0
  49. package/dist/blast/name.js.map +1 -0
  50. package/dist/blast/render.d.ts +37 -0
  51. package/dist/blast/render.d.ts.map +1 -0
  52. package/dist/blast/render.js +300 -0
  53. package/dist/blast/render.js.map +1 -0
  54. package/dist/blast/viz.d.ts +17 -0
  55. package/dist/blast/viz.d.ts.map +1 -0
  56. package/dist/blast/viz.js +324 -0
  57. package/dist/blast/viz.js.map +1 -0
  58. package/dist/claude/hooks.d.ts +13 -0
  59. package/dist/claude/hooks.d.ts.map +1 -1
  60. package/dist/claude/hooks.js +44 -4
  61. package/dist/claude/hooks.js.map +1 -1
  62. package/dist/claude/shim-template.d.ts.map +1 -1
  63. package/dist/claude/shim-template.js +45 -15
  64. package/dist/claude/shim-template.js.map +1 -1
  65. package/dist/claude/state.d.ts +5 -0
  66. package/dist/claude/state.d.ts.map +1 -1
  67. package/dist/claude/state.js.map +1 -1
  68. package/dist/cli-picker.d.ts +44 -5
  69. package/dist/cli-picker.d.ts.map +1 -1
  70. package/dist/cli-picker.js +73 -26
  71. package/dist/cli-picker.js.map +1 -1
  72. package/dist/cli.js +270 -14
  73. package/dist/cli.js.map +1 -1
  74. package/dist/context/build.d.ts +8 -0
  75. package/dist/context/build.d.ts.map +1 -1
  76. package/dist/context/build.js +57 -9
  77. package/dist/context/build.js.map +1 -1
  78. package/dist/context/check.d.ts.map +1 -1
  79. package/dist/context/check.js +7 -6
  80. package/dist/context/check.js.map +1 -1
  81. package/dist/context/node-file.d.ts.map +1 -1
  82. package/dist/context/node-file.js +9 -2
  83. package/dist/context/node-file.js.map +1 -1
  84. package/dist/graph/bindings.d.ts.map +1 -1
  85. package/dist/graph/bindings.js +245 -2
  86. package/dist/graph/bindings.js.map +1 -1
  87. package/dist/graph/build.d.ts.map +1 -1
  88. package/dist/graph/build.js +36 -16
  89. package/dist/graph/build.js.map +1 -1
  90. package/dist/graph/check.d.ts +4 -0
  91. package/dist/graph/check.d.ts.map +1 -1
  92. package/dist/graph/check.js +8 -1
  93. package/dist/graph/check.js.map +1 -1
  94. package/dist/graph/container.d.ts +63 -0
  95. package/dist/graph/container.d.ts.map +1 -0
  96. package/dist/graph/container.js +185 -0
  97. package/dist/graph/container.js.map +1 -0
  98. package/dist/graph/enrich.d.ts +18 -0
  99. package/dist/graph/enrich.d.ts.map +1 -1
  100. package/dist/graph/enrich.js +55 -2
  101. package/dist/graph/enrich.js.map +1 -1
  102. package/dist/graph/extract.d.ts +8 -1
  103. package/dist/graph/extract.d.ts.map +1 -1
  104. package/dist/graph/extract.js +401 -12
  105. package/dist/graph/extract.js.map +1 -1
  106. package/dist/graph/generic.d.ts +34 -1
  107. package/dist/graph/generic.d.ts.map +1 -1
  108. package/dist/graph/generic.js +176 -6
  109. package/dist/graph/generic.js.map +1 -1
  110. package/dist/graph/invariants.d.ts +32 -0
  111. package/dist/graph/invariants.d.ts.map +1 -0
  112. package/dist/graph/invariants.js +49 -0
  113. package/dist/graph/invariants.js.map +1 -0
  114. package/dist/graph/queries/clojure.scm +58 -0
  115. package/dist/graph/resolve.d.ts.map +1 -1
  116. package/dist/graph/resolve.js +249 -23
  117. package/dist/graph/resolve.js.map +1 -1
  118. package/dist/graph/scopes.d.ts +2 -2
  119. package/dist/graph/scopes.d.ts.map +1 -1
  120. package/dist/graph/scopes.js +8 -6
  121. package/dist/graph/scopes.js.map +1 -1
  122. package/dist/graph/source-files.d.ts +13 -4
  123. package/dist/graph/source-files.d.ts.map +1 -1
  124. package/dist/graph/source-files.js +35 -12
  125. package/dist/graph/source-files.js.map +1 -1
  126. package/dist/graph/types.d.ts +3 -1
  127. package/dist/graph/types.d.ts.map +1 -1
  128. package/dist/graph/workspace-cli.d.ts +2 -0
  129. package/dist/graph/workspace-cli.d.ts.map +1 -1
  130. package/dist/graph/workspace-cli.js +10 -4
  131. package/dist/graph/workspace-cli.js.map +1 -1
  132. package/dist/graph/workspace.d.ts +11 -6
  133. package/dist/graph/workspace.d.ts.map +1 -1
  134. package/dist/graph/workspace.js +24 -13
  135. package/dist/graph/workspace.js.map +1 -1
  136. package/dist/graph/write.d.ts.map +1 -1
  137. package/dist/graph/write.js +25 -2
  138. package/dist/graph/write.js.map +1 -1
  139. package/dist/hosts/antigravity.d.ts +7 -0
  140. package/dist/hosts/antigravity.d.ts.map +1 -0
  141. package/dist/hosts/antigravity.js +42 -0
  142. package/dist/hosts/antigravity.js.map +1 -0
  143. package/dist/hosts/codex-hooks.d.ts +3 -3
  144. package/dist/hosts/codex-hooks.d.ts.map +1 -1
  145. package/dist/hosts/codex-hooks.js +27 -21
  146. package/dist/hosts/codex-hooks.js.map +1 -1
  147. package/dist/hosts/init.d.ts.map +1 -1
  148. package/dist/hosts/init.js +6 -1
  149. package/dist/hosts/init.js.map +1 -1
  150. package/dist/hosts/mcp-config.d.ts.map +1 -1
  151. package/dist/hosts/mcp-config.js +7 -0
  152. package/dist/hosts/mcp-config.js.map +1 -1
  153. package/dist/hosts/plan.d.ts +1 -1
  154. package/dist/hosts/plan.d.ts.map +1 -1
  155. package/dist/hosts/plan.js +2 -0
  156. package/dist/hosts/plan.js.map +1 -1
  157. package/dist/hosts/registry.d.ts.map +1 -1
  158. package/dist/hosts/registry.js +13 -0
  159. package/dist/hosts/registry.js.map +1 -1
  160. package/dist/ingest/fs.d.ts +9 -2
  161. package/dist/ingest/fs.d.ts.map +1 -1
  162. package/dist/ingest/fs.js +100 -16
  163. package/dist/ingest/fs.js.map +1 -1
  164. package/dist/mcp/server.d.ts.map +1 -1
  165. package/dist/mcp/server.js +45 -2
  166. package/dist/mcp/server.js.map +1 -1
  167. package/dist/telemetry/contract.d.ts +90 -0
  168. package/dist/telemetry/contract.d.ts.map +1 -0
  169. package/dist/telemetry/contract.js +203 -0
  170. package/dist/telemetry/contract.js.map +1 -0
  171. package/dist/telemetry/flush.d.ts +21 -0
  172. package/dist/telemetry/flush.d.ts.map +1 -0
  173. package/dist/telemetry/flush.js +84 -0
  174. package/dist/telemetry/flush.js.map +1 -0
  175. package/dist/telemetry/gate.d.ts +16 -0
  176. package/dist/telemetry/gate.d.ts.map +1 -0
  177. package/dist/telemetry/gate.js +64 -0
  178. package/dist/telemetry/gate.js.map +1 -0
  179. package/dist/telemetry/identity.d.ts +33 -0
  180. package/dist/telemetry/identity.d.ts.map +1 -0
  181. package/dist/telemetry/identity.js +80 -0
  182. package/dist/telemetry/identity.js.map +1 -0
  183. package/dist/telemetry/index.d.ts +18 -0
  184. package/dist/telemetry/index.d.ts.map +1 -0
  185. package/dist/telemetry/index.js +16 -0
  186. package/dist/telemetry/index.js.map +1 -0
  187. package/dist/telemetry/key.d.ts +21 -0
  188. package/dist/telemetry/key.d.ts.map +1 -0
  189. package/dist/telemetry/key.js +49 -0
  190. package/dist/telemetry/key.js.map +1 -0
  191. package/dist/telemetry/notice.d.ts +19 -0
  192. package/dist/telemetry/notice.d.ts.map +1 -0
  193. package/dist/telemetry/notice.js +90 -0
  194. package/dist/telemetry/notice.js.map +1 -0
  195. package/dist/telemetry/queue.d.ts +35 -0
  196. package/dist/telemetry/queue.d.ts.map +1 -0
  197. package/dist/telemetry/queue.js +162 -0
  198. package/dist/telemetry/queue.js.map +1 -0
  199. package/dist/telemetry/send.d.ts +19 -0
  200. package/dist/telemetry/send.d.ts.map +1 -0
  201. package/dist/telemetry/send.js +84 -0
  202. package/dist/telemetry/send.js.map +1 -0
  203. package/dist/telemetry/sessions.d.ts +8 -0
  204. package/dist/telemetry/sessions.d.ts.map +1 -0
  205. package/dist/telemetry/sessions.js +81 -0
  206. package/dist/telemetry/sessions.js.map +1 -0
  207. package/dist/telemetry/track.d.ts +43 -0
  208. package/dist/telemetry/track.d.ts.map +1 -0
  209. package/dist/telemetry/track.js +108 -0
  210. package/dist/telemetry/track.js.map +1 -0
  211. package/dist/upkeep-run.d.ts +15 -0
  212. package/dist/upkeep-run.d.ts.map +1 -0
  213. package/dist/upkeep-run.js +60 -0
  214. package/dist/upkeep-run.js.map +1 -0
  215. package/dist/upkeep.d.ts +114 -0
  216. package/dist/upkeep.d.ts.map +1 -0
  217. package/dist/upkeep.js +232 -0
  218. package/dist/upkeep.js.map +1 -0
  219. package/dist/util/num.d.ts +3 -0
  220. package/dist/util/num.d.ts.map +1 -0
  221. package/dist/util/num.js +20 -0
  222. package/dist/util/num.js.map +1 -0
  223. package/dist/util/state.d.ts +11 -4
  224. package/dist/util/state.d.ts.map +1 -1
  225. package/dist/util/state.js +9 -0
  226. package/dist/util/state.js.map +1 -1
  227. package/dist/viewer/app.js +1 -1
  228. package/dist/viewer/index.html +1 -0
  229. package/dist/viewer/style.css +23 -2
  230. package/dist/viz/assemble.d.ts +33 -0
  231. package/dist/viz/assemble.d.ts.map +1 -1
  232. package/dist/viz/assemble.js.map +1 -1
  233. package/dist/viz/export.d.ts +39 -0
  234. package/dist/viz/export.d.ts.map +1 -0
  235. package/dist/viz/export.js +98 -0
  236. package/dist/viz/export.js.map +1 -0
  237. package/package.json +15 -5
  238. package/scripts/stamp-telemetry-key.mjs +53 -0
@@ -10,6 +10,8 @@ import Parser from "tree-sitter";
10
10
  import TypeScript from "tree-sitter-typescript";
11
11
  import Python from "tree-sitter-python";
12
12
  import Go from "tree-sitter-go";
13
+ import Java from "tree-sitter-java";
14
+ import PHP from "tree-sitter-php";
13
15
  import { basename } from "node:path";
14
16
  import { contentHash } from "../util/id.js";
15
17
  import { collectBindings, goReceiverVarOf, resolveRecvType } from "./bindings.js";
@@ -40,11 +42,17 @@ const EXTENSIONS = [
40
42
  { ext: ".pyi", grammar: "python", label: "python" },
41
43
  { ext: ".py", grammar: "python", label: "python" },
42
44
  { ext: ".go", grammar: "go", label: "go" },
45
+ { ext: ".java", grammar: "java", label: "java" },
46
+ { ext: ".php", grammar: "php", label: "php" },
43
47
  ];
44
48
  function entryFor(path) {
45
49
  const p = path.toLowerCase();
46
50
  return EXTENSIONS.find((e) => p.endsWith(e.ext));
47
51
  }
52
+ /** Every file extension a depth-tier (hand-written) extractor claims. */
53
+ export function depthExtensions() {
54
+ return EXTENSIONS.map((e) => e.ext);
55
+ }
48
56
  /** Map a file path to a supported language, or null if unsupported. */
49
57
  export function languageOf(path) {
50
58
  return entryFor(path)?.grammar ?? null;
@@ -116,11 +124,62 @@ const GO_KINDS = {
116
124
  function_declaration: "function",
117
125
  method_declaration: "method",
118
126
  };
127
+ // Java: a record is a nominal data carrier, so it takes "struct" — the same role
128
+ // Go's struct plays — rather than "class", which would make a service and a DTO
129
+ // indistinguishable in a repo where DTOs are most of the type surface.
130
+ const JAVA_KINDS = {
131
+ class_declaration: "class",
132
+ interface_declaration: "interface",
133
+ enum_declaration: "enum",
134
+ record_declaration: "struct",
135
+ annotation_type_declaration: "interface",
136
+ annotation_type_element_declaration: "method",
137
+ method_declaration: "method",
138
+ constructor_declaration: "method",
139
+ };
140
+ /** Java type declarations: they set `enclosingClass` for the methods nested in them,
141
+ * which "class"-only logic would miss for a record's or interface's members. */
142
+ const JAVA_TYPE_KINDS = new Set(["class", "interface", "enum", "struct"]);
143
+ // PHP: definition node types are all distinct (no py-style function→method
144
+ // promotion needed — a class body uses `method_declaration`, not
145
+ // `function_definition`). `trait_declaration` maps to the PHP-only `trait` kind.
146
+ const PHP_KINDS = {
147
+ function_definition: "function",
148
+ method_declaration: "method",
149
+ class_declaration: "class",
150
+ interface_declaration: "interface",
151
+ trait_declaration: "trait",
152
+ enum_declaration: "enum",
153
+ };
119
154
  const KINDS_BY_LANG = {
120
155
  typescript: TS_KINDS,
121
156
  tsx: TS_KINDS,
122
157
  python: PY_KINDS,
123
158
  go: GO_KINDS,
159
+ java: JAVA_KINDS,
160
+ php: PHP_KINDS,
161
+ };
162
+ /**
163
+ * The node type(s) that constitute a call site, per language.
164
+ *
165
+ * Java is the reason this is a set rather than a string: `method_invocation` and
166
+ * `object_creation_expression` (`new Foo()`) are separate node types, and a Java
167
+ * codebase's constructor calls are a large share of its real edges. PHP is
168
+ * likewise multi-shape: a call is a function / member / nullsafe-member / scoped
169
+ * call, never a single `call_expression`.
170
+ */
171
+ const CALL_TYPES = {
172
+ typescript: new Set(["call_expression"]),
173
+ tsx: new Set(["call_expression"]),
174
+ python: new Set(["call"]),
175
+ go: new Set(["call_expression"]),
176
+ java: new Set(["method_invocation", "object_creation_expression"]),
177
+ php: new Set([
178
+ "function_call_expression",
179
+ "member_call_expression",
180
+ "nullsafe_member_call_expression",
181
+ "scoped_call_expression",
182
+ ]),
124
183
  };
125
184
  const FUNCTION_VALUE_TYPES = new Set([
126
185
  "arrow_function",
@@ -134,6 +193,8 @@ const GRAMMARS = {
134
193
  tsx: TypeScript.tsx,
135
194
  python: Python,
136
195
  go: Go,
196
+ java: Java,
197
+ php: PHP.php,
137
198
  };
138
199
  /** tree-sitter's string `parse()` fails with "Invalid argument" on any input
139
200
  * ≥ 32 KB, which silently drops large files — often the most important ones (a
@@ -233,7 +294,11 @@ function walk(node, ctx, out, edges, minted) {
233
294
  ? !desc.name.startsWith("_")
234
295
  : ctx.lang === "go"
235
296
  ? goExported(desc.name)
236
- : tsExported(node),
297
+ : ctx.lang === "java"
298
+ ? javaExported(node)
299
+ : ctx.lang === "php"
300
+ ? phpExported(node)
301
+ : tsExported(node),
237
302
  origin: "ast",
238
303
  body_hash: contentHash(desc.hashNode.text),
239
304
  body_text: searchBody(desc.hashNode.text),
@@ -241,13 +306,21 @@ function walk(node, ctx, out, edges, minted) {
241
306
  summary: null,
242
307
  crux: null,
243
308
  ...(owner !== undefined ? { owner } : {}),
309
+ ...(desc.arity !== undefined ? { arity: desc.arity } : {}),
310
+ ...(desc.variadic ? { variadic: true } : {}),
244
311
  });
245
312
  // structural containment
246
313
  edges.push({ source: ctx.parentId, relation: "contains", targetId: id, file: ctx.rel });
247
- // class heritage
248
- if (desc.kind === "class")
314
+ // class heritage — in Java an interface may also `extends`, and a record/enum
315
+ // may `implements`, so every type declaration is a heritage site, not just a class.
316
+ const javaTypeDecl = ctx.lang === "java" && JAVA_TYPE_KINDS.has(desc.kind);
317
+ if (desc.kind === "class" || javaTypeDecl)
249
318
  edges.push(...heritageEdges(node, id, ctx));
250
- const enclosingClass = desc.kind === "class" ? desc.name : isGoMethod ? goReceiverType(node) : ctx.enclosingClass;
319
+ const enclosingClass = desc.kind === "class" || javaTypeDecl
320
+ ? desc.name
321
+ : isGoMethod
322
+ ? goReceiverType(node)
323
+ : ctx.enclosingClass;
251
324
  const childCtx = {
252
325
  ...ctx,
253
326
  scope: [...ctx.scope, idPart],
@@ -264,8 +337,8 @@ function walk(node, ctx, out, edges, minted) {
264
337
  return;
265
338
  }
266
339
  // not a definition — capture calls/imports/references, then descend with the same context
267
- const callType = ctx.lang === "python" ? "call" : "call_expression";
268
- if (node.type === callType) {
340
+ const callTypes = CALL_TYPES[ctx.lang];
341
+ if (callTypes.has(node.type)) {
269
342
  const callee = calleeName(node, ctx.lang);
270
343
  if (callee) {
271
344
  const callEdge = {
@@ -275,6 +348,10 @@ function walk(node, ctx, out, edges, minted) {
275
348
  viaMember: callee.viaMember,
276
349
  file: ctx.rel,
277
350
  };
351
+ // Java only: the call site's argument count, to pick the right overload.
352
+ const argCount = ctx.lang === "java" ? javaArgCount(node) : undefined;
353
+ if (argCount !== undefined)
354
+ callEdge.argCount = argCount;
278
355
  const recvType = resolveRecvType(callee.receiver, ctx);
279
356
  edges.push(recvType ? { ...callEdge, recvType } : callEdge);
280
357
  }
@@ -287,8 +364,19 @@ function walk(node, ctx, out, edges, minted) {
287
364
  // above already recorded them, so do not descend and emit false references.
288
365
  return;
289
366
  }
367
+ else if (ctx.lang === "php" && node.type === "use_declaration") {
368
+ // Trait composition inside a class body (`use HasFactory, Notifiable;`).
369
+ // Modelled as `implements`: like an interface, a trait is a contract of
370
+ // behaviour the class mixes in (Graft's Relation set has no `uses`).
371
+ for (const t of node.namedChildren) {
372
+ if (t.type === "name" || t.type === "qualified_name") {
373
+ edges.push({ source: ctx.parentId, relation: "implements", name: t.text.replace(/^.*\\/, ""), file: ctx.rel });
374
+ }
375
+ }
376
+ return;
377
+ }
290
378
  else if (node.type === "identifier" &&
291
- !isDirectCallee(node, callType) &&
379
+ !isDirectCallee(node, callTypes) &&
292
380
  !isDeclarationName(node)) {
293
381
  const imported = ctx.importedSymbols.get(node.text);
294
382
  if (imported) {
@@ -338,6 +426,19 @@ function collectTsImportBindings(node, specifier, out) {
338
426
  for (const child of node.namedChildren)
339
427
  collectTsImportBindings(child, specifier, out);
340
428
  }
429
+ /**
430
+ * Do these two wrappers stand for the same syntax node? `===` does not answer that:
431
+ * node-tree-sitter materializes `SyntaxNode` objects on demand and caches them
432
+ * weakly, so reaching one node twice can return two different JS objects. Comparing
433
+ * wrappers makes a purely syntactic question depend on collector timing — two cold
434
+ * builds of unchanged source then disagree on `references` edges (#116).
435
+ *
436
+ * `id` is the stable identity, unique within one tree, so the tree is compared too.
437
+ * A `Tree` is one object per parse (unlike its nodes), so `===` is right for it.
438
+ */
439
+ function sameSyntaxNode(a, b) {
440
+ return !!a && !!b && a.tree === b.tree && a.id === b.id;
441
+ }
341
442
  /**
342
443
  * A parameter or local declaration wins over an import inside that function.
343
444
  * Drop that imported binding for the whole function rather than create a false
@@ -349,7 +450,7 @@ function withoutShadowedImports(imports, definition) {
349
450
  const shadowed = new Set();
350
451
  const definitionValue = definition.childForFieldName("value");
351
452
  const visit = (node) => {
352
- if (node !== definition && node !== definitionValue && isFunctionBoundary(node)) {
453
+ if (!sameSyntaxNode(node, definition) && !sameSyntaxNode(node, definitionValue) && isFunctionBoundary(node)) {
353
454
  const name = node.childForFieldName("name");
354
455
  if (name?.type === "identifier")
355
456
  shadowed.add(name.text);
@@ -384,21 +485,41 @@ function isFunctionBoundary(node) {
384
485
  node.type === "function_expression" ||
385
486
  node.type === "function");
386
487
  }
387
- /** A direct invocation already emits a stronger `calls` edge. */
388
- function isDirectCallee(node, callType) {
488
+ /** A direct invocation already emits a stronger `calls` edge. Java names the callee
489
+ * in a `name` field (there is no `function` field on `method_invocation`), so both
490
+ * spellings count. */
491
+ function isDirectCallee(node, callTypes) {
389
492
  const parent = node.parent;
390
- return parent?.type === callType && parent.childForFieldName("function") === node;
493
+ if (!parent || !callTypes.has(parent.type))
494
+ return false;
495
+ return (sameSyntaxNode(parent.childForFieldName("function"), node) ||
496
+ sameSyntaxNode(parent.childForFieldName("name"), node));
391
497
  }
392
498
  /** Definition/declaration identifiers name a new binding; they do not use one. */
393
499
  function isDeclarationName(node) {
394
500
  const parent = node.parent;
395
- return parent?.childForFieldName("name") === node;
501
+ return sameSyntaxNode(parent?.childForFieldName("name"), node);
396
502
  }
397
503
  /** Recognize the definition shapes: mapped node types, Go's type/method forms, and
398
504
  * TS arrow-consts. */
399
505
  function describe(node, ctx) {
400
506
  if (ctx.lang === "go")
401
507
  return describeGo(node, ctx);
508
+ if (ctx.lang === "java")
509
+ return describeJava(node, ctx);
510
+ // PHP closures: `$h = function () {…}` / `fn() => …`, and bare callbacks
511
+ // (`$routes->get('/x', function () {…})`). Captured as function nodes so a
512
+ // closure-only file (a routing table, a DI container) keeps its structure
513
+ // and the calls inside attribute to the closure, not the file.
514
+ if (ctx.lang === "php" && (node.type === "anonymous_function" || node.type === "arrow_function")) {
515
+ const body = node.childForFieldName("body");
516
+ return {
517
+ name: phpClosureName(node),
518
+ kind: "function",
519
+ headerEnd: body ? body.startIndex : node.endIndex,
520
+ hashNode: node,
521
+ };
522
+ }
402
523
  const mapped = ctx.kinds[node.type];
403
524
  if (mapped) {
404
525
  const name = node.childForFieldName("name")?.text;
@@ -470,6 +591,46 @@ function describeGo(node, _ctx) {
470
591
  }
471
592
  return null;
472
593
  }
594
+ /** Java definition shapes. Uniform in a way Go's are not: every declaration carries
595
+ * a `name` field and (for types and most members) a `body`, so one mapped lookup
596
+ * covers classes, interfaces, enums, records, methods, and constructors. Methods are
597
+ * lexically nested in their type, so — unlike Go — they need no receiver qualification. */
598
+ function describeJava(node, ctx) {
599
+ const mapped = ctx.kinds[node.type];
600
+ if (!mapped)
601
+ return null;
602
+ const name = node.childForFieldName("name")?.text;
603
+ if (!name)
604
+ return null;
605
+ const body = node.childForFieldName("body");
606
+ const desc = {
607
+ name,
608
+ kind: mapped,
609
+ headerEnd: body ? body.startIndex : node.endIndex,
610
+ hashNode: node,
611
+ };
612
+ // Only callables carry arity. A record declaration also has a `parameters` node,
613
+ // but its components are not an overload set and must never be filtered against.
614
+ if (node.type === "method_declaration" || node.type === "constructor_declaration") {
615
+ const params = node.childForFieldName("parameters");
616
+ if (params) {
617
+ const declared = params.namedChildren.filter((c) => c.type === "formal_parameter" || c.type === "spread_parameter");
618
+ desc.arity = declared.length;
619
+ if (declared.some((c) => c.type === "spread_parameter"))
620
+ desc.variadic = true;
621
+ }
622
+ }
623
+ return desc;
624
+ }
625
+ /** Java visibility: `public` (or `protected`) on the declaration's own modifier list.
626
+ * A package-private or private member is not part of the API surface. Read off the
627
+ * `modifiers` child's tokens, ignoring annotations, which live in the same node. */
628
+ function javaExported(node) {
629
+ const mods = node.namedChildren.find((c) => c.type === "modifiers");
630
+ if (!mods)
631
+ return false;
632
+ return mods.children.some((c) => c.type === "public" || c.type === "protected");
633
+ }
473
634
  /** The receiver's base type name for a Go method, unwrapping a pointer receiver
474
635
  * (`func (u *User) …` → `User`). Null if it can't be read. */
475
636
  function goReceiverType(node) {
@@ -487,8 +648,51 @@ function goExported(name) {
487
648
  const first = own[0] ?? "";
488
649
  return first !== first.toLowerCase() && first === first.toUpperCase();
489
650
  }
651
+ /** PHP visibility: a class member is "exported" unless it is `private`/`protected`.
652
+ * Top-level functions/classes carry no visibility modifier and are always visible. */
653
+ function phpExported(node) {
654
+ const vis = node.namedChildren.find((c) => c.type === "visibility_modifier");
655
+ return vis ? vis.text === "public" : true;
656
+ }
657
+ /** Name for a PHP closure / arrow-fn: the variable it's assigned to
658
+ * (`$handler = fn(...)` -> `handler`, mirroring how TS names arrow-consts),
659
+ * else the anonymous `{closure}` (deduplicated per file by mintId).
660
+ *
661
+ * The "is this the assignment's right-hand side" check compares tree-sitter node
662
+ * `.id` (a stable per-tree node identity) rather than `===` on the wrapper
663
+ * objects: the binding does not guarantee that two traversals to the same
664
+ * underlying node hand back the same JS wrapper, so `right === node` can be false
665
+ * even when they are the same node — producing a stray `{closure}` name that
666
+ * makes `graft check` report the graph STALE against its own stored output. */
667
+ function phpClosureName(node) {
668
+ const parent = node.parent;
669
+ if (parent?.type === "assignment_expression" && parent.childForFieldName("right")?.id === node.id) {
670
+ const left = parent.childForFieldName("left");
671
+ if (left?.type === "variable_name")
672
+ return left.text.replace(/^\$/, "");
673
+ }
674
+ return "{closure}";
675
+ }
490
676
  function heritageEdges(node, classId, ctx) {
491
677
  const edges = [];
678
+ if (ctx.lang === "java") {
679
+ // `superclass` holds `extends X`; `super_interfaces` holds `implements A, B`
680
+ // (and, on an interface declaration, `extends A, B` — which tree-sitter-java
681
+ // still spells `extends_interfaces`).
682
+ for (const child of node.namedChildren) {
683
+ const relation = child.type === "superclass"
684
+ ? "extends"
685
+ : child.type === "super_interfaces" || child.type === "extends_interfaces"
686
+ ? "implements"
687
+ : null;
688
+ if (!relation)
689
+ continue;
690
+ for (const t of typeIdentifiersIn(child)) {
691
+ edges.push({ source: classId, relation, name: t, file: ctx.rel });
692
+ }
693
+ }
694
+ return edges;
695
+ }
492
696
  if (ctx.lang === "python") {
493
697
  const supers = node.childForFieldName("superclasses"); // argument_list
494
698
  for (const c of supers?.namedChildren ?? []) {
@@ -498,6 +702,21 @@ function heritageEdges(node, classId, ctx) {
498
702
  }
499
703
  return edges;
500
704
  }
705
+ if (ctx.lang === "php") {
706
+ // `class C extends B implements I, J` → base_clause (extends) +
707
+ // class_interface_clause (implements); names may be namespace-qualified.
708
+ for (const clause of node.namedChildren) {
709
+ const relation = clause.type === "base_clause" ? "extends" : clause.type === "class_interface_clause" ? "implements" : null;
710
+ if (!relation)
711
+ continue;
712
+ for (const t of clause.namedChildren) {
713
+ if (t.type === "name" || t.type === "qualified_name") {
714
+ edges.push({ source: classId, relation, name: t.text.replace(/^.*\\/, ""), file: ctx.rel });
715
+ }
716
+ }
717
+ }
718
+ return edges;
719
+ }
501
720
  const heritage = node.namedChildren.find((c) => c.type === "class_heritage");
502
721
  for (const clause of heritage?.namedChildren ?? []) {
503
722
  const relation = clause.type === "implements_clause"
@@ -515,7 +734,46 @@ function heritageEdges(node, classId, ctx) {
515
734
  }
516
735
  return edges;
517
736
  }
737
+ /** Every `type_identifier` under a heritage clause, so `implements A, B<C>` yields
738
+ * each named type rather than the clause's raw text. */
739
+ function typeIdentifiersIn(node) {
740
+ const out = [];
741
+ const visit = (n) => {
742
+ if (n.type === "type_identifier")
743
+ out.push(n.text);
744
+ for (const c of n.namedChildren)
745
+ visit(c);
746
+ };
747
+ visit(node);
748
+ return out;
749
+ }
518
750
  function calleeName(node, lang) {
751
+ // Java first: `method_invocation` has NO `function` field (it splits the callee
752
+ // into `object` + `name`), so the shared lookup below would return null for every
753
+ // Java call site and the language would extract nodes with no call edges at all.
754
+ if (lang === "java") {
755
+ if (node.type === "object_creation_expression") {
756
+ // `new Foo()` — the constructed type is the call target, named as the graph
757
+ // names it.
758
+ const name = javaConstructedTypeName(node.childForFieldName("type"));
759
+ return name ? { name, viaMember: false } : null;
760
+ }
761
+ const nameNode = node.childForFieldName("name");
762
+ if (!nameNode)
763
+ return null;
764
+ const obj = node.childForFieldName("object");
765
+ // No `object` means an implicit-`this` call (`decorate(name)`), which in Java is a
766
+ // method call, not a free function — Java has none. Reporting it as a plain call
767
+ // would send it to the function-only resolver and drop it, losing the most common
768
+ // intra-class edge there is. Spelling it as a `this` member call routes it through
769
+ // owner-qualified resolution, which also walks the superclass chain and stays
770
+ // conservative: an unmatched name (e.g. a static import) resolves to nothing.
771
+ if (!obj)
772
+ return { name: nameNode.text, viaMember: true, receiver: "this" };
773
+ return { name: nameNode.text, viaMember: true, receiver: javaReceiver(obj) };
774
+ }
775
+ if (lang === "php")
776
+ return phpCallee(node);
519
777
  const fn = node.childForFieldName("function");
520
778
  if (!fn)
521
779
  return null;
@@ -538,6 +796,67 @@ function calleeName(node, lang) {
538
796
  }
539
797
  return null;
540
798
  }
799
+ /** The number of arguments at a Java call site (`method_invocation` or
800
+ * `object_creation_expression`), read off the `arguments` list. Undefined when the
801
+ * list is absent, which keeps resolution at its previous name-only behavior rather
802
+ * than filtering on a count we never established. */
803
+ function javaArgCount(node) {
804
+ const args = node.childForFieldName("arguments");
805
+ return args ? args.namedChildren.length : undefined;
806
+ }
807
+ /**
808
+ * The name a `new` CONSTRUCTS, as the graph names it — or null when this pass cannot
809
+ * say, in which case the construction resolves to nothing.
810
+ *
811
+ * Erasing type arguments is the only transformation here, because it is the only one
812
+ * that provably does not change which type is being named:
813
+ *
814
+ * Box -> Box
815
+ * Box<String> -> Box (the node is `Box`; the arguments are not part of it)
816
+ * Box<> -> Box
817
+ *
818
+ * A QUALIFIED name is deliberately dropped rather than reduced to its final segment:
819
+ *
820
+ * java.io.File -> null (not the repo's own `File`)
821
+ * Beta.Builder -> null (not `Alpha.Builder` in the same file)
822
+ *
823
+ * Collapsing those was the first attempt at this fix, and it traded lost edges for
824
+ * WRONG ones — `new java.io.File(…)` resolved to an unrelated in-repo `File`, and a
825
+ * nested `Beta.Builder` bound to a sibling `Alpha.Builder` at `extracted` confidence,
826
+ * because the same-file tiebreak takes the first candidate. Dropping keeps this pass
827
+ * on the resolver's own rule: resolve precisely, or not at all.
828
+ *
829
+ * Deliberately NOT shared with bindings.ts's `javaTypeName`. That one answers "what
830
+ * type does this variable HOLD", where reducing `java.util.List` to `List` is a local
831
+ * heuristic with different stakes; this one answers "what type is being constructed",
832
+ * and the two questions do not have the same safe answer. Supporting qualified
833
+ * construction properly needs an import-aware type index, not a longer helper.
834
+ */
835
+ function javaConstructedTypeName(node) {
836
+ if (!node)
837
+ return null;
838
+ if (node.type === "generic_type")
839
+ return javaConstructedTypeName(node.namedChildren[0]);
840
+ return node.type === "type_identifier" ? node.text : null;
841
+ }
842
+ /** A Java call's receiver text: a bare identifier (`repo.save()`), `this`, or
843
+ * `this.x` for a field access (`this.repo.save()`). A chained call or a qualified
844
+ * static reference yields none — there is no confident local clue to bind. */
845
+ function javaReceiver(obj) {
846
+ if (!obj)
847
+ return undefined;
848
+ if (obj.type === "identifier")
849
+ return obj.text;
850
+ if (obj.type === "this")
851
+ return "this";
852
+ if (obj.type === "field_access") {
853
+ const inner = obj.childForFieldName("object");
854
+ const field = obj.childForFieldName("field");
855
+ if (inner?.type === "this" && field)
856
+ return `this.${field.text}`;
857
+ }
858
+ return undefined;
859
+ }
541
860
  /** py `attribute` node's receiver text: bare identifier, or `self.x` for a
542
861
  * chained `self.x.y()`. Anything else (e.g. a chained call `f().g()`) → none. */
543
862
  function pyReceiver(fn) {
@@ -552,6 +871,58 @@ function pyReceiver(fn) {
552
871
  }
553
872
  return undefined;
554
873
  }
874
+ /**
875
+ * PHP call shapes: `foo()` (function_call_expression), `$obj->m()` /
876
+ * `$obj?->m()` (member/nullsafe_member_call_expression), and `Cls::m()`
877
+ * (scoped_call_expression). The called name is the trailing `name`; the
878
+ * receiver, when locally knowable (`$this`, `self`/`static`/`parent`), feeds
879
+ * receiver-typed resolution the same way Python's `self` and Go's receiver do.
880
+ */
881
+ function phpCallee(node) {
882
+ if (node.type === "function_call_expression") {
883
+ const fn = node.childForFieldName("function");
884
+ const name = fn ? phpName(fn) : null;
885
+ return name ? { name, viaMember: false } : null;
886
+ }
887
+ const nameNode = node.childForFieldName("name");
888
+ if (!nameNode)
889
+ return null;
890
+ if (node.type === "scoped_call_expression") {
891
+ return { name: nameNode.text, viaMember: true, receiver: phpScopeReceiver(node.childForFieldName("scope")) };
892
+ }
893
+ // member_call_expression / nullsafe_member_call_expression
894
+ return { name: nameNode.text, viaMember: true, receiver: phpObjReceiver(node.childForFieldName("object")) };
895
+ }
896
+ /** A PHP callee identifier: bare `name`, or the trailing segment of a
897
+ * `qualified_name` (`\App\helpers\slug` → `slug`). Dynamic calls (`$fn()`) → null. */
898
+ function phpName(node) {
899
+ if (node.type === "name")
900
+ return node.text;
901
+ if (node.type === "qualified_name")
902
+ return node.text.replace(/^.*\\/, "") || null;
903
+ return null;
904
+ }
905
+ /** `$obj->m()` receiver: `$this` normalizes to `this` (→ enclosing class); any
906
+ * other variable is returned verbatim for a bindings lookup. */
907
+ function phpObjReceiver(obj) {
908
+ if (obj?.type !== "variable_name")
909
+ return undefined;
910
+ return obj.text === "$this" ? "this" : obj.text;
911
+ }
912
+ /** `Cls::m()` receiver: `self`/`static`/`parent` normalize to `self` (→ enclosing
913
+ * class); an explicit class name is the trailing segment of its qualified path. */
914
+ function phpScopeReceiver(scope) {
915
+ if (!scope)
916
+ return undefined;
917
+ const text = scope.text;
918
+ if (scope.type === "relative_scope" || text === "self" || text === "static" || text === "parent")
919
+ return "self";
920
+ if (scope.type === "name")
921
+ return text;
922
+ if (scope.type === "qualified_name")
923
+ return text.replace(/^.*\\/, "");
924
+ return undefined;
925
+ }
555
926
  /** ts `member_expression` node's receiver text: `this`, `this.x`, or a bare identifier. */
556
927
  function tsReceiver(fn) {
557
928
  const obj = fn.childForFieldName("object");
@@ -572,9 +943,20 @@ function isImport(node, lang) {
572
943
  // (`import ( … )`) forms each yield one edge as the walk recurses into the list.
573
944
  if (lang === "go")
574
945
  return node.type === "import_spec";
946
+ if (lang === "java")
947
+ return node.type === "import_declaration";
948
+ // PHP: one edge per imported symbol — the clause leaf inside a (possibly
949
+ // grouped) `use A\B, C\D;` / `use A\{B, C};` declaration.
950
+ if (lang === "php")
951
+ return node.type === "namespace_use_clause";
575
952
  return node.type === "import_statement" || node.type === "import_from_statement";
576
953
  }
577
954
  function importSpecifier(node, lang) {
955
+ if (lang === "php") {
956
+ // namespace_use_clause → its `qualified_name`/`name`, e.g. `App\Models\Animal`.
957
+ const q = node.namedChildren.find((c) => c.type === "qualified_name" || c.type === "name");
958
+ return q ? q.text.replace(/^\\/, "") : null;
959
+ }
578
960
  if (lang === "python") {
579
961
  const m = node.childForFieldName("module_name") ??
580
962
  node.namedChildren.find((c) => c.type === "dotted_name" || c.type === "relative_import");
@@ -585,6 +967,13 @@ function importSpecifier(node, lang) {
585
967
  const path = node.childForFieldName("path") ?? node.namedChildren.at(-1);
586
968
  return path ? path.text.replace(/^["`]|["`]$/g, "") : null;
587
969
  }
970
+ if (lang === "java") {
971
+ // `import a.b.C;` / `import static a.b.C.d;` / `import a.b.*;` — the fully
972
+ // qualified name is the scoped_identifier; a wildcard `*` is a separate token
973
+ // and is dropped, leaving the package as the import target.
974
+ const id = node.namedChildren.find((c) => c.type === "scoped_identifier" || c.type === "identifier");
975
+ return id?.text ?? null;
976
+ }
588
977
  const str = node.namedChildren.find((c) => c.type === "string");
589
978
  if (!str)
590
979
  return null;