codegraph-brain 0.8.0__tar.gz → 0.10.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_brain-0.10.0/.release-please-manifest.json +3 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/CHANGELOG.md +21 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/PKG-INFO +1 -1
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/specs/2026-07-31-finder-bug-class-taxonomy.md +211 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/pyproject.toml +1 -1
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/extractors/python_extractor.py +60 -1
- codegraph_brain-0.10.0/src/cgis/guardian/axes.py +130 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/guardian/chunked.py +14 -21
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/guardian/collector.py +1 -1
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/guardian/findings.py +20 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/guardian/prompts.py +85 -32
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/query/drift/fingerprint.py +118 -2
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/test_fingerprint.py +171 -1
- codegraph_brain-0.10.0/tests/unit/test_guardian_axes.py +214 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/test_guardian_chunked.py +3 -4
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/test_python_extractor.py +87 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/uv.lock +1 -1
- codegraph_brain-0.8.0/.release-please-manifest.json +0 -3
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/.claude-plugin/marketplace.json +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/.github/workflows/autodoc.yml +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/.github/workflows/ci.yml +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/.github/workflows/guardian.yml +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/.github/workflows/pr-title.yml +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/.github/workflows/release-please.yml +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/.gitignore +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/.pre-commit-config.yaml +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/.python-version +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/CLAUDE.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/CONTRIBUTING.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/LICENSE +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/Makefile +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/PRIVACY.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/README.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/benchmarks/guardian/CURATION.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/benchmarks/guardian/pr-122.yaml +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/benchmarks/guardian/pr-140.yaml +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/benchmarks/guardian/pr-141.yaml +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/benchmarks/guardian/pr-142.yaml +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/benchmarks/guardian/pr-143.yaml +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/benchmarks/guardian/pr-144.yaml +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/benchmarks/guardian/pr-278.yaml +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/benchmarks/guardian/pr-313.yaml +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/benchmarks/guardian/results.jsonl +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/data/.gitkeep +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/AUDIT.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/CASE_STUDY.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/architecture/HOW_IT_WORKS.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/architecture/ONTOLOGY.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/architecture/PATTERNS_AND_TRIADS.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/architecture/SELF_PORTRAIT.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/architecture/diagrams/pipeline_flow.mermaid +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/architecture/health_badge.json +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/assets/.gitignore +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/assets/cgis-app-avatar.png +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/assets/cgis-app-avatar.svg +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/assets/generate_avatar.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/examples/.gitkeep +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/how-to/AGENT_ONBOARDING.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/how-to/CLI_USAGE.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/how-to/MCP_REFERENCE.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/lab-notes/2026-06-11-chunked-review-negative-result.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/ontology/.gitkeep +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/ontology/core.yaml +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/ontology/domains.yaml +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/ontology/patterns.yaml +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/ontology/tolerances.lock +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/specs/2026-06-09-domain-pattern-fingerprint-design.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/specs/2026-06-09-pattern-alphabet-motif-basis-design.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/specs/2026-06-10-guardian-sprint-design.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/specs/2026-06-11-fastapi-di-edges-design.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/specs/2026-06-11-guardian-chunked-review-design.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/specs/2026-06-11-guardian-chunker-design.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/specs/2026-06-11-mcp-drift-validate-fqn-design.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/specs/2026-06-11-resolver-split-design.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/specs/2026-06-11-symbol-import-edges-design.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/specs/2026-06-12-drift-empty-domains-design.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/specs/2026-06-12-gate-semantics-design.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/specs/2026-06-12-init-ontology-design.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/specs/2026-06-12-release-please-ci-design.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/specs/2026-06-13-suggest-packages-design.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/specs/2026-06-13-tangle-anti-pattern-design.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/specs/2026-07-29-guardian-skeptic-scoring-design.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/specs/2026-07-30-cgis-fractal-design.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/specs/2026-07-30-cgis-fractal-plan.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/specs/2026-07-30-chunk-source-filter-design.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/specs/2026-07-30-chunk-source-filter-plan.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/specs/2026-07-30-genai-client-close-design.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/specs/2026-07-30-genai-client-close-plan.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/specs/2026-07-30-guardian-precision-bench-design.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/specs/2026-07-30-guardian-precision-bench-plan.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/specs/2026-07-30-guardian-timeout-retry-design.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/specs/2026-07-30-guardian-timeout-retry-plan.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/specs/BLUEPRINT.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/specs/PRD.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/specs/TDD.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/specs/plans/2026-06-09-fingerprint-drift.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/specs/plans/2026-06-10-guardian-context-skeptic-inline.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/specs/plans/2026-06-10-guardian-structured-findings-bench.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/specs/plans/2026-06-10-motif-basis-part-b.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/specs/plans/2026-06-10-unified-pattern-alphabet.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/specs/plans/2026-06-11-fastapi-di-edges.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/specs/plans/2026-06-11-guardian-chunked-review.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/specs/plans/2026-06-11-guardian-chunker.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/specs/plans/2026-06-11-mcp-drift-validate-fqn.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/specs/plans/2026-06-11-resolver-split.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/specs/plans/2026-06-12-drift-empty-domains.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/specs/plans/2026-06-12-gate-semantics.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/specs/plans/2026-06-12-init-ontology.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/specs/plans/2026-06-12-release-please-ci.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/specs/plans/2026-06-12-symbol-import-edges.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/specs/plans/2026-06-13-suggest-packages.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/specs/plans/2026-07-29-guardian-skeptic-scoring.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/main.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/plugin/.claude-plugin/plugin.json +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/plugin/.mcp.json +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/plugin/README.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/plugin/skills/cgis/SKILL.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/plugin/skills/ingest/SKILL.md +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/release-please-config.json +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/scripts/gen_ideal_graph.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/scripts/generate_health.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/scripts/generate_mcp_ref.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/scripts/generate_schema_docs.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/scripts/guardian_bench.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/scripts/guardian_review.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/scripts/inject_readme_graph.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/scripts/probe_closure_gap.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/scripts/probe_tier_ladder.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/__init__.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/__main__.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/api/.gitkeep +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/api/__init__.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/api/mcp_server.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/cli.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/core/.gitkeep +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/core/models.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/extractors/.gitkeep +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/extractors/_python_ast.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/extractors/_python_classes.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/extractors/_python_functions.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/extractors/_python_imports.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/extractors/_python_types.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/extractors/base.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/extractors/typescript_extractor.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/guardian/__init__.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/guardian/bench.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/guardian/chunker.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/guardian/core.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/guardian/diff_index.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/guardian/github_poster.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/guardian/metrics.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/guardian/providers/__init__.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/guardian/providers/base.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/guardian/providers/gemini.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/guardian/providers/mistral.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/guardian/providers/ollama.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/guardian/recording.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/guardian/render.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/guardian/runner.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/guardian/skeptic.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/pipeline.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/py.typed +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/query/analysis/__init__.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/query/analysis/analyzer.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/query/analysis/anomaly.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/query/analysis/cohesion.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/query/analysis/health.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/query/analysis/suggest_service.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/query/context/__init__.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/query/context/audit.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/query/context/context_service.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/query/context/prompt.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/query/context/snippet.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/query/drift/__init__.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/query/drift/_scc.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/query/drift/drift.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/query/drift/drift_service.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/query/drift/fractal.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/query/drift/ontology_init.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/query/drift/quotient.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/query/drift/triads.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/query/engine.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/query/fqn.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/query/render/__init__.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/query/render/graph_json.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/query/render/mermaid.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/query/render/metrics.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/resolver/.gitkeep +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/resolver/__init__.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/resolver/engine.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/resolver/indices.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/resolver/symbols.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/resolver/uplift.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/storage/.gitkeep +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/src/cgis/storage/sqlite_store.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/integration/.gitkeep +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/self_parsing/.gitkeep +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/self_parsing/__init__.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/self_parsing/conftest.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/self_parsing/test_architecture.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/self_parsing/test_drift.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/self_parsing/test_fractal.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/self_parsing/test_init_ontology_roundtrip.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/self_parsing/test_self_parse.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/self_parsing/test_self_parse_ts.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/self_parsing/test_suggest.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/.gitkeep +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/conftest.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/guardian_stubs.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/test___main__.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/test_analyzer.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/test_audit.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/test_cli.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/test_cohesion.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/test_context_service.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/test_di_acceptance.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/test_drift.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/test_drift_service.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/test_fqn.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/test_fractal.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/test_gen_ideal_graph.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/test_generate_mcp_ref.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/test_graph_json.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/test_guardian_bench.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/test_guardian_chunker.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/test_guardian_collector.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/test_guardian_core.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/test_guardian_diff_index.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/test_guardian_findings.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/test_guardian_metrics.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/test_guardian_poster.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/test_guardian_providers.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/test_guardian_recording.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/test_guardian_render.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/test_guardian_runner.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/test_guardian_skeptic.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/test_health_scorer.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/test_import_acceptance.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/test_mcp_server.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/test_mermaid.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/test_metrics.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/test_models.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/test_ontology_compliance.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/test_ontology_init.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/test_patterns_yaml.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/test_pipeline.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/test_prompt.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/test_quotient.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/test_resolver.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/test_resolver_indices.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/test_resolver_symbols.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/test_snippet.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/test_sqlite_store.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/test_suggest_service.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/test_triads.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/test_typescript_extractor.py +0 -0
- {codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/tests/unit/test_uplift.py +0 -0
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.10.0](https://github.com/zaebee/codegraph-brain/compare/codegraph-brain-v0.9.0...codegraph-brain-v0.10.0) (2026-08-01)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **guardian:** kinship-grouped axis batches behind GUARDIAN_FEATURES=axes_paired ([#334](https://github.com/zaebee/codegraph-brain/issues/334)) ([7951dcb](https://github.com/zaebee/codegraph-brain/commit/7951dcb60a2fff2f232b144af76043a9d06d3eee))
|
|
9
|
+
* **guardian:** per-axis review fan-out behind GUARDIAN_FEATURES=axes ([#333](https://github.com/zaebee/codegraph-brain/issues/333)) ([ada3205](https://github.com/zaebee/codegraph-brain/commit/ada3205e54433356f3a1e9ef4944ed5475ed2d7c)), closes [#331](https://github.com/zaebee/codegraph-brain/issues/331)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
### Documentation
|
|
13
|
+
|
|
14
|
+
* **spec:** record the Arm A result — fails its gate, and shows the mechanism ([#330](https://github.com/zaebee/codegraph-brain/issues/330)) ([af8d543](https://github.com/zaebee/codegraph-brain/commit/af8d543fb656af233a961ef081bc8fed5f4331a0))
|
|
15
|
+
|
|
16
|
+
## [0.9.0](https://github.com/zaebee/codegraph-brain/compare/codegraph-brain-v0.8.0...codegraph-brain-v0.9.0) (2026-08-01)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### Features
|
|
20
|
+
|
|
21
|
+
* **drift:** look through transparent re-exports in the IMPORTS census ([#329](https://github.com/zaebee/codegraph-brain/issues/329)) ([25b2ce6](https://github.com/zaebee/codegraph-brain/commit/25b2ce6049ae878ebeef05cd47b08dd67faa763d))
|
|
22
|
+
* **extractor:** detect transparent re-exports ([#182](https://github.com/zaebee/codegraph-brain/issues/182) direction 1, slice 1) ([#327](https://github.com/zaebee/codegraph-brain/issues/327)) ([6e6e654](https://github.com/zaebee/codegraph-brain/commit/6e6e654b4ce17c52487507d2d425a4f81045f731))
|
|
23
|
+
|
|
3
24
|
## [0.8.0](https://github.com/zaebee/codegraph-brain/compare/codegraph-brain-v0.7.5...codegraph-brain-v0.8.0) (2026-08-01)
|
|
4
25
|
|
|
5
26
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codegraph-brain
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.10.0
|
|
4
4
|
Summary: Semantic code graph for AI agents — deterministic FQN resolution, impact analysis and architectural drift gates, exposed over MCP.
|
|
5
5
|
Project-URL: Homepage, https://github.com/zaebee/codegraph-brain
|
|
6
6
|
Project-URL: Repository, https://github.com/zaebee/codegraph-brain
|
{codegraph_brain-0.8.0 → codegraph_brain-0.10.0}/docs/specs/2026-07-31-finder-bug-class-taxonomy.md
RENAMED
|
@@ -243,3 +243,214 @@ say so explicitly rather than reporting the combined delta.
|
|
|
243
243
|
Unchanged from above, plus: do not run Arm B before Arm A has a number. The
|
|
244
244
|
float few-shot regression happened because two effects were entangled in one
|
|
245
245
|
measurement.
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
# Arm A result — 2026-08-01: fails its gate, and shows why
|
|
250
|
+
|
|
251
|
+
Run with the prod pairing (finder `mistral-medium-latest`, skeptic
|
|
252
|
+
`gemini-2.5-flash`), one run per fixture over all eight, baseline and treatment
|
|
253
|
+
sweeps back to back.
|
|
254
|
+
|
|
255
|
+
**Treatment:** each of five focus areas gained one clause naming what makes an
|
|
256
|
+
instance *not* a finding — a guard that dominates the use, an existing tolerance
|
|
257
|
+
compare, a test that already reaches the path, `Any` as the honest type of
|
|
258
|
+
parsed data. No per-class few-shot, per #247.
|
|
259
|
+
|
|
260
|
+
## The gate says no
|
|
261
|
+
|
|
262
|
+
| PR | noise | matched |
|
|
263
|
+
|---|---|---|
|
|
264
|
+
| 122 | 11 → **19** | 4 → 4 |
|
|
265
|
+
| 140 | 20 → 13 | 6 → **5** |
|
|
266
|
+
| 141 | 7 → **8** | 0 → 0 |
|
|
267
|
+
| 142 | 0 → 0 | 0 → 0 |
|
|
268
|
+
| 143 | 6 → **14** | 4 → **3** |
|
|
269
|
+
| 144 | 6 → 2 | 2 → 2 |
|
|
270
|
+
| 278 | 7 → 7 | 1 → 1 |
|
|
271
|
+
| **313** | 6 → 4 | **0 → 2** |
|
|
272
|
+
|
|
273
|
+
```
|
|
274
|
+
1. mean noise -25% FAIL 7.88 -> 8.38 (+6%, wrong direction)
|
|
275
|
+
2. matched drops nowhere FAIL pr-140, pr-143
|
|
276
|
+
3. pr-141 does not regress FAIL 7 -> 8
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Three of three. The clauses do not ship.
|
|
280
|
+
|
|
281
|
+
## The finding inside the failure
|
|
282
|
+
|
|
283
|
+
**pr-313 went from recall 0.000 to 1.000** — the finder surfaced both ground-truth
|
|
284
|
+
entries it had previously missed entirely. Precisely: one is categorised
|
|
285
|
+
`security` (`dangling-symlink-write-primitive`) and one `logic`
|
|
286
|
+
(`oserror-escapes-the-tool`), so this is one security-class hit plus one logic
|
|
287
|
+
hit, not two security hits. That is the fixture added in #315 because the finder
|
|
288
|
+
was blind to the class.
|
|
289
|
+
|
|
290
|
+
And the total is unchanged: **matched 17 → 17**. Two lost on pr-140/143, two
|
|
291
|
+
gained on pr-313. Attention moved; precision did not improve.
|
|
292
|
+
|
|
293
|
+
That is the same mechanism that killed the float few-shot experiment: the model
|
|
294
|
+
reallocates focus rather than applying an extra criterion. It is worth stating
|
|
295
|
+
plainly because the naive reading of "noise went up, recall moved around" is
|
|
296
|
+
"the change did nothing", and that is not what happened — it did something
|
|
297
|
+
specific and undesired.
|
|
298
|
+
|
|
299
|
+
## What this implies for Arm B
|
|
300
|
+
|
|
301
|
+
It strengthens the case for testing Arm B rather than weakening it. If a clause
|
|
302
|
+
that merely *mentions* guards can pull the finder onto a class it was blind to,
|
|
303
|
+
an explicit named class is a more direct instrument for the same effect — and
|
|
304
|
+
Arm B's gate is written around exactly that (pr-278 or pr-313 matched at least
|
|
305
|
+
once in three runs).
|
|
306
|
+
|
|
307
|
+
It also confirms the sequencing rule already in this document: these two effects
|
|
308
|
+
must not be measured together. Had both shipped at once, the pr-313 gain and the
|
|
309
|
+
pr-140/143 loss would have arrived as one number.
|
|
310
|
+
|
|
311
|
+
## Honest limits
|
|
312
|
+
|
|
313
|
+
- **n=1 per arm.** Finder variance on an unchanged diff has been measured at
|
|
314
|
+
6 → 46 → 36 findings. Criteria 2 and 3 turn on single-finding deltas and are
|
|
315
|
+
fragile at this sample size. Criterion 1 is the sturdier signal: noise moved
|
|
316
|
+
the wrong way, by +6% across eight fixtures.
|
|
317
|
+
- Cost: ~1.1M tokens for the two sweeps.
|
|
318
|
+
|
|
319
|
+
## Why the raw rows are not in results.jsonl
|
|
320
|
+
|
|
321
|
+
Both arms carry the same `guardian_sha` — the treatment was a working-tree edit,
|
|
322
|
+
never committed — so the rows would be indistinguishable once merged into the
|
|
323
|
+
corpus. The aggregate above is the record. Anyone repeating this should either
|
|
324
|
+
commit the prompt variant behind a flag first, or add an arm label to the
|
|
325
|
+
metrics row.
|
|
326
|
+
|
|
327
|
+
**Methodology note for whoever repeats this:** the first attempt was ruined by
|
|
328
|
+
launching the sweep twice against one results file, after an empty (buffered)
|
|
329
|
+
log was misread as a dead process. Check for a running bench by process name,
|
|
330
|
+
not by looking at its log.
|
|
331
|
+
|
|
332
|
+
`pgrep -f guardian_bench.py` is **not** enough — measured with the bench fully
|
|
333
|
+
stopped it still returns 2, because the shell wrapper running the check carries
|
|
334
|
+
the pattern in its own command line. pgrep excludes its own pid, not its
|
|
335
|
+
parent's. Match on the process name instead:
|
|
336
|
+
|
|
337
|
+
```sh
|
|
338
|
+
ps -eo comm,cmd | awk '$1 ~ /^python/ && /guardian_bench\.py/' | wc -l
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
Returns 0 when nothing is running. A false positive here is not harmless: it
|
|
342
|
+
fires the pre-launch guard and silently skips the sweep.
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
# Per-axis fan-out (#331) — measured 2026-08-01: the hypothesis holds, the cost does not
|
|
347
|
+
|
|
348
|
+
Seven finder calls per PR, one per focus area, each seeing the whole diff.
|
|
349
|
+
Stopped at four of eight fixtures: the verdict was already decided and the
|
|
350
|
+
remaining four could not change it.
|
|
351
|
+
|
|
352
|
+
| PR | noise | recall |
|
|
353
|
+
|---|---|---|
|
|
354
|
+
| 122 | 11 → **55** (5.0×) | 0.364 → 0.455 |
|
|
355
|
+
| 140 | 20 → **70** (3.5×) | 0.400 → 0.533 |
|
|
356
|
+
| 141 | 7 → **39** (5.6×) | 1.000 → 1.000 |
|
|
357
|
+
| 142 | 0 → 0 | 0.000 → 0.000 |
|
|
358
|
+
|
|
359
|
+
Mean noise **9.5 → 41.0, a 4.3× rise**. Gate criterion 1 fails by an order of
|
|
360
|
+
magnitude, not a margin.
|
|
361
|
+
|
|
362
|
+
## Both halves of the hypothesis were right
|
|
363
|
+
|
|
364
|
+
**Separating axes does lift recall** — up on every fixture that had anything to
|
|
365
|
+
find (+0.09, +0.13). Attention competition is real, and removing it works.
|
|
366
|
+
|
|
367
|
+
**And it multiplies noise, exactly as chunking did.** pr-141 is the sharpest
|
|
368
|
+
evidence: it is the noise probe, recall was already 1.000, there was nothing to
|
|
369
|
+
gain — and noise still rose 5.6×. On that fixture the change bought only noise.
|
|
370
|
+
|
|
371
|
+
Raw finding counts show the mechanism directly: 126, 139 and 94 findings per PR
|
|
372
|
+
before dedup (82, 102, 60 after), against roughly 15 for the single prompt.
|
|
373
|
+
Every extra call is another opportunity to invent something.
|
|
374
|
+
|
|
375
|
+
## Why it stopped at four
|
|
376
|
+
|
|
377
|
+
#331 pre-registered the rule: *"If noise inflates but per-axis recall improves,
|
|
378
|
+
that is a result, not a partial pass — record it and stop, exactly as #160 did."*
|
|
379
|
+
The multiplier was consistent across all four, so four more fixtures would have
|
|
380
|
+
cost real money to confirm a verdict already in hand.
|
|
381
|
+
|
|
382
|
+
## What the numbers say about the fix
|
|
383
|
+
|
|
384
|
+
Noise scaled roughly **linearly with call count**: 7 calls, 4.3×. That predicts
|
|
385
|
+
~1.8× at three calls and ~1.2× at two — the last of which would sit close enough
|
|
386
|
+
to baseline to pass criterion 1 while keeping part of the recall.
|
|
387
|
+
|
|
388
|
+
So the next variant is not "abandon fan-out", it is "fan out less". Shipped
|
|
389
|
+
behind `GUARDIAN_FEATURES=axes_paired`: two calls, grouped by what a defect *is*
|
|
390
|
+
rather than arbitrarily —
|
|
391
|
+
|
|
392
|
+
- **correctness**: logic, unvalidated external data, float equality
|
|
393
|
+
- **contracts**: type safety, library boundaries, ontology, test coverage
|
|
394
|
+
|
|
395
|
+
Kin travel together because the two measured competitions (#247, #330) were both
|
|
396
|
+
between *dissimilar* classes.
|
|
397
|
+
|
|
398
|
+
A caveat for whoever runs it: there is no `security` axis to separate, since Arm
|
|
399
|
+
B was never shipped. The recall gains above therefore came from separation in
|
|
400
|
+
general, not from isolating a blind class — so this variant tests the cheaper
|
|
401
|
+
half of the idea, and the blind-class question stays open in #258.
|
|
402
|
+
|
|
403
|
+
## Paired grouping — measured the same day, on the same four fixtures
|
|
404
|
+
|
|
405
|
+
Two calls instead of seven, batched by kinship.
|
|
406
|
+
|
|
407
|
+
| PR | baseline | paired (2) | per-axis (7) |
|
|
408
|
+
|---|---|---|---|
|
|
409
|
+
| 122 | 11 / 0.364 | **21 / 0.545** | 55 / 0.455 |
|
|
410
|
+
| 140 | 20 / 0.400 | 27 / **0.333** | 70 / 0.533 |
|
|
411
|
+
| 141 | 7 / 1.000 | **9** / 1.000 | 39 / 1.000 |
|
|
412
|
+
| 142 | 0 / 0.000 | 0 / 0.000 | 0 / 0.000 |
|
|
413
|
+
|
|
414
|
+
```
|
|
415
|
+
mean noise 9.50 -> 14.25 (1.5x) -> 41.00 (4.3x)
|
|
416
|
+
mean recall 0.441 -> 0.470 -> 0.497
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
### The linear model held, and the recall did too
|
|
420
|
+
|
|
421
|
+
Noise was predicted at ~1.2× for two calls from the linear-in-call-count fit;
|
|
422
|
+
the measurement is **1.5×**, against 4.3× at seven. The order of magnitude was
|
|
423
|
+
right.
|
|
424
|
+
|
|
425
|
+
The part that was **not** predicted: recall barely moved. Seven calls give
|
|
426
|
+
0.497, two give 0.470 — **three times less noise for 0.027 of recall.** Most of
|
|
427
|
+
the benefit of separation is already there at two calls.
|
|
428
|
+
|
|
429
|
+
On pr-122 two calls beat seven on *both* axes — recall 0.545 against 0.455, at
|
|
430
|
+
noise 21 against 55. More calls is not more recall: seven axes appear to
|
|
431
|
+
fragment too far, leaving each call too little to reason with.
|
|
432
|
+
|
|
433
|
+
### It still fails the gate
|
|
434
|
+
|
|
435
|
+
- **Criterion 1** — noise must not exceed baseline. 1.5× does not pass.
|
|
436
|
+
- **Criterion 3** — no PR more than 0.05 below baseline recall. pr-140 dropped
|
|
437
|
+
0.400 → 0.333.
|
|
438
|
+
|
|
439
|
+
So this does not ship either. But it is a qualitatively different result from
|
|
440
|
+
the seven-axis run: not "the hypothesis failed" but "the cost is down threefold
|
|
441
|
+
and 1.5× remains".
|
|
442
|
+
|
|
443
|
+
### Limits
|
|
444
|
+
|
|
445
|
+
n=1 per variant across four fixtures, against a finder whose variance on an
|
|
446
|
+
unchanged diff has been measured at 6 → 46 → 36 findings. The gap between 0.470
|
|
447
|
+
and 0.497 is well inside that. The noise multipliers (1.5× vs 4.3×) are the
|
|
448
|
+
sturdy part — they are large and consistent per fixture.
|
|
449
|
+
|
|
450
|
+
### What it points at
|
|
451
|
+
|
|
452
|
+
Since recall survives batching, the next thing worth testing is **one call that
|
|
453
|
+
walks the axes in sequence** — separation of attention without a second call at
|
|
454
|
+
all. Failing that, keep two calls and attack the residual noise from the skeptic
|
|
455
|
+
side, which is currently tuned for single-prompt volume rather than for a merged
|
|
456
|
+
union.
|
|
@@ -7,7 +7,7 @@ packages = ["src/cgis"]
|
|
|
7
7
|
|
|
8
8
|
[project]
|
|
9
9
|
name = "codegraph-brain"
|
|
10
|
-
version = "0.
|
|
10
|
+
version = "0.10.0"
|
|
11
11
|
description = "Semantic code graph for AI agents — deterministic FQN resolution, impact analysis and architectural drift gates, exposed over MCP."
|
|
12
12
|
readme = "README.md"
|
|
13
13
|
requires-python = ">=3.12"
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"""Implements Python Extractor."""
|
|
2
2
|
|
|
3
|
+
import re
|
|
4
|
+
|
|
3
5
|
import tree_sitter_python as tspython
|
|
4
6
|
from tree_sitter import Language, Parser
|
|
5
7
|
from tree_sitter import Node as BaseNode
|
|
@@ -29,6 +31,61 @@ def file_path_to_module_fqn(file_path: str, source_root: str | None = None) -> s
|
|
|
29
31
|
return _file_path_to_module_fqn(file_path, source_root)
|
|
30
32
|
|
|
31
33
|
|
|
34
|
+
def _identifiers_outside_imports(root: BaseNode, code_bytes: bytes) -> set[str]:
|
|
35
|
+
"""Every identifier used in the module body, excluding import statements.
|
|
36
|
+
|
|
37
|
+
The walker already refuses to recurse into import nodes, so the same rule is
|
|
38
|
+
applied here: a name that appears *only* in an import is never used by this
|
|
39
|
+
module.
|
|
40
|
+
"""
|
|
41
|
+
used: set[str] = set()
|
|
42
|
+
stack = [root]
|
|
43
|
+
while stack:
|
|
44
|
+
node = stack.pop()
|
|
45
|
+
if node.type in ("import_statement", "import_from_statement"):
|
|
46
|
+
continue
|
|
47
|
+
if node.type == "identifier":
|
|
48
|
+
# `o.thing` and `f(thing=1)` mention `thing` without using the imported
|
|
49
|
+
# name — counting them would let an unrelated attribute or keyword
|
|
50
|
+
# silence a real re-export of that name. Only the `attribute`/`name`
|
|
51
|
+
# field is excluded; the object side of `o.thing` is a genuine use.
|
|
52
|
+
parent = node.parent
|
|
53
|
+
shadowed = parent is not None and (
|
|
54
|
+
(parent.type == "attribute" and parent.child_by_field_name("attribute") == node)
|
|
55
|
+
or (
|
|
56
|
+
parent.type == "keyword_argument" and parent.child_by_field_name("name") == node
|
|
57
|
+
)
|
|
58
|
+
)
|
|
59
|
+
if not shadowed:
|
|
60
|
+
used.add(code_bytes[node.start_byte : node.end_byte].decode("utf8"))
|
|
61
|
+
elif node.type == "string" and node.parent is not None and node.parent.type == "type":
|
|
62
|
+
# A forward-reference annotation — `store: "SQLiteStore | None"` — holds
|
|
63
|
+
# no identifier nodes, so without this a TYPE_CHECKING import used only
|
|
64
|
+
# in quoted annotations reads as unused. Restricted to annotation
|
|
65
|
+
# strings (parent `type`): scanning every string would let a docstring
|
|
66
|
+
# mentioning a name mask a real re-export.
|
|
67
|
+
text = code_bytes[node.start_byte : node.end_byte].decode("utf8")
|
|
68
|
+
used.update(re.findall(r"[A-Za-z_][A-Za-z0-9_]*", text))
|
|
69
|
+
stack.extend(node.children)
|
|
70
|
+
return used
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def find_reexports(root: BaseNode, code_bytes: bytes, import_map: dict[str, str]) -> dict[str, str]:
|
|
74
|
+
"""Return the imports this module never uses — i.e. passes straight through (#182).
|
|
75
|
+
|
|
76
|
+
A transparent re-export lets one module absorb another's import edges, so an
|
|
77
|
+
N-way coupling can be linearised on paper without changing what actually
|
|
78
|
+
depends on what. Naming them is the precondition for the IMPORTS census
|
|
79
|
+
being able to look through them.
|
|
80
|
+
|
|
81
|
+
"Imported but unused" is the whole test, and in a linted tree that is a
|
|
82
|
+
precise proxy: ruff's F401 removes genuine dead imports, so what survives is
|
|
83
|
+
deliberate — `X as X`, an `__all__` entry, or an explicit noqa.
|
|
84
|
+
"""
|
|
85
|
+
used = _identifiers_outside_imports(root, code_bytes)
|
|
86
|
+
return {local: target for local, target in import_map.items() if local not in used}
|
|
87
|
+
|
|
88
|
+
|
|
32
89
|
class PythonExtractor(BaseExtractor):
|
|
33
90
|
"""
|
|
34
91
|
A concrete extractor for Python source code using tree-sitter.
|
|
@@ -83,6 +140,8 @@ class PythonExtractor(BaseExtractor):
|
|
|
83
140
|
update={"metadata": {**nodes[i].metadata, "local_types": lt}}
|
|
84
141
|
)
|
|
85
142
|
|
|
143
|
+
reexports = find_reexports(root_node, code_bytes, import_map)
|
|
144
|
+
|
|
86
145
|
file_node = Node(
|
|
87
146
|
id=module_fqn,
|
|
88
147
|
type=NodeType.FILE,
|
|
@@ -90,7 +149,7 @@ class PythonExtractor(BaseExtractor):
|
|
|
90
149
|
file_path=file_path,
|
|
91
150
|
start_line=1,
|
|
92
151
|
end_line=root_node.end_point.row + 1,
|
|
93
|
-
metadata={"import_map": import_map},
|
|
152
|
+
metadata={"import_map": import_map, "reexports": reexports},
|
|
94
153
|
)
|
|
95
154
|
nodes.insert(0, file_node)
|
|
96
155
|
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""Per-axis review: one finder call per focus area instead of one carrying all (#331).
|
|
2
|
+
|
|
3
|
+
The finder appears to work against a roughly fixed attention budget spread over
|
|
4
|
+
the axes named in a single prompt. Editing that prompt reallocates the budget
|
|
5
|
+
rather than adding capacity — measured twice:
|
|
6
|
+
|
|
7
|
+
- a targeted float few-shot cracked nothing and dropped pr-144 from 3/5 to 1/5,
|
|
8
|
+
pulling focus off the yaml class it had been catching (#247);
|
|
9
|
+
- exculpating clauses took pr-313 from recall 0.000 to 1.000 while the corpus
|
|
10
|
+
total stayed at matched 17 -> 17, two lost elsewhere (#330).
|
|
11
|
+
|
|
12
|
+
Axes cannot compete for attention if they are not in the same context window.
|
|
13
|
+
Each call still sees the *whole* diff — only the question narrows, which is what
|
|
14
|
+
separates this from chunked review (#154), where each call saw a fragment and
|
|
15
|
+
part of the failure was dilution inside the chunk.
|
|
16
|
+
|
|
17
|
+
The known risk is the one chunking failed on: every extra call is another
|
|
18
|
+
opportunity to invent a false positive, and there noise grew superlinearly
|
|
19
|
+
(#160). That is why this ships behind a flag and why the pre-registered gate in
|
|
20
|
+
#331 puts noise first.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import asyncio
|
|
24
|
+
|
|
25
|
+
import structlog
|
|
26
|
+
|
|
27
|
+
from cgis.guardian.collector import ContextCollector
|
|
28
|
+
from cgis.guardian.core import finder_pass
|
|
29
|
+
from cgis.guardian.findings import Finding, ReviewResult, dedup_findings
|
|
30
|
+
from cgis.guardian.prompts import AXIS_GROUPS, PER_AXIS_GROUPS
|
|
31
|
+
from cgis.guardian.providers.base import BaseProvider
|
|
32
|
+
from cgis.guardian.skeptic import apply_judgements, judge_all, skeptic_status_for
|
|
33
|
+
|
|
34
|
+
log = structlog.getLogger(__name__)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
async def _axis_pass(
|
|
38
|
+
provider: BaseProvider, context: dict[str, str], label: str, axes: tuple[str, ...]
|
|
39
|
+
) -> ReviewResult | None:
|
|
40
|
+
"""Run the finder for one axis; None when that axis's call fails.
|
|
41
|
+
|
|
42
|
+
A flaky call must cost one axis, not the review — the same guard the chunked
|
|
43
|
+
orchestrator puts around each chunk.
|
|
44
|
+
"""
|
|
45
|
+
try:
|
|
46
|
+
return await finder_pass(provider, {**context, "focus_group": ",".join(axes)})
|
|
47
|
+
except Exception:
|
|
48
|
+
log.warning("Axis finder call failed; group skipped.", group=label, exc_info=True)
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
async def run_axis_review(
|
|
53
|
+
*,
|
|
54
|
+
provider: BaseProvider,
|
|
55
|
+
collector: ContextCollector,
|
|
56
|
+
skeptic_provider: BaseProvider | None,
|
|
57
|
+
concurrency: int = 4,
|
|
58
|
+
) -> ReviewResult:
|
|
59
|
+
"""Fan out over focus areas, merge, dedup, then one skeptic pass over the union.
|
|
60
|
+
|
|
61
|
+
One skeptic pass rather than one per axis: judging is already the larger half
|
|
62
|
+
of the token bill, and per-axis judging would multiply it for no expected
|
|
63
|
+
gain — the axes disagree about *what to look for*, not about whether a given
|
|
64
|
+
finding is real.
|
|
65
|
+
"""
|
|
66
|
+
context = collector.collect_all()
|
|
67
|
+
# "axes" = one call per axis; "axes_paired" = kinship batches (#331 follow-up).
|
|
68
|
+
groups = AXIS_GROUPS if "axes_paired" in collector.features else PER_AXIS_GROUPS
|
|
69
|
+
axes = list(groups)
|
|
70
|
+
|
|
71
|
+
semaphore = asyncio.Semaphore(concurrency)
|
|
72
|
+
|
|
73
|
+
async def guarded(axis: str) -> ReviewResult | None:
|
|
74
|
+
async with semaphore:
|
|
75
|
+
return await _axis_pass(provider, context, axis, groups[axis])
|
|
76
|
+
|
|
77
|
+
results = await asyncio.gather(*(guarded(axis) for axis in axes))
|
|
78
|
+
|
|
79
|
+
findings: list[Finding] = []
|
|
80
|
+
summaries: list[str] = []
|
|
81
|
+
failed = 0
|
|
82
|
+
for axis, result in zip(axes, results, strict=True):
|
|
83
|
+
if result is None:
|
|
84
|
+
failed += 1
|
|
85
|
+
continue
|
|
86
|
+
if result.parse_failed:
|
|
87
|
+
# Unparseable output is not a finding-free axis; count it as failed so
|
|
88
|
+
# the summary cannot read as "this axis found nothing".
|
|
89
|
+
failed += 1
|
|
90
|
+
log.warning("Axis output did not parse; axis skipped.", axis=axis)
|
|
91
|
+
continue
|
|
92
|
+
findings.extend(result.findings)
|
|
93
|
+
summaries.append(f"- [{axis}]: {result.summary}")
|
|
94
|
+
|
|
95
|
+
if failed == len(axes):
|
|
96
|
+
return ReviewResult(
|
|
97
|
+
findings=[],
|
|
98
|
+
summary="Every axis pass failed; no review produced.",
|
|
99
|
+
parse_failed=True,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
merged = ReviewResult(
|
|
103
|
+
findings=dedup_findings(findings),
|
|
104
|
+
summary="\n".join(summaries),
|
|
105
|
+
)
|
|
106
|
+
log.info(
|
|
107
|
+
"Axis fan-out complete.",
|
|
108
|
+
axes=len(axes),
|
|
109
|
+
failed=failed,
|
|
110
|
+
raw_findings=len(findings),
|
|
111
|
+
after_dedup=len(merged.findings),
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
if skeptic_provider is None or not merged.findings:
|
|
115
|
+
return merged
|
|
116
|
+
|
|
117
|
+
judgements = await judge_all(
|
|
118
|
+
skeptic_provider, merged.findings, context.get("diff", ""), concurrency
|
|
119
|
+
)
|
|
120
|
+
judged = sum(1 for j in judgements if j is not None)
|
|
121
|
+
if judged == 0:
|
|
122
|
+
log.warning("Every skeptic judgement failed; returning unverified findings.")
|
|
123
|
+
return merged.model_copy(
|
|
124
|
+
update={
|
|
125
|
+
"findings": apply_judgements(merged.findings, judgements),
|
|
126
|
+
"skeptic_status": skeptic_status_for(judged, len(judgements)),
|
|
127
|
+
"skeptic_judged": judged,
|
|
128
|
+
"skeptic_total": len(judgements),
|
|
129
|
+
}
|
|
130
|
+
)
|
|
@@ -8,10 +8,11 @@ complete world instead — its own diff, full files, and impact graph.
|
|
|
8
8
|
import structlog
|
|
9
9
|
from pydantic import BaseModel
|
|
10
10
|
|
|
11
|
+
from cgis.guardian.axes import run_axis_review
|
|
11
12
|
from cgis.guardian.chunker import Chunk, build_chunks, split_diff_by_file
|
|
12
13
|
from cgis.guardian.collector import ContextCollector
|
|
13
14
|
from cgis.guardian.core import GuardianReviewer, finder_pass
|
|
14
|
-
from cgis.guardian.findings import Finding, ReviewResult
|
|
15
|
+
from cgis.guardian.findings import Finding, ReviewResult, dedup_findings
|
|
15
16
|
from cgis.guardian.providers.base import BaseProvider
|
|
16
17
|
from cgis.guardian.skeptic import (
|
|
17
18
|
apply_judgements,
|
|
@@ -85,25 +86,6 @@ def _chunk_survivors(chunk: Chunk, findings: list[Finding]) -> list[Finding]:
|
|
|
85
86
|
return survivors
|
|
86
87
|
|
|
87
88
|
|
|
88
|
-
def _dedup(findings: list[Finding]) -> list[Finding]:
|
|
89
|
-
"""Drop duplicate (file, line, category) findings, keeping the higher confidence.
|
|
90
|
-
|
|
91
|
-
Cross-chunk duplicates are impossible after the per-chunk file filter
|
|
92
|
-
(chunks partition files) — this is insurance against intra-pass
|
|
93
|
-
duplicates. First-occurrence order is preserved.
|
|
94
|
-
"""
|
|
95
|
-
best: dict[tuple[str, int | None, str], Finding] = {}
|
|
96
|
-
order: list[tuple[str, int | None, str]] = []
|
|
97
|
-
for finding in findings:
|
|
98
|
-
key = (finding.file, finding.line, finding.category)
|
|
99
|
-
if key not in best:
|
|
100
|
-
best[key] = finding
|
|
101
|
-
order.append(key)
|
|
102
|
-
elif finding.confidence > best[key].confidence:
|
|
103
|
-
best[key] = finding
|
|
104
|
-
return [best[k] for k in order]
|
|
105
|
-
|
|
106
|
-
|
|
107
89
|
async def _single_pass(
|
|
108
90
|
provider: BaseProvider,
|
|
109
91
|
collector: ContextCollector,
|
|
@@ -187,7 +169,7 @@ async def run_chunked_review(
|
|
|
187
169
|
bullets.append(f"- [{label}]: {result.summary}")
|
|
188
170
|
|
|
189
171
|
merged = ReviewResult(
|
|
190
|
-
findings=
|
|
172
|
+
findings=dedup_findings(kept),
|
|
191
173
|
summary="\n".join(bullets),
|
|
192
174
|
parse_failed=failed == len(chunks),
|
|
193
175
|
)
|
|
@@ -229,6 +211,17 @@ async def run_review_routed(
|
|
|
229
211
|
degrade to all-isolated chunks = one API call per file with zero
|
|
230
212
|
connectivity benefit — strictly worse than the status quo.
|
|
231
213
|
"""
|
|
214
|
+
if collector.features & {"axes", "axes_paired"}:
|
|
215
|
+
# Per-axis fan-out (#331) is checked first: it needs no graph DB, and
|
|
216
|
+
# combining it with chunked would multiply calls by axes x chunks while
|
|
217
|
+
# entangling two effects in one measurement — the mistake #330 recorded.
|
|
218
|
+
return RoutedReview(
|
|
219
|
+
result=await run_axis_review(
|
|
220
|
+
provider=provider, collector=collector, skeptic_provider=skeptic_provider
|
|
221
|
+
),
|
|
222
|
+
chunk_count=None,
|
|
223
|
+
)
|
|
224
|
+
|
|
232
225
|
chunked = "chunked" in collector.features
|
|
233
226
|
if chunked and (collector.db_path is None or not collector.db_path.exists()):
|
|
234
227
|
log.warning("chunked requested but no graph DB; falling back to single pass.")
|
|
@@ -16,7 +16,7 @@ from cgis.storage.sqlite_store import SQLiteStore
|
|
|
16
16
|
|
|
17
17
|
log = structlog.getLogger(__name__)
|
|
18
18
|
|
|
19
|
-
VALID_FEATURES = frozenset({"full_files", "flow", "drift", "chunked"})
|
|
19
|
+
VALID_FEATURES = frozenset({"full_files", "flow", "drift", "chunked", "axes", "axes_paired"})
|
|
20
20
|
|
|
21
21
|
_MAX_FILE_LINES = 1200
|
|
22
22
|
_MAX_TOTAL_CHARS = 120_000
|
|
@@ -72,3 +72,23 @@ def extract_json(text: str) -> str:
|
|
|
72
72
|
# No newline before the closing fence (e.g. `{...}```` on one line):
|
|
73
73
|
# valid JSON never ends with backticks, so stripping the suffix is safe.
|
|
74
74
|
return body.strip().removesuffix("```").strip()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def dedup_findings(findings: list[Finding]) -> list[Finding]:
|
|
78
|
+
"""Drop duplicate (file, line, category) findings, keeping the higher confidence.
|
|
79
|
+
|
|
80
|
+
Shared by every fan-out reviewer: chunked review partitions files so
|
|
81
|
+
cross-chunk duplicates cannot occur and this is insurance, but per-axis
|
|
82
|
+
review (#331) asks several axes about the same lines, where collisions are
|
|
83
|
+
expected rather than exceptional. First-occurrence order is preserved.
|
|
84
|
+
"""
|
|
85
|
+
best: dict[tuple[str, int | None, str], Finding] = {}
|
|
86
|
+
order: list[tuple[str, int | None, str]] = []
|
|
87
|
+
for finding in findings:
|
|
88
|
+
key = (finding.file, finding.line, finding.category)
|
|
89
|
+
if key not in best:
|
|
90
|
+
best[key] = finding
|
|
91
|
+
order.append(key)
|
|
92
|
+
elif finding.confidence > best[key].confidence:
|
|
93
|
+
best[key] = finding
|
|
94
|
+
return [best[k] for k in order]
|