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,107 @@
1
+ # Migrating a language extractor out of extract.py
2
+
3
+ `graphify/extract.py` is being split into this package, one language per PR
4
+ (upstream issue #1212). This is the playbook for porting ONE language. It is
5
+ written so an AI agent can execute it in a single session.
6
+
7
+ ## Status
8
+
9
+ | module | migrated |
10
+ |---|---|
11
+ | blade | yes |
12
+ | zig | yes |
13
+ | elixir | yes |
14
+ | razor | yes |
15
+ | dart | yes |
16
+ | rust | yes |
17
+ | go | yes |
18
+ | powershell (ps1 + psd1 manifest) | yes |
19
+ | fortran | yes |
20
+ | sql | yes |
21
+ | dm (dm/dmm/dmi/dmf) | yes |
22
+ | bash | yes |
23
+ | apex | yes |
24
+ | terraform | yes |
25
+ | sln | yes |
26
+ | pascal_forms (dfm + lfm) | yes |
27
+ | json_config | yes |
28
+ | (config-driven core: python, js, java, c, cpp, csharp, kotlin, scala, php, lua, swift, groovy, vue, svelte, astro, xaml, groovy) | no — shared _extract_generic core, move as one batch |
29
+ | (other bespoke: julia, verilog, markdown, objc, csproj, slnx, lazarus_package, pascal) | no |
30
+
31
+ Note: config-driven extractors (python, js, java, c, cpp, ruby, csharp,
32
+ kotlin, scala, php, lua, swift, groovy) depend on the shared
33
+ `_extract_generic` core (~1,300 lines). Do NOT port them one-by-one; the core
34
+ must move first as its own coordinated batch. Pick a bespoke extractor.
35
+
36
+ ## Invariants (non-negotiable)
37
+
38
+ 1. **Verbatim moves only.** No renames, no docstring edits, no reformatting,
39
+ no added annotations, no "improvements". Verify: save the block before
40
+ cutting and confirm the pasted block is byte-identical.
41
+ 2. **One language per PR.** Small diffs keep review trivial and avoid
42
+ conflicts with other in-flight ports.
43
+ 3. **Facade re-export is mandatory.** `extract.py` must keep exporting every
44
+ moved name (`from graphify.extractors.<mod> import extract_<lang> # noqa: F401`
45
+ in the marked migration block, kept alphabetical). Existing importers
46
+ (`__main__.py`, `watch.py`, `pg_introspect.py`, tests) must not change.
47
+ 4. **Never import from `graphify.extract` inside this package.** Import
48
+ direction is strictly extract.py -> extractors/. If you need a helper that
49
+ lives in extract.py, classify it (below) and move it.
50
+ 5. **Zero test edits** outside `tests/test_extractors_registry.py`. The
51
+ untouched language tests passing IS the proof of behavior preservation.
52
+
53
+ ## Helper classification
54
+
55
+ For every `_name` your function references that is defined OUTSIDE it:
56
+
57
+ - run `grep -c '_name' graphify/extract.py` AFTER your candidate move;
58
+ - remaining uses > 0 -> **shared**: move it to `base.py` and add it to the
59
+ facade re-import in extract.py;
60
+ - remaining uses = 0 -> **private**: move it into your language module.
61
+
62
+ Closures, constants, and `import` statements defined INSIDE your function
63
+ move with it for free — leave them exactly where they are. Only add a
64
+ module-header import for names the pasted code references at module scope
65
+ that are not satisfied internally, and verify each header import is used.
66
+
67
+ ## Pre-flight
68
+
69
+ 1. Check upstream for conflicts: open PRs/issues mentioning your language,
70
+ and churn: `git log --oneline --since="3 months ago" upstream/<default> | grep -i <lang>`.
71
+ High churn -> pick another language.
72
+ 2. Confirm your extractor is bespoke (its `extract_<lang>` is a full function,
73
+ not a 5-line `_extract_generic(path, LanguageConfig(...))` wrapper).
74
+ 3. Check whether tests/ exercises your language's behavior (grep for
75
+ `test_<lang>`). If it has no behavioral tests, the byte-identity check in
76
+ step 3 below is the ENTIRE proof of preservation — include the
77
+ `git diff --color-moved` evidence in your PR description.
78
+
79
+ ## Steps
80
+
81
+ 1. Append a failing test to `tests/test_extractors_registry.py`:
82
+ module import + facade identity + registry identity (copy an existing
83
+ `test_<lang>_migrated` as the template).
84
+ 2. `grep -n 'def extract_<lang>' graphify/extract.py`; the span ends at the
85
+ line before the next top-level statement (`^def ` or `^_CONST`). Beware
86
+ neighbors: top-level constants AFTER your function may belong to the NEXT
87
+ function (e.g. `_CONFIG_JSON_*` sit after where extract_razor used to be
88
+ but were never razor's).
89
+ 3. Save the span to a temp file. Create `graphify/extractors/<lang>.py` with
90
+ module docstring (`"""<Lang> extractor. Moved verbatim from graphify/extract.py."""`),
91
+ `from __future__ import annotations`, minimal stdlib imports, base imports,
92
+ then paste the function. Verify byte-identity against the temp file.
93
+ 4. Delete the span from extract.py, leaving exactly two blank lines between
94
+ the now-adjacent top-level definitions; add the facade re-import; add the
95
+ registry entry in `__init__.py` (alphabetical); update the Status table
96
+ above.
97
+ 5. `uv run pytest -q` -> 0 failures, no test file changed except the registry
98
+ test. If ImportError/NameError: a helper was misclassified — go to
99
+ Helper classification.
100
+ 6. One commit: `refactor(extract): move extract_<lang> to extractors/<lang>.py (verbatim)`.
101
+
102
+ ## What NOT to do
103
+
104
+ - Do not rewire dispatch, add classes, or add lazy imports — mechanism layers
105
+ come later, by separate agreement (see #1212 discussion).
106
+ - Do not port two languages in one PR "while you're at it".
107
+ - Do not touch `__main__.py`.
@@ -0,0 +1,66 @@
1
+ """Per-language extractors, incrementally migrated out of graphify/extract.py.
2
+
3
+ Dispatch still flows through graphify.extract (the facade re-exports every
4
+ moved name), so importing from graphify.extract keeps working unchanged.
5
+ LANGUAGE_EXTRACTORS is the registry seed; wiring dispatch through it is a
6
+ later, separate step. See MIGRATION.md for how to port another language.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ from pathlib import Path
11
+ from typing import Callable
12
+
13
+ from graphify.extractors.apex import extract_apex
14
+ from graphify.extractors.bash import extract_bash
15
+ from graphify.extractors.blade import extract_blade
16
+ from graphify.extractors.commonlisp import extract_commonlisp
17
+ from graphify.extractors.dart import extract_dart
18
+ from graphify.extractors.dm import extract_dm, extract_dmf, extract_dmi, extract_dmm
19
+ from graphify.extractors.elixir import extract_elixir
20
+ from graphify.extractors.fortran import extract_fortran
21
+ from graphify.extractors.go import extract_go
22
+ from graphify.extractors.json_config import extract_json
23
+ from graphify.extractors.julia import extract_julia
24
+ from graphify.extractors.markdown import extract_markdown
25
+ from graphify.extractors.objc import extract_objc
26
+ from graphify.extractors.pascal import extract_pascal
27
+ from graphify.extractors.pascal_forms import extract_delphi_form, extract_lazarus_form
28
+ from graphify.extractors.powershell import extract_powershell, extract_powershell_manifest
29
+ from graphify.extractors.razor import extract_razor
30
+ from graphify.extractors.rust import extract_rust
31
+ from graphify.extractors.sln import extract_sln
32
+ from graphify.extractors.sql import extract_sql
33
+ from graphify.extractors.terraform import extract_terraform
34
+ from graphify.extractors.verilog import extract_verilog
35
+ from graphify.extractors.zig import extract_zig
36
+
37
+ LANGUAGE_EXTRACTORS: dict[str, Callable[[Path], dict]] = {
38
+ "apex": extract_apex,
39
+ "bash": extract_bash,
40
+ "blade": extract_blade,
41
+ "commonlisp": extract_commonlisp,
42
+ "dart": extract_dart,
43
+ "delphi_form": extract_delphi_form,
44
+ "dm": extract_dm,
45
+ "dmf": extract_dmf,
46
+ "dmi": extract_dmi,
47
+ "dmm": extract_dmm,
48
+ "elixir": extract_elixir,
49
+ "fortran": extract_fortran,
50
+ "go": extract_go,
51
+ "json": extract_json,
52
+ "julia": extract_julia,
53
+ "lazarus_form": extract_lazarus_form,
54
+ "markdown": extract_markdown,
55
+ "objc": extract_objc,
56
+ "pascal": extract_pascal,
57
+ "powershell": extract_powershell,
58
+ "powershell_manifest": extract_powershell_manifest,
59
+ "razor": extract_razor,
60
+ "rust": extract_rust,
61
+ "sln": extract_sln,
62
+ "sql": extract_sql,
63
+ "terraform": extract_terraform,
64
+ "verilog": extract_verilog,
65
+ "zig": extract_zig,
66
+ }
@@ -0,0 +1,215 @@
1
+ """Apex 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
7
+
8
+
9
+ def extract_apex(path: Path) -> dict:
10
+ """Extract classes, interfaces, enums, methods, and Salesforce constructs from
11
+ Apex .cls and .trigger files using regex (no tree-sitter grammar on PyPI)."""
12
+ import re as _re
13
+ try:
14
+ source = path.read_text(encoding="utf-8", errors="replace")
15
+ except OSError:
16
+ return {"nodes": [], "edges": []}
17
+
18
+ str_path = str(path)
19
+ stem = _file_stem(path)
20
+ file_nid = _make_id(str_path)
21
+
22
+ nodes: list[dict] = []
23
+ edges: list[dict] = []
24
+ seen_ids: set[str] = set()
25
+
26
+ def add_node(nid: str, label: str, line: int) -> None:
27
+ if nid not in seen_ids:
28
+ seen_ids.add(nid)
29
+ nodes.append({
30
+ "id": nid,
31
+ "label": label,
32
+ "file_type": "code",
33
+ "source_file": str_path,
34
+ "source_location": f"L{line}",
35
+ })
36
+
37
+ def add_edge(src: str, tgt: str, relation: str, line: int,
38
+ confidence: str = "EXTRACTED") -> None:
39
+ edges.append({
40
+ "source": src,
41
+ "target": tgt,
42
+ "relation": relation,
43
+ "confidence": confidence,
44
+ "source_file": str_path,
45
+ "source_location": f"L{line}",
46
+ "weight": 1.0,
47
+ })
48
+
49
+ add_node(file_nid, path.name, 1)
50
+
51
+ lines = source.splitlines()
52
+
53
+ _ACCESS = r"(?:public|private|protected|global|webService)?"
54
+ _SHARING = r"(?:\s+(?:with|without|inherited)\s+sharing)?"
55
+ _MOD = r"(?:\s+(?:abstract|virtual|override|static|final|transient|testMethod))?"
56
+ _ANNOTATION = r"(?:\s*@\w+(?:\s*\([^)]*\))?\s*)*"
57
+
58
+ cls_re = _re.compile(
59
+ rf"^{_ANNOTATION}\s*{_ACCESS}{_SHARING}{_MOD}\s*class\s+(\w+)"
60
+ rf"(?:\s+extends\s+(\w+))?(?:\s+implements\s+([\w,\s]+))?\s*\{{?",
61
+ _re.IGNORECASE,
62
+ )
63
+ iface_re = _re.compile(
64
+ rf"^{_ANNOTATION}\s*{_ACCESS}{_SHARING}{_MOD}\s*interface\s+(\w+)"
65
+ rf"(?:\s+extends\s+([\w,\s]+))?\s*\{{?",
66
+ _re.IGNORECASE,
67
+ )
68
+ enum_re = _re.compile(
69
+ rf"^{_ANNOTATION}\s*{_ACCESS}{_SHARING}{_MOD}\s*enum\s+(\w+)\s*\{{?",
70
+ _re.IGNORECASE,
71
+ )
72
+ trigger_re = _re.compile(
73
+ r"^\s*trigger\s+(\w+)\s+on\s+(\w+)\s*\(",
74
+ _re.IGNORECASE,
75
+ )
76
+ method_re = _re.compile(
77
+ rf"^{_ANNOTATION}\s*{_ACCESS}{_MOD}\s*(?:static\s+)?[\w<>\[\]]+\s+(\w+)\s*\([^)]*\)\s*(?:throws\s+\w+\s*)?\{{?",
78
+ _re.IGNORECASE,
79
+ )
80
+ annotation_re = _re.compile(r"@(\w+)", _re.IGNORECASE)
81
+ soql_re = _re.compile(r"\[\s*SELECT\b[^\]]+FROM\s+(\w+)", _re.IGNORECASE)
82
+ dml_re = _re.compile(r"\b(insert|update|delete|upsert|merge|undelete)\s+\w", _re.IGNORECASE)
83
+
84
+ _CONTROL_FLOW = frozenset({
85
+ "if", "else", "for", "while", "do", "switch", "try", "catch",
86
+ "finally", "return", "throw", "new", "void", "null",
87
+ "true", "false", "this", "super", "class", "interface", "enum",
88
+ "trigger", "on",
89
+ })
90
+
91
+ current_class_nid: str | None = None
92
+ pending_annotations: list[str] = []
93
+
94
+ for lineno, line_text in enumerate(lines, start=1):
95
+ stripped = line_text.strip()
96
+
97
+ if stripped.startswith("@"):
98
+ for m in annotation_re.finditer(stripped):
99
+ pending_annotations.append(m.group(1).lower())
100
+ continue
101
+
102
+ tm = trigger_re.match(stripped)
103
+ if tm:
104
+ trig_name, sobject = tm.group(1), tm.group(2)
105
+ trig_nid = _make_id(stem, trig_name)
106
+ add_node(trig_nid, trig_name, lineno)
107
+ add_edge(file_nid, trig_nid, "contains", lineno)
108
+ sob_nid = _make_id(sobject)
109
+ if sob_nid not in seen_ids:
110
+ add_node(sob_nid, sobject, lineno)
111
+ add_edge(trig_nid, sob_nid, "uses", lineno, confidence="INFERRED")
112
+ current_class_nid = trig_nid
113
+ pending_annotations = []
114
+ continue
115
+
116
+ cm = cls_re.match(stripped)
117
+ if cm:
118
+ class_name = cm.group(1)
119
+ if class_name.lower() in _CONTROL_FLOW:
120
+ pending_annotations = []
121
+ continue
122
+ class_nid = _make_id(stem, class_name)
123
+ add_node(class_nid, class_name, lineno)
124
+ add_edge(file_nid, class_nid, "contains", lineno)
125
+ if cm.group(2):
126
+ base = cm.group(2).strip()
127
+ base_nid = _make_id(stem, base)
128
+ if base_nid not in seen_ids:
129
+ base_nid = _make_id(base)
130
+ if base_nid not in seen_ids:
131
+ add_node(base_nid, base, lineno)
132
+ add_edge(class_nid, base_nid, "extends", lineno, confidence="INFERRED")
133
+ if cm.group(3):
134
+ for iface in cm.group(3).split(","):
135
+ iface = iface.strip()
136
+ if iface:
137
+ iface_nid = _make_id(stem, iface)
138
+ if iface_nid not in seen_ids:
139
+ iface_nid = _make_id(iface)
140
+ if iface_nid not in seen_ids:
141
+ add_node(iface_nid, iface, lineno)
142
+ add_edge(class_nid, iface_nid, "implements", lineno, confidence="INFERRED")
143
+ current_class_nid = class_nid
144
+ pending_annotations = []
145
+ continue
146
+
147
+ im = iface_re.match(stripped)
148
+ if im:
149
+ iface_name = im.group(1)
150
+ if iface_name.lower() in _CONTROL_FLOW:
151
+ pending_annotations = []
152
+ continue
153
+ iface_nid = _make_id(stem, iface_name)
154
+ add_node(iface_nid, iface_name, lineno)
155
+ add_edge(file_nid if current_class_nid is None else current_class_nid,
156
+ iface_nid, "contains", lineno)
157
+ if im.group(2):
158
+ for parent in im.group(2).split(","):
159
+ parent = parent.strip()
160
+ if parent:
161
+ parent_nid = _make_id(stem, parent)
162
+ if parent_nid not in seen_ids:
163
+ parent_nid = _make_id(parent)
164
+ if parent_nid not in seen_ids:
165
+ add_node(parent_nid, parent, lineno)
166
+ add_edge(iface_nid, parent_nid, "extends", lineno, confidence="INFERRED")
167
+ pending_annotations = []
168
+ continue
169
+
170
+ em = enum_re.match(stripped)
171
+ if em:
172
+ enum_name = em.group(1)
173
+ if enum_name.lower() in _CONTROL_FLOW:
174
+ pending_annotations = []
175
+ continue
176
+ enum_nid = _make_id(stem, enum_name)
177
+ add_node(enum_nid, enum_name, lineno)
178
+ add_edge(file_nid if current_class_nid is None else current_class_nid,
179
+ enum_nid, "contains", lineno)
180
+ pending_annotations = []
181
+ continue
182
+
183
+ if current_class_nid is not None:
184
+ mm = method_re.match(stripped)
185
+ if mm:
186
+ method_name = mm.group(1)
187
+ if method_name.lower() not in _CONTROL_FLOW:
188
+ method_nid = _make_id(current_class_nid, method_name)
189
+ method_label = f".{method_name}()"
190
+ add_node(method_nid, method_label, lineno)
191
+ add_edge(current_class_nid, method_nid, "method", lineno)
192
+ if "auraenabled" in pending_annotations or "invocablemethod" in pending_annotations:
193
+ add_edge(file_nid, method_nid, "contains", lineno, confidence="INFERRED")
194
+ pending_annotations = []
195
+ continue
196
+
197
+ pending_annotations = []
198
+
199
+ for sm in soql_re.finditer(line_text):
200
+ sobject = sm.group(1)
201
+ sob_nid = _make_id(sobject)
202
+ if sob_nid not in seen_ids:
203
+ add_node(sob_nid, sobject, lineno)
204
+ src = current_class_nid or file_nid
205
+ add_edge(src, sob_nid, "uses", lineno, confidence="INFERRED")
206
+
207
+ for dm in dml_re.finditer(line_text):
208
+ dml_op = dm.group(1).lower()
209
+ dml_nid = _make_id(f"dml_{dml_op}")
210
+ if dml_nid not in seen_ids:
211
+ add_node(dml_nid, dml_op, lineno)
212
+ src = current_class_nid or file_nid
213
+ add_edge(src, dml_nid, "uses", lineno, confidence="INFERRED")
214
+
215
+ return {"nodes": nodes, "edges": edges}
@@ -0,0 +1,85 @@
1
+ # DO NOT import from graphify.extract here — direction is extract.py → extractors/ only.
2
+ from __future__ import annotations
3
+
4
+ from pathlib import Path
5
+
6
+ from graphify.ids import make_id
7
+
8
+ # Language built-in globals that AST may classify as call targets when used as
9
+ # constructors or coercion functions (e.g. String(x), Number(x), Boolean(x)).
10
+ # Without this filter they become god-nodes accumulating spurious edges from
11
+ # every call site. Filter applied at same-file and cross-file resolution.
12
+ # See issue #726.
13
+ _LANGUAGE_BUILTIN_GLOBALS: frozenset[str] = frozenset({
14
+ # JavaScript / TypeScript ECMAScript built-ins
15
+ "String", "Number", "Boolean", "Object", "Array", "Symbol", "BigInt",
16
+ "Date", "RegExp", "Error", "TypeError", "RangeError", "SyntaxError",
17
+ "ReferenceError", "EvalError", "URIError",
18
+ "Promise", "Map", "Set", "WeakMap", "WeakSet", "JSON", "Math",
19
+ "Reflect", "Proxy", "Intl",
20
+ "parseInt", "parseFloat", "isNaN", "isFinite",
21
+ "encodeURIComponent", "decodeURIComponent", "encodeURI", "decodeURI",
22
+ # Browser / Node common globals
23
+ "URL", "URLSearchParams", "FormData", "Blob", "File",
24
+ "Headers", "Request", "Response", "AbortController", "AbortSignal",
25
+ "TextEncoder", "TextDecoder", "console",
26
+ # Python built-in callables
27
+ "str", "int", "float", "bool", "list", "dict", "set", "tuple", "bytes",
28
+ "len", "range", "enumerate", "zip", "map", "filter", "sum", "min", "max",
29
+ "print", "open", "isinstance", "type", "super", "sorted", "reversed",
30
+ "any", "all", "abs", "round", "next", "iter", "hash", "id", "repr",
31
+ "callable", "getattr", "setattr", "hasattr", "delattr", "vars", "dir",
32
+ # Swift standard library / Foundation / SwiftUI (#2147). Value-type
33
+ # initializers (Data(x), Int(x), UUID()) and protocol conformance targets
34
+ # appear from virtually every file of a Swift codebase, exactly like the
35
+ # ECMAScript constructors above. String/Date/URL/Error are already listed.
36
+ "Int", "Int8", "Int16", "Int32", "Int64",
37
+ "UInt", "UInt8", "UInt16", "UInt32", "UInt64",
38
+ "Double", "Float", "Bool", "Character",
39
+ "Sendable", "Codable", "Decodable", "Encodable", "Equatable", "Hashable",
40
+ "Identifiable", "Comparable", "CaseIterable", "RawRepresentable",
41
+ "CustomStringConvertible", "CustomDebugStringConvertible", "AnyObject",
42
+ "LocalizedError",
43
+ "Data", "UUID", "Decimal", "Calendar", "Locale", "TimeZone", "Bundle",
44
+ "IndexPath", "IndexSet", "NotificationCenter", "UserDefaults",
45
+ "FileManager", "URLSession", "URLRequest", "URLComponents",
46
+ "JSONDecoder", "JSONEncoder", "DateFormatter", "NumberFormatter",
47
+ "ISO8601DateFormatter",
48
+ "NSObject", "NSString", "NSError", "NSLock", "NSAttributedString",
49
+ "DispatchQueue", "DispatchGroup", "OperationQueue", "RunLoop",
50
+ "View", "Color", "Font",
51
+ })
52
+
53
+
54
+ def _make_id(*parts: str) -> str:
55
+ return make_id(*parts)
56
+
57
+
58
+ def _file_stem(path: Path) -> str:
59
+ """Stem used as the node-ID prefix for a file and its symbols.
60
+
61
+ The full path (extension dropped) is preserved as path segments; ``make_id``
62
+ later collapses the separators to underscores. Using every segment — not just
63
+ the immediate parent dir (#1504) — means same-named files in different
64
+ directories get distinct IDs instead of colliding into one
65
+ last-writer-wins node:
66
+
67
+ docs/v1/api/README.md -> docs/v1/api/README -> docs_v1_api_readme
68
+ docs/v2/api/README.md -> docs/v2/api/README -> docs_v2_api_readme
69
+
70
+ Top-level files keep a bare stem (``setup.py`` -> ``setup``). When passed an
71
+ absolute path the whole path is encoded; the extract() id-remap post-pass
72
+ re-derives the canonical repo-relative form from ``source_file`` so the on-disk
73
+ location can't leak into the persisted IDs (#502).
74
+
75
+ Returns "" for a path with no name (``Path('.')`` — a source_file that equals
76
+ the scan root, so it has no per-file stem). Guarding here keeps
77
+ ``path.with_suffix("")`` from raising ``ValueError: '.' has an empty name`` and
78
+ protects every caller, not just ``_semantic_id_remap`` (#1618)."""
79
+ if not path.name:
80
+ return ""
81
+ return path.with_suffix("").as_posix()
82
+
83
+
84
+ def _read_text(node, source: bytes) -> str:
85
+ return source[node.start_byte:node.end_byte].decode("utf-8", errors="replace")