graphitect 0.2.0__py3-none-any.whl

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 (336) hide show
  1. graphify/__init__.py +30 -0
  2. graphify/__main__.py +757 -0
  3. graphify/_minhash.py +107 -0
  4. graphify/affected.py +318 -0
  5. graphify/always_on/agents-md.md +12 -0
  6. graphify/always_on/antigravity-rules.md +14 -0
  7. graphify/always_on/claude-md.md +9 -0
  8. graphify/always_on/gemini-md.md +9 -0
  9. graphify/always_on/kiro-steering.md +5 -0
  10. graphify/always_on/vscode-instructions.md +17 -0
  11. graphify/analyze.py +769 -0
  12. graphify/benchmark.py +152 -0
  13. graphify/build.py +2300 -0
  14. graphify/cache.py +1746 -0
  15. graphify/callflow_html.py +2051 -0
  16. graphify/cargo_introspect.py +109 -0
  17. graphify/cli.py +4745 -0
  18. graphify/cluster.py +409 -0
  19. graphify/command-kilo.md +15 -0
  20. graphify/cross_repo_calls.py +216 -0
  21. graphify/cross_repo_types.py +75 -0
  22. graphify/csharp_dispatch.py +154 -0
  23. graphify/dedup.py +1213 -0
  24. graphify/detect.py +2566 -0
  25. graphify/diagnostics.py +406 -0
  26. graphify/export.py +1349 -0
  27. graphify/exporters/__init__.py +1 -0
  28. graphify/exporters/base.py +14 -0
  29. graphify/exporters/graphdb.py +173 -0
  30. graphify/exporters/html.py +637 -0
  31. graphify/extract.py +7856 -0
  32. graphify/extractors/MIGRATION.md +107 -0
  33. graphify/extractors/__init__.py +66 -0
  34. graphify/extractors/apex.py +215 -0
  35. graphify/extractors/base.py +85 -0
  36. graphify/extractors/bash.py +579 -0
  37. graphify/extractors/blade.py +53 -0
  38. graphify/extractors/commonlisp.py +540 -0
  39. graphify/extractors/csharp.py +448 -0
  40. graphify/extractors/dart.py +564 -0
  41. graphify/extractors/dm.py +494 -0
  42. graphify/extractors/elixir.py +241 -0
  43. graphify/extractors/engine.py +6509 -0
  44. graphify/extractors/fortran.py +311 -0
  45. graphify/extractors/go.py +527 -0
  46. graphify/extractors/json_config.py +240 -0
  47. graphify/extractors/julia.py +289 -0
  48. graphify/extractors/markdown.py +408 -0
  49. graphify/extractors/models.py +131 -0
  50. graphify/extractors/objc.py +566 -0
  51. graphify/extractors/ocaml.py +289 -0
  52. graphify/extractors/pascal.py +688 -0
  53. graphify/extractors/pascal_forms.py +196 -0
  54. graphify/extractors/powershell.py +522 -0
  55. graphify/extractors/razor.py +192 -0
  56. graphify/extractors/resolution.py +3584 -0
  57. graphify/extractors/robot.py +296 -0
  58. graphify/extractors/rust.py +470 -0
  59. graphify/extractors/sln.py +92 -0
  60. graphify/extractors/sql.py +720 -0
  61. graphify/extractors/terraform.py +181 -0
  62. graphify/extractors/verilog.py +329 -0
  63. graphify/extractors/zig.py +181 -0
  64. graphify/file_slice.py +246 -0
  65. graphify/global_graph.py +194 -0
  66. graphify/google_workspace.py +237 -0
  67. graphify/hooks.py +933 -0
  68. graphify/ids.py +93 -0
  69. graphify/ingest.py +358 -0
  70. graphify/install.py +2366 -0
  71. graphify/llm.py +3544 -0
  72. graphify/manifest.py +4 -0
  73. graphify/manifest_ingest.py +311 -0
  74. graphify/mcp_ingest.py +386 -0
  75. graphify/multigraph_compat.py +212 -0
  76. graphify/pascal_resolution.py +129 -0
  77. graphify/paths.py +436 -0
  78. graphify/pg_introspect.py +165 -0
  79. graphify/prs.py +770 -0
  80. graphify/querylog.py +80 -0
  81. graphify/reflect.py +882 -0
  82. graphify/report.py +346 -0
  83. graphify/resolver_registry.py +85 -0
  84. graphify/ruby_resolution.py +242 -0
  85. graphify/scip_ingest.py +363 -0
  86. graphify/security.py +460 -0
  87. graphify/semantic_cleanup.py +336 -0
  88. graphify/serve.py +2608 -0
  89. graphify/skill-agents.md +710 -0
  90. graphify/skill-aider.md +1283 -0
  91. graphify/skill-amp.md +710 -0
  92. graphify/skill-claw.md +713 -0
  93. graphify/skill-codex.md +710 -0
  94. graphify/skill-copilot.md +713 -0
  95. graphify/skill-devin.md +1410 -0
  96. graphify/skill-droid.md +710 -0
  97. graphify/skill-kilo.md +722 -0
  98. graphify/skill-kiro.md +713 -0
  99. graphify/skill-opencode.md +705 -0
  100. graphify/skill-pi.md +713 -0
  101. graphify/skill-trae.md +711 -0
  102. graphify/skill-vscode.md +709 -0
  103. graphify/skill-windows.md +755 -0
  104. graphify/skill.md +713 -0
  105. graphify/skills/agents/references/add-watch.md +56 -0
  106. graphify/skills/agents/references/exports.md +87 -0
  107. graphify/skills/agents/references/extraction-spec.md +70 -0
  108. graphify/skills/agents/references/github-and-merge.md +46 -0
  109. graphify/skills/agents/references/hooks.md +33 -0
  110. graphify/skills/agents/references/query.md +311 -0
  111. graphify/skills/agents/references/transcribe.md +52 -0
  112. graphify/skills/agents/references/update.md +210 -0
  113. graphify/skills/amp/references/add-watch.md +56 -0
  114. graphify/skills/amp/references/exports.md +87 -0
  115. graphify/skills/amp/references/extraction-spec.md +70 -0
  116. graphify/skills/amp/references/github-and-merge.md +46 -0
  117. graphify/skills/amp/references/hooks.md +33 -0
  118. graphify/skills/amp/references/query.md +311 -0
  119. graphify/skills/amp/references/transcribe.md +52 -0
  120. graphify/skills/amp/references/update.md +210 -0
  121. graphify/skills/claude/references/add-watch.md +56 -0
  122. graphify/skills/claude/references/exports.md +87 -0
  123. graphify/skills/claude/references/extraction-spec.md +70 -0
  124. graphify/skills/claude/references/github-and-merge.md +46 -0
  125. graphify/skills/claude/references/hooks.md +33 -0
  126. graphify/skills/claude/references/query.md +311 -0
  127. graphify/skills/claude/references/transcribe.md +52 -0
  128. graphify/skills/claude/references/update.md +210 -0
  129. graphify/skills/claw/references/add-watch.md +56 -0
  130. graphify/skills/claw/references/exports.md +87 -0
  131. graphify/skills/claw/references/extraction-spec.md +31 -0
  132. graphify/skills/claw/references/github-and-merge.md +46 -0
  133. graphify/skills/claw/references/hooks.md +33 -0
  134. graphify/skills/claw/references/query.md +311 -0
  135. graphify/skills/claw/references/transcribe.md +52 -0
  136. graphify/skills/claw/references/update.md +210 -0
  137. graphify/skills/codex/references/add-watch.md +56 -0
  138. graphify/skills/codex/references/exports.md +87 -0
  139. graphify/skills/codex/references/extraction-spec.md +31 -0
  140. graphify/skills/codex/references/github-and-merge.md +46 -0
  141. graphify/skills/codex/references/hooks.md +33 -0
  142. graphify/skills/codex/references/query.md +311 -0
  143. graphify/skills/codex/references/transcribe.md +52 -0
  144. graphify/skills/codex/references/update.md +210 -0
  145. graphify/skills/copilot/references/add-watch.md +56 -0
  146. graphify/skills/copilot/references/exports.md +87 -0
  147. graphify/skills/copilot/references/extraction-spec.md +70 -0
  148. graphify/skills/copilot/references/github-and-merge.md +46 -0
  149. graphify/skills/copilot/references/hooks.md +33 -0
  150. graphify/skills/copilot/references/query.md +311 -0
  151. graphify/skills/copilot/references/transcribe.md +52 -0
  152. graphify/skills/copilot/references/update.md +210 -0
  153. graphify/skills/droid/references/add-watch.md +56 -0
  154. graphify/skills/droid/references/exports.md +87 -0
  155. graphify/skills/droid/references/extraction-spec.md +70 -0
  156. graphify/skills/droid/references/github-and-merge.md +46 -0
  157. graphify/skills/droid/references/hooks.md +33 -0
  158. graphify/skills/droid/references/query.md +311 -0
  159. graphify/skills/droid/references/transcribe.md +52 -0
  160. graphify/skills/droid/references/update.md +210 -0
  161. graphify/skills/kilo/references/add-watch.md +56 -0
  162. graphify/skills/kilo/references/exports.md +87 -0
  163. graphify/skills/kilo/references/extraction-spec.md +70 -0
  164. graphify/skills/kilo/references/github-and-merge.md +46 -0
  165. graphify/skills/kilo/references/hooks.md +33 -0
  166. graphify/skills/kilo/references/query.md +311 -0
  167. graphify/skills/kilo/references/transcribe.md +52 -0
  168. graphify/skills/kilo/references/update.md +210 -0
  169. graphify/skills/kiro/references/add-watch.md +56 -0
  170. graphify/skills/kiro/references/exports.md +87 -0
  171. graphify/skills/kiro/references/extraction-spec.md +31 -0
  172. graphify/skills/kiro/references/github-and-merge.md +46 -0
  173. graphify/skills/kiro/references/hooks.md +33 -0
  174. graphify/skills/kiro/references/query.md +311 -0
  175. graphify/skills/kiro/references/transcribe.md +52 -0
  176. graphify/skills/kiro/references/update.md +210 -0
  177. graphify/skills/opencode/references/add-watch.md +56 -0
  178. graphify/skills/opencode/references/exports.md +87 -0
  179. graphify/skills/opencode/references/extraction-spec.md +70 -0
  180. graphify/skills/opencode/references/github-and-merge.md +46 -0
  181. graphify/skills/opencode/references/hooks.md +33 -0
  182. graphify/skills/opencode/references/query.md +311 -0
  183. graphify/skills/opencode/references/transcribe.md +52 -0
  184. graphify/skills/opencode/references/update.md +210 -0
  185. graphify/skills/pi/references/add-watch.md +56 -0
  186. graphify/skills/pi/references/exports.md +87 -0
  187. graphify/skills/pi/references/extraction-spec.md +31 -0
  188. graphify/skills/pi/references/github-and-merge.md +46 -0
  189. graphify/skills/pi/references/hooks.md +33 -0
  190. graphify/skills/pi/references/query.md +311 -0
  191. graphify/skills/pi/references/transcribe.md +52 -0
  192. graphify/skills/pi/references/update.md +210 -0
  193. graphify/skills/trae/references/add-watch.md +56 -0
  194. graphify/skills/trae/references/exports.md +87 -0
  195. graphify/skills/trae/references/extraction-spec.md +70 -0
  196. graphify/skills/trae/references/github-and-merge.md +46 -0
  197. graphify/skills/trae/references/hooks.md +35 -0
  198. graphify/skills/trae/references/query.md +311 -0
  199. graphify/skills/trae/references/transcribe.md +52 -0
  200. graphify/skills/trae/references/update.md +210 -0
  201. graphify/skills/vscode/references/add-watch.md +56 -0
  202. graphify/skills/vscode/references/exports.md +87 -0
  203. graphify/skills/vscode/references/extraction-spec.md +70 -0
  204. graphify/skills/vscode/references/github-and-merge.md +46 -0
  205. graphify/skills/vscode/references/hooks.md +33 -0
  206. graphify/skills/vscode/references/query.md +311 -0
  207. graphify/skills/vscode/references/transcribe.md +52 -0
  208. graphify/skills/vscode/references/update.md +210 -0
  209. graphify/skills/windows/references/add-watch.md +56 -0
  210. graphify/skills/windows/references/exports.md +87 -0
  211. graphify/skills/windows/references/extraction-spec.md +70 -0
  212. graphify/skills/windows/references/github-and-merge.md +46 -0
  213. graphify/skills/windows/references/hooks.md +33 -0
  214. graphify/skills/windows/references/query.md +311 -0
  215. graphify/skills/windows/references/transcribe.md +52 -0
  216. graphify/skills/windows/references/update.md +210 -0
  217. graphify/symbol_resolution.py +556 -0
  218. graphify/transcribe.py +186 -0
  219. graphify/tree_html.py +603 -0
  220. graphify/validate.py +95 -0
  221. graphify/watch.py +2280 -0
  222. graphify/wiki.py +405 -0
  223. graphitect/__init__.py +28 -0
  224. graphitect/__main__.py +4 -0
  225. graphitect/_vendor/__init__.py +2 -0
  226. graphitect/_vendor/archify/LICENSE +22 -0
  227. graphitect/_vendor/archify/SKILL.md +137 -0
  228. graphitect/_vendor/archify/THIRD_PARTY_NOTICES.md +69 -0
  229. graphitect/_vendor/archify/assets/JetBrainsMono-OFL.txt +93 -0
  230. graphitect/_vendor/archify/assets/template.html +14935 -0
  231. graphitect/_vendor/archify/bin/archify.mjs +2091 -0
  232. graphitect/_vendor/archify/bin/open-artifact.mjs +86 -0
  233. graphitect/_vendor/archify/bin/preview.mjs +653 -0
  234. graphitect/_vendor/archify/bin/visual-check.mjs +829 -0
  235. graphitect/_vendor/archify/brand-marks/README.md +31 -0
  236. graphitect/_vendor/archify/brand-marks/catalog.json +131 -0
  237. graphitect/_vendor/archify/delta/architecture-delta.mjs +1221 -0
  238. graphitect/_vendor/archify/examples/agent-run.lifecycle.json +60 -0
  239. graphitect/_vendor/archify/examples/agent-tool-call.workflow.json +94 -0
  240. graphitect/_vendor/archify/examples/async-job-roundtrip.sequence.json +61 -0
  241. graphitect/_vendor/archify/examples/brand-aware-delivery.architecture.json +47 -0
  242. graphitect/_vendor/archify/examples/cache-miss-request.sequence.json +82 -0
  243. graphitect/_vendor/archify/examples/checkout-platform.base.architecture.json +31 -0
  244. graphitect/_vendor/archify/examples/checkout-platform.head.architecture.json +31 -0
  245. graphitect/_vendor/archify/examples/dataflow-product-analytics.html +15045 -0
  246. graphitect/_vendor/archify/examples/deployment-release.lifecycle.json +49 -0
  247. graphitect/_vendor/archify/examples/event-stream.dataflow.json +57 -0
  248. graphitect/_vendor/archify/examples/incident-response.workflow.json +64 -0
  249. graphitect/_vendor/archify/examples/lifecycle-agent-run.html +14980 -0
  250. graphitect/_vendor/archify/examples/product-analytics.dataflow.json +76 -0
  251. graphitect/_vendor/archify/examples/production-deployment.architecture.json +71 -0
  252. graphitect/_vendor/archify/examples/release-delivery.workflow.json +62 -0
  253. graphitect/_vendor/archify/examples/sequence-cache-miss-request.html +15060 -0
  254. graphitect/_vendor/archify/examples/web-app-rendered.html +15009 -0
  255. graphitect/_vendor/archify/examples/web-app.architecture.json +46 -0
  256. graphitect/_vendor/archify/examples/workflow-agent-tool-call-rendered.html +15051 -0
  257. graphitect/_vendor/archify/migrations/workflow-v2.mjs +279 -0
  258. graphitect/_vendor/archify/package-lock.json +149 -0
  259. graphitect/_vendor/archify/package.json +39 -0
  260. graphitect/_vendor/archify/recipes/scenarios.mjs +391 -0
  261. graphitect/_vendor/archify/references/authoring-contract.md +243 -0
  262. graphitect/_vendor/archify/references/brand-marks.md +65 -0
  263. graphitect/_vendor/archify/references/delivery-contract.md +120 -0
  264. graphitect/_vendor/archify/references/viewer-runtime.md +45 -0
  265. graphitect/_vendor/archify/renderers/architecture/grid.mjs +62 -0
  266. graphitect/_vendor/archify/renderers/architecture/render-architecture.mjs +1078 -0
  267. graphitect/_vendor/archify/renderers/dataflow/README.md +104 -0
  268. graphitect/_vendor/archify/renderers/dataflow/render-dataflow.mjs +483 -0
  269. graphitect/_vendor/archify/renderers/lifecycle/README.md +115 -0
  270. graphitect/_vendor/archify/renderers/lifecycle/render-lifecycle.mjs +561 -0
  271. graphitect/_vendor/archify/renderers/sequence/README.md +114 -0
  272. graphitect/_vendor/archify/renderers/sequence/render-sequence.mjs +464 -0
  273. graphitect/_vendor/archify/renderers/shared/brand-marks.mjs +563 -0
  274. graphitect/_vendor/archify/renderers/shared/cli.mjs +218 -0
  275. graphitect/_vendor/archify/renderers/shared/desktop-readability.mjs +26 -0
  276. graphitect/_vendor/archify/renderers/shared/diagnostics.mjs +127 -0
  277. graphitect/_vendor/archify/renderers/shared/engineering-profiles.mjs +157 -0
  278. graphitect/_vendor/archify/renderers/shared/generated-brand-marks.mjs +2003 -0
  279. graphitect/_vendor/archify/renderers/shared/generated-validators.mjs +13 -0
  280. graphitect/_vendor/archify/renderers/shared/geometry.mjs +1423 -0
  281. graphitect/_vendor/archify/renderers/shared/i18n.mjs +595 -0
  282. graphitect/_vendor/archify/renderers/shared/layout-report.mjs +40 -0
  283. graphitect/_vendor/archify/renderers/shared/legend.mjs +217 -0
  284. graphitect/_vendor/archify/renderers/shared/output-path.mjs +340 -0
  285. graphitect/_vendor/archify/renderers/shared/repository-evidence.mjs +238 -0
  286. graphitect/_vendor/archify/renderers/shared/repository-location.mjs +58 -0
  287. graphitect/_vendor/archify/renderers/shared/text-fit.mjs +49 -0
  288. graphitect/_vendor/archify/renderers/shared/utils.mjs +232 -0
  289. graphitect/_vendor/archify/renderers/shared/validator.mjs +86 -0
  290. graphitect/_vendor/archify/renderers/workflow/README.md +223 -0
  291. graphitect/_vendor/archify/renderers/workflow/render-workflow.mjs +35 -0
  292. graphitect/_vendor/archify/renderers/workflow/workflow-compiler.mjs +4400 -0
  293. graphitect/_vendor/archify/renderers/workflow/workflow-migration-geometry.mjs +144 -0
  294. graphitect/_vendor/archify/schemas/README.md +211 -0
  295. graphitect/_vendor/archify/schemas/architecture.schema.json +178 -0
  296. graphitect/_vendor/archify/schemas/common.schema.json +115 -0
  297. graphitect/_vendor/archify/schemas/dataflow.schema.json +243 -0
  298. graphitect/_vendor/archify/schemas/lifecycle.schema.json +266 -0
  299. graphitect/_vendor/archify/schemas/sequence.schema.json +223 -0
  300. graphitect/_vendor/archify/schemas/workflow.schema.json +428 -0
  301. graphitect/_vendor/archify/scripts/check-render-output.mjs +836 -0
  302. graphitect/_vendor/archify/scripts/check-update.mjs +1667 -0
  303. graphitect/_vendor/archify/scripts/generate-brand-marks.mjs +141 -0
  304. graphitect/_vendor/archify/scripts/generate-validators.mjs +66 -0
  305. graphitect/_vendor/archify/scripts/render-examples.mjs +26 -0
  306. graphitect/_vendor/archify/scripts/update-contract.mjs +182 -0
  307. graphitect/_vendor/archify/skill-release.json +10 -0
  308. graphitect/cli.py +981 -0
  309. graphitect/deliver/__init__.py +5 -0
  310. graphitect/deliver/archify_adapter.py +1877 -0
  311. graphitect/deliver/archify_ir.py +160 -0
  312. graphitect/deliver/archify_repair.py +135 -0
  313. graphitect/deliver/doc_compiler.py +916 -0
  314. graphitect/ground/__init__.py +5 -0
  315. graphitect/ground/describe_source.py +27 -0
  316. graphitect/ground/fullread_source.py +56 -0
  317. graphitect/ground/graphify_source.py +107 -0
  318. graphitect/models.py +118 -0
  319. graphitect/skill/SKILL.md +80 -0
  320. graphitect/skill/agents/openai.yaml +4 -0
  321. graphitect/synthesize/__init__.py +5 -0
  322. graphitect/synthesize/engine.py +281 -0
  323. graphitect/synthesize/llm_backend.py +331 -0
  324. graphitect/synthesize/questions.py +139 -0
  325. graphitect/synthesize/rubric.py +104 -0
  326. graphitect-0.2.0.dist-info/METADATA +284 -0
  327. graphitect-0.2.0.dist-info/RECORD +336 -0
  328. graphitect-0.2.0.dist-info/WHEEL +5 -0
  329. graphitect-0.2.0.dist-info/entry_points.txt +2 -0
  330. graphitect-0.2.0.dist-info/licenses/LICENSE +21 -0
  331. graphitect-0.2.0.dist-info/licenses/LICENSE-ARCHIFY-MIT +22 -0
  332. graphitect-0.2.0.dist-info/licenses/LICENSE-GRAPHIFY-APACHE-2.0 +202 -0
  333. graphitect-0.2.0.dist-info/licenses/LICENSE-GRAPHIFY-MIT +21 -0
  334. graphitect-0.2.0.dist-info/licenses/NOTICE-ARCHIFY-THIRD-PARTY.md +69 -0
  335. graphitect-0.2.0.dist-info/licenses/NOTICE-GRAPHIFY +8 -0
  336. graphitect-0.2.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,240 @@
