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.
- codegraph_ir-0.5.0/.mcp.json +8 -0
- codegraph_ir-0.5.0/AGENTS.md +38 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/PKG-INFO +28 -5
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/README.md +24 -3
- codegraph_ir-0.5.0/benchmarks/differential_check.py +234 -0
- codegraph_ir-0.5.0/benchmarks/rung3-ablation-camera-tracking.json +895 -0
- codegraph_ir-0.5.0/benchmarks/rung3-results-camera-tracking.json +751 -0
- codegraph_ir-0.5.0/benchmarks/rung3-uncovered-camera-tracking-differential.json +210 -0
- codegraph_ir-0.5.0/benchmarks/rung3-uncovered-camera-tracking.json +499 -0
- codegraph_ir-0.5.0/benchmarks/rung3_rewrite.py +408 -0
- codegraph_ir-0.5.0/benchmarks/rung4-artifacts/sqlite_rust_functions.rs +325 -0
- codegraph_ir-0.5.0/benchmarks/rung4-linkback-report.json +39 -0
- codegraph_ir-0.5.0/benchmarks/rung4-results-sqlite-probed.json +2097 -0
- codegraph_ir-0.5.0/benchmarks/rung4-results-sqlite.json +927 -0
- codegraph_ir-0.5.0/benchmarks/rung4_c_to_rust.py +703 -0
- codegraph_ir-0.5.0/benchmarks/rung4_linkback.py +209 -0
- codegraph_ir-0.5.0/docs/experiment-log.md +511 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/docs/languages.md +45 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/docs/roadmap.md +1 -1
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/docs/status.md +8 -1
- codegraph_ir-0.5.0/docs/vision-rewrite.md +148 -0
- codegraph_ir-0.5.0/docs/writing-an-adapter.md +329 -0
- codegraph_ir-0.5.0/examples/c-adapter/NOTES.md +184 -0
- codegraph_ir-0.5.0/examples/c-adapter/README.md +18 -0
- codegraph_ir-0.5.0/examples/rust-adapter/NOTES.md +145 -0
- codegraph_ir-0.5.0/examples/rust-adapter/README.md +15 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/pyproject.toml +6 -2
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/__init__.py +1 -1
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/analyses/call_graph.py +5 -2
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/analyses/cfg.py +10 -3
- codegraph_ir-0.5.0/src/cgir/analyses/decompose.py +265 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/analyses/effects.py +13 -3
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/analyses/symbols.py +27 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/cli.py +167 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/ir/graph.py +5 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/languages/base.py +31 -1
- codegraph_ir-0.5.0/src/cgir/languages/c.py +948 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/languages/cache.py +19 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/languages/go.py +22 -5
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/languages/python.py +29 -5
- codegraph_ir-0.5.0/src/cgir/languages/registry.py +106 -0
- codegraph_ir-0.5.0/src/cgir/languages/rust.py +998 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/languages/typescript.py +30 -5
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/report/search.py +7 -3
- codegraph_ir-0.5.0/src/cgir/rewrite.py +320 -0
- codegraph_ir-0.5.0/src/cgir/verify.py +336 -0
- codegraph_ir-0.5.0/tests/unit/test_c_adapter.py +1108 -0
- codegraph_ir-0.5.0/tests/unit/test_decompose.py +129 -0
- codegraph_ir-0.5.0/tests/unit/test_incremental_verify.py +144 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_pins.py +18 -0
- codegraph_ir-0.5.0/tests/unit/test_plugin_registry.py +136 -0
- codegraph_ir-0.5.0/tests/unit/test_raise_confidence.py +110 -0
- codegraph_ir-0.5.0/tests/unit/test_rewrite_orchestrator.py +225 -0
- codegraph_ir-0.5.0/tests/unit/test_rust_adapter.py +634 -0
- codegraph_ir-0.3.0/docs/experiment-log.md +0 -204
- codegraph_ir-0.3.0/src/cgir/languages/registry.py +0 -27
- codegraph_ir-0.3.0/src/cgir/verify.py +0 -180
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/.github/workflows/ci.yml +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/.github/workflows/release.yml +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/.gitignore +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/.pre-commit-config.yaml +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/.pre-commit-hooks.yaml +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/.python-version +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/CLAUDE.md +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/Code-IR.md +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/LICENSE +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/RELEASING.md +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/action.yml +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/docs/README.md +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/docs/agents.md +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/docs/architecture-rules.md +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/docs/architecture.md +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/docs/development.md +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/docs/feature-research.md +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/docs/gate-noise.md +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/docs/github-action.md +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/docs/lsp.md +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/docs/plan-0.2-0.4.md +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/docs/plan-0.3.md +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/docs/strategy.md +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/examples/agent-pr-contract-drift.md +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/schemas/component_spec.schema.json +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/analyses/__init__.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/analyses/coverage_link.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/analyses/entrypoints.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/analyses/param_flow.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/analyses/pdg.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/analyses/purity.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/analyses/reaching_defs.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/api/__init__.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/api/lsp_server.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/api/mcp_server.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/api/server.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/config.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/export/__init__.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/export/graphml.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/export/html_viz.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/export/json_export.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/export/mermaid.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/export/neo4j.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/hooks.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/ir/__init__.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/ir/component_spec.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/ir/edges.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/ir/nodes.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/languages/__init__.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/manifest.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/pipeline.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/py.typed +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/regenerate/__init__.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/regenerate/prompt_pack.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/regenerate/regenerator.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/report/__init__.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/report/diff.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/report/flow.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/report/impact.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/report/lint.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/report/pack.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/report/pins.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/report/stats.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/slicing/__init__.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/slicing/slicer.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/sources/__init__.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/sources/base.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/sources/codeql_source.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/sources/joern_source.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/sources/tree_sitter_source.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/trace/__init__.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/trace/trace_map.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/src/cgir/watch.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/__init__.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/conftest.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/fixtures/python_sample/orchestrator.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/fixtures/python_sample/pricing.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/fixtures/ts_sample/api/service.ts +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/fixtures/ts_sample/util.ts +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/integration/__init__.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/integration/test_api.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/integration/test_cli_scan.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/integration/test_hook.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/integration/test_init.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/__init__.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_action.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_call_graph.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_cfg.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_component_spec.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_confidence.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_coverage_link.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_diff.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_effects.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_entrypoints.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_go_adapter.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_graphml.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_html_viz.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_impact.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_impact_run.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_ir_graph.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_language_adapter.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_lint.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_lsp.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_manifest.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_mcp_tools.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_mermaid.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_pack.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_param_flow.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_pdg.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_purity.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_python_di.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_reaching_defs.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_regenerator.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_search.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_shapes.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_slicer.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_source_cache.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_stats.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_symbols.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_trace_map.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_tree_sitter_source.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_typescript_adapter.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_verify.py +0 -0
- {codegraph_ir-0.3.0 → codegraph_ir-0.5.0}/tests/unit/test_watch.py +0 -0
|
@@ -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
|
+
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-
|
|
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
|
|
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 (
|
|
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
|
|
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 (
|
|
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()
|