codegraph-brain 0.14.0__tar.gz → 0.15.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.14.0 → codegraph_brain-0.15.0}/.github/workflows/guardian.yml +6 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/.gitignore +1 -0
- codegraph_brain-0.15.0/.release-please-manifest.json +3 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/CHANGELOG.md +16 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/PKG-INFO +1 -1
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/pyproject.toml +1 -1
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/scripts/guardian_bench.py +4 -1
- codegraph_brain-0.15.0/scripts/guardian_replay_skeptic.py +264 -0
- codegraph_brain-0.15.0/scripts/recordings_from_corpus.py +207 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/guardian/axes.py +7 -1
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/guardian/chunked.py +9 -1
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/guardian/core.py +8 -1
- codegraph_brain-0.15.0/src/cgis/guardian/evidence.py +230 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/guardian/skeptic.py +53 -5
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_backfill_calibration_fingerprint.py +98 -24
- codegraph_brain-0.15.0/tests/unit/test_guardian_evidence.py +423 -0
- codegraph_brain-0.15.0/tests/unit/test_guardian_replay_skeptic.py +358 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_guardian_skeptic.py +55 -10
- codegraph_brain-0.15.0/tests/unit/test_recordings_from_corpus.py +245 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/uv.lock +1 -1
- codegraph_brain-0.14.0/.release-please-manifest.json +0 -3
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/.claude-plugin/marketplace.json +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/.gitattributes +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/.github/workflows/autodoc.yml +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/.github/workflows/ci.yml +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/.github/workflows/pr-title.yml +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/.github/workflows/release-please.yml +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/.pre-commit-config.yaml +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/.python-version +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/CLAUDE.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/CONTRIBUTING.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/LICENSE +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/Makefile +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/PRIVACY.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/README.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/benchmarks/guardian/CURATION.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/benchmarks/guardian/calibration.jsonl +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/benchmarks/guardian/pr-122.yaml +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/benchmarks/guardian/pr-140.yaml +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/benchmarks/guardian/pr-141.yaml +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/benchmarks/guardian/pr-142.yaml +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/benchmarks/guardian/pr-143.yaml +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/benchmarks/guardian/pr-144.yaml +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/benchmarks/guardian/pr-278.yaml +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/benchmarks/guardian/pr-313.yaml +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/benchmarks/guardian/results.jsonl +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/benchmarks/martian/README.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/benchmarks/martian/cal_dot_com.json +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/benchmarks/martian/discourse.json +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/benchmarks/martian/grafana.json +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/benchmarks/martian/keycloak.json +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/benchmarks/martian/sentry.json +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/benchmarks/martian-judged.jsonl +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/benchmarks/martian-p3-judged-run1.jsonl +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/benchmarks/martian-p3-judged-run1.jsonl.corrupted-backup +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/benchmarks/martian-p3-run1.jsonl +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/benchmarks/martian-p3-run2.jsonl +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/benchmarks/martian-p3-run3.jsonl +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/benchmarks/martian-plan.json +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/benchmarks/martian-repeat-judged.jsonl +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/benchmarks/martian-repeat-reviews.jsonl +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/benchmarks/martian-reviews.jsonl +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/data/.gitkeep +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/AUDIT.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/CASE_STUDY.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/GUARDIAN_LOCAL_BENCH.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/GUARDIAN_REMOTE_OLLAMA.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/architecture/HOW_IT_WORKS.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/architecture/ONTOLOGY.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/architecture/PATTERNS_AND_TRIADS.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/architecture/SELF_PORTRAIT.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/architecture/diagrams/pipeline_flow.mermaid +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/architecture/health_badge.json +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/assets/.gitignore +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/assets/cgis-app-avatar.png +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/assets/cgis-app-avatar.svg +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/assets/generate_avatar.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/examples/.gitkeep +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/how-to/AGENT_ONBOARDING.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/how-to/CLI_USAGE.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/how-to/MCP_REFERENCE.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/lab-notes/2026-06-11-chunked-review-negative-result.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/ontology/.gitkeep +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/ontology/core.yaml +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/ontology/domains.yaml +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/ontology/patterns.yaml +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/ontology/tolerances.lock +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/specs/2026-06-09-domain-pattern-fingerprint-design.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/specs/2026-06-09-pattern-alphabet-motif-basis-design.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/specs/2026-06-10-guardian-sprint-design.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/specs/2026-06-11-fastapi-di-edges-design.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/specs/2026-06-11-guardian-chunked-review-design.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/specs/2026-06-11-guardian-chunker-design.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/specs/2026-06-11-mcp-drift-validate-fqn-design.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/specs/2026-06-11-resolver-split-design.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/specs/2026-06-11-symbol-import-edges-design.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/specs/2026-06-12-drift-empty-domains-design.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/specs/2026-06-12-gate-semantics-design.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/specs/2026-06-12-init-ontology-design.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/specs/2026-06-12-release-please-ci-design.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/specs/2026-06-13-suggest-packages-design.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/specs/2026-06-13-tangle-anti-pattern-design.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/specs/2026-07-29-guardian-skeptic-scoring-design.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/specs/2026-07-30-cgis-fractal-design.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/specs/2026-07-30-cgis-fractal-plan.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/specs/2026-07-30-chunk-source-filter-design.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/specs/2026-07-30-chunk-source-filter-plan.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/specs/2026-07-30-genai-client-close-design.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/specs/2026-07-30-genai-client-close-plan.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/specs/2026-07-30-guardian-precision-bench-design.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/specs/2026-07-30-guardian-precision-bench-plan.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/specs/2026-07-30-guardian-timeout-retry-design.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/specs/2026-07-30-guardian-timeout-retry-plan.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/specs/2026-07-31-finder-bug-class-taxonomy.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/specs/2026-08-01-aura-autoevolution-poc.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/specs/2026-08-11-guardian-code-review-bench.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/specs/2026-08-14-review-fingerprint-design.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/specs/BLUEPRINT.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/specs/PRD.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/specs/TDD.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/specs/plans/2026-06-09-fingerprint-drift.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/specs/plans/2026-06-10-guardian-context-skeptic-inline.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/specs/plans/2026-06-10-guardian-structured-findings-bench.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/specs/plans/2026-06-10-motif-basis-part-b.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/specs/plans/2026-06-10-unified-pattern-alphabet.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/specs/plans/2026-06-11-fastapi-di-edges.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/specs/plans/2026-06-11-guardian-chunked-review.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/specs/plans/2026-06-11-guardian-chunker.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/specs/plans/2026-06-11-mcp-drift-validate-fqn.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/specs/plans/2026-06-11-resolver-split.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/specs/plans/2026-06-12-drift-empty-domains.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/specs/plans/2026-06-12-gate-semantics.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/specs/plans/2026-06-12-init-ontology.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/specs/plans/2026-06-12-release-please-ci.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/specs/plans/2026-06-12-symbol-import-edges.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/specs/plans/2026-06-13-suggest-packages.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/specs/plans/2026-07-29-guardian-skeptic-scoring.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/docs/specs/plans/2026-08-14-review-fingerprint.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/main.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/plugin/.claude-plugin/plugin.json +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/plugin/.mcp.json +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/plugin/README.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/plugin/skills/cgis/SKILL.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/plugin/skills/ingest/SKILL.md +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/release-please-config.json +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/scripts/backfill_calibration_fingerprint.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/scripts/backfill_review_fingerprint.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/scripts/check_pytest_raises.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/scripts/colab_bench.sh +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/scripts/gen_ideal_graph.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/scripts/generate_health.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/scripts/generate_mcp_ref.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/scripts/generate_schema_docs.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/scripts/guardian_calibrate.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/scripts/guardian_martian.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/scripts/guardian_review.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/scripts/inject_readme_graph.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/scripts/ollama_visitor.sh +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/scripts/probe_closure_gap.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/scripts/probe_tier_ladder.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/__init__.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/__main__.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/api/.gitkeep +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/api/__init__.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/api/mcp_server.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/cli.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/core/.gitkeep +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/core/models.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/extractors/.gitkeep +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/extractors/_python_ast.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/extractors/_python_classes.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/extractors/_python_functions.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/extractors/_python_imports.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/extractors/_python_types.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/extractors/base.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/extractors/python_extractor.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/extractors/registry.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/extractors/typescript_extractor.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/guardian/__init__.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/guardian/bench.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/guardian/calibrate.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/guardian/chunker.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/guardian/collector.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/guardian/diff_index.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/guardian/findings.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/guardian/github_poster.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/guardian/martian.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/guardian/metrics.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/guardian/prompts.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/guardian/providers/__init__.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/guardian/providers/base.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/guardian/providers/gemini.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/guardian/providers/mistral.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/guardian/providers/ollama.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/guardian/recording.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/guardian/render.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/guardian/review_fingerprint.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/guardian/runner.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/pipeline.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/py.typed +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/query/analysis/__init__.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/query/analysis/analyzer.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/query/analysis/anomaly.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/query/analysis/cohesion.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/query/analysis/health.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/query/analysis/suggest_service.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/query/context/__init__.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/query/context/audit.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/query/context/context_service.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/query/context/prompt.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/query/context/snippet.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/query/drift/__init__.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/query/drift/_scc.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/query/drift/drift.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/query/drift/drift_service.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/query/drift/fingerprint.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/query/drift/fractal.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/query/drift/ontology_init.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/query/drift/quotient.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/query/drift/triads.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/query/engine.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/query/fqn.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/query/render/__init__.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/query/render/graph_json.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/query/render/mermaid.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/query/render/metrics.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/resolver/.gitkeep +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/resolver/__init__.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/resolver/engine.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/resolver/indices.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/resolver/symbols.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/resolver/uplift.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/storage/.gitkeep +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/src/cgis/storage/sqlite_store.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/integration/.gitkeep +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/self_parsing/.gitkeep +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/self_parsing/__init__.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/self_parsing/conftest.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/self_parsing/test_architecture.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/self_parsing/test_drift.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/self_parsing/test_fractal.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/self_parsing/test_init_ontology_roundtrip.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/self_parsing/test_self_parse.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/self_parsing/test_self_parse_ts.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/self_parsing/test_suggest.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/.gitkeep +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/conftest.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/guardian_stubs.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/review_path_inventory.txt +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test___main__.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_analyzer.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_audit.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_backfill_review_fingerprint.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_check_pytest_raises.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_cli.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_cohesion.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_context_service.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_di_acceptance.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_drift.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_drift_service.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_extractor_registry.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_fingerprint.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_fqn.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_fractal.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_gen_ideal_graph.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_generate_mcp_ref.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_graph_json.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_guardian_axes.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_guardian_bench.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_guardian_bench_script.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_guardian_calibrate.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_guardian_calibrate_script.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_guardian_chunked.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_guardian_chunker.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_guardian_collector.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_guardian_core.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_guardian_diff_index.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_guardian_findings.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_guardian_martian.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_guardian_martian_script.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_guardian_martian_union.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_guardian_metrics.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_guardian_mistral_sampling.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_guardian_ollama_sampling.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_guardian_ollama_truncation.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_guardian_poster.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_guardian_providers.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_guardian_providers_name.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_guardian_recording.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_guardian_render.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_guardian_review_script.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_guardian_runner.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_guardian_salvage.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_health_scorer.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_import_acceptance.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_mcp_server.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_mermaid.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_metrics.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_models.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_ontology_compliance.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_ontology_init.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_patterns_yaml.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_pipeline.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_prompt.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_python_extractor.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_quotient.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_resolver.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_resolver_indices.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_resolver_symbols.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_review_fingerprint_closure.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_review_fingerprint_contract.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_review_fingerprint_digest.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_review_fingerprint_record.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_snippet.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_sqlite_store.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_suggest_service.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_temperature_source.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_triads.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_typescript_extractor.py +0 -0
- {codegraph_brain-0.14.0 → codegraph_brain-0.15.0}/tests/unit/test_uplift.py +0 -0
|
@@ -90,6 +90,12 @@ jobs:
|
|
|
90
90
|
GUARDIAN_FEATURES: ${{ vars.GUARDIAN_FEATURES }}
|
|
91
91
|
GUARDIAN_SKEPTIC: ${{ vars.GUARDIAN_SKEPTIC }}
|
|
92
92
|
GUARDIAN_SKEPTIC_MODEL: ${{ vars.GUARDIAN_SKEPTIC_MODEL }}
|
|
93
|
+
# Static-evidence pass for the skeptic (#401). Wired but not set:
|
|
94
|
+
# the repository variable is deliberately absent, so enabling it is a
|
|
95
|
+
# decision made in repo settings rather than a code change nobody
|
|
96
|
+
# reviews. An unset variable expands to "", which the flag reads as
|
|
97
|
+
# off — only the literal "1" enables it.
|
|
98
|
+
GUARDIAN_EVIDENCE: ${{ vars.GUARDIAN_EVIDENCE }}
|
|
93
99
|
run: |
|
|
94
100
|
uv run --frozen python scripts/guardian_review.py \
|
|
95
101
|
--output guardian_report.md \
|
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.15.0](https://github.com/zaebee/codegraph-brain/compare/codegraph-brain-v0.14.1...codegraph-brain-v0.15.0) (2026-08-17)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **bench:** rebuild frozen finder passes from the corpus, free ([#246](https://github.com/zaebee/codegraph-brain/issues/246)) ([#399](https://github.com/zaebee/codegraph-brain/issues/399)) ([b73f6e0](https://github.com/zaebee/codegraph-brain/commit/b73f6e0d40c5bdc4dadca4acd468d7835670be28))
|
|
9
|
+
* **guardian:** a replay arm that pairs each finding against its own baseline ([#401](https://github.com/zaebee/codegraph-brain/issues/401)) ([#404](https://github.com/zaebee/codegraph-brain/issues/404)) ([b2ab59a](https://github.com/zaebee/codegraph-brain/commit/b2ab59a8d7b5f62a307874b8a0eb9573273fa0ea))
|
|
10
|
+
* **guardian:** let the skeptic see what the checkers say ([#401](https://github.com/zaebee/codegraph-brain/issues/401)) ([#402](https://github.com/zaebee/codegraph-brain/issues/402)) ([af82147](https://github.com/zaebee/codegraph-brain/commit/af821473080bd3a717679e36c90164a43ca07f7b))
|
|
11
|
+
|
|
12
|
+
## [0.14.1](https://github.com/zaebee/codegraph-brain/compare/codegraph-brain-v0.14.0...codegraph-brain-v0.14.1) (2026-08-16)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* **bench:** cutting a release turned the new reachability tests red ([#397](https://github.com/zaebee/codegraph-brain/issues/397)) ([efbca37](https://github.com/zaebee/codegraph-brain/commit/efbca37c13e452fdf7266619b36d86b0e11bb795))
|
|
18
|
+
|
|
3
19
|
## [0.14.0](https://github.com/zaebee/codegraph-brain/compare/codegraph-brain-v0.13.0...codegraph-brain-v0.14.0) (2026-08-16)
|
|
4
20
|
|
|
5
21
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: codegraph-brain
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.15.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
|
|
@@ -7,7 +7,7 @@ packages = ["src/cgis"]
|
|
|
7
7
|
|
|
8
8
|
[project]
|
|
9
9
|
name = "codegraph-brain"
|
|
10
|
-
version = "0.
|
|
10
|
+
version = "0.15.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"
|
|
@@ -166,7 +166,10 @@ async def _judge_recording(
|
|
|
166
166
|
result = recording.result
|
|
167
167
|
if skeptic is None or not result.findings:
|
|
168
168
|
return result
|
|
169
|
-
|
|
169
|
+
# The replay path holds a recording, not a checkout: there is no project
|
|
170
|
+
# root, so no checkers to run. Stated as None rather than defaulted — which
|
|
171
|
+
# is the point of the argument being required (#401).
|
|
172
|
+
judgements = await judge_all(skeptic[0], result.findings, recording.diff, evidence=None)
|
|
170
173
|
judged = sum(1 for j in judgements if j is not None)
|
|
171
174
|
return result.model_copy(
|
|
172
175
|
update={
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
"""Re-judge a frozen finder pass with evidence, paired against its own baseline (#401).
|
|
2
|
+
|
|
3
|
+
#402 gave the skeptic the repository's checker output. Whether that changes
|
|
4
|
+
verdicts is unmeasured, and the obvious comparison — run the guardian twice —
|
|
5
|
+
cannot answer it: the finder is sampled, so two runs disagree about *what was
|
|
6
|
+
claimed* before anyone judges anything. Every number would mix the effect with
|
|
7
|
+
the draw.
|
|
8
|
+
|
|
9
|
+
A recorded finder pass removes that. `guardian.yml` writes `guardian_finder.json`
|
|
10
|
+
on every review, and those findings carry the verdicts of the run that produced
|
|
11
|
+
them. So the baseline already exists: the same findings, the same diff, the same
|
|
12
|
+
skeptic model, judged without evidence. Re-judging them *with* evidence is one
|
|
13
|
+
arm, paired at the level of the individual finding, and it costs one skeptic call
|
|
14
|
+
per finding and no finder call at all.
|
|
15
|
+
|
|
16
|
+
Evidence is collected in a worktree at the reviewed commit, not in the current
|
|
17
|
+
checkout. The checkers must report on the code the finder actually saw; today's
|
|
18
|
+
tree is a different subject, and a verdict flip caused by that would be
|
|
19
|
+
attributed to the evidence.
|
|
20
|
+
|
|
21
|
+
Every step refuses rather than guesses. A recording without verdicts has no
|
|
22
|
+
baseline. Evidence that cannot be collected makes this a plain re-run of the
|
|
23
|
+
control arm, which is not the experiment and must not be reported as one.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
import argparse
|
|
27
|
+
import asyncio
|
|
28
|
+
import contextlib
|
|
29
|
+
import json
|
|
30
|
+
import os
|
|
31
|
+
import re
|
|
32
|
+
import subprocess
|
|
33
|
+
import sys
|
|
34
|
+
import tempfile
|
|
35
|
+
from collections import Counter
|
|
36
|
+
from collections.abc import Iterator, Mapping, Sequence
|
|
37
|
+
from pathlib import Path
|
|
38
|
+
|
|
39
|
+
from cgis.guardian.diff_index import split_diff_by_file
|
|
40
|
+
from cgis.guardian.evidence import Evidence, collect_evidence
|
|
41
|
+
from cgis.guardian.providers.base import BaseProvider
|
|
42
|
+
from cgis.guardian.recording import load_finder_recording
|
|
43
|
+
from cgis.guardian.runner import build_provider, build_skeptic_provider
|
|
44
|
+
from cgis.guardian.skeptic import FindingJudgement, judge_all
|
|
45
|
+
|
|
46
|
+
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
47
|
+
|
|
48
|
+
#: Verdicts a recorded finding may carry. A finding outside this set was never
|
|
49
|
+
#: ruled on, so it cannot be a baseline for anything.
|
|
50
|
+
_VERDICTS = frozenset({"confirmed", "refuted", "uncertain"})
|
|
51
|
+
|
|
52
|
+
#: Words that mark a rationale as resting on the checker output rather than
|
|
53
|
+
#: on the diff. Deliberately broad — a false positive here only inflates the
|
|
54
|
+
#: attributable count, which is the number under scrutiny, so it is reported
|
|
55
|
+
#: beside the total rather than as a conclusion.
|
|
56
|
+
_CHECKER_MENTION = re.compile(r"mypy|ruff|checker|type check|linter|All checks passed", re.I)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class NoBaselineError(RuntimeError):
|
|
60
|
+
"""Raised when a recording carries no verdicts to compare against.
|
|
61
|
+
|
|
62
|
+
A replay without a baseline produces a column of new verdicts and nothing to
|
|
63
|
+
subtract, which reads like a result and is not one.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class NoSkepticError(RuntimeError):
|
|
68
|
+
"""Raised when no skeptic is configured, so there is nothing to replay.
|
|
69
|
+
|
|
70
|
+
Its own type rather than `NoBaselineError`, whose docstring says "a recording
|
|
71
|
+
carries no verdicts". Reusing it here would make that sentence false and put
|
|
72
|
+
a configuration problem and a data problem behind one name — and the two have
|
|
73
|
+
nothing in common but the moment they are noticed.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class NoEvidenceError(RuntimeError):
|
|
78
|
+
"""Raised when evidence cannot be collected at the reviewed commit.
|
|
79
|
+
|
|
80
|
+
Refused rather than degraded: judging without evidence is the control arm,
|
|
81
|
+
already recorded. Running it again and labelling it the treatment would make
|
|
82
|
+
the experiment report that evidence changes nothing, which is exactly the
|
|
83
|
+
conclusion it must not be able to reach by accident.
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def baseline_verdicts(path: Path) -> list[str]:
|
|
88
|
+
"""The verdicts of the run that produced this recording, in finding order.
|
|
89
|
+
|
|
90
|
+
Read from the raw JSON rather than through `load_finder_recording`, which
|
|
91
|
+
strips verdicts on purpose — the replay must start from unjudged findings or
|
|
92
|
+
the skeptic is shown its own previous answers.
|
|
93
|
+
"""
|
|
94
|
+
raw = json.loads(path.read_text(encoding="utf-8"))
|
|
95
|
+
# `or {}` rather than a `.get` default: a key present and explicitly null
|
|
96
|
+
# returns the null, not the default, so `{"result": null}` would reach
|
|
97
|
+
# `.get("findings")` as None and raise AttributeError. This module promises
|
|
98
|
+
# `NoBaselineError` for a recording it cannot use; crashing with a type error
|
|
99
|
+
# instead makes that promise false for one shape of bad input.
|
|
100
|
+
findings = (raw.get("result") or {}).get("findings") or []
|
|
101
|
+
verdicts = [f.get("verdict") for f in findings]
|
|
102
|
+
unruled = [i for i, v in enumerate(verdicts) if v not in _VERDICTS]
|
|
103
|
+
if not verdicts or unruled:
|
|
104
|
+
_msg = (
|
|
105
|
+
f"{path} carries no usable baseline: {len(unruled)} of {len(verdicts)} findings "
|
|
106
|
+
f"have no verdict. A replay needs the control arm it is being compared against."
|
|
107
|
+
)
|
|
108
|
+
raise NoBaselineError(_msg)
|
|
109
|
+
return [str(v) for v in verdicts]
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def changed_files(diff: str) -> tuple[str, ...]:
|
|
113
|
+
"""The files the finder was shown, taken from the diff it was shown.
|
|
114
|
+
|
|
115
|
+
From the recording rather than from git: the point is to check the same
|
|
116
|
+
files the claims are about, and a path list re-derived today would drift
|
|
117
|
+
with the branch.
|
|
118
|
+
"""
|
|
119
|
+
return tuple(split_diff_by_file(diff))
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@contextlib.contextmanager
|
|
123
|
+
def worktree_at(ref: str, repo_root: Path) -> Iterator[Path]:
|
|
124
|
+
"""A detached worktree at `ref`, removed on the way out."""
|
|
125
|
+
with tempfile.TemporaryDirectory(prefix="replay-") as tmp:
|
|
126
|
+
path = Path(tmp) / "wt"
|
|
127
|
+
try:
|
|
128
|
+
subprocess.run(
|
|
129
|
+
["git", "worktree", "add", "--detach", str(path), ref],
|
|
130
|
+
cwd=repo_root,
|
|
131
|
+
capture_output=True,
|
|
132
|
+
text=True,
|
|
133
|
+
check=True,
|
|
134
|
+
)
|
|
135
|
+
except subprocess.CalledProcessError as exc:
|
|
136
|
+
# `CalledProcessError` stringifies to "Command '[...]' returned
|
|
137
|
+
# non-zero exit status 1" and drops the captured stderr entirely —
|
|
138
|
+
# the same reasoning `fetch_changed_files` records in
|
|
139
|
+
# guardian_martian.py:138. Here the lost message is the one that
|
|
140
|
+
# says whether the ref is unknown, the tree is locked, or the path
|
|
141
|
+
# exists, and those have three different fixes.
|
|
142
|
+
_msg = f"Cannot create a worktree at {ref!r}: {exc.stderr.strip() or '(no stderr)'}"
|
|
143
|
+
raise RuntimeError(_msg) from exc
|
|
144
|
+
try:
|
|
145
|
+
yield path
|
|
146
|
+
finally:
|
|
147
|
+
subprocess.run(
|
|
148
|
+
["git", "worktree", "remove", "--force", str(path)],
|
|
149
|
+
cwd=repo_root,
|
|
150
|
+
capture_output=True,
|
|
151
|
+
check=False,
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def flips(baseline: Sequence[str], replayed: Sequence[FindingJudgement | None]) -> Counter[str]:
|
|
156
|
+
"""Counts of `baseline -> replayed`, positionally paired.
|
|
157
|
+
|
|
158
|
+
Positional because `judge_all` returns one entry per finding in order and
|
|
159
|
+
`apply_judgements` relies on that same alignment. A failed call is its own
|
|
160
|
+
outcome rather than folded into a verdict: absence of a judgement is not a
|
|
161
|
+
judgement, and counting it as one would let an API outage look like the
|
|
162
|
+
skeptic changing its mind.
|
|
163
|
+
"""
|
|
164
|
+
if len(baseline) != len(replayed):
|
|
165
|
+
_msg = f"{len(baseline)} baseline verdicts against {len(replayed)} replayed; not paired."
|
|
166
|
+
raise ValueError(_msg)
|
|
167
|
+
return Counter(
|
|
168
|
+
f"{was} -> {now.verdict if now is not None else 'call failed'}"
|
|
169
|
+
for was, now in zip(baseline, replayed, strict=True)
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def cites_a_checker(judgement: FindingJudgement | None) -> bool:
|
|
174
|
+
"""Whether the rationale points at the evidence rather than at the diff.
|
|
175
|
+
|
|
176
|
+
This is the attributable half. A flip whose reason is "the quoted code does
|
|
177
|
+
not do what the claim says" is the skeptic working as it always has; a flip
|
|
178
|
+
whose reason is "ruff reported All checks passed" is the evidence doing
|
|
179
|
+
something, and only the second supports any claim about this feature.
|
|
180
|
+
"""
|
|
181
|
+
if judgement is None:
|
|
182
|
+
return False
|
|
183
|
+
return bool(_CHECKER_MENTION.search(judgement.rationale or ""))
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
async def replay(
|
|
187
|
+
recording_path: Path, ref: str, repo_root: Path, env: Mapping[str, str]
|
|
188
|
+
) -> tuple[Counter[str], int]:
|
|
189
|
+
"""Judge the recorded findings again, with evidence; return the flips and citations."""
|
|
190
|
+
baseline = baseline_verdicts(recording_path)
|
|
191
|
+
recording = load_finder_recording(recording_path)
|
|
192
|
+
findings = recording.result.findings
|
|
193
|
+
if len(findings) != len(baseline):
|
|
194
|
+
_msg = f"{len(findings)} findings loaded against {len(baseline)} baseline verdicts."
|
|
195
|
+
raise NoBaselineError(_msg)
|
|
196
|
+
|
|
197
|
+
# The worktree and the checkers are blocking, and `replay` is a coroutine.
|
|
198
|
+
# Nothing else runs on this script's loop today, so the harm is latent rather
|
|
199
|
+
# than active — but the function is importable and its signature promises a
|
|
200
|
+
# coroutine, and two minutes of a frozen loop is not something a caller
|
|
201
|
+
# should have to read the body to discover.
|
|
202
|
+
evidence = await asyncio.to_thread(_evidence_at, ref, repo_root, recording.diff)
|
|
203
|
+
if evidence is None:
|
|
204
|
+
_msg = (
|
|
205
|
+
f"No evidence could be collected at {ref}. Judging without it repeats the "
|
|
206
|
+
f"control arm, which is already recorded; reporting that as the treatment "
|
|
207
|
+
f"would make this experiment conclude that evidence changes nothing."
|
|
208
|
+
)
|
|
209
|
+
raise NoEvidenceError(_msg)
|
|
210
|
+
judgements = await judge_all(skeptic_from(env), findings, recording.diff, evidence=evidence)
|
|
211
|
+
return flips(baseline, judgements), sum(1 for j in judgements if cites_a_checker(j))
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _evidence_at(ref: str, repo_root: Path, diff: str) -> Evidence | None:
|
|
215
|
+
"""Checker output for the recorded files, taken at the reviewed commit.
|
|
216
|
+
|
|
217
|
+
The worktree lives only as long as the collection: the checkers read the
|
|
218
|
+
tree, and nothing afterwards needs it.
|
|
219
|
+
"""
|
|
220
|
+
with worktree_at(ref, repo_root) as tree:
|
|
221
|
+
return collect_evidence(tree, changed_files(diff))
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def skeptic_from(env: Mapping[str, str]) -> BaseProvider:
|
|
225
|
+
"""The configured skeptic, or a refusal naming what is missing.
|
|
226
|
+
|
|
227
|
+
The finder is built only to learn its provider name, which decides the
|
|
228
|
+
skeptic's default opposite — no finder call is ever made here, and that is
|
|
229
|
+
the whole economy of this arm.
|
|
230
|
+
"""
|
|
231
|
+
finder, _ = build_provider(env)
|
|
232
|
+
skeptic = build_skeptic_provider(env, primary=finder.name)
|
|
233
|
+
if skeptic is None:
|
|
234
|
+
_msg = (
|
|
235
|
+
"No skeptic is configured, so there is nothing to replay. Set GUARDIAN_SKEPTIC "
|
|
236
|
+
"and the matching API key."
|
|
237
|
+
)
|
|
238
|
+
raise NoSkepticError(_msg)
|
|
239
|
+
return skeptic[0]
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def main() -> int:
|
|
243
|
+
"""Replay one recording and print the paired comparison."""
|
|
244
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
245
|
+
parser.add_argument("--recording", type=Path, required=True)
|
|
246
|
+
parser.add_argument("--at", required=True, help="ref or sha the recording was taken at")
|
|
247
|
+
parser.add_argument("--repo-root", type=Path, default=REPO_ROOT)
|
|
248
|
+
args = parser.parse_args()
|
|
249
|
+
|
|
250
|
+
matrix, cited = asyncio.run(replay(args.recording, args.at, args.repo_root, os.environ))
|
|
251
|
+
total = sum(matrix.values())
|
|
252
|
+
print(f"{total} findings re-judged with evidence, paired against their recorded verdicts\n")
|
|
253
|
+
for transition, count in sorted(matrix.items()):
|
|
254
|
+
moved = (
|
|
255
|
+
"" if transition.split(" -> ")[0] == transition.split(" -> ")[1] else " <- changed"
|
|
256
|
+
)
|
|
257
|
+
print(f" {transition:34} {count:3}{moved}")
|
|
258
|
+
print(f"\n rationales citing a checker: {cited}/{total}")
|
|
259
|
+
print(" (only these are attributable to the evidence; the rest are the skeptic reading code)")
|
|
260
|
+
return 0
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
if __name__ == "__main__":
|
|
264
|
+
sys.exit(main())
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"""Rebuild frozen finder passes from the recorded corpus, without paying a model (#246).
|
|
2
|
+
|
|
3
|
+
`--replay-finder` isolates the skeptic: the finder is skipped and its recorded
|
|
4
|
+
findings are judged instead, so a skeptic variant costs skeptic calls only. That
|
|
5
|
+
is what makes #246 answerable on a budget — but it needs recordings, and none
|
|
6
|
+
were ever written to disk.
|
|
7
|
+
|
|
8
|
+
They do not have to be re-run. `benchmarks/guardian/results.jsonl` already holds
|
|
9
|
+
the findings of every scored review, and the diff they were found in is
|
|
10
|
+
regenerable from git, because the benchmark reviews this repository's own PRs
|
|
11
|
+
and every fixture's `base` and `head` resolve locally. So a recording costs one
|
|
12
|
+
`git diff` and nothing else.
|
|
13
|
+
|
|
14
|
+
**Only rows that had no skeptic are eligible**, and the reason is not
|
|
15
|
+
tidiness. `load_finder_recording` strips verdicts on read, so a judged row would
|
|
16
|
+
not smuggle its verdicts into the next arm — but `apply_judgements` rewrites
|
|
17
|
+
confidence to `round(confidence * 0.9)` on an `uncertain` verdict and the load
|
|
18
|
+
path does not restore it (#279). A recording taken from a judged run therefore
|
|
19
|
+
carries confidences that are not the finder's own, on exactly the subset a
|
|
20
|
+
skeptic experiment is about.
|
|
21
|
+
|
|
22
|
+
Verified before use rather than after: `tests/unit/test_recordings_from_corpus.py`
|
|
23
|
+
re-scores every rebuilt recording and reproduces the number the corpus recorded —
|
|
24
|
+
72 of 72, under the scoring policy in force when each row was written. The seven
|
|
25
|
+
that differ under today's rule are exactly the seven carrying `ambiguous_hits`,
|
|
26
|
+
which is the #345 policy change that `CURATION.md` says results.jsonl is
|
|
27
|
+
deliberately not rescored for.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
import argparse
|
|
31
|
+
import json
|
|
32
|
+
import subprocess
|
|
33
|
+
import sys
|
|
34
|
+
from pathlib import Path
|
|
35
|
+
from typing import Any
|
|
36
|
+
|
|
37
|
+
import yaml
|
|
38
|
+
|
|
39
|
+
from cgis.guardian.findings import Finding, ReviewResult
|
|
40
|
+
from cgis.guardian.recording import save_finder_recording
|
|
41
|
+
|
|
42
|
+
#: Fields `annotate_matches` adds to a stored finding. Stripped on the way back
|
|
43
|
+
#: to a `Finding`, which does not declare them.
|
|
44
|
+
_ANNOTATIONS = ("matched", "verdict", "skeptic_note")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class NotAFrozenPassError(RuntimeError):
|
|
48
|
+
"""Raised when a row cannot stand in for an unjudged finder pass.
|
|
49
|
+
|
|
50
|
+
Refused rather than cleaned up: a row whose findings were already judged has
|
|
51
|
+
confidences the skeptic rewrote (#279), and silently accepting it would make
|
|
52
|
+
two arms of an experiment differ by something nobody chose.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class MissingFixtureError(RuntimeError):
|
|
57
|
+
"""Raised when a row names a PR with no ground-truth fixture, or unresolvable shas.
|
|
58
|
+
|
|
59
|
+
The diff is the whole reason a replay needs no worktree. Without one there
|
|
60
|
+
is nothing to record, and inventing an empty diff would produce a recording
|
|
61
|
+
that replays as "the finder saw nothing".
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def is_frozen_pass(row: dict[str, Any]) -> bool:
|
|
66
|
+
"""True for a scored row whose findings are the finder's own, unjudged."""
|
|
67
|
+
if "matched" not in row or "precision" not in row:
|
|
68
|
+
return False
|
|
69
|
+
if row.get("skeptic_model"):
|
|
70
|
+
return False
|
|
71
|
+
return all(f.get("verdict") is None for f in (row.get("findings") or []))
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def row_key(row: dict[str, Any]) -> str:
|
|
75
|
+
"""`pr@timestamp` — the same identity `guardian_calibrate` writes.
|
|
76
|
+
|
|
77
|
+
Used as the recording's filename so a replayed number can be traced to the
|
|
78
|
+
exact run it came from. Without that, a green replay could mean "reproduced
|
|
79
|
+
*a* run", which is not the claim being made.
|
|
80
|
+
"""
|
|
81
|
+
return f"{row['pr']}@{row['timestamp']}"
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def diff_for(pr: int, bench_dir: Path, repo_root: Path) -> str:
|
|
85
|
+
"""The diff the finder saw, regenerated from the fixture's base and head.
|
|
86
|
+
|
|
87
|
+
`base...head`, three dots, mirroring `ContextCollector._diff_range` — the
|
|
88
|
+
two-dot form would include everything that landed on the base branch after
|
|
89
|
+
the PR was cut, which is not what any recorded review was shown.
|
|
90
|
+
"""
|
|
91
|
+
fixture = bench_dir / f"pr-{pr}.yaml"
|
|
92
|
+
if not fixture.is_file():
|
|
93
|
+
_msg = f"No ground-truth fixture for pr-{pr} at {fixture}."
|
|
94
|
+
raise MissingFixtureError(_msg)
|
|
95
|
+
spec = yaml.safe_load(fixture.read_text(encoding="utf-8"))
|
|
96
|
+
if not isinstance(spec, dict):
|
|
97
|
+
_msg = (
|
|
98
|
+
f"pr-{pr}: {fixture} is not a mapping ({type(spec).__name__}); "
|
|
99
|
+
f"it names no base or head."
|
|
100
|
+
)
|
|
101
|
+
raise MissingFixtureError(_msg)
|
|
102
|
+
base, head = spec.get("base"), spec.get("head")
|
|
103
|
+
for name, sha in (("base", base), ("head", head)):
|
|
104
|
+
if (
|
|
105
|
+
not sha
|
|
106
|
+
or subprocess.run(
|
|
107
|
+
["git", "rev-parse", "--verify", "--quiet", f"{sha}^{{commit}}"],
|
|
108
|
+
capture_output=True,
|
|
109
|
+
cwd=repo_root,
|
|
110
|
+
check=False,
|
|
111
|
+
).returncode
|
|
112
|
+
):
|
|
113
|
+
_msg = (
|
|
114
|
+
f"pr-{pr}: {name} {sha!r} does not resolve in {repo_root}. Fixture shas are a "
|
|
115
|
+
f"second population beside the corpora's `guardian_sha`, and PR heads are never "
|
|
116
|
+
f"ancestors of the trunk because everything here is squash-merged — so they "
|
|
117
|
+
f"survive only if pinned. Publish it: "
|
|
118
|
+
f"git push origin {sha}:refs/tags/bench/fixture/pr-{pr}-{name}"
|
|
119
|
+
)
|
|
120
|
+
raise MissingFixtureError(_msg)
|
|
121
|
+
# `check=True` would surface a git failure as CalledProcessError, which is
|
|
122
|
+
# not what this function promises: every other way it cannot produce a diff
|
|
123
|
+
# raises MissingFixtureError, and a caller catching that would miss this one.
|
|
124
|
+
# Both shas are verified above, so a failure here is a broken repository
|
|
125
|
+
# rather than bad input — but "unlikely" is not the same as "cannot", and a
|
|
126
|
+
# refusal contract with one escape hatch is not a contract.
|
|
127
|
+
result = subprocess.run(
|
|
128
|
+
["git", "diff", f"{base}...{head}"],
|
|
129
|
+
capture_output=True,
|
|
130
|
+
text=True,
|
|
131
|
+
cwd=repo_root,
|
|
132
|
+
check=False,
|
|
133
|
+
)
|
|
134
|
+
if result.returncode != 0:
|
|
135
|
+
_msg = (
|
|
136
|
+
f"pr-{pr}: git diff {base}...{head} exited {result.returncode}: "
|
|
137
|
+
f"{result.stderr.strip() or '(no stderr)'}"
|
|
138
|
+
)
|
|
139
|
+
raise MissingFixtureError(_msg)
|
|
140
|
+
if not result.stdout.strip():
|
|
141
|
+
_msg = f"pr-{pr}: {base}...{head} is an empty diff; a review of nothing is not a pass."
|
|
142
|
+
raise MissingFixtureError(_msg)
|
|
143
|
+
return result.stdout
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def recording_for(row: dict[str, Any], diff: str) -> tuple[ReviewResult, str]:
|
|
147
|
+
"""A `ReviewResult` + diff pair equivalent to what the finder produced.
|
|
148
|
+
|
|
149
|
+
`summary` is empty because the corpus never stored one. Nothing downstream
|
|
150
|
+
reads it — the skeptic judges findings against diff hunks and the scorer
|
|
151
|
+
counts matches — so an empty string is honest about what is known rather
|
|
152
|
+
than a reconstruction of prose nobody kept.
|
|
153
|
+
"""
|
|
154
|
+
if not is_frozen_pass(row):
|
|
155
|
+
_msg = (
|
|
156
|
+
f"{row_key(row)} is not an unjudged finder pass (skeptic={row.get('skeptic_model')!r})."
|
|
157
|
+
)
|
|
158
|
+
raise NotAFrozenPassError(_msg)
|
|
159
|
+
findings = [
|
|
160
|
+
Finding.model_validate({k: v for k, v in f.items() if k not in _ANNOTATIONS})
|
|
161
|
+
for f in (row.get("findings") or [])
|
|
162
|
+
]
|
|
163
|
+
return ReviewResult(
|
|
164
|
+
findings=findings, summary="", parse_failed=bool(row.get("parse_failed"))
|
|
165
|
+
), diff
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def build(results: Path, out_dir: Path, bench_dir: Path, repo_root: Path) -> list[Path]:
|
|
169
|
+
"""Write one recording per frozen pass; return the paths written."""
|
|
170
|
+
rows = [
|
|
171
|
+
json.loads(line)
|
|
172
|
+
for line in results.read_text(encoding="utf-8").splitlines()
|
|
173
|
+
if line.strip()
|
|
174
|
+
]
|
|
175
|
+
frozen = [r for r in rows if is_frozen_pass(r)]
|
|
176
|
+
out_dir.mkdir(parents=True, exist_ok=True)
|
|
177
|
+
diffs: dict[int, str] = {}
|
|
178
|
+
written: list[Path] = []
|
|
179
|
+
for row in frozen:
|
|
180
|
+
pr = row["pr"]
|
|
181
|
+
if pr not in diffs:
|
|
182
|
+
diffs[pr] = diff_for(pr, bench_dir, repo_root)
|
|
183
|
+
result, diff = recording_for(row, diffs[pr])
|
|
184
|
+
path = out_dir / f"{row_key(row).replace(':', '-')}.json"
|
|
185
|
+
save_finder_recording(path, result, diff)
|
|
186
|
+
written.append(path)
|
|
187
|
+
return written
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def main() -> int:
|
|
191
|
+
"""Rebuild every frozen finder pass and report what was written."""
|
|
192
|
+
repo_root = Path(__file__).resolve().parent.parent
|
|
193
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
194
|
+
parser.add_argument(
|
|
195
|
+
"--results", type=Path, default=repo_root / "benchmarks/guardian/results.jsonl"
|
|
196
|
+
)
|
|
197
|
+
parser.add_argument("--out", type=Path, default=repo_root / ".guardian-recordings")
|
|
198
|
+
parser.add_argument("--bench-dir", type=Path, default=repo_root / "benchmarks/guardian")
|
|
199
|
+
parser.add_argument("--repo-root", type=Path, default=repo_root)
|
|
200
|
+
args = parser.parse_args()
|
|
201
|
+
written = build(args.results, args.out, args.bench_dir, args.repo_root)
|
|
202
|
+
print(f"{len(written)} recordings written to {args.out}")
|
|
203
|
+
return 0
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
if __name__ == "__main__":
|
|
207
|
+
sys.exit(main())
|
|
@@ -21,11 +21,13 @@ opportunity to invent a false positive, and there noise grew superlinearly
|
|
|
21
21
|
"""
|
|
22
22
|
|
|
23
23
|
import asyncio
|
|
24
|
+
import os
|
|
24
25
|
|
|
25
26
|
import structlog
|
|
26
27
|
|
|
27
28
|
from cgis.guardian.collector import ContextCollector
|
|
28
29
|
from cgis.guardian.core import finder_pass
|
|
30
|
+
from cgis.guardian.evidence import evidence_for
|
|
29
31
|
from cgis.guardian.findings import Finding, ReviewResult, dedup_findings
|
|
30
32
|
from cgis.guardian.prompts import AXIS_GROUPS, PER_AXIS_GROUPS
|
|
31
33
|
from cgis.guardian.providers.base import BaseProvider
|
|
@@ -115,7 +117,11 @@ async def run_axis_review(
|
|
|
115
117
|
return merged
|
|
116
118
|
|
|
117
119
|
judgements = await judge_all(
|
|
118
|
-
skeptic_provider,
|
|
120
|
+
skeptic_provider,
|
|
121
|
+
merged.findings,
|
|
122
|
+
context.get("diff", ""),
|
|
123
|
+
concurrency,
|
|
124
|
+
evidence=await evidence_for(collector, os.environ),
|
|
119
125
|
)
|
|
120
126
|
judged = sum(1 for j in judgements if j is not None)
|
|
121
127
|
if judged == 0:
|
|
@@ -5,6 +5,8 @@ finder LGTMs large PRs (attention dilution); each chunk gets a small,
|
|
|
5
5
|
complete world instead — its own diff, full files, and impact graph.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
+
import os
|
|
9
|
+
|
|
8
10
|
import structlog
|
|
9
11
|
from pydantic import BaseModel
|
|
10
12
|
|
|
@@ -12,6 +14,7 @@ from cgis.guardian.axes import run_axis_review
|
|
|
12
14
|
from cgis.guardian.chunker import Chunk, build_chunks, split_diff_by_file
|
|
13
15
|
from cgis.guardian.collector import ContextCollector
|
|
14
16
|
from cgis.guardian.core import GuardianReviewer, finder_pass
|
|
17
|
+
from cgis.guardian.evidence import evidence_for
|
|
15
18
|
from cgis.guardian.findings import Finding, ReviewResult, dedup_findings
|
|
16
19
|
from cgis.guardian.providers.base import BaseProvider
|
|
17
20
|
from cgis.guardian.skeptic import (
|
|
@@ -182,7 +185,12 @@ async def run_chunked_review(
|
|
|
182
185
|
"diff": "\n".join(c["diff"] for c in finding_contexts),
|
|
183
186
|
"full_files": "\n\n".join(c["full_files"] for c in finding_contexts if "full_files" in c),
|
|
184
187
|
}
|
|
185
|
-
judgements = await judge_all(
|
|
188
|
+
judgements = await judge_all(
|
|
189
|
+
skeptic_provider,
|
|
190
|
+
merged.findings,
|
|
191
|
+
skeptic_context["diff"],
|
|
192
|
+
evidence=await evidence_for(collector, os.environ),
|
|
193
|
+
)
|
|
186
194
|
judged = sum(1 for j in judgements if j is not None)
|
|
187
195
|
if judged == 0:
|
|
188
196
|
log.warning("Every skeptic judgement failed; returning unverified findings.")
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
"""Main orchestrator that wires together collector, prompts, and LLM provider."""
|
|
2
2
|
|
|
3
|
+
import os
|
|
4
|
+
|
|
3
5
|
import structlog
|
|
4
6
|
from pydantic import ValidationError
|
|
5
7
|
|
|
6
8
|
from cgis.guardian.collector import ContextCollector
|
|
9
|
+
from cgis.guardian.evidence import evidence_for
|
|
7
10
|
from cgis.guardian.findings import ReviewResult, extract_json, salvage_findings
|
|
8
11
|
from cgis.guardian.prompts import PromptBuilder
|
|
9
12
|
from cgis.guardian.providers.base import BaseProvider
|
|
@@ -131,7 +134,11 @@ class GuardianReviewer:
|
|
|
131
134
|
if self.skeptic_provider is None or not result.findings:
|
|
132
135
|
return result
|
|
133
136
|
judgements = await judge_all(
|
|
134
|
-
self.skeptic_provider,
|
|
137
|
+
self.skeptic_provider,
|
|
138
|
+
result.findings,
|
|
139
|
+
context.get("diff", ""),
|
|
140
|
+
self.concurrency,
|
|
141
|
+
evidence=await evidence_for(self.context_collector, os.environ),
|
|
135
142
|
)
|
|
136
143
|
judged = sum(1 for j in judgements if j is not None)
|
|
137
144
|
if judged == 0:
|