1
+ """Json_config extractor. Moved verbatim from graphify/extract.py."""
2
+ from __future__ import annotations
3
+
4
+
5
+ from pathlib import Path
6
+ from graphify.extractors.base import _file_stem, _make_id, _read_text
7
+ from graphify.ids import normalize_id
8
+
9
+
10
+ _CONFIG_JSON_NAMES = frozenset({
11
+ "package.json", "tsconfig.json", "jsconfig.json", "composer.json",
12
+ "deno.json", "deno.jsonc", "bower.json", "manifest.json",
13
+ "app.json", "now.json", "vercel.json", "angular.json", "nest-cli.json",
14
+ "biome.json", "biome.jsonc", "renovate.json", ".babelrc", ".babelrc.json",
15
+ ".eslintrc.json", ".prettierrc.json", ".prettierrc", "babel.config.json",
16
+ })
17
+
18
+ _CONFIG_JSON_KEYS = frozenset({
19
+ "dependencies", "devDependencies", "peerDependencies",
20
+ "optionalDependencies", "bundleDependencies", "bundledDependencies",
21
+ "extends", "$ref", "$schema", "compilerOptions",
22
+ })
23
+
24
+ def _is_config_json(path: Path, obj_node, source: bytes) -> bool:
25
+ """True if a .json file is a recognized config/manifest worth AST-extracting.
26
+
27
+ Matches by filename first (cheap), then falls back to a top-level key probe
28
+ so arbitrarily-named config files (e.g. ``api.tsconfig.json``,
29
+ ``foo.eslintrc.json``) are still picked up. Returns False for data JSON so it
30
+ is skipped by the structural pass (#1224)."""
31
+ name = path.name.casefold()
32
+ if name in _CONFIG_JSON_NAMES:
33
+ return True
34
+ # Common compound config names: *.eslintrc.json, *.prettierrc.json, etc.
35
+ if name.endswith((".eslintrc.json", ".prettierrc.json", ".babelrc.json",
36
+ "tsconfig.json", "jsconfig.json")):
37
+ return True
38
+ # Top-level key probe: scan the root object's immediate keys (no deep walk).
39
+ for top_key in obj_node.children:
40
+ if top_key.type != "pair":
41
+ continue
42
+ key_node = top_key.child_by_field_name("key")
43
+ if key_node is None:
44
+ continue
45
+ kc = key_node.child_by_field_name("string_content")
46
+ text = _read_text(kc, source) if kc else _read_text(key_node, source).strip('"\'')
47
+ if text in _CONFIG_JSON_KEYS:
48
+ return True
49
+ return False
50
+
51
+ def extract_json(path: Path) -> dict:
52
+ """Extract structure and dependency edges from a *config/manifest* .json file.
53
+
54
+ Data-shaped JSON (eval fixtures, datasets, GeoJSON, API response dumps) is
55
+ deliberately skipped — AST-walking it produced hundreds of orphan key-nodes
56
+ and duplicate communities that swamped real structure (#1224). Recognition
57
+ is by filename (package.json, tsconfig.json, …) or a top-level key probe
58
+ (dependencies / extends / $ref / $schema / compilerOptions)."""
59
+ _JSON_MAX_BYTES = 1_048_576 # 1 MiB — skip large fixture dumps / GeoJSON blobs
60
+
61
+ try:
62
+ import tree_sitter_json as tsjson
63
+ from tree_sitter import Language, Parser
64
+ except ImportError:
65
+ return {"nodes": [], "edges": [], "error": "tree-sitter-json not installed"}
66
+
67
+ try:
68
+ # Bounded read instead of stat()+read() to eliminate TOCTOU (J-1):
69
+ # read one byte beyond the limit so we can detect oversized files even
70
+ # if the file grows between stat and read.
71
+ with path.open("rb") as _f:
72
+ source = _f.read(_JSON_MAX_BYTES + 1)
73
+ if len(source) > _JSON_MAX_BYTES:
74
+ return {"nodes": [], "edges": [], "error": "json file too large to index"}
75
+ language = Language(tsjson.language())
76
+ parser = Parser(language)
77
+ tree = parser.parse(source)
78
+ root = tree.root_node
79
+ except Exception as e:
80
+ return {"nodes": [], "edges": [], "error": str(e)}
81
+
82
+ stem = _file_stem(path)
83
+ str_path = str(path)
84
+ nodes: list[dict] = []
85
+ edges: list[dict] = []
86
+ seen_ids: set[str] = set()
87
+
88
+ # Keys whose string values become imports (package.json dep blocks)
89
+ _DEP_KEYS = frozenset({
90
+ "dependencies", "devDependencies", "peerDependencies",
91
+ "optionalDependencies", "bundleDependencies", "bundledDependencies",
92
+ })
93
+
94
+ def add_node(nid: str, label: str, line: int, file_type: str = "code") -> None:
95
+ if nid and nid not in seen_ids:
96
+ seen_ids.add(nid)
97
+ nodes.append({"id": nid, "label": label, "file_type": file_type,
98
+ "source_file": str_path, "source_location": f"L{line}"})
99
+
100
+ def add_edge(src: str, tgt: str, relation: str, line: int,
101
+ context: str | None = None) -> None:
102
+ if not src or not tgt or src == tgt:
103
+ return
104
+ edge = {"source": src, "target": tgt, "relation": relation,
105
+ "confidence": "EXTRACTED", "source_file": str_path,
106
+ "source_location": f"L{line}", "weight": 1.0}
107
+ if context:
108
+ edge["context"] = context
109
+ edges.append(edge)
110
+
111
+ file_nid = _make_id(str(path))
112
+ add_node(file_nid, path.name, 1)
113
+
114
+ def _key_text(pair_node) -> str | None:
115
+ """Extract the string content of a pair's key."""
116
+ key_node = pair_node.child_by_field_name("key")
117
+ if key_node is None:
118
+ return None
119
+ if key_node.type == "string":
120
+ content = key_node.child_by_field_name("string_content")
121
+ if content:
122
+ return _read_text(content, source)
123
+ # fallback: strip surrounding quotes
124
+ raw = _read_text(key_node, source)
125
+ return raw.strip('"\'')
126
+ return _read_text(key_node, source)
127
+
128
+ def _val_node(pair_node):
129
+ return pair_node.child_by_field_name("value")
130
+
131
+ def walk_object(obj_node, parent_nid: str, parent_key: str | None,
132
+ depth: int, pair_count: list) -> None:
133
+ if depth > 6:
134
+ return
135
+ for child in obj_node.children:
136
+ if child.type != "pair":
137
+ continue
138
+ if pair_count[0] >= 500: # check per-pair so the cap is honoured exactly (J-3)
139
+ return
140
+ pair_count[0] += 1
141
+ key = _key_text(child)
142
+ if not key:
143
+ continue
144
+ # A key that normalizes to nothing (a JSONC `"//"` comment key, say)
145
+ # would collapse `_make_id(stem, key)` down to the bare file-stem id,
146
+ # which is absolute-path-derived and leaks the scan path (#1899). Such
147
+ # keys carry no graph signal, so drop them.
148
+ if not normalize_id(key):
149
+ continue
150
+ key_nid = _make_id(stem, *(([parent_key] if parent_key else []) + [key]))
151
+ if not key_nid:
152
+ continue
153
+ line = child.start_point[0] + 1
154
+ add_node(key_nid, key, line)
155
+ add_edge(parent_nid, key_nid, "contains", line)
156
+
157
+ val = _val_node(child)
158
+ if val is None:
159
+ continue
160
+
161
+ if val.type == "object":
162
+ walk_object(val, key_nid, key, depth + 1, pair_count)
163
+
164
+ elif val.type == "array" and key == "extends":
165
+ # Only a genuine `extends` array (tsconfig 5.0+, eslint) is
166
+ # inheritance. This branch used to fire for *every* array under
167
+ # a tracked key, so `compilerOptions.lib: ["dom","esnext"]` and
168
+ # `exclude: ["node_modules",".next"]` each emitted an `extends`
169
+ # edge — array membership relabelled as inheritance. The string
170
+ # branch below already gates on `key == "extends"`; this matches
171
+ # it. Non-`extends` arrays keep their `contains` edge only.
172
+ # Prefix with "ref_" so external refs don't collide with real
173
+ # code/file node IDs that share the same collapsed _make_id (J-4).
174
+ for item in val.children:
175
+ if item.type == "string":
176
+ content = item.child_by_field_name("string_content")
177
+ ref = _read_text(content, source) if content else _read_text(item, source).strip('"\'')
178
+ if ref:
179
+ ref_nid = _make_id("ref", ref)
180
+ if ref_nid:
181
+ add_node(ref_nid, ref, line, file_type="concept")
182
+ add_edge(key_nid, ref_nid, "extends", line, context="import")
183
+
184
+ elif val.type == "string":
185
+ content = val.child_by_field_name("string_content")
186
+ val_text = _read_text(content, source) if content else _read_text(val, source).strip('"\'')
187
+
188
+ if key == "extends" and val_text:
189
+ # Namespace external refs to avoid ID collision with file nodes (J-4)
190
+ ref_nid = _make_id("ref", val_text)
191
+ if ref_nid:
192
+ add_node(ref_nid, val_text, line, file_type="concept")
193
+ add_edge(file_nid, ref_nid, "extends", line, context="import")
194
+
195
+ elif key == "$ref" and val_text:
196
+ # Namespace $ref values to prevent edge hijacking into code nodes (J-4)
197
+ ref_nid = _make_id("ref", val_text)
198
+ if ref_nid:
199
+ add_edge(parent_nid, ref_nid, "references", line)
200
+
201
+ elif parent_key in _DEP_KEYS and val_text:
202
+ # Two defects fixed here, both from the #1764 shape:
203
+ #
204
+ # 1. The edge ran `key_nid -> dep_nid`, and both nodes carry
205
+ # the *same label* (the dependency name), so every
206
+ # dependency produced a visually self-referential
207
+ # `react --imports--> react` (22 of them on a mid-size
208
+ # Next.js repo). The self-loop guard in
209
+ # test_extract_json_import_and_extends_targets_are_real_nodes
210
+ # compares node ids, and these two ids differ, so the
211
+ # duplicate slipped past it. Sourcing the edge at the
212
+ # manifest yields `package.json --imports--> react`.
213
+ #
214
+ # 2. `_make_id(key)` minted a *bare* id, skipping the "ref"
215
+ # namespacing every other external reference in this file
216
+ # applies (J-4). A dependency named `utils`, `colors` or
217
+ # `types` could then be collapsed onto a same-named local
218
+ # module by build.py's alias index -- the #1638 failure
219
+ # mode reached through a different door.
220
+ dep_nid = _make_id("ref", key)
221
+ if dep_nid:
222
+ add_node(dep_nid, key, line, file_type="concept")
223
+ add_edge(file_nid, dep_nid, "imports", line, context="import")
224
+
225
+ # Entry: find root document → object
226
+ doc = root
227
+ if doc.type == "document" and doc.child_count > 0:
228
+ doc = doc.children[0]
229
+ if doc.type == "object":
230
+ # Only AST-extract recognized config/manifest JSON. Data JSON (fixtures,
231
+ # datasets, GeoJSON, API dumps) is skipped so it doesn't explode into
232
+ # orphan key-nodes (#1224); it's left to the LLM semantic pass.
233
+ if not _is_config_json(path, doc, source):
234
+ return {"nodes": [], "edges": [], "skipped": "data json (not a config/manifest)"}
235
+ walk_object(doc, file_nid, None, 0, [0])
236
+ else:
237
+ # Top-level array or scalar => data JSON, never a config/manifest.
238
+ return {"nodes": [], "edges": [], "skipped": "data json (non-object root)"}
239
+
240
+ return {"nodes": nodes, "edges": edges}
@@ -0,0 +1,289 @@
1
+ """julia — moved verbatim from graphify/extract.py."""
2
+ from __future__ import annotations
3
+
4
+ from graphify.extractors.base import _file_stem, _make_id, _read_text
5
+ from graphify.extractors.engine import _semantic_reference_edge
6
+ from pathlib import Path
7
+
8
+
9
+ def extract_julia(path: Path) -> dict:
10
+ """Extract modules, structs, functions, imports, and calls from a .jl file."""
11
+ try:
12
+ import tree_sitter_julia as tsjulia
13
+ from tree_sitter import Language, Parser
14
+ except ImportError:
15
+ return {"nodes": [], "edges": [], "error": "tree-sitter-julia not installed"}
16
+
17
+ try:
18
+ language = Language(tsjulia.language())
19
+ parser = Parser(language)
20
+ source = path.read_bytes()
21
+ tree = parser.parse(source)
22
+ root = tree.root_node
23
+ except Exception as e:
24
+ return {"nodes": [], "edges": [], "error": str(e)}
25
+
26
+ stem = _file_stem(path)
27
+ str_path = str(path)
28
+ nodes: list[dict] = []
29
+ edges: list[dict] = []
30
+ seen_ids: set[str] = set()
31
+ function_bodies: list[tuple[str, object]] = []
32
+
33
+ def add_node(nid: str, label: str, line: int) -> None:
34
+ if nid not in seen_ids:
35
+ seen_ids.add(nid)
36
+ nodes.append({
37
+ "id": nid,
38
+ "label": label,
39
+ "file_type": "code",
40
+ "source_file": str_path,
41
+ "source_location": f"L{line}",
42
+ })
43
+
44
+ def add_edge(src: str, tgt: str, relation: str, line: int,
45
+ confidence: str = "EXTRACTED", weight: float = 1.0,
46
+ context: str | None = None) -> None:
47
+ edge = {
48
+ "source": src,
49
+ "target": tgt,
50
+ "relation": relation,
51
+ "confidence": confidence,
52
+ "source_file": str_path,
53
+ "source_location": f"L{line}",
54
+ "weight": weight,
55
+ }
56
+ if context:
57
+ edge["context"] = context
58
+ edges.append(edge)
59
+
60
+ file_nid = _make_id(str(path))
61
+ add_node(file_nid, path.name, 1)
62
+
63
+ def ensure_named_node(name: str, line: int) -> str:
64
+ nid = _make_id(stem, name)
65
+ if nid in seen_ids:
66
+ return nid
67
+ nid = _make_id(name)
68
+ if nid not in seen_ids:
69
+ # The name isn't defined in this file, so this is a cross-file reference
70
+ # (e.g. a `Thing` type annotation imported from another module). Emit a
71
+ # SOURCELESS stub — like the inheritance-base path below — so the
72
+ # corpus-level rewire can collapse it onto the real definition. A sourced
73
+ # stub here makes _disambiguate_colliding_node_ids bake the referencing
74
+ # file's path (with extension) into the id and blocks the rewire, which is
75
+ # the phantom-duplicate-node bug (#1402).
76
+ seen_ids.add(nid)
77
+ nodes.append({
78
+ "id": nid,
79
+ "label": name,
80
+ "file_type": "code",
81
+ "source_file": "",
82
+ "source_location": "",
83
+ "origin_file": str_path,
84
+ })
85
+ return nid
86
+
87
+ def _type_head_names(type_head) -> tuple[str | None, str | None]:
88
+ """Return (type_name, supertype_name) from a Julia `type_head`.
89
+
90
+ A bare declaration (`Foo`) exposes an `identifier`; a subtyping
91
+ declaration (`Foo <: Bar`) wraps both names in a `binary_expression`.
92
+ Both the struct and abstract-type paths need this, so parse it once.
93
+ """
94
+ bin_expr = next(
95
+ (c for c in type_head.children if c.type == "binary_expression"), None
96
+ )
97
+ if bin_expr:
98
+ identifiers = [c for c in bin_expr.children if c.type == "identifier"]
99
+ if identifiers:
100
+ name = _read_text(identifiers[0], source)
101
+ super_name = _read_text(identifiers[-1], source) if len(identifiers) >= 2 else None
102
+ return name, super_name
103
+ return None, None
104
+ name_node = next((c for c in type_head.children if c.type == "identifier"), None)
105
+ return (_read_text(name_node, source) if name_node else None), None
106
+
107
+ def _func_name_from_signature(sig_node) -> str | None:
108
+ """Extract function name from a Julia signature node (call_expression > identifier)."""
109
+ for child in sig_node.children:
110
+ if child.type == "call_expression":
111
+ callee = child.children[0] if child.children else None
112
+ if callee and callee.type == "identifier":
113
+ return _read_text(callee, source)
114
+ return None
115
+
116
+ def walk_calls(body_node, func_nid: str) -> None:
117
+ if body_node is None:
118
+ return
119
+ t = body_node.type
120
+ if t in ("function_definition", "short_function_definition"):
121
+ return
122
+ if t == "call_expression" and body_node.children:
123
+ callee = body_node.children[0]
124
+ # Direct call: foo(...)
125
+ if callee.type == "identifier":
126
+ callee_name = _read_text(callee, source)
127
+ target_nid = _make_id(stem, callee_name)
128
+ add_edge(func_nid, target_nid, "calls", body_node.start_point[0] + 1,
129
+ confidence="EXTRACTED", context="call")
130
+ # Method call: obj.method(...)
131
+ elif callee.type == "field_expression" and len(callee.children) >= 3:
132
+ method_node = callee.children[-1]
133
+ method_name = _read_text(method_node, source)
134
+ target_nid = _make_id(stem, method_name)
135
+ add_edge(func_nid, target_nid, "calls", body_node.start_point[0] + 1,
136
+ confidence="EXTRACTED", context="call")
137
+ for child in body_node.children:
138
+ walk_calls(child, func_nid)
139
+
140
+ def walk(node, scope_nid: str) -> None:
141
+ t = node.type
142
+
143
+ # Module
144
+ if t == "module_definition":
145
+ name_node = next((c for c in node.children if c.type == "identifier"), None)
146
+ if name_node:
147
+ mod_name = _read_text(name_node, source)
148
+ mod_nid = _make_id(stem, mod_name)
149
+ line = node.start_point[0] + 1
150
+ add_node(mod_nid, mod_name, line)
151
+ add_edge(file_nid, mod_nid, "defines", line)
152
+ for child in node.children:
153
+ walk(child, mod_nid)
154
+ return
155
+
156
+ # Struct (struct / mutable struct — both map to struct_definition in tree-sitter-julia)
157
+ if t == "struct_definition":
158
+ # type_head may contain: identifier (simple) or binary_expression (Foo <: Bar)
159
+ type_head = next((c for c in node.children if c.type == "type_head"), None)
160
+ if not type_head:
161
+ return
162
+ struct_name, super_name = _type_head_names(type_head)
163
+ if not struct_name:
164
+ return
165
+ struct_nid = _make_id(stem, struct_name)
166
+ line = node.start_point[0] + 1
167
+ add_node(struct_nid, struct_name, line)
168
+ add_edge(scope_nid, struct_nid, "defines", line)
169
+ if super_name:
170
+ add_edge(struct_nid, ensure_named_node(super_name, line),
171
+ "inherits", line, confidence="EXTRACTED")
172
+ # Field types: each `name::Type` lowers to a typed_expression child of struct_definition
173
+ for child in node.children:
174
+ if child.type == "typed_expression":
175
+ type_ids = [c for c in child.children if c.type == "identifier"]
176
+ if len(type_ids) >= 2:
177
+ field_line = child.start_point[0] + 1
178
+ type_name = _read_text(type_ids[-1], source)
179
+ type_nid = ensure_named_node(type_name, field_line)
180
+ edges.append(_semantic_reference_edge(
181
+ struct_nid, type_nid, "field", str_path, field_line))
182
+ return
183
+
184
+ # Abstract type
185
+ if t == "abstract_definition":
186
+ # type_head is a bare `identifier` (`abstract type Foo end`) or a
187
+ # `binary_expression` for the subtyping form (`abstract type Foo <: Bar end`).
188
+ # The latter was dropped entirely — abstract types are the backbone of
189
+ # Julia's dispatch hierarchies, so an intermediate `Foo <: Bar` vanishing
190
+ # broke the inheritance chain and lost the type node itself.
191
+ type_head = next((c for c in node.children if c.type == "type_head"), None)
192
+ if type_head:
193
+ abs_name, super_name = _type_head_names(type_head)
194
+ if abs_name:
195
+ abs_nid = _make_id(stem, abs_name)
196
+ line = node.start_point[0] + 1
197
+ add_node(abs_nid, abs_name, line)
198
+ add_edge(scope_nid, abs_nid, "defines", line)
199
+ if super_name:
200
+ add_edge(abs_nid, ensure_named_node(super_name, line),
201
+ "inherits", line, confidence="EXTRACTED")
202
+ return
203
+
204
+ # Function: function foo(...) ... end
205
+ if t == "function_definition":
206
+ sig_node = next((c for c in node.children if c.type == "signature"), None)
207
+ if sig_node:
208
+ func_name = _func_name_from_signature(sig_node)
209
+ if func_name:
210
+ func_nid = _make_id(stem, func_name)
211
+ line = node.start_point[0] + 1
212
+ add_node(func_nid, f"{func_name}()", line)
213
+ add_edge(scope_nid, func_nid, "defines", line)
214
+ function_bodies.append((func_nid, node))
215
+ return
216
+
217
+ # Short function: foo(x) = expr
218
+ if t == "assignment":
219
+ lhs = node.children[0] if node.children else None
220
+ if lhs and lhs.type == "call_expression" and lhs.children:
221
+ callee = lhs.children[0]
222
+ if callee.type == "identifier":
223
+ func_name = _read_text(callee, source)
224
+ func_nid = _make_id(stem, func_name)
225
+ line = node.start_point[0] + 1
226
+ add_node(func_nid, f"{func_name}()", line)
227
+ add_edge(scope_nid, func_nid, "defines", line)
228
+ # Only walk the RHS (index 2 after lhs and operator) to avoid self-loops
229
+ rhs = node.children[-1] if len(node.children) >= 3 else None
230
+ if rhs:
231
+ function_bodies.append((func_nid, rhs))
232
+ return
233
+
234
+ # Using / Import
235
+ if t in ("using_statement", "import_statement"):
236
+ line = node.start_point[0] + 1
237
+
238
+ def _julia_mod_name(n):
239
+ # identifier (`Foo`), scoped_identifier (`Base.Threads`), or
240
+ # import_path (relative `..Sibling`) -> the module name. Only bare
241
+ # identifiers were handled, so qualified/relative imports — and the
242
+ # scoped package of a `selected_import` — were silently dropped.
243
+ if n.type == "import_path":
244
+ ids = [c for c in n.children if c.type == "identifier"]
245
+ return _read_text(ids[-1], source) if ids else None
246
+ if n.type in ("identifier", "scoped_identifier"):
247
+ return _read_text(n, source)
248
+ return None
249
+
250
+ def _emit_import(name):
251
+ if not name:
252
+ return
253
+ imp_nid = _make_id(name)
254
+ add_node(imp_nid, name, line)
255
+ add_edge(scope_nid, imp_nid, "imports", line, context="import")
256
+
257
+ for child in node.children:
258
+ if child.type in ("identifier", "scoped_identifier", "import_path"):
259
+ _emit_import(_julia_mod_name(child))
260
+ elif child.type == "selected_import":
261
+ # `import Base.Threads: nthreads` — the package (first named
262
+ # child) may itself be a scoped_identifier/import_path.
263
+ pkg = next(
264
+ (c for c in child.children
265
+ if c.type in ("identifier", "scoped_identifier", "import_path")),
266
+ None,
267
+ )
268
+ if pkg is not None:
269
+ _emit_import(_julia_mod_name(pkg))
270
+ return
271
+
272
+ for child in node.children:
273
+ walk(child, scope_nid)
274
+
275
+ walk(root, file_nid)
276
+
277
+ for func_nid, body_node in function_bodies:
278
+ # For function_definition nodes, walk children directly to avoid
279
+ # the boundary check returning early on the top-level node itself.
280
+ # Skip the "signature" child — it contains the function's own call_expression
281
+ # which would create a self-loop.
282
+ if body_node.type == "function_definition":
283
+ for child in body_node.children:
284
+ if child.type != "signature":
285
+ walk_calls(child, func_nid)
286
+ else:
287
+ walk_calls(body_node, func_nid)
288
+
289
+ return {"nodes": nodes, "edges": edges}