codegraph-ir 0.3.0__tar.gz → 0.5.0__tar.gz

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 (181) hide show
  1. codegraph_ir-0.5.0/.mcp.json +8 -0
  2. codegraph_ir-0.5.0/AGENTS.md +38 -0
  3. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/PKG-INFO +28 -5
  4. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/README.md +24 -3
  5. codegraph_ir-0.5.0/benchmarks/differential_check.py +234 -0
  6. codegraph_ir-0.5.0/benchmarks/rung3-ablation-camera-tracking.json +895 -0
  7. codegraph_ir-0.5.0/benchmarks/rung3-results-camera-tracking.json +751 -0
  8. codegraph_ir-0.5.0/benchmarks/rung3-uncovered-camera-tracking-differential.json +210 -0
  9. codegraph_ir-0.5.0/benchmarks/rung3-uncovered-camera-tracking.json +499 -0
  10. codegraph_ir-0.5.0/benchmarks/rung3_rewrite.py +408 -0
  11. codegraph_ir-0.5.0/benchmarks/rung4-artifacts/sqlite_rust_functions.rs +325 -0
  12. codegraph_ir-0.5.0/benchmarks/rung4-linkback-report.json +39 -0
  13. codegraph_ir-0.5.0/benchmarks/rung4-results-sqlite-probed.json +2097 -0
  14. codegraph_ir-0.5.0/benchmarks/rung4-results-sqlite.json +927 -0
  15. codegraph_ir-0.5.0/benchmarks/rung4_c_to_rust.py +703 -0
  16. codegraph_ir-0.5.0/benchmarks/rung4_linkback.py +209 -0
  17. codegraph_ir-0.5.0/docs/experiment-log.md +511 -0
  18. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/docs/languages.md +45 -0
  19. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/docs/roadmap.md +1 -1
  20. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/docs/status.md +8 -1
  21. codegraph_ir-0.5.0/docs/vision-rewrite.md +148 -0
  22. codegraph_ir-0.5.0/docs/writing-an-adapter.md +329 -0
  23. codegraph_ir-0.5.0/examples/c-adapter/NOTES.md +184 -0
  24. codegraph_ir-0.5.0/examples/c-adapter/README.md +18 -0
  25. codegraph_ir-0.5.0/examples/rust-adapter/NOTES.md +145 -0
  26. codegraph_ir-0.5.0/examples/rust-adapter/README.md +15 -0
  27. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/pyproject.toml +6 -2
  28. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/__init__.py +1 -1
  29. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/analyses/call_graph.py +5 -2
  30. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/analyses/cfg.py +10 -3
  31. codegraph_ir-0.5.0/src/cgir/analyses/decompose.py +265 -0
  32. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/analyses/effects.py +13 -3
  33. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/analyses/symbols.py +27 -0
  34. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/cli.py +167 -0
  35. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/ir/graph.py +5 -0
  36. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/languages/base.py +31 -1
  37. codegraph_ir-0.5.0/src/cgir/languages/c.py +948 -0
  38. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/languages/cache.py +19 -0
  39. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/languages/go.py +22 -5
  40. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/languages/python.py +29 -5
  41. codegraph_ir-0.5.0/src/cgir/languages/registry.py +106 -0
  42. codegraph_ir-0.5.0/src/cgir/languages/rust.py +998 -0
  43. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/languages/typescript.py +30 -5
  44. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/report/search.py +7 -3
  45. codegraph_ir-0.5.0/src/cgir/rewrite.py +320 -0
  46. codegraph_ir-0.5.0/src/cgir/verify.py +336 -0
  47. codegraph_ir-0.5.0/tests/unit/test_c_adapter.py +1108 -0
  48. codegraph_ir-0.5.0/tests/unit/test_decompose.py +129 -0
  49. codegraph_ir-0.5.0/tests/unit/test_incremental_verify.py +144 -0
  50. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_pins.py +18 -0
  51. codegraph_ir-0.5.0/tests/unit/test_plugin_registry.py +136 -0
  52. codegraph_ir-0.5.0/tests/unit/test_raise_confidence.py +110 -0
  53. codegraph_ir-0.5.0/tests/unit/test_rewrite_orchestrator.py +225 -0
  54. codegraph_ir-0.5.0/tests/unit/test_rust_adapter.py +634 -0
  55. codegraph_ir-0.3.0/docs/experiment-log.md +0 -204
  56. codegraph_ir-0.3.0/src/cgir/languages/registry.py +0 -27
  57. codegraph_ir-0.3.0/src/cgir/verify.py +0 -180
  58. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/.github/workflows/ci.yml +0 -0
  59. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/.github/workflows/release.yml +0 -0
  60. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/.gitignore +0 -0
  61. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/.pre-commit-config.yaml +0 -0
  62. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/.pre-commit-hooks.yaml +0 -0
  63. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/.python-version +0 -0
  64. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/CLAUDE.md +0 -0
  65. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/Code-IR.md +0 -0
  66. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/LICENSE +0 -0
  67. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/RELEASING.md +0 -0
  68. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/action.yml +0 -0
  69. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/docs/README.md +0 -0
  70. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/docs/agents.md +0 -0
  71. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/docs/architecture-rules.md +0 -0
  72. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/docs/architecture.md +0 -0
  73. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/docs/development.md +0 -0
  74. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/docs/feature-research.md +0 -0
  75. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/docs/gate-noise.md +0 -0
  76. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/docs/github-action.md +0 -0
  77. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/docs/lsp.md +0 -0
  78. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/docs/plan-0.2-0.4.md +0 -0
  79. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/docs/plan-0.3.md +0 -0
  80. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/docs/strategy.md +0 -0
  81. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/examples/agent-pr-contract-drift.md +0 -0
  82. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/schemas/component_spec.schema.json +0 -0
  83. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/analyses/__init__.py +0 -0
  84. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/analyses/coverage_link.py +0 -0
  85. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/analyses/entrypoints.py +0 -0
  86. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/analyses/param_flow.py +0 -0
  87. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/analyses/pdg.py +0 -0
  88. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/analyses/purity.py +0 -0
  89. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/analyses/reaching_defs.py +0 -0
  90. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/api/__init__.py +0 -0
  91. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/api/lsp_server.py +0 -0
  92. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/api/mcp_server.py +0 -0
  93. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/api/server.py +0 -0
  94. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/config.py +0 -0
  95. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/export/__init__.py +0 -0
  96. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/export/graphml.py +0 -0
  97. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/export/html_viz.py +0 -0
  98. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/export/json_export.py +0 -0
  99. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/export/mermaid.py +0 -0
  100. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/export/neo4j.py +0 -0
  101. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/hooks.py +0 -0
  102. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/ir/__init__.py +0 -0
  103. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/ir/component_spec.py +0 -0
  104. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/ir/edges.py +0 -0
  105. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/ir/nodes.py +0 -0
  106. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/languages/__init__.py +0 -0
  107. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/manifest.py +0 -0
  108. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/pipeline.py +0 -0
  109. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/py.typed +0 -0
  110. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/regenerate/__init__.py +0 -0
  111. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/regenerate/prompt_pack.py +0 -0
  112. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/regenerate/regenerator.py +0 -0
  113. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/report/__init__.py +0 -0
  114. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/report/diff.py +0 -0
  115. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/report/flow.py +0 -0
  116. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/report/impact.py +0 -0
  117. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/report/lint.py +0 -0
  118. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/report/pack.py +0 -0
  119. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/report/pins.py +0 -0
  120. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/report/stats.py +0 -0
  121. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/slicing/__init__.py +0 -0
  122. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/slicing/slicer.py +0 -0
  123. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/sources/__init__.py +0 -0
  124. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/sources/base.py +0 -0
  125. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/sources/codeql_source.py +0 -0
  126. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/sources/joern_source.py +0 -0
  127. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/sources/tree_sitter_source.py +0 -0
  128. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/trace/__init__.py +0 -0
  129. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/trace/trace_map.py +0 -0
  130. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/watch.py +0 -0
  131. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/__init__.py +0 -0
  132. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/conftest.py +0 -0
  133. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/fixtures/python_sample/orchestrator.py +0 -0
  134. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/fixtures/python_sample/pricing.py +0 -0
  135. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/fixtures/ts_sample/api/service.ts +0 -0
  136. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/fixtures/ts_sample/util.ts +0 -0
  137. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/integration/__init__.py +0 -0
  138. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/integration/test_api.py +0 -0
  139. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/integration/test_cli_scan.py +0 -0
  140. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/integration/test_hook.py +0 -0
  141. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/integration/test_init.py +0 -0
  142. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/__init__.py +0 -0
  143. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_action.py +0 -0
  144. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_call_graph.py +0 -0
  145. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_cfg.py +0 -0
  146. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_component_spec.py +0 -0
  147. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_confidence.py +0 -0
  148. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_coverage_link.py +0 -0
  149. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_diff.py +0 -0
  150. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_effects.py +0 -0
  151. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_entrypoints.py +0 -0
  152. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_go_adapter.py +0 -0
  153. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_graphml.py +0 -0
  154. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_html_viz.py +0 -0
  155. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_impact.py +0 -0
  156. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_impact_run.py +0 -0
  157. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_ir_graph.py +0 -0
  158. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_language_adapter.py +0 -0
  159. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_lint.py +0 -0
  160. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_lsp.py +0 -0
  161. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_manifest.py +0 -0
  162. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_mcp_tools.py +0 -0
  163. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_mermaid.py +0 -0
  164. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_pack.py +0 -0
  165. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_param_flow.py +0 -0
  166. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_pdg.py +0 -0
  167. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_purity.py +0 -0
  168. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_python_di.py +0 -0
  169. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_reaching_defs.py +0 -0
  170. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_regenerator.py +0 -0
  171. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_search.py +0 -0
  172. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_shapes.py +0 -0
  173. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_slicer.py +0 -0
  174. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_source_cache.py +0 -0
  175. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_stats.py +0 -0
  176. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_symbols.py +0 -0
  177. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_trace_map.py +0 -0
  178. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_tree_sitter_source.py +0 -0
  179. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_typescript_adapter.py +0 -0
  180. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_verify.py +0 -0
  181. {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_watch.py +0 -0
@@ -0,0 +1,8 @@
1
+ {
2
+ "mcpServers": {
3
+ "cgir": {
4
+ "command": "cgir",
5
+ "args": ["mcp", "--index", ".cgir"]
6
+ }
7
+ }
8
+ }
@@ -0,0 +1,38 @@
1
+ # AGENTS.md
2
+
3
+ This file provides guidance to Codex (Codex.ai/code) when working with code in this repository.
4
+
5
+ ## Where the docs live
6
+
7
+ - [`Code-IR.md`](./Code-IR.md) — authoritative product specification.
8
+ - [`docs/`](./docs/) — working docs for engineers and Codex. Start at [`docs/README.md`](./docs/README.md). Key files:
9
+ - [`docs/architecture.md`](./docs/architecture.md) — layered pipeline, data model, extension seams.
10
+ - [`docs/status.md`](./docs/status.md) — what runs today vs. what's stubbed.
11
+ - [`docs/roadmap.md`](./docs/roadmap.md) — milestone sequencing.
12
+ - [`docs/development.md`](./docs/development.md) — install, commands, **red-green TDD workflow**, milestone-tag convention.
13
+
14
+ When `AGENTS.md` and a doc disagree, the doc is canonical for content; this file is canonical for "what Codex must remember to do."
15
+
16
+ ## Common commands
17
+
18
+ ```bash
19
+ uv pip install -e ".[dev,api]"
20
+ pytest -q
21
+ ruff check . && ruff format --check . && mypy src
22
+ cgir scan tests/fixtures/python_sample --out /tmp/cgir-out
23
+ ```
24
+
25
+ Full command catalogue and CI workflow are in [`docs/development.md`](./docs/development.md).
26
+
27
+ ## Working conventions (must-follow)
28
+
29
+ - **Red-green TDD for milestones.** Every `milestone:` or `# STUB:` tag is a TDD entry point. Cycle: write failing tests pinning the public contract → implement until green → refactor. Detail in [`docs/development.md`](./docs/development.md). Don't skip the red phase — even in auto mode.
30
+ - **Milestone-tag hygiene.** Backlog is `grep -rn "milestone:\|STUB:" src/`. Completing a milestone means the tag *literally disappears*. Don't leave both real and stub paths in source.
31
+ - **Pipeline order is fixed.** New analyses wire into `src/cgir/pipeline.py:scan_repo` in the order `ingest → symbols → call_graph → cfg → pdg → effects → purity → slice → export`. The CLI and the HTTP API are thin surfaces over that one driver. New graph backends subclass `GraphSource` in `src/cgir/sources/base.py`.
32
+ - **Vocabulary is fixed by the spec.** `NodeKind` and `EdgeKind` enums in `src/cgir/ir/` come straight from `Code-IR.md` §Data model. Don't add ad-hoc kinds without updating the spec first.
33
+ - **`ComponentSpec` is the agent-facing contract.** Schema lives in two places: `schemas/component_spec.schema.json` (published) and `src/cgir/ir/component_spec.py:COMPONENT_SPEC_SCHEMA` (runtime source of truth). Change both, add a schema test.
34
+ - **Local-first parsing.** No network in the ingest or analysis layers. Only the optional regeneration step touches an LLM, and it gates on `ComponentSpec` rather than raw source.
35
+
36
+ ## Out of scope (per spec)
37
+
38
+ Don't propose work in these directions without explicit user buy-in: full compiler replacement, exact semantic equivalence for all dynamic/runtime features, build-system emulation, or perfect cross-language decompilation. Dynamic dispatch, `eval`, monkeypatching, and reflection are acknowledged precision limits — flag them rather than try to solve them perfectly.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codegraph-ir
3
- Version: 0.3.0
3
+ Version: 0.5.0
4
4
  Summary: The deterministic contract layer for AI-modified codebases — effects, purity, contracts, and blast radius, with zero LLM calls.
5
5
  Project-URL: Homepage, https://github.com/asonkiya/llm-semantic-compilers
6
6
  Project-URL: Repository, https://github.com/asonkiya/llm-semantic-compilers
@@ -43,8 +43,10 @@ Requires-Python: >=3.11
43
43
  Requires-Dist: jsonschema>=4.21
44
44
  Requires-Dist: networkx>=3.2
45
45
  Requires-Dist: pydantic>=2.6
46
- Requires-Dist: tree-sitter-go<0.25,>=0.23
46
+ Requires-Dist: tree-sitter-c<0.23.3,>=0.23
47
+ Requires-Dist: tree-sitter-go<0.24,>=0.23
47
48
  Requires-Dist: tree-sitter-python<0.24,>=0.23
49
+ Requires-Dist: tree-sitter-rust<0.23.3,>=0.23
48
50
  Requires-Dist: tree-sitter-typescript<0.24,>=0.23
49
51
  Requires-Dist: tree-sitter<0.25,>=0.23
50
52
  Requires-Dist: typer>=0.12
@@ -70,8 +72,8 @@ Description-Content-Type: text/markdown
70
72
  # CodeGraph IR (CGIR)
71
73
 
72
74
  **The deterministic contract layer for AI-modified codebases.** Agents write
73
- more of the code than you can review. CGIR reads a repo and — with **zero LLM
74
- calls** — tells you what each component *is* (effects, purity, contract,
75
+ more of the code than you can review. CGIR reads a repo (Python, TypeScript,
76
+ Go, Rust, C) and — with **zero LLM calls** — tells you what each component *is* (effects, purity, contract,
75
77
  entrypoints, call surface) and whether a change *altered* it. Think **ruff,
76
78
  but for architecture instead of style**: fast, static, hallucination-free.
77
79
 
@@ -92,7 +94,7 @@ For library/agent use in a project: `uv pip install codegraph-ir`
92
94
  ## The local loop
93
95
 
94
96
  ```bash
95
- cgir scan . # build the .cgir index (Python, TypeScript, Go)
97
+ cgir scan . # build the .cgir index (Py, TS, Go, Rust, C)
96
98
  cgir watch . # keep it live: re-scan + show contract drift on save
97
99
  cgir pack app.service.charge --repo . # minimal context bundle for one component
98
100
  cgir impact app.service.charge # blast radius: affected callers, entrypoints, tests
@@ -133,6 +135,27 @@ agent-PR case study.
133
135
  Setup guide for agents (MCP config + CLAUDE.md snippet): [`docs/agents.md`](./docs/agents.md).
134
136
  Or via the [pre-commit framework](https://pre-commit.com): hook id `cgir-contract-check`.
135
137
 
138
+ ## How it compares (honestly)
139
+
140
+ | | **CGIR** | CodeGraph-style MCP graphs | Greptile / CodeRabbit | import-linter / ArchUnit | oasdiff / Pact |
141
+ |---|---|---|---|---|---|
142
+ | Effects & purity contracts per function | ✅ | ❌ | ❌ LLM judgment | ❌ | ❌ |
143
+ | Invariants declared in source (`# cgir: pure`) & enforced | ✅ | ❌ | ❌ | imports only | API boundary only |
144
+ | Deterministic (same input → same verdict, zero LLM) | ✅ | ✅ structure only | ❌ | ✅ | ✅ |
145
+ | Catches "service silently stopped calling the backend" | ✅ | ❌ | sometimes | ❌ | at spec boundaries |
146
+ | Blast radius + coverage-grounded test selection | ✅ | ❌ | ❌ | ❌ | ❌ |
147
+ | Agent context over MCP | ✅ contract packs | ✅ broader retrieval | ❌ | ❌ | ❌ |
148
+ | Finds logic bugs | ❌ | ❌ | ✅ | ❌ | ❌ |
149
+ | Languages | 5 builtin + [plugin API](./docs/languages.md) | 30+ | most | per-tool | spec-level |
150
+ | Cycle / layer rules | ✅ | ❌ | ❌ | ✅ mature | ❌ |
151
+ | Cost | free, local | mostly free | ~$24–30/user/mo | free | free |
152
+
153
+ Where CGIR loses, we say so: it does **not** find logic bugs (a subtly wrong
154
+ algorithm with unchanged effects passes the gate — pair it with tests or a
155
+ reviewer), supports 3 languages not 38, and its effect detection is static
156
+ analysis with [documented, measured limits](./docs/gate-noise.md) — every
157
+ tag carries a confidence tier so you know which claims are verified.
158
+
136
159
  ## Docs
137
160
 
138
161
  - [`docs/strategy.md`](./docs/strategy.md) — positioning: the deterministic contract layer
@@ -1,8 +1,8 @@
1
1
  # CodeGraph IR (CGIR)
2
2
 
3
3
  **The deterministic contract layer for AI-modified codebases.** Agents write
4
- more of the code than you can review. CGIR reads a repo and — with **zero LLM
5
- calls** — tells you what each component *is* (effects, purity, contract,
4
+ more of the code than you can review. CGIR reads a repo (Python, TypeScript,
5
+ Go, Rust, C) and — with **zero LLM calls** — tells you what each component *is* (effects, purity, contract,
6
6
  entrypoints, call surface) and whether a change *altered* it. Think **ruff,
7
7
  but for architecture instead of style**: fast, static, hallucination-free.
8
8
 
@@ -23,7 +23,7 @@ For library/agent use in a project: `uv pip install codegraph-ir`
23
23
  ## The local loop
24
24
 
25
25
  ```bash
26
- cgir scan . # build the .cgir index (Python, TypeScript, Go)
26
+ cgir scan . # build the .cgir index (Py, TS, Go, Rust, C)
27
27
  cgir watch . # keep it live: re-scan + show contract drift on save
28
28
  cgir pack app.service.charge --repo . # minimal context bundle for one component
29
29
  cgir impact app.service.charge # blast radius: affected callers, entrypoints, tests
@@ -64,6 +64,27 @@ agent-PR case study.
64
64
  Setup guide for agents (MCP config + CLAUDE.md snippet): [`docs/agents.md`](./docs/agents.md).
65
65
  Or via the [pre-commit framework](https://pre-commit.com): hook id `cgir-contract-check`.
66
66
 
67
+ ## How it compares (honestly)
68
+
69
+ | | **CGIR** | CodeGraph-style MCP graphs | Greptile / CodeRabbit | import-linter / ArchUnit | oasdiff / Pact |
70
+ |---|---|---|---|---|---|
71
+ | Effects & purity contracts per function | ✅ | ❌ | ❌ LLM judgment | ❌ | ❌ |
72
+ | Invariants declared in source (`# cgir: pure`) & enforced | ✅ | ❌ | ❌ | imports only | API boundary only |
73
+ | Deterministic (same input → same verdict, zero LLM) | ✅ | ✅ structure only | ❌ | ✅ | ✅ |
74
+ | Catches "service silently stopped calling the backend" | ✅ | ❌ | sometimes | ❌ | at spec boundaries |
75
+ | Blast radius + coverage-grounded test selection | ✅ | ❌ | ❌ | ❌ | ❌ |
76
+ | Agent context over MCP | ✅ contract packs | ✅ broader retrieval | ❌ | ❌ | ❌ |
77
+ | Finds logic bugs | ❌ | ❌ | ✅ | ❌ | ❌ |
78
+ | Languages | 5 builtin + [plugin API](./docs/languages.md) | 30+ | most | per-tool | spec-level |
79
+ | Cycle / layer rules | ✅ | ❌ | ❌ | ✅ mature | ❌ |
80
+ | Cost | free, local | mostly free | ~$24–30/user/mo | free | free |
81
+
82
+ Where CGIR loses, we say so: it does **not** find logic bugs (a subtly wrong
83
+ algorithm with unchanged effects passes the gate — pair it with tests or a
84
+ reviewer), supports 3 languages not 38, and its effect detection is static
85
+ analysis with [documented, measured limits](./docs/gate-noise.md) — every
86
+ tag carries a confidence tier so you know which claims are verified.
87
+
67
88
  ## Docs
68
89
 
69
90
  - [`docs/strategy.md`](./docs/strategy.md) — positioning: the deterministic contract layer
@@ -0,0 +1,234 @@
1
+ """Differential equivalence check for rung-3 winners (the rung-5 seed).
2
+
3
+ For each solved component in a rung3_rewrite results file, run the *winning
4
+ candidate* and the *original* side by side on randomly synthesized inputs
5
+ (depth-limited generation from type annotations) and compare outputs.
6
+ Sound for this worklist because the components are pure functions: same
7
+ input must give same output, and argument mutation is checked separately
8
+ via deep-copied inputs.
9
+
10
+ This measures what the gating stage could NOT: the false-pass rate of a
11
+ filter on components with no tests. Components whose signatures we can't
12
+ synthesize (Any, ndarray, unresolvable hints) are reported as such, never
13
+ silently counted.
14
+
15
+ Run with the target repo's interpreter:
16
+ <target>/.venv/bin/python benchmarks/differential_check.py \
17
+ --results benchmarks/rung3-uncovered-camera-tracking.json \
18
+ --repo <target> --n 300
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import argparse
24
+ import collections.abc as cabc
25
+ import copy
26
+ import dataclasses
27
+ import enum
28
+ import importlib
29
+ import json
30
+ import math
31
+ import random
32
+ import sys
33
+ import types
34
+ import typing
35
+ from pathlib import Path
36
+ from typing import Any
37
+
38
+
39
+ class Unsynthesizable(Exception):
40
+ pass
41
+
42
+
43
+ _MAX_DEPTH = 5
44
+
45
+
46
+ def _synth(tp: Any, rng: random.Random, depth: int = 0) -> Any:
47
+ if depth > _MAX_DEPTH:
48
+ raise Unsynthesizable(f"depth limit at {tp!r}")
49
+ origin, targs = typing.get_origin(tp), typing.get_args(tp)
50
+
51
+ if tp is type(None) or tp is None:
52
+ return None
53
+ if tp is int:
54
+ return rng.randint(-100, 100)
55
+ if tp is float:
56
+ return rng.choice([0.0, round(rng.uniform(-100.0, 100.0), 3)])
57
+ if tp is bool:
58
+ return rng.random() < 0.5
59
+ if tp is str:
60
+ return "".join(rng.choice("abcdefgh_xyz") for _ in range(rng.randint(0, 8)))
61
+ if tp is list:
62
+ tp, origin, targs = list[str], list, (str,)
63
+ if tp is dict:
64
+ tp, origin, targs = dict[str, str], dict, (str, str)
65
+ if origin in (list, cabc.Sequence, cabc.Iterable, cabc.MutableSequence):
66
+ elem = targs[0] if targs else str
67
+ return [_synth(elem, rng, depth + 1) for _ in range(rng.randint(0, 6))]
68
+ if origin in (set, frozenset):
69
+ elem = targs[0] if targs else str
70
+ try:
71
+ return {_synth(elem, rng, depth + 1) for _ in range(rng.randint(0, 5))}
72
+ except TypeError as exc: # unhashable synthesized element
73
+ raise Unsynthesizable(f"unhashable set element {elem!r}") from exc
74
+ if origin in (dict, cabc.Mapping, cabc.MutableMapping):
75
+ kt, vt = targs if len(targs) == 2 else (str, str)
76
+ return {
77
+ _synth(kt, rng, depth + 1): _synth(vt, rng, depth + 1) for _ in range(rng.randint(0, 5))
78
+ }
79
+ if origin is tuple:
80
+ if len(targs) == 2 and targs[1] is Ellipsis:
81
+ return tuple(_synth(targs[0], rng, depth + 1) for _ in range(rng.randint(0, 5)))
82
+ return tuple(_synth(t, rng, depth + 1) for t in targs) if targs else ()
83
+ if origin in (typing.Union, types.UnionType):
84
+ return _synth(rng.choice(targs), rng, depth + 1)
85
+ if origin is typing.Literal:
86
+ return rng.choice(targs)
87
+ if isinstance(tp, type) and issubclass(tp, enum.Enum):
88
+ return rng.choice(list(tp))
89
+ if dataclasses.is_dataclass(tp) and isinstance(tp, type):
90
+ hints = typing.get_type_hints(tp)
91
+ kwargs = {}
92
+ for f in dataclasses.fields(tp):
93
+ if not f.init:
94
+ continue
95
+ kwargs[f.name] = _synth(hints.get(f.name, str), rng, depth + 1)
96
+ return tp(**kwargs)
97
+ if isinstance(tp, type) and hasattr(tp, "__required_keys__"): # TypedDict
98
+ hints = typing.get_type_hints(tp)
99
+ return {k: _synth(v, rng, depth + 1) for k, v in hints.items()}
100
+ raise Unsynthesizable(repr(tp))
101
+
102
+
103
+ def _eq(a: Any, b: Any) -> bool:
104
+ if isinstance(a, float) and isinstance(b, float):
105
+ if math.isnan(a) and math.isnan(b):
106
+ return True
107
+ return math.isclose(a, b, rel_tol=1e-9, abs_tol=1e-12)
108
+ if (
109
+ isinstance(a, int | float)
110
+ and isinstance(b, int | float)
111
+ and not (isinstance(a, bool) is not isinstance(b, bool))
112
+ ):
113
+ return math.isclose(float(a), float(b), rel_tol=1e-9, abs_tol=1e-12)
114
+ if type(a) is not type(b):
115
+ return False
116
+ if dataclasses.is_dataclass(a) and not isinstance(a, type):
117
+ return _eq(dataclasses.asdict(a), dataclasses.asdict(b))
118
+ if isinstance(a, dict):
119
+ return a.keys() == b.keys() and all(_eq(v, b[k]) for k, v in a.items())
120
+ if isinstance(a, list | tuple):
121
+ return len(a) == len(b) and all(_eq(x, y) for x, y in zip(a, b, strict=True))
122
+ try:
123
+ return bool(a == b)
124
+ except Exception:
125
+ return False
126
+
127
+
128
+ def _call(fn: Any, args: list[Any]) -> tuple[str, Any]:
129
+ try:
130
+ return "ok", fn(*copy.deepcopy(args))
131
+ except Exception as exc:
132
+ return "exc", type(exc).__name__
133
+
134
+
135
+ def check_component(cid: str, candidate_src: str, repo: Path, n: int, seed: int) -> dict[str, Any]:
136
+ module_name, func_name = cid.rsplit(".", 1)
137
+ out: dict[str, Any] = {"component_id": cid, "status": "", "trials": 0, "mismatches": 0}
138
+ try:
139
+ mod = importlib.import_module(module_name)
140
+ except Exception as exc:
141
+ out["status"] = f"import_error: {type(exc).__name__}: {exc}"
142
+ return out
143
+ original = getattr(mod, func_name)
144
+ ns = dict(vars(mod))
145
+ try:
146
+ exec(compile(candidate_src, f"<candidate:{cid}>", "exec"), ns)
147
+ except Exception as exc:
148
+ out["status"] = f"candidate_exec_error: {exc}"
149
+ return out
150
+ cand = ns[func_name]
151
+
152
+ try:
153
+ hints = typing.get_type_hints(original)
154
+ except Exception as exc:
155
+ out["status"] = f"unsynthesizable: hints failed ({exc})"
156
+ return out
157
+ import inspect
158
+
159
+ params = [p for p in inspect.signature(original).parameters.values()]
160
+ if any(p.kind in (p.VAR_POSITIONAL, p.VAR_KEYWORD) for p in params):
161
+ out["status"] = "unsynthesizable: *args/**kwargs"
162
+ return out
163
+ if any(p.name not in hints for p in params):
164
+ out["status"] = "unsynthesizable: unannotated parameter"
165
+ return out
166
+
167
+ rng = random.Random(seed)
168
+ example = None
169
+ for trial in range(n):
170
+ try:
171
+ args = [_synth(hints[p.name], rng) for p in params]
172
+ except Unsynthesizable as exc:
173
+ out["status"] = f"unsynthesizable: {exc}"
174
+ return out
175
+ kind_o, val_o = _call(original, args)
176
+ kind_o2, val_o2 = _call(original, args)
177
+ if kind_o != kind_o2 or (kind_o == "ok" and not _eq(val_o, val_o2)):
178
+ out["status"] = "nondeterministic_original"
179
+ return out
180
+ kind_c, val_c = _call(cand, args)
181
+ out["trials"] = trial + 1
182
+ same = kind_o == kind_c and (kind_o == "exc" or _eq(val_o, val_c))
183
+ if not same:
184
+ out["mismatches"] += 1
185
+ if example is None:
186
+ example = {
187
+ "args": repr(args)[:400],
188
+ "original": f"{kind_o}: {val_o!r}"[:300],
189
+ "candidate": f"{kind_c}: {val_c!r}"[:300],
190
+ }
191
+ out["status"] = "equivalent" if out["mismatches"] == 0 else "mismatch"
192
+ if example:
193
+ out["example"] = example
194
+ return out
195
+
196
+
197
+ def main() -> None:
198
+ ap = argparse.ArgumentParser()
199
+ ap.add_argument("--results", type=Path, required=True)
200
+ ap.add_argument("--repo", type=Path, required=True)
201
+ ap.add_argument("--out", type=Path, default=None)
202
+ ap.add_argument("--n", type=int, default=300)
203
+ ap.add_argument("--seed", type=int, default=1)
204
+ ap.add_argument("--arm", default=None)
205
+ args = ap.parse_args()
206
+
207
+ sys.path.insert(0, str(args.repo))
208
+ report = json.loads(args.results.read_text())
209
+ rows = []
210
+ for res in report["results"]:
211
+ if not res["solved_by"] or (args.arm and res["arm"] != args.arm):
212
+ continue
213
+ winner = res["attempts"][-1]
214
+ assert winner["gate_ok"], res["component_id"]
215
+ row = check_component(
216
+ res["component_id"], winner["candidate"], args.repo, args.n, args.seed
217
+ )
218
+ row["arm"] = res["arm"]
219
+ row["solved_by"] = res["solved_by"]
220
+ rows.append(row)
221
+ print(f"{row['status']:<40s} {res['component_id']}", flush=True)
222
+
223
+ counts: dict[str, int] = {}
224
+ for row in rows:
225
+ key = row["status"].split(":")[0]
226
+ counts[key] = counts.get(key, 0) + 1
227
+ summary = {"n_checked": len(rows), "verdicts": counts, "trials_per_component": args.n}
228
+ out_path = args.out or args.results.with_name(args.results.stem + "-differential.json")
229
+ out_path.write_text(json.dumps({"summary": summary, "rows": rows}, indent=2) + "\n")
230
+ print(json.dumps(summary, indent=2))
231
+
232
+
233
+ if __name__ == "__main__":
234
+ main()