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,540 @@
1
+ """Common Lisp extractor for .lisp/.cl/.lsp/.asd, backed by tree-sitter-commonlisp."""
2
+ from __future__ import annotations
3
+
4
+ import warnings
5
+ from pathlib import Path
6
+
7
+ from graphify.extractors.base import _file_stem, _make_id
8
+
9
+
10
+ # Standard CL definer forms that introduce data/type/variable bindings
11
+ # (not callable, no body to walk for calls)
12
+ _CL_DATA_DEFINERS = frozenset({
13
+ "defstruct", "deftype", "define-condition",
14
+ "defvar", "defparameter", "defconstant",
15
+ "define-symbol-macro",
16
+ })
17
+
18
+ # Subset of data definers that take a VALUE as their second argument, which
19
+ # may itself be a string literal. For these, we must not mistake the value
20
+ # for a docstring.
21
+ _CL_VALUE_DEFINERS = frozenset({"defvar", "defparameter", "defconstant"})
22
+
23
+ # Standard CL macro-style definers
24
+ _CL_MACRO_DEFINERS = frozenset({
25
+ "define-modify-macro", "define-compiler-macro",
26
+ "define-setf-expander", "defsetf",
27
+ })
28
+
29
+ # Symbols that start with "def" but are NOT definitions (denylist for the
30
+ # def-prefix heuristic that catches custom definers like definline-maybe)
31
+ _CL_NOT_DEFINERS = frozenset({
32
+ "default", "default-value", "defaults", "define",
33
+ "defer", "deferred", "deflate", "deftest",
34
+ })
35
+
36
+ # CL special forms / macros that should not count as "calls" in the graph
37
+ _CL_SPECIAL_FORMS = frozenset({
38
+ "let", "let*", "flet", "labels", "macrolet",
39
+ "if", "when", "unless", "cond", "case", "ecase", "typecase", "etypecase",
40
+ "progn", "prog1", "prog2", "block", "return-from", "tagbody", "go",
41
+ "lambda", "function", "funcall", "apply",
42
+ "setf", "setq", "psetf", "psetq", "incf", "decf", "push", "pop",
43
+ "loop", "do", "do*", "dolist", "dotimes", "map", "mapcar", "mapc",
44
+ "format", "print", "princ", "prin1", "write", "terpri",
45
+ "and", "or", "not", "null",
46
+ "values", "multiple-value-bind", "multiple-value-setq",
47
+ "declare", "the", "locally", "eval-when",
48
+ "quote", "backquote",
49
+ "error", "signal", "warn", "assert", "check-type",
50
+ "handler-case", "handler-bind", "restart-case", "ignore-errors",
51
+ "unwind-protect", "catch", "throw",
52
+ "with-open-file", "with-output-to-string", "with-input-from-string",
53
+ "with-slots", "with-accessors",
54
+ "defvar", "defparameter", "defconstant",
55
+ "in-package", "require", "provide", "use-package",
56
+ "t", "nil",
57
+ })
58
+
59
+
60
+ def extract_commonlisp(path: Path) -> dict:
61
+ """Extract packages, classes, functions, methods, macros, and calls from a Common Lisp file."""
62
+ try:
63
+ import tree_sitter_commonlisp as tscl
64
+ from tree_sitter import Language, Parser
65
+ except ImportError:
66
+ return {"nodes": [], "edges": [], "error": "tree-sitter-commonlisp not installed"}
67
+
68
+ try:
69
+ # tree-sitter-commonlisp 0.4.1 (latest) ships the old binding that returns
70
+ # an int pointer from language(); tree_sitter.Language only accepts that
71
+ # int via a deprecated path. Silence that one warning at the call site
72
+ # until the grammar ships a PyCapsule binding.
73
+ with warnings.catch_warnings():
74
+ warnings.filterwarnings(
75
+ "ignore",
76
+ message="int argument support is deprecated",
77
+ category=DeprecationWarning,
78
+ )
79
+ language = Language(tscl.language())
80
+ parser = Parser(language)
81
+ source = path.read_bytes()
82
+ tree = parser.parse(source)
83
+ root = tree.root_node
84
+ except Exception as e:
85
+ return {"nodes": [], "edges": [], "error": str(e)}
86
+
87
+ # Path-qualified, not the bare `path.stem`: same-named .lisp files in
88
+ # different directories must not collide (#1504). Pre-collapsed through
89
+ # `_make_id` because `_cl_id` would otherwise map the `/` separators to
90
+ # `_slash` via _CL_CHAR_MAP.
91
+ stem = _make_id(_file_stem(path))
92
+ str_path = str(path)
93
+ nodes: list[dict] = []
94
+ edges: list[dict] = []
95
+ seen_ids: set[str] = set()
96
+ function_bodies: list[tuple[str, object]] = [] # (nid, body_nodes)
97
+ current_package: str | None = None
98
+
99
+ def _text(node) -> str:
100
+ return source[node.start_byte:node.end_byte].decode("utf-8", errors="replace")
101
+
102
+ # CL symbols can contain operator chars like = < > ? ! + * / that the
103
+ # generic _make_id strips. Map them to readable suffixes so upi=, upi<,
104
+ # upi> become distinct ids.
105
+ _CL_CHAR_MAP = {
106
+ '=': '_eq', '<': '_lt', '>': '_gt', '?': '_p', '!': '_bang',
107
+ '+': '_plus', '*': '_star', '/': '_slash', '%': '_pct', '&': '_amp',
108
+ }
109
+
110
+ def _cl_id(*parts: str) -> str:
111
+ normalized = []
112
+ for p in parts:
113
+ normalized.append(''.join(_CL_CHAR_MAP.get(c, c) for c in p))
114
+ return _make_id(*normalized)
115
+
116
+ def add_node(nid: str, label: str, line: int, file_type: str = "code") -> None:
117
+ if nid not in seen_ids:
118
+ seen_ids.add(nid)
119
+ nodes.append({
120
+ "id": nid,
121
+ "label": label,
122
+ "file_type": file_type,
123
+ "source_file": str_path,
124
+ "source_location": f"L{line}",
125
+ })
126
+
127
+ def add_edge(src: str, tgt: str, relation: str, line: int,
128
+ confidence: str = "EXTRACTED", weight: float = 1.0) -> None:
129
+ edges.append({
130
+ "source": src,
131
+ "target": tgt,
132
+ "relation": relation,
133
+ "confidence": confidence,
134
+ "source_file": str_path,
135
+ "source_location": f"L{line}",
136
+ "weight": weight,
137
+ })
138
+
139
+ file_nid = _cl_id(stem)
140
+ add_node(file_nid, path.name, 1)
141
+
142
+ def add_import_stub(mod_name: str) -> str:
143
+ """Mint a SOURCELESS stub for an imported package so its `imports` edge
144
+ has a real target instead of dangling. The corpus rewire collapses it
145
+ onto a unique in-corpus `defpackage` of the same name; an external one
146
+ (cl, alexandria) stays a sourceless leaf. `origin_file` is stripped
147
+ before persist (no #1899 leak); a sourced bare stub would salt the id."""
148
+ nid = _cl_id(mod_name)
149
+ if nid not in seen_ids:
150
+ seen_ids.add(nid)
151
+ nodes.append({
152
+ "id": nid,
153
+ "label": mod_name,
154
+ "file_type": "code",
155
+ "source_file": "",
156
+ "source_location": "",
157
+ "origin_file": str_path,
158
+ })
159
+ return nid
160
+
161
+ def ensure_class_ref(name: str, line: int) -> str:
162
+ """Resolve a superclass name to a node id.
163
+
164
+ If the class is defined in THIS file, bind to its local node. Otherwise
165
+ mint a SOURCELESS stub (like `add_import_stub`) so the corpus-level
166
+ rewire can collapse it onto the real `defclass` in another file. Without
167
+ this, a cross-file superclass had no matching node and its `inherits`
168
+ edge was pruned by the dangling-edge filter — silently erasing every
169
+ inheritance edge whose parent lives in a different file."""
170
+ local = _cl_id(stem, name)
171
+ if local in seen_ids:
172
+ return local
173
+ stub = _cl_id(name)
174
+ if stub not in seen_ids:
175
+ seen_ids.add(stub)
176
+ nodes.append({
177
+ "id": stub,
178
+ "label": name,
179
+ "file_type": "code",
180
+ "source_file": "",
181
+ "source_location": "",
182
+ "origin_file": str_path,
183
+ })
184
+ return stub
185
+
186
+ def _first_sym(node) -> str | None:
187
+ """Get the first sym_lit text from a list_lit's children."""
188
+ for child in node.children:
189
+ if child.type == "sym_lit":
190
+ return _text(child)
191
+ return None
192
+
193
+ def _kwd_text(node) -> str:
194
+ """Extract the symbol name from a kwd_lit, stripping the leading colon."""
195
+ t = _text(node)
196
+ return t.lstrip(":").lstrip("#:")
197
+
198
+ def _handle_defpackage(node) -> None:
199
+ nonlocal current_package
200
+ children = node.children
201
+ pkg_name = None
202
+ for child in children:
203
+ if child.type == "kwd_lit" and pkg_name is None:
204
+ pkg_name = _kwd_text(child)
205
+ break
206
+ if child.type == "sym_lit" and _text(child) != "defpackage":
207
+ pkg_name = _text(child)
208
+ break
209
+ if not pkg_name:
210
+ return
211
+ current_package = pkg_name
212
+ pkg_nid = _cl_id(stem, pkg_name)
213
+ line = node.start_point[0] + 1
214
+ add_node(pkg_nid, pkg_name, line)
215
+ add_edge(file_nid, pkg_nid, "contains", line)
216
+
217
+ # Extract :use clauses as imports
218
+ for child in children:
219
+ if child.type == "list_lit":
220
+ first = _first_sym(child)
221
+ if first is None:
222
+ # Could be (:use ...) with kwd_lit
223
+ for gc in child.children:
224
+ if gc.type == "kwd_lit" and _kwd_text(gc) == "use":
225
+ for uc in child.children:
226
+ if uc.type == "kwd_lit" and uc != gc:
227
+ mod_name = _kwd_text(uc)
228
+ if mod_name != "use":
229
+ tgt_nid = add_import_stub(mod_name)
230
+ add_edge(pkg_nid, tgt_nid, "imports",
231
+ child.start_point[0] + 1)
232
+ break
233
+
234
+ def _handle_defclass(node) -> None:
235
+ children = node.children
236
+ # Find class name: second sym_lit after "defclass"
237
+ sym_lits = [c for c in children if c.type == "sym_lit"]
238
+ if len(sym_lits) < 2:
239
+ return
240
+ class_name = _text(sym_lits[1])
241
+ line = node.start_point[0] + 1
242
+ class_nid = _cl_id(stem, class_name)
243
+ add_node(class_nid, class_name, line)
244
+
245
+ parent_nid = file_nid
246
+ if current_package:
247
+ pkg_nid = _cl_id(stem, current_package)
248
+ if pkg_nid in seen_ids:
249
+ parent_nid = pkg_nid
250
+ add_edge(parent_nid, class_nid, "contains", line)
251
+
252
+ # Superclasses (third element, a list)
253
+ list_lits = [c for c in children if c.type == "list_lit"]
254
+ if list_lits:
255
+ superclass_list = list_lits[0]
256
+ for sc in superclass_list.children:
257
+ if sc.type == "sym_lit":
258
+ sc_name = _text(sc)
259
+ sc_nid = ensure_class_ref(sc_name, superclass_list.start_point[0] + 1)
260
+ add_edge(class_nid, sc_nid, "inherits",
261
+ superclass_list.start_point[0] + 1)
262
+
263
+ def _handle_defun_node(node) -> None:
264
+ """Handle a defun AST node (covers defun, defmethod, defgeneric, defmacro)."""
265
+ header = None
266
+ for child in node.children:
267
+ if child.type == "defun_header":
268
+ header = child
269
+ break
270
+ if not header:
271
+ return
272
+
273
+ # Determine keyword type
274
+ keyword_type = "defun"
275
+ func_name = None
276
+ for child in header.children:
277
+ if child.type == "defun_keyword":
278
+ for kc in child.children:
279
+ if kc.type in ("defun", "defmethod", "defgeneric", "defmacro"):
280
+ keyword_type = kc.type
281
+ break
282
+ if child.type == "sym_lit" and func_name is None:
283
+ func_name = _text(child)
284
+
285
+ if not func_name:
286
+ return
287
+
288
+ line = node.start_point[0] + 1
289
+ func_nid = _cl_id(stem, func_name)
290
+
291
+ if keyword_type == "defmethod":
292
+ label = f".{func_name}()"
293
+ elif keyword_type == "defmacro":
294
+ label = f"{func_name} (macro)"
295
+ elif keyword_type == "defgeneric":
296
+ label = f"{func_name} (generic)"
297
+ else:
298
+ label = f"{func_name}()"
299
+
300
+ add_node(func_nid, label, line)
301
+
302
+ parent_nid = file_nid
303
+ if current_package:
304
+ pkg_nid = _cl_id(stem, current_package)
305
+ if pkg_nid in seen_ids:
306
+ parent_nid = pkg_nid
307
+ if keyword_type == "defmethod":
308
+ add_edge(parent_nid, func_nid, "method", line)
309
+ else:
310
+ add_edge(parent_nid, func_nid, "contains", line)
311
+
312
+ # Method specializers: (defmethod name ((param class) ...))
313
+ if keyword_type == "defmethod":
314
+ for child in header.children:
315
+ if child.type == "list_lit":
316
+ # This is the parameter list
317
+ for param in child.children:
318
+ if param.type == "list_lit":
319
+ syms = [c for c in param.children if c.type == "sym_lit"]
320
+ if len(syms) >= 2:
321
+ specializer_name = _text(syms[1])
322
+ spec_nid = _cl_id(stem, specializer_name)
323
+ add_edge(func_nid, spec_nid, "specializes",
324
+ param.start_point[0] + 1)
325
+ break
326
+
327
+ # Docstring
328
+ for child in node.children:
329
+ if child.type == "str_lit":
330
+ doc_text = _text(child).strip('"')
331
+ if doc_text:
332
+ doc_nid = _cl_id(func_nid, "rationale")
333
+ add_node(doc_nid, doc_text[:120], child.start_point[0] + 1, file_type="rationale")
334
+ add_edge(doc_nid, func_nid, "rationale_for",
335
+ child.start_point[0] + 1)
336
+ break
337
+
338
+ # Collect body for call extraction
339
+ body_nodes = [c for c in node.children
340
+ if c.type not in ("(", ")", "defun_header", "str_lit")]
341
+ if body_nodes:
342
+ function_bodies.append((func_nid, body_nodes))
343
+
344
+ def _extract_def_name(node, def_keyword: str) -> str | None:
345
+ """Get the name being defined. May be a sym_lit or a list_lit whose
346
+ first sym_lit is the name (e.g. (defstruct (foo :conc-name "BAR-") ...))."""
347
+ seen_keyword = False
348
+ for child in node.children:
349
+ if not seen_keyword:
350
+ if child.type == "sym_lit" and _text(child).lower() == def_keyword:
351
+ seen_keyword = True
352
+ continue
353
+ if child.type == "sym_lit":
354
+ return _text(child)
355
+ if child.type == "list_lit":
356
+ for gc in child.children:
357
+ if gc.type == "sym_lit":
358
+ return _text(gc)
359
+ return None
360
+ return None
361
+
362
+ def _collect_def_body(node, def_keyword: str) -> list:
363
+ """Collect body forms from (DEFKEYWORD name (params) [doc] body...).
364
+ Skips the def keyword, the name, the params list (if present), and
365
+ a leading docstring str_lit."""
366
+ children = [c for c in node.children if c.type not in ("(", ")")]
367
+ idx = 0
368
+ # Skip def keyword
369
+ if idx < len(children) and children[idx].type == "sym_lit" \
370
+ and _text(children[idx]).lower() == def_keyword:
371
+ idx += 1
372
+ # Skip name (sym_lit or list_lit)
373
+ if idx < len(children) and children[idx].type in ("sym_lit", "list_lit"):
374
+ idx += 1
375
+ # Skip params list (the next list_lit, if any)
376
+ if idx < len(children) and children[idx].type == "list_lit":
377
+ idx += 1
378
+ # Skip leading docstring
379
+ if idx < len(children) and children[idx].type == "str_lit":
380
+ idx += 1
381
+ return children[idx:]
382
+
383
+ def _handle_def_form(node, def_keyword: str) -> None:
384
+ """Handle a generic (DEFKEYWORD name ...) form. Covers standard CL
385
+ definers (defstruct, deftype, defvar, define-condition, etc.) and
386
+ custom def-prefixed macros (definline, definline-maybe, etc.)."""
387
+ name = _extract_def_name(node, def_keyword)
388
+ if not name:
389
+ return
390
+ line = node.start_point[0] + 1
391
+ nid = _cl_id(stem, name)
392
+
393
+ kw = def_keyword.lower()
394
+ if kw in _CL_DATA_DEFINERS:
395
+ label = name
396
+ is_callable = False
397
+ elif kw in _CL_MACRO_DEFINERS or "macro" in kw:
398
+ label = f"{name} (macro)"
399
+ is_callable = True
400
+ else:
401
+ # Custom def-prefixed: assume function-like
402
+ label = f"{name}()"
403
+ is_callable = True
404
+
405
+ add_node(nid, label, line)
406
+
407
+ parent_nid = file_nid
408
+ if current_package:
409
+ pkg_nid = _cl_id(stem, current_package)
410
+ if pkg_nid in seen_ids:
411
+ parent_nid = pkg_nid
412
+ add_edge(parent_nid, nid, "contains", line)
413
+
414
+ # Docstring. Skip for defvar/defparameter/defconstant — their second
415
+ # argument is a value (possibly a string literal) which would be
416
+ # wrongly captured as a docstring.
417
+ if kw not in _CL_VALUE_DEFINERS:
418
+ for child in node.children:
419
+ if child.type == "str_lit":
420
+ doc_text = _text(child).strip('"')
421
+ if doc_text:
422
+ doc_nid = _cl_id(nid, "rationale")
423
+ add_node(doc_nid, doc_text[:120], child.start_point[0] + 1, file_type="rationale")
424
+ add_edge(doc_nid, nid, "rationale_for",
425
+ child.start_point[0] + 1)
426
+ break
427
+
428
+ if is_callable:
429
+ body_nodes = _collect_def_body(node, def_keyword)
430
+ if body_nodes:
431
+ function_bodies.append((nid, body_nodes))
432
+
433
+ def _is_def_prefixed(sym: str) -> bool:
434
+ """Heuristic: does this symbol look like a definition macro?"""
435
+ if sym in _CL_NOT_DEFINERS:
436
+ return False
437
+ return sym.startswith("def") or sym.startswith("define-")
438
+
439
+ def _process_form(top) -> bool:
440
+ """Dispatch on a single list_lit form. Returns True if it was
441
+ recognized as a definition or package directive (caller must NOT
442
+ recurse into it). Returns False if unrecognized, so the caller can
443
+ recurse to find defs nested inside wrapper macros like
444
+ (optimizing ...), (eval-when ...), (progn ...), etc."""
445
+ nonlocal current_package
446
+
447
+ # Check for defun node type inside list_lit
448
+ for child in top.children:
449
+ if child.type == "defun":
450
+ _handle_defun_node(child)
451
+ return True
452
+
453
+ first = _first_sym(top)
454
+ if not first:
455
+ return False
456
+ first_lower = first.lower()
457
+
458
+ if first_lower == "defpackage":
459
+ _handle_defpackage(top)
460
+ return True
461
+ if first_lower == "in-package":
462
+ for child in top.children:
463
+ if child.type == "kwd_lit":
464
+ current_package = _kwd_text(child)
465
+ break
466
+ if child.type == "sym_lit" and _text(child) != "in-package":
467
+ current_package = _text(child)
468
+ break
469
+ return True
470
+ if first_lower == "defclass":
471
+ _handle_defclass(top)
472
+ return True
473
+ if first_lower in ("require", "ql:quickload"):
474
+ for child in top.children:
475
+ if child.type in ("kwd_lit", "str_lit"):
476
+ mod_name = _kwd_text(child) if child.type == "kwd_lit" else _text(child).strip('"')
477
+ if mod_name:
478
+ tgt_nid = add_import_stub(mod_name)
479
+ add_edge(file_nid, tgt_nid, "imports",
480
+ top.start_point[0] + 1)
481
+ break
482
+ return True
483
+ if first_lower in _CL_DATA_DEFINERS or first_lower in _CL_MACRO_DEFINERS:
484
+ _handle_def_form(top, first_lower)
485
+ return True
486
+ if _is_def_prefixed(first_lower):
487
+ # Custom definer (definline, definline-maybe, defcomponent, etc.)
488
+ _handle_def_form(top, first_lower)
489
+ return True
490
+
491
+ return False
492
+
493
+ def _walk_forms(parent) -> None:
494
+ """Walk list_lit children of `parent`, processing each. If a form
495
+ isn't recognized, recurse into it — many CL codebases wrap
496
+ definitions in macros like (optimizing ...), (eval-when ...), or
497
+ (progn ...) that aren't themselves definers but contain defs.
498
+ Also descends into reader conditionals (#+feature / #-feature),
499
+ which wrap their guarded form in an include_reader_macro node."""
500
+ for top in parent.children:
501
+ if top.type == "list_lit":
502
+ if not _process_form(top):
503
+ _walk_forms(top)
504
+ elif top.type == "include_reader_macro":
505
+ _walk_forms(top)
506
+
507
+ _walk_forms(root)
508
+
509
+ # Call extraction pass
510
+ label_to_nid: dict[str, str] = {}
511
+ for n in nodes:
512
+ raw = n["label"]
513
+ normalised = raw.replace(" (macro)", "").replace(" (generic)", "").strip("()").lstrip(".")
514
+ label_to_nid[normalised.lower()] = n["id"]
515
+
516
+ seen_call_pairs: set[tuple[str, str]] = set()
517
+
518
+ def walk_calls(node, caller_nid: str) -> None:
519
+ if node.type == "defun":
520
+ return
521
+ if node.type == "list_lit":
522
+ callee = _first_sym(node)
523
+ if callee and callee.lower() not in _CL_SPECIAL_FORMS:
524
+ tgt_nid = label_to_nid.get(callee.lower())
525
+ if tgt_nid and tgt_nid != caller_nid:
526
+ pair = (caller_nid, tgt_nid)
527
+ if pair not in seen_call_pairs:
528
+ seen_call_pairs.add(pair)
529
+ add_edge(caller_nid, tgt_nid, "calls",
530
+ node.start_point[0] + 1, confidence="EXTRACTED", weight=1.0)
531
+ for child in node.children:
532
+ walk_calls(child, caller_nid)
533
+
534
+ for caller_nid, body_nodes in function_bodies:
535
+ for body_node in body_nodes:
536
+ walk_calls(body_node, caller_nid)
537
+
538
+ clean_edges = [e for e in edges if e["source"] in seen_ids and
539
+ (e["target"] in seen_ids or e["relation"] in ("imports", "imports_from"))]
540
+ return {"nodes": nodes, "edges": clean_edges}