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,181 @@
1
+ """Terraform 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 _make_id
7
+
8
+
9
+ _TF_META_HEADS = frozenset({"count", "each", "self", "path", "terraform"})
10
+
11
+ def extract_terraform(path: Path) -> dict:
12
+ """Extract Terraform/HCL blocks and the references between them via tree-sitter.
13
+
14
+ Nodes: resources, data sources, modules, variables, outputs, providers, and
15
+ locals. Edges: `contains` (file -> block), `references` (block -> the blocks
16
+ it interpolates, e.g. `aws_instance.web` -> `var.region`), and `depends_on`
17
+ (explicit dependency edges).
18
+
19
+ Node IDs are scoped by the parent directory, not the file stem, because
20
+ Terraform resources are module(directory)-scoped: a resource defined in
21
+ main.tf is referenced from other .tf files in the same directory. Directory
22
+ scoping lets those cross-file references resolve when per-file extractions
23
+ are merged (stem scoping would split a definition from its references).
24
+ """
25
+ try:
26
+ import tree_sitter_hcl as tshcl
27
+ from tree_sitter import Language, Parser
28
+ except ImportError:
29
+ return {"nodes": [], "edges": [], "error": "tree_sitter_hcl not installed. Run: pip install tree-sitter-hcl"}
30
+
31
+ try:
32
+ language = Language(tshcl.language())
33
+ parser = Parser(language)
34
+ source = path.read_bytes()
35
+ tree = parser.parse(source)
36
+ root = tree.root_node
37
+ except Exception as e:
38
+ return {"nodes": [], "edges": [], "error": str(e)}
39
+
40
+ str_path = str(path)
41
+ file_nid = _make_id(str_path)
42
+ scope = path.parent.name or "tf"
43
+
44
+ nodes: list[dict] = [{"id": file_nid, "label": path.name, "file_type": "code",
45
+ "source_file": str_path, "source_location": None}]
46
+ edges: list[dict] = []
47
+ seen_ids: set[str] = {file_nid}
48
+ seen_edges: set[tuple[str, str, str]] = set()
49
+
50
+ def _read(n) -> str:
51
+ return source[n.start_byte:n.end_byte].decode("utf-8", errors="replace")
52
+
53
+ def _label_text(n) -> str:
54
+ return _read(n).strip().strip('"')
55
+
56
+ def _add_node(address: str, label: str, line: int) -> str:
57
+ nid = _make_id(scope, address)
58
+ if nid not in seen_ids:
59
+ seen_ids.add(nid)
60
+ nodes.append({"id": nid, "label": label, "file_type": "code",
61
+ "source_file": str_path, "source_location": f"L{line}"})
62
+ edges.append({"source": file_nid, "target": nid, "relation": "contains",
63
+ "confidence": "EXTRACTED", "source_file": str_path,
64
+ "source_location": f"L{line}", "weight": 1.0})
65
+ return nid
66
+
67
+ def _add_edge(src: str, address: str, relation: str, line: int) -> None:
68
+ tgt = _make_id(scope, address)
69
+ if src == tgt:
70
+ return
71
+ key = (src, tgt, relation)
72
+ if key in seen_edges:
73
+ return
74
+ seen_edges.add(key)
75
+ edges.append({"source": src, "target": tgt, "relation": relation,
76
+ "confidence": "EXTRACTED", "source_file": str_path,
77
+ "source_location": f"L{line}", "weight": 1.0})
78
+
79
+ def _block_parts(block) -> tuple:
80
+ btype = None
81
+ labels: list[str] = []
82
+ for c in block.children:
83
+ if c.type in ("block_start", "body", "block_end"):
84
+ break
85
+ if c.type == "identifier" and btype is None:
86
+ btype = _read(c)
87
+ elif c.type in ("string_lit", "identifier"):
88
+ labels.append(_label_text(c))
89
+ return btype, labels
90
+
91
+ def _ref_address(expr):
92
+ head = _read(expr)
93
+ parent = expr.parent
94
+ attrs: list[str] = []
95
+ if parent is not None:
96
+ seen_self = False
97
+ for c in parent.children:
98
+ if c.id == expr.id:
99
+ seen_self = True
100
+ continue
101
+ if seen_self and c.type == "get_attr":
102
+ name = None
103
+ for gc in c.children:
104
+ if gc.type == "identifier":
105
+ name = _read(gc)
106
+ break
107
+ if name is None:
108
+ break
109
+ attrs.append(name)
110
+ elif seen_self and c.type not in ("get_attr",):
111
+ break
112
+ if head in _TF_META_HEADS or not head:
113
+ return None
114
+ if head == "var":
115
+ return f"var.{attrs[0]}" if attrs else None
116
+ if head == "local":
117
+ return f"local.{attrs[0]}" if attrs else None
118
+ if head == "module":
119
+ return f"module.{attrs[0]}" if attrs else None
120
+ if head == "data":
121
+ return f"data.{attrs[0]}.{attrs[1]}" if len(attrs) >= 2 else None
122
+ return f"{head}.{attrs[0]}" if attrs else None
123
+
124
+ def _collect_refs(node, owner_nid: str, relation: str) -> None:
125
+ rel = relation
126
+ if node.type == "attribute":
127
+ key_node = node.child_by_field_name("key") or (
128
+ node.children[0] if node.children else None
129
+ )
130
+ if key_node is not None and _read(key_node) == "depends_on":
131
+ rel = "depends_on"
132
+ if node.type == "variable_expr":
133
+ addr = _ref_address(node)
134
+ if addr:
135
+ _add_edge(owner_nid, addr, rel, node.start_point[0] + 1)
136
+ for c in node.children:
137
+ if c.is_named:
138
+ _collect_refs(c, owner_nid, rel)
139
+
140
+ def _body_of(block):
141
+ for c in block.children:
142
+ if c.type == "body":
143
+ return c
144
+ return None
145
+
146
+ body = next((c for c in root.children if c.type == "body"), root)
147
+ for block in body.children:
148
+ if block.type != "block":
149
+ continue
150
+ btype, labels = _block_parts(block)
151
+ line = block.start_point[0] + 1
152
+ blk_body = _body_of(block)
153
+ if btype == "resource" and len(labels) >= 2:
154
+ owner = _add_node(f"{labels[0]}.{labels[1]}", f"{labels[0]}.{labels[1]}", line)
155
+ elif btype == "data" and len(labels) >= 2:
156
+ owner = _add_node(f"data.{labels[0]}.{labels[1]}", f"data.{labels[0]}.{labels[1]}", line)
157
+ elif btype == "module" and labels:
158
+ owner = _add_node(f"module.{labels[0]}", f"module.{labels[0]}", line)
159
+ elif btype == "variable" and labels:
160
+ owner = _add_node(f"var.{labels[0]}", f"var.{labels[0]}", line)
161
+ elif btype == "output" and labels:
162
+ owner = _add_node(f"output.{labels[0]}", f"output.{labels[0]}", line)
163
+ elif btype == "provider" and labels:
164
+ owner = _add_node(f"provider.{labels[0]}", f"provider.{labels[0]}", line)
165
+ elif btype == "locals" and blk_body is not None:
166
+ for attr in blk_body.children:
167
+ if attr.type != "attribute":
168
+ continue
169
+ key_node = attr.children[0] if attr.children else None
170
+ if key_node is None:
171
+ continue
172
+ key = _read(key_node)
173
+ lnid = _add_node(f"local.{key}", f"local.{key}", attr.start_point[0] + 1)
174
+ _collect_refs(attr, lnid, "references")
175
+ continue
176
+ else:
177
+ continue
178
+ if blk_body is not None:
179
+ _collect_refs(blk_body, owner, "references")
180
+
181
+ return {"nodes": nodes, "edges": edges}
@@ -0,0 +1,329 @@
1
+ """Verilog extractor. Moved verbatim from graphify/extract.py."""
2
+ from __future__ import annotations
3
+
4
+ import re
5
+
6
+ from pathlib import Path
7
+ from graphify.extractors.base import _file_stem, _make_id, _read_text
8
+
9
+
10
+ def _sv_first_identifier(node, source: bytes) -> str | None:
11
+ """First `simple_identifier` under node in pre-order, or None.
12
+
13
+ tree-sitter-verilog 1.0.3 nests declaration names a few levels deep instead
14
+ of exposing a `name` field. Scope the search to the right child node (e.g.
15
+ `function_identifier`) or this returns the return-type instead of the name.
16
+ """
17
+ if node is None:
18
+ return None
19
+ for child in node.children:
20
+ if child.type == "simple_identifier":
21
+ return _read_text(child, source)
22
+ found = _sv_first_identifier(child, source)
23
+ if found:
24
+ return found
25
+ return None
26
+
27
+ def _sv_child(node, type_name: str) -> object | None:
28
+ if node is None:
29
+ return None
30
+ for child in node.children:
31
+ if child.type == type_name:
32
+ return child
33
+ return None
34
+
35
+ _SV_BUILTIN_TYPES = frozenset({
36
+ "bit", "logic", "reg", "wire", "int", "integer", "shortint", "longint",
37
+ "byte", "time", "real", "shortreal", "void", "string", "type", "event",
38
+ "mailbox", "semaphore", "process", "chandle",
39
+ })
40
+
41
+ _SV_NON_TYPE_WORDS = frozenset({
42
+ "return", "if", "else", "for", "foreach", "while", "case", "begin", "end",
43
+ "function", "task", "class", "endclass", "endfunction", "endtask",
44
+ })
45
+
46
+ _SV_PARENS_INNER = r"(?:[^()]|\([^()]*\))*"
47
+
48
+ _SV_PARENS = r"\(" + _SV_PARENS_INNER + r"\)"
49
+
50
+ _SV_FUNC_RE = re.compile(
51
+ r"\bfunction\s+([A-Za-z_]\w*(?:\s*#\s*" + _SV_PARENS + r")?)\s+(\w+)\s*"
52
+ r"\((" + _SV_PARENS_INNER + r")\)\s*;",
53
+ re.MULTILINE,
54
+ )
55
+
56
+ _SV_PARAM_RE = re.compile(
57
+ r"\s*(?:input|output|inout|ref|const\s+ref)?\s*"
58
+ r"([A-Za-z_]\w*(?:\s*#\s*" + _SV_PARENS + r")?)\s+\w+"
59
+ )
60
+
61
+ def _sv_strip_comments(text: str) -> str:
62
+ text = re.sub(r"/\*.*?\*/", "", text, flags=re.DOTALL)
63
+ return re.sub(r"//.*", "", text)
64
+
65
+ def _sv_split_type_list(text: str) -> list[str]:
66
+ parts: list[str] = []
67
+ depth = 0
68
+ start = 0
69
+ for idx, ch in enumerate(text):
70
+ if ch == "(":
71
+ depth += 1
72
+ elif ch == ")":
73
+ depth = max(0, depth - 1)
74
+ elif ch == "," and depth == 0:
75
+ item = text[start:idx].strip()
76
+ if item:
77
+ parts.append(item)
78
+ start = idx + 1
79
+ item = text[start:].strip()
80
+ if item:
81
+ parts.append(item)
82
+ return parts
83
+
84
+ def _sv_collect_type_refs(type_text: str, generic: bool = False,
85
+ skip: frozenset[str] = frozenset()) -> list[tuple[str, str]]:
86
+ refs: list[tuple[str, str]] = []
87
+ text = type_text.strip()
88
+ if not text:
89
+ return refs
90
+ head = re.match(r"([A-Za-z_]\w*)", text)
91
+ if head:
92
+ name = head.group(1)
93
+ # `skip` carries the enclosing class's `#(type T = ...)` parameters so
94
+ # they are not mistaken for referenced types.
95
+ if name not in _SV_BUILTIN_TYPES and name not in _SV_NON_TYPE_WORDS and name not in skip:
96
+ refs.append((name, "generic_arg" if generic else "type"))
97
+ params = re.search(r"#\s*\((" + _SV_PARENS_INNER + r")\)", text)
98
+ if params:
99
+ for arg in _sv_split_type_list(params.group(1)):
100
+ refs.extend(_sv_collect_type_refs(arg, generic=True, skip=skip))
101
+ return refs
102
+
103
+ def _augment_systemverilog_semantics(
104
+ raw: str,
105
+ stem: str,
106
+ str_path: str,
107
+ file_nid: str,
108
+ nodes: list[dict],
109
+ edges: list[dict],
110
+ seen_ids: set[str],
111
+ ) -> None:
112
+ label_to_nid = {node["label"]: node["id"] for node in nodes}
113
+
114
+ def line_for(offset: int) -> int:
115
+ return raw.count("\n", 0, offset) + 1
116
+
117
+ def add_node(nid: str, label: str, line: int) -> None:
118
+ if nid not in seen_ids:
119
+ seen_ids.add(nid)
120
+ nodes.append({"id": nid, "label": label, "file_type": "code",
121
+ "source_file": str_path, "source_location": f"L{line}",
122
+ "confidence_score": 1.0})
123
+ label_to_nid[label] = nid
124
+
125
+ def ensure_type(label: str, line: int) -> str:
126
+ if label in label_to_nid:
127
+ return label_to_nid[label]
128
+ nid = _make_id(stem, label)
129
+ add_node(nid, label, line)
130
+ return nid
131
+
132
+ def add_edge(src: str, target_label: str, relation: str, line: int, context: str | None = None) -> None:
133
+ tgt = ensure_type(target_label, line)
134
+ edge = {"source": src, "target": tgt, "relation": relation,
135
+ "confidence": "EXTRACTED", "confidence_score": 1.0,
136
+ "source_file": str_path, "source_location": f"L{line}", "weight": 1.0}
137
+ if context:
138
+ edge["context"] = context
139
+ edges.append(edge)
140
+
141
+ text = _sv_strip_comments(raw)
142
+ # Consuming `endclass` (rather than a lookahead) makes each match own its
143
+ # terminator, so back-to-back or malformed classes cannot bleed bodies.
144
+ class_re = re.compile(
145
+ r"\b(?:(interface)\s+)?class\s+(\w+)([^;{]*)\s*;(.*?)\bendclass\b",
146
+ re.DOTALL,
147
+ )
148
+ for match in class_re.finditer(text):
149
+ class_name = match.group(2)
150
+ header = match.group(3) or ""
151
+ body = match.group(4) or ""
152
+ line = line_for(match.start())
153
+ # `#(type T = Payload)` declares `T` as a class type parameter, not a
154
+ # referenced type — collect these to skip below.
155
+ type_params = frozenset(re.findall(r"\btype\s+(\w+)", header))
156
+ class_nid = _make_id(stem, class_name)
157
+ add_node(class_nid, class_name, line)
158
+ edges.append({"source": file_nid, "target": class_nid, "relation": "defines",
159
+ "confidence": "EXTRACTED", "confidence_score": 1.0,
160
+ "source_file": str_path, "source_location": f"L{line}", "weight": 1.0})
161
+
162
+ ext = re.search(r"\bextends\s+(\w+)", header)
163
+ if ext:
164
+ add_edge(class_nid, ext.group(1), "inherits", line)
165
+ impl = re.search(r"\bimplements\s+([^;{]+)", header)
166
+ if impl:
167
+ for iface_name in _sv_split_type_list(impl.group(1)):
168
+ add_edge(class_nid, iface_name.split("#", 1)[0].strip(), "implements", line)
169
+
170
+ body_without_functions = re.sub(
171
+ r"\bfunction\b.*?\bendfunction\b",
172
+ lambda m: "\n" * m.group(0).count("\n"),
173
+ body,
174
+ flags=re.DOTALL,
175
+ )
176
+ # Optional leading class-property qualifiers (rand/local/protected/etc.)
177
+ # must be consumed: otherwise a qualified field like `rand Config x;`
178
+ # (three tokens) fails the `<type> <name>;` shape and its type reference
179
+ # is silently dropped.
180
+ for field in re.finditer(r"^\s*(?:(?:rand|randc|local|protected|static|const|automatic|var)\s+)*([A-Za-z_]\w*(?:\s*#\s*\([^;]+?\))?)\s+\w+\s*;", body_without_functions, re.MULTILINE):
181
+ # Count to the start of the type token (group 1), not the match
182
+ # start: `^\s*` consumes the leading newline(s), so field.start()
183
+ # would resolve to the class's line instead of the field's.
184
+ field_line = line + body_without_functions.count("\n", 0, field.start(1))
185
+ for ref_name, role in _sv_collect_type_refs(field.group(1), skip=type_params):
186
+ add_edge(class_nid, ref_name, "references", field_line, "generic_arg" if role == "generic_arg" else "field")
187
+
188
+ for fm in _SV_FUNC_RE.finditer(body):
189
+ return_type, func_name, params = fm.group(1), fm.group(2), fm.group(3)
190
+ func_line = line + body.count("\n", 0, fm.start())
191
+ func_nid = _make_id(class_nid, func_name)
192
+ add_node(func_nid, func_name, func_line)
193
+ edges.append({"source": class_nid, "target": func_nid, "relation": "method",
194
+ "confidence": "EXTRACTED", "confidence_score": 1.0,
195
+ "source_file": str_path, "source_location": f"L{func_line}", "weight": 1.0})
196
+ for ref_name, role in _sv_collect_type_refs(return_type, skip=type_params):
197
+ add_edge(func_nid, ref_name, "references", func_line, "generic_arg" if role == "generic_arg" else "return_type")
198
+ for param in _sv_split_type_list(params):
199
+ pm = _SV_PARAM_RE.match(param)
200
+ if not pm:
201
+ continue
202
+ for ref_name, role in _sv_collect_type_refs(pm.group(1), skip=type_params):
203
+ add_edge(func_nid, ref_name, "references", func_line, "generic_arg" if role == "generic_arg" else "parameter_type")
204
+
205
+ def extract_verilog(path: Path) -> dict:
206
+ """Extract modules, functions, tasks, package imports, instantiations, and
207
+ SystemVerilog class semantics (inherits/implements edges, field/parameter/
208
+ return-type references) from .v/.sv files."""
209
+ try:
210
+ import tree_sitter_verilog as tsverilog
211
+ from tree_sitter import Language, Parser
212
+ except ImportError:
213
+ return {"nodes": [], "edges": [], "error": "tree_sitter_verilog not installed"}
214
+
215
+ try:
216
+ language = Language(tsverilog.language())
217
+ parser = Parser(language)
218
+ source = path.read_bytes()
219
+ tree = parser.parse(source)
220
+ root = tree.root_node
221
+ except Exception as e:
222
+ return {"nodes": [], "edges": [], "error": str(e)}
223
+
224
+ stem = _file_stem(path)
225
+ str_path = str(path)
226
+ nodes: list[dict] = []
227
+ edges: list[dict] = []
228
+ seen_ids: set[str] = set()
229
+
230
+ def add_node(nid: str, label: str, line: int) -> None:
231
+ if nid not in seen_ids:
232
+ seen_ids.add(nid)
233
+ nodes.append({"id": nid, "label": label, "file_type": "code",
234
+ "source_file": str_path, "source_location": f"L{line}",
235
+ "confidence_score": 1.0})
236
+
237
+ def add_edge(src: str, tgt: str, relation: str, line: int,
238
+ confidence: str = "EXTRACTED", score: float = 1.0) -> None:
239
+ edges.append({"source": src, "target": tgt, "relation": relation,
240
+ "confidence": confidence, "confidence_score": score,
241
+ "source_file": str_path, "source_location": f"L{line}", "weight": 1.0})
242
+
243
+ file_nid = _make_id(str(path))
244
+ add_node(file_nid, path.name, 1)
245
+
246
+ def walk(node, module_nid: str | None = None) -> None:
247
+ t = node.type
248
+
249
+ # SystemVerilog class bodies are handled by _augment_systemverilog_semantics
250
+ # (regex over source text). Skip their subtrees so in-class methods are not
251
+ # double-emitted here — and with the wrong, return-type-derived name.
252
+ if t in ("class_declaration", "interface_class_declaration"):
253
+ return
254
+
255
+ if t == "module_declaration":
256
+ mod_name = _sv_first_identifier(_sv_child(node, "module_header"), source)
257
+ if mod_name:
258
+ line = node.start_point[0] + 1
259
+ nid = _make_id(stem, mod_name)
260
+ add_node(nid, mod_name, line)
261
+ add_edge(file_nid, nid, "defines", line)
262
+ for child in node.children:
263
+ walk(child, nid)
264
+ return
265
+
266
+ # `function_prototype` only appears inside class/interface-class bodies
267
+ # (skipped above) and nests its name differently; it is intentionally not
268
+ # handled here.
269
+ elif t == "function_declaration":
270
+ fn_body = _sv_child(node, "function_body_declaration")
271
+ func_name = _sv_first_identifier(_sv_child(fn_body, "function_identifier"), source)
272
+ if func_name:
273
+ line = node.start_point[0] + 1
274
+ parent = module_nid or file_nid
275
+ nid = _make_id(parent, func_name)
276
+ add_node(nid, f"{func_name}()", line)
277
+ add_edge(parent, nid, "contains", line)
278
+
279
+ elif t == "task_declaration":
280
+ tk_body = _sv_child(node, "task_body_declaration")
281
+ task_name = _sv_first_identifier(_sv_child(tk_body, "task_identifier"), source)
282
+ if task_name:
283
+ line = node.start_point[0] + 1
284
+ parent = module_nid or file_nid
285
+ nid = _make_id(parent, task_name)
286
+ add_node(nid, task_name, line)
287
+ add_edge(parent, nid, "contains", line)
288
+
289
+ elif t == "package_import_declaration":
290
+ for child in node.children:
291
+ if child.type == "package_import_item":
292
+ pkg_text = _read_text(child, source)
293
+ pkg_name = pkg_text.split("::")[0].strip()
294
+ if pkg_name:
295
+ line = node.start_point[0] + 1
296
+ tgt_nid = _make_id(pkg_name)
297
+ add_node(tgt_nid, pkg_name, line)
298
+ src_nid = module_nid or file_nid
299
+ add_edge(src_nid, tgt_nid, "imports_from", line)
300
+
301
+ elif t in ("module_instantiation", "checker_instantiation"):
302
+ # `leaf u_leaf();` parses as checker_instantiation in 1.0.3;
303
+ # module_instantiation (when it occurs) exposes a `module_type` field.
304
+ # Both reduce to the first identifier under the node — the instantiated
305
+ # type, not the instance name (which appears later).
306
+ if module_nid:
307
+ type_node = node.child_by_field_name("module_type")
308
+ inst_type = (_read_text(type_node, source).strip() if type_node
309
+ else _sv_first_identifier(node, source))
310
+ if inst_type:
311
+ line = node.start_point[0] + 1
312
+ tgt_nid = _make_id(inst_type)
313
+ add_node(tgt_nid, inst_type, line)
314
+ add_edge(module_nid, tgt_nid, "instantiates", line)
315
+
316
+ for child in node.children:
317
+ walk(child, module_nid)
318
+
319
+ walk(root)
320
+ _augment_systemverilog_semantics(
321
+ source.decode("utf-8", errors="replace"),
322
+ stem,
323
+ str_path,
324
+ file_nid,
325
+ nodes,
326
+ edges,
327
+ seen_ids,
328
+ )
329
+ return {"nodes": nodes, "edges": edges}