codegraph-brain 0.9.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.9.0 → codegraph_brain-0.10.0}/CHANGELOG.md +13 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/PKG-INFO +1 -1
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/specs/2026-07-31-finder-bug-class-taxonomy.md +211 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/pyproject.toml +1 -1
- codegraph_brain-0.10.0/src/cgis/guardian/axes.py +130 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/guardian/chunked.py +14 -21
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/guardian/collector.py +1 -1
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/guardian/findings.py +20 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/guardian/prompts.py +85 -32
- codegraph_brain-0.10.0/tests/unit/test_guardian_axes.py +214 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/test_guardian_chunked.py +3 -4
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/uv.lock +1 -1
- codegraph_brain-0.9.0/.release-please-manifest.json +0 -3
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/.claude-plugin/marketplace.json +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/.github/workflows/autodoc.yml +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/.github/workflows/ci.yml +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/.github/workflows/guardian.yml +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/.github/workflows/pr-title.yml +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/.github/workflows/release-please.yml +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/.gitignore +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/.pre-commit-config.yaml +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/.python-version +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/CLAUDE.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/CONTRIBUTING.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/LICENSE +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/Makefile +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/PRIVACY.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/README.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/benchmarks/guardian/CURATION.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/benchmarks/guardian/pr-122.yaml +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/benchmarks/guardian/pr-140.yaml +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/benchmarks/guardian/pr-141.yaml +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/benchmarks/guardian/pr-142.yaml +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/benchmarks/guardian/pr-143.yaml +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/benchmarks/guardian/pr-144.yaml +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/benchmarks/guardian/pr-278.yaml +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/benchmarks/guardian/pr-313.yaml +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/benchmarks/guardian/results.jsonl +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/data/.gitkeep +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/AUDIT.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/CASE_STUDY.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/architecture/HOW_IT_WORKS.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/architecture/ONTOLOGY.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/architecture/PATTERNS_AND_TRIADS.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/architecture/SELF_PORTRAIT.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/architecture/diagrams/pipeline_flow.mermaid +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/architecture/health_badge.json +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/assets/.gitignore +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/assets/cgis-app-avatar.png +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/assets/cgis-app-avatar.svg +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/assets/generate_avatar.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/examples/.gitkeep +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/how-to/AGENT_ONBOARDING.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/how-to/CLI_USAGE.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/how-to/MCP_REFERENCE.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/lab-notes/2026-06-11-chunked-review-negative-result.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/ontology/.gitkeep +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/ontology/core.yaml +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/ontology/domains.yaml +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/ontology/patterns.yaml +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/ontology/tolerances.lock +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/specs/2026-06-09-domain-pattern-fingerprint-design.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/specs/2026-06-09-pattern-alphabet-motif-basis-design.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/specs/2026-06-10-guardian-sprint-design.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/specs/2026-06-11-fastapi-di-edges-design.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/specs/2026-06-11-guardian-chunked-review-design.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/specs/2026-06-11-guardian-chunker-design.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/specs/2026-06-11-mcp-drift-validate-fqn-design.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/specs/2026-06-11-resolver-split-design.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/specs/2026-06-11-symbol-import-edges-design.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/specs/2026-06-12-drift-empty-domains-design.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/specs/2026-06-12-gate-semantics-design.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/specs/2026-06-12-init-ontology-design.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/specs/2026-06-12-release-please-ci-design.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/specs/2026-06-13-suggest-packages-design.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/specs/2026-06-13-tangle-anti-pattern-design.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/specs/2026-07-29-guardian-skeptic-scoring-design.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/specs/2026-07-30-cgis-fractal-design.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/specs/2026-07-30-cgis-fractal-plan.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/specs/2026-07-30-chunk-source-filter-design.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/specs/2026-07-30-chunk-source-filter-plan.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/specs/2026-07-30-genai-client-close-design.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/specs/2026-07-30-genai-client-close-plan.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/specs/2026-07-30-guardian-precision-bench-design.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/specs/2026-07-30-guardian-precision-bench-plan.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/specs/2026-07-30-guardian-timeout-retry-design.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/specs/2026-07-30-guardian-timeout-retry-plan.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/specs/BLUEPRINT.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/specs/PRD.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/specs/TDD.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/specs/plans/2026-06-09-fingerprint-drift.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/specs/plans/2026-06-10-guardian-context-skeptic-inline.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/specs/plans/2026-06-10-guardian-structured-findings-bench.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/specs/plans/2026-06-10-motif-basis-part-b.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/specs/plans/2026-06-10-unified-pattern-alphabet.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/specs/plans/2026-06-11-fastapi-di-edges.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/specs/plans/2026-06-11-guardian-chunked-review.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/specs/plans/2026-06-11-guardian-chunker.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/specs/plans/2026-06-11-mcp-drift-validate-fqn.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/specs/plans/2026-06-11-resolver-split.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/specs/plans/2026-06-12-drift-empty-domains.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/specs/plans/2026-06-12-gate-semantics.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/specs/plans/2026-06-12-init-ontology.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/specs/plans/2026-06-12-release-please-ci.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/specs/plans/2026-06-12-symbol-import-edges.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/specs/plans/2026-06-13-suggest-packages.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/docs/specs/plans/2026-07-29-guardian-skeptic-scoring.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/main.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/plugin/.claude-plugin/plugin.json +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/plugin/.mcp.json +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/plugin/README.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/plugin/skills/cgis/SKILL.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/plugin/skills/ingest/SKILL.md +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/release-please-config.json +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/scripts/gen_ideal_graph.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/scripts/generate_health.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/scripts/generate_mcp_ref.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/scripts/generate_schema_docs.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/scripts/guardian_bench.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/scripts/guardian_review.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/scripts/inject_readme_graph.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/scripts/probe_closure_gap.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/scripts/probe_tier_ladder.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/__init__.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/__main__.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/api/.gitkeep +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/api/__init__.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/api/mcp_server.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/cli.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/core/.gitkeep +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/core/models.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/extractors/.gitkeep +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/extractors/_python_ast.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/extractors/_python_classes.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/extractors/_python_functions.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/extractors/_python_imports.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/extractors/_python_types.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/extractors/base.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/extractors/python_extractor.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/extractors/typescript_extractor.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/guardian/__init__.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/guardian/bench.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/guardian/chunker.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/guardian/core.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/guardian/diff_index.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/guardian/github_poster.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/guardian/metrics.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/guardian/providers/__init__.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/guardian/providers/base.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/guardian/providers/gemini.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/guardian/providers/mistral.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/guardian/providers/ollama.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/guardian/recording.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/guardian/render.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/guardian/runner.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/guardian/skeptic.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/pipeline.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/py.typed +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/query/analysis/__init__.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/query/analysis/analyzer.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/query/analysis/anomaly.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/query/analysis/cohesion.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/query/analysis/health.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/query/analysis/suggest_service.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/query/context/__init__.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/query/context/audit.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/query/context/context_service.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/query/context/prompt.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/query/context/snippet.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/query/drift/__init__.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/query/drift/_scc.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/query/drift/drift.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/query/drift/drift_service.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/query/drift/fingerprint.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/query/drift/fractal.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/query/drift/ontology_init.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/query/drift/quotient.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/query/drift/triads.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/query/engine.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/query/fqn.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/query/render/__init__.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/query/render/graph_json.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/query/render/mermaid.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/query/render/metrics.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/resolver/.gitkeep +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/resolver/__init__.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/resolver/engine.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/resolver/indices.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/resolver/symbols.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/resolver/uplift.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/storage/.gitkeep +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/src/cgis/storage/sqlite_store.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/integration/.gitkeep +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/self_parsing/.gitkeep +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/self_parsing/__init__.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/self_parsing/conftest.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/self_parsing/test_architecture.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/self_parsing/test_drift.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/self_parsing/test_fractal.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/self_parsing/test_init_ontology_roundtrip.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/self_parsing/test_self_parse.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/self_parsing/test_self_parse_ts.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/self_parsing/test_suggest.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/.gitkeep +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/conftest.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/guardian_stubs.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/test___main__.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/test_analyzer.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/test_audit.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/test_cli.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/test_cohesion.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/test_context_service.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/test_di_acceptance.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/test_drift.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/test_drift_service.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/test_fingerprint.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/test_fqn.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/test_fractal.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/test_gen_ideal_graph.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/test_generate_mcp_ref.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/test_graph_json.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/test_guardian_bench.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/test_guardian_chunker.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/test_guardian_collector.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/test_guardian_core.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/test_guardian_diff_index.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/test_guardian_findings.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/test_guardian_metrics.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/test_guardian_poster.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/test_guardian_providers.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/test_guardian_recording.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/test_guardian_render.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/test_guardian_runner.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/test_guardian_skeptic.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/test_health_scorer.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/test_import_acceptance.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/test_mcp_server.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/test_mermaid.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/test_metrics.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/test_models.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/test_ontology_compliance.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/test_ontology_init.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/test_patterns_yaml.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/test_pipeline.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/test_prompt.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/test_python_extractor.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/test_quotient.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/test_resolver.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/test_resolver_indices.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/test_resolver_symbols.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/test_snippet.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/test_sqlite_store.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/test_suggest_service.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/test_triads.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/test_typescript_extractor.py +0 -0
- {codegraph_brain-0.9.0 → codegraph_brain-0.10.0}/tests/unit/test_uplift.py +0 -0
|
@@ -1,5 +1,18 @@
|
|
|
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
|
+
|
|
3
16
|
## [0.9.0](https://github.com/zaebee/codegraph-brain/compare/codegraph-brain-v0.8.0...codegraph-brain-v0.9.0) (2026-08-01)
|
|
4
17
|
|
|
5
18
|
|
|
@@ -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.9.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"
|
|
@@ -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]
|
|
@@ -1,5 +1,75 @@
|
|
|
1
1
|
"""Builds system and user prompts for the Guardian LLM reviewer."""
|
|
2
2
|
|
|
3
|
+
#: The finder's focus areas, one entry per axis. A mapping rather than one blob
|
|
4
|
+
#: so a review can ask about a single axis at a time (#331): axes sharing a
|
|
5
|
+
#: context window compete for what looks like a fixed attention budget, measured
|
|
6
|
+
#: twice (#247, #330). Joining every value reproduces the original prompt exactly.
|
|
7
|
+
FOCUS_AREAS: dict[str, str] = {
|
|
8
|
+
"logic": """\
|
|
9
|
+
**Logic bugs** — inputs that produce wrong output, division by zero, off-by-one errors,
|
|
10
|
+
incorrect algorithm behaviour. Think: empty collections, None values, boundary conditions.""",
|
|
11
|
+
"external_data": """\
|
|
12
|
+
**Unvalidated external data** — a value read from config/YAML/JSON, env, or a request is
|
|
13
|
+
used as a `dict`/`list`/`set`/iterable (subscripted, iterated, passed to `set()`/`dict()`,
|
|
14
|
+
`.items()`, `for x in value`) without first checking its type or presence. A YAML key the
|
|
15
|
+
author expects to be a mapping can legally be a scalar or a list; a `value or {}` idiom
|
|
16
|
+
catches `None` but lets a non-dict truthy value through to operations that then misbehave
|
|
17
|
+
silently rather than erroring. Flag each such use that lacks a type/shape guard.""",
|
|
18
|
+
"float_equality": """\
|
|
19
|
+
**Exact-equality on floats / money** — bare `==` or `!=` comparing floating-point or
|
|
20
|
+
Decimal values, *including in test assertions* (`assert x == 0.3`). Floating-point
|
|
21
|
+
rounding makes these flaky or wrong; they should use a tolerance compare. Check changed
|
|
22
|
+
test files for this too.""",
|
|
23
|
+
"test_coverage": """\
|
|
24
|
+
**Missing test coverage** — code paths in the diff that have no test. Focus on edge cases
|
|
25
|
+
that could silently return wrong results (not just "coverage for coverage's sake").""",
|
|
26
|
+
"type_safety": """\
|
|
27
|
+
**Type safety** — implicit `Any`, missing return type annotations, unsafe casts, Pydantic
|
|
28
|
+
models constructed with wrong field types.""",
|
|
29
|
+
"library_contract": """\
|
|
30
|
+
**Library boundary contracts** — for each call into a third-party library, verify that
|
|
31
|
+
the data passed in matches the library's expected convention:
|
|
32
|
+
- Units and coordinate systems (e.g. center vs top-left, row/col vs x/y,
|
|
33
|
+
naive vs timezone-aware datetimes)
|
|
34
|
+
- Ordering assumptions (e.g. does array order affect rendering or sort stability?)
|
|
35
|
+
- Ownership and mutation (e.g. does the library mutate its input?)
|
|
36
|
+
Trace the value from where it's produced to where it's consumed across the library call.
|
|
37
|
+
If the producing code and consuming code have different assumptions, that's a bug.""",
|
|
38
|
+
"ontology": """\
|
|
39
|
+
**Ontology compliance** — wrong NodeType/EdgeType assignments, FQNs not derived from file
|
|
40
|
+
paths, unresolved calls not using `raw_call:` prefix.""",
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
#: How the axes are batched into finder calls. Per-axis measured 4.3x the
|
|
45
|
+
#: single-prompt noise across four fixtures (#331) — the superlinear growth that
|
|
46
|
+
#: killed chunked review (#160) — while recall rose on every fixture that had
|
|
47
|
+
#: anything to find. Noise scaled roughly linearly with call count, so fewer,
|
|
48
|
+
#: kinship-grouped calls should keep part of the recall and most of the quiet.
|
|
49
|
+
#:
|
|
50
|
+
#: The split is by what a defect *is*: "wrong answer" shapes in one call,
|
|
51
|
+
#: "declared contract vs actual" in the other. Axes that competed in the two
|
|
52
|
+
#: measured cases (#247, #330) were dissimilar ones, so kin travel together.
|
|
53
|
+
AXIS_GROUPS: dict[str, tuple[str, ...]] = {
|
|
54
|
+
"correctness": ("logic", "external_data", "float_equality"),
|
|
55
|
+
"contracts": ("type_safety", "library_contract", "ontology", "test_coverage"),
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
#: One call per axis — the seven-way fan-out benched in #331.
|
|
59
|
+
PER_AXIS_GROUPS: dict[str, tuple[str, ...]] = {name: (name,) for name in FOCUS_AREAS}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def render_group(axes: tuple[str, ...]) -> str:
|
|
63
|
+
"""Render the focus areas for one batch of axes."""
|
|
64
|
+
return "\n\n".join(FOCUS_AREAS[axis] for axis in axes)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def render_focus_areas(axis: str | None = None) -> str:
|
|
68
|
+
"""Return every focus area joined, or a single one when `axis` names it."""
|
|
69
|
+
if axis is not None:
|
|
70
|
+
return FOCUS_AREAS[axis]
|
|
71
|
+
return "\n\n".join(FOCUS_AREAS.values())
|
|
72
|
+
|
|
3
73
|
|
|
4
74
|
class PromptBuilder:
|
|
5
75
|
"""Constructs the system and user prompts."""
|
|
@@ -82,6 +152,20 @@ observe-only — do NOT flag it.
|
|
|
82
152
|
|
|
83
153
|
"""
|
|
84
154
|
|
|
155
|
+
# `focus_axis` narrows the prompt to one axis (#331). Absent = every axis,
|
|
156
|
+
# which renders exactly the text this block replaced.
|
|
157
|
+
# `focus_group` carries the axes for this call, comma-separated. Absent =
|
|
158
|
+
# every axis, which renders exactly the text the inline block used to be.
|
|
159
|
+
group = context.get("focus_group")
|
|
160
|
+
# Whitespace-tolerant: the only producer joins without spaces, but this is
|
|
161
|
+
# a public context key and a stray space would surface as a KeyError deep
|
|
162
|
+
# inside prompt building rather than as anything a caller could act on.
|
|
163
|
+
focus_areas_section = (
|
|
164
|
+
render_group(tuple(part.strip() for part in group.split(",")))
|
|
165
|
+
if group
|
|
166
|
+
else render_focus_areas()
|
|
167
|
+
)
|
|
168
|
+
|
|
85
169
|
return f"""Review the following Pull Request diff for real defects.
|
|
86
170
|
|
|
87
171
|
{contributing_section}{ontology_section}### 3. CHANGES TO REVIEW (git diff)
|
|
@@ -126,38 +210,7 @@ observe-only — do NOT flag it.
|
|
|
126
210
|
---
|
|
127
211
|
### WHAT TO LOOK FOR (focus areas):
|
|
128
212
|
|
|
129
|
-
|
|
130
|
-
incorrect algorithm behaviour. Think: empty collections, None values, boundary conditions.
|
|
131
|
-
|
|
132
|
-
**Unvalidated external data** — a value read from config/YAML/JSON, env, or a request is
|
|
133
|
-
used as a `dict`/`list`/`set`/iterable (subscripted, iterated, passed to `set()`/`dict()`,
|
|
134
|
-
`.items()`, `for x in value`) without first checking its type or presence. A YAML key the
|
|
135
|
-
author expects to be a mapping can legally be a scalar or a list; a `value or {{}}` idiom
|
|
136
|
-
catches `None` but lets a non-dict truthy value through to operations that then misbehave
|
|
137
|
-
silently rather than erroring. Flag each such use that lacks a type/shape guard.
|
|
138
|
-
|
|
139
|
-
**Exact-equality on floats / money** — bare `==` or `!=` comparing floating-point or
|
|
140
|
-
Decimal values, *including in test assertions* (`assert x == 0.3`). Floating-point
|
|
141
|
-
rounding makes these flaky or wrong; they should use a tolerance compare. Check changed
|
|
142
|
-
test files for this too.
|
|
143
|
-
|
|
144
|
-
**Missing test coverage** — code paths in the diff that have no test. Focus on edge cases
|
|
145
|
-
that could silently return wrong results (not just "coverage for coverage's sake").
|
|
146
|
-
|
|
147
|
-
**Type safety** — implicit `Any`, missing return type annotations, unsafe casts, Pydantic
|
|
148
|
-
models constructed with wrong field types.
|
|
149
|
-
|
|
150
|
-
**Library boundary contracts** — for each call into a third-party library, verify that
|
|
151
|
-
the data passed in matches the library's expected convention:
|
|
152
|
-
- Units and coordinate systems (e.g. center vs top-left, row/col vs x/y,
|
|
153
|
-
naive vs timezone-aware datetimes)
|
|
154
|
-
- Ordering assumptions (e.g. does array order affect rendering or sort stability?)
|
|
155
|
-
- Ownership and mutation (e.g. does the library mutate its input?)
|
|
156
|
-
Trace the value from where it's produced to where it's consumed across the library call.
|
|
157
|
-
If the producing code and consuming code have different assumptions, that's a bug.
|
|
158
|
-
|
|
159
|
-
**Ontology compliance** — wrong NodeType/EdgeType assignments, FQNs not derived from file
|
|
160
|
-
paths, unresolved calls not using `raw_call:` prefix.
|
|
213
|
+
{focus_areas_section}
|
|
161
214
|
|
|
162
215
|
---
|
|
163
216
|
### WORKED EXAMPLES (how the per-function walk turns into a finding):
|