codegraph-brain 0.14.1__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.
Files changed (320) hide show
  1. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/.github/workflows/guardian.yml +6 -0
  2. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/.gitignore +1 -0
  3. codegraph_brain-0.15.0/.release-please-manifest.json +3 -0
  4. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/CHANGELOG.md +9 -0
  5. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/PKG-INFO +1 -1
  6. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/pyproject.toml +1 -1
  7. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/scripts/guardian_bench.py +4 -1
  8. codegraph_brain-0.15.0/scripts/guardian_replay_skeptic.py +264 -0
  9. codegraph_brain-0.15.0/scripts/recordings_from_corpus.py +207 -0
  10. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/guardian/axes.py +7 -1
  11. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/guardian/chunked.py +9 -1
  12. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/guardian/core.py +8 -1
  13. codegraph_brain-0.15.0/src/cgis/guardian/evidence.py +230 -0
  14. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/guardian/skeptic.py +53 -5
  15. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_backfill_calibration_fingerprint.py +43 -0
  16. codegraph_brain-0.15.0/tests/unit/test_guardian_evidence.py +423 -0
  17. codegraph_brain-0.15.0/tests/unit/test_guardian_replay_skeptic.py +358 -0
  18. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_guardian_skeptic.py +55 -10
  19. codegraph_brain-0.15.0/tests/unit/test_recordings_from_corpus.py +245 -0
  20. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/uv.lock +1 -1
  21. codegraph_brain-0.14.1/.release-please-manifest.json +0 -3
  22. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/.claude-plugin/marketplace.json +0 -0
  23. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/.gitattributes +0 -0
  24. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/.github/workflows/autodoc.yml +0 -0
  25. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/.github/workflows/ci.yml +0 -0
  26. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/.github/workflows/pr-title.yml +0 -0
  27. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/.github/workflows/release-please.yml +0 -0
  28. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/.pre-commit-config.yaml +0 -0
  29. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/.python-version +0 -0
  30. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/CLAUDE.md +0 -0
  31. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/CONTRIBUTING.md +0 -0
  32. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/LICENSE +0 -0
  33. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/Makefile +0 -0
  34. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/PRIVACY.md +0 -0
  35. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/README.md +0 -0
  36. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/benchmarks/guardian/CURATION.md +0 -0
  37. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/benchmarks/guardian/calibration.jsonl +0 -0
  38. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/benchmarks/guardian/pr-122.yaml +0 -0
  39. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/benchmarks/guardian/pr-140.yaml +0 -0
  40. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/benchmarks/guardian/pr-141.yaml +0 -0
  41. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/benchmarks/guardian/pr-142.yaml +0 -0
  42. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/benchmarks/guardian/pr-143.yaml +0 -0
  43. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/benchmarks/guardian/pr-144.yaml +0 -0
  44. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/benchmarks/guardian/pr-278.yaml +0 -0
  45. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/benchmarks/guardian/pr-313.yaml +0 -0
  46. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/benchmarks/guardian/results.jsonl +0 -0
  47. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/benchmarks/martian/README.md +0 -0
  48. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/benchmarks/martian/cal_dot_com.json +0 -0
  49. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/benchmarks/martian/discourse.json +0 -0
  50. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/benchmarks/martian/grafana.json +0 -0
  51. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/benchmarks/martian/keycloak.json +0 -0
  52. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/benchmarks/martian/sentry.json +0 -0
  53. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/benchmarks/martian-judged.jsonl +0 -0
  54. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/benchmarks/martian-p3-judged-run1.jsonl +0 -0
  55. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/benchmarks/martian-p3-judged-run1.jsonl.corrupted-backup +0 -0
  56. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/benchmarks/martian-p3-run1.jsonl +0 -0
  57. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/benchmarks/martian-p3-run2.jsonl +0 -0
  58. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/benchmarks/martian-p3-run3.jsonl +0 -0
  59. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/benchmarks/martian-plan.json +0 -0
  60. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/benchmarks/martian-repeat-judged.jsonl +0 -0
  61. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/benchmarks/martian-repeat-reviews.jsonl +0 -0
  62. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/benchmarks/martian-reviews.jsonl +0 -0
  63. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/data/.gitkeep +0 -0
  64. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/AUDIT.md +0 -0
  65. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/CASE_STUDY.md +0 -0
  66. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/GUARDIAN_LOCAL_BENCH.md +0 -0
  67. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/GUARDIAN_REMOTE_OLLAMA.md +0 -0
  68. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/architecture/HOW_IT_WORKS.md +0 -0
  69. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/architecture/ONTOLOGY.md +0 -0
  70. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/architecture/PATTERNS_AND_TRIADS.md +0 -0
  71. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/architecture/SELF_PORTRAIT.md +0 -0
  72. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/architecture/diagrams/pipeline_flow.mermaid +0 -0
  73. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/architecture/health_badge.json +0 -0
  74. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/assets/.gitignore +0 -0
  75. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/assets/cgis-app-avatar.png +0 -0
  76. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/assets/cgis-app-avatar.svg +0 -0
  77. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/assets/generate_avatar.py +0 -0
  78. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/examples/.gitkeep +0 -0
  79. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/how-to/AGENT_ONBOARDING.md +0 -0
  80. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/how-to/CLI_USAGE.md +0 -0
  81. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/how-to/MCP_REFERENCE.md +0 -0
  82. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/lab-notes/2026-06-11-chunked-review-negative-result.md +0 -0
  83. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/ontology/.gitkeep +0 -0
  84. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/ontology/core.yaml +0 -0
  85. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/ontology/domains.yaml +0 -0
  86. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/ontology/patterns.yaml +0 -0
  87. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/ontology/tolerances.lock +0 -0
  88. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/specs/2026-06-09-domain-pattern-fingerprint-design.md +0 -0
  89. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/specs/2026-06-09-pattern-alphabet-motif-basis-design.md +0 -0
  90. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/specs/2026-06-10-guardian-sprint-design.md +0 -0
  91. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/specs/2026-06-11-fastapi-di-edges-design.md +0 -0
  92. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/specs/2026-06-11-guardian-chunked-review-design.md +0 -0
  93. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/specs/2026-06-11-guardian-chunker-design.md +0 -0
  94. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/specs/2026-06-11-mcp-drift-validate-fqn-design.md +0 -0
  95. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/specs/2026-06-11-resolver-split-design.md +0 -0
  96. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/specs/2026-06-11-symbol-import-edges-design.md +0 -0
  97. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/specs/2026-06-12-drift-empty-domains-design.md +0 -0
  98. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/specs/2026-06-12-gate-semantics-design.md +0 -0
  99. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/specs/2026-06-12-init-ontology-design.md +0 -0
  100. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/specs/2026-06-12-release-please-ci-design.md +0 -0
  101. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/specs/2026-06-13-suggest-packages-design.md +0 -0
  102. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/specs/2026-06-13-tangle-anti-pattern-design.md +0 -0
  103. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/specs/2026-07-29-guardian-skeptic-scoring-design.md +0 -0
  104. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/specs/2026-07-30-cgis-fractal-design.md +0 -0
  105. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/specs/2026-07-30-cgis-fractal-plan.md +0 -0
  106. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/specs/2026-07-30-chunk-source-filter-design.md +0 -0
  107. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/specs/2026-07-30-chunk-source-filter-plan.md +0 -0
  108. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/specs/2026-07-30-genai-client-close-design.md +0 -0
  109. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/specs/2026-07-30-genai-client-close-plan.md +0 -0
  110. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/specs/2026-07-30-guardian-precision-bench-design.md +0 -0
  111. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/specs/2026-07-30-guardian-precision-bench-plan.md +0 -0
  112. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/specs/2026-07-30-guardian-timeout-retry-design.md +0 -0
  113. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/specs/2026-07-30-guardian-timeout-retry-plan.md +0 -0
  114. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/specs/2026-07-31-finder-bug-class-taxonomy.md +0 -0
  115. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/specs/2026-08-01-aura-autoevolution-poc.md +0 -0
  116. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/specs/2026-08-11-guardian-code-review-bench.md +0 -0
  117. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/specs/2026-08-14-review-fingerprint-design.md +0 -0
  118. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/specs/BLUEPRINT.md +0 -0
  119. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/specs/PRD.md +0 -0
  120. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/specs/TDD.md +0 -0
  121. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/specs/plans/2026-06-09-fingerprint-drift.md +0 -0
  122. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/specs/plans/2026-06-10-guardian-context-skeptic-inline.md +0 -0
  123. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/specs/plans/2026-06-10-guardian-structured-findings-bench.md +0 -0
  124. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/specs/plans/2026-06-10-motif-basis-part-b.md +0 -0
  125. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/specs/plans/2026-06-10-unified-pattern-alphabet.md +0 -0
  126. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/specs/plans/2026-06-11-fastapi-di-edges.md +0 -0
  127. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/specs/plans/2026-06-11-guardian-chunked-review.md +0 -0
  128. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/specs/plans/2026-06-11-guardian-chunker.md +0 -0
  129. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/specs/plans/2026-06-11-mcp-drift-validate-fqn.md +0 -0
  130. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/specs/plans/2026-06-11-resolver-split.md +0 -0
  131. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/specs/plans/2026-06-12-drift-empty-domains.md +0 -0
  132. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/specs/plans/2026-06-12-gate-semantics.md +0 -0
  133. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/specs/plans/2026-06-12-init-ontology.md +0 -0
  134. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/specs/plans/2026-06-12-release-please-ci.md +0 -0
  135. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/specs/plans/2026-06-12-symbol-import-edges.md +0 -0
  136. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/specs/plans/2026-06-13-suggest-packages.md +0 -0
  137. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/specs/plans/2026-07-29-guardian-skeptic-scoring.md +0 -0
  138. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/docs/specs/plans/2026-08-14-review-fingerprint.md +0 -0
  139. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/main.py +0 -0
  140. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/plugin/.claude-plugin/plugin.json +0 -0
  141. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/plugin/.mcp.json +0 -0
  142. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/plugin/README.md +0 -0
  143. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/plugin/skills/cgis/SKILL.md +0 -0
  144. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/plugin/skills/ingest/SKILL.md +0 -0
  145. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/release-please-config.json +0 -0
  146. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/scripts/backfill_calibration_fingerprint.py +0 -0
  147. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/scripts/backfill_review_fingerprint.py +0 -0
  148. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/scripts/check_pytest_raises.py +0 -0
  149. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/scripts/colab_bench.sh +0 -0
  150. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/scripts/gen_ideal_graph.py +0 -0
  151. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/scripts/generate_health.py +0 -0
  152. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/scripts/generate_mcp_ref.py +0 -0
  153. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/scripts/generate_schema_docs.py +0 -0
  154. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/scripts/guardian_calibrate.py +0 -0
  155. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/scripts/guardian_martian.py +0 -0
  156. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/scripts/guardian_review.py +0 -0
  157. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/scripts/inject_readme_graph.py +0 -0
  158. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/scripts/ollama_visitor.sh +0 -0
  159. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/scripts/probe_closure_gap.py +0 -0
  160. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/scripts/probe_tier_ladder.py +0 -0
  161. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/__init__.py +0 -0
  162. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/__main__.py +0 -0
  163. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/api/.gitkeep +0 -0
  164. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/api/__init__.py +0 -0
  165. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/api/mcp_server.py +0 -0
  166. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/cli.py +0 -0
  167. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/core/.gitkeep +0 -0
  168. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/core/models.py +0 -0
  169. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/extractors/.gitkeep +0 -0
  170. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/extractors/_python_ast.py +0 -0
  171. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/extractors/_python_classes.py +0 -0
  172. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/extractors/_python_functions.py +0 -0
  173. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/extractors/_python_imports.py +0 -0
  174. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/extractors/_python_types.py +0 -0
  175. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/extractors/base.py +0 -0
  176. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/extractors/python_extractor.py +0 -0
  177. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/extractors/registry.py +0 -0
  178. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/extractors/typescript_extractor.py +0 -0
  179. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/guardian/__init__.py +0 -0
  180. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/guardian/bench.py +0 -0
  181. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/guardian/calibrate.py +0 -0
  182. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/guardian/chunker.py +0 -0
  183. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/guardian/collector.py +0 -0
  184. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/guardian/diff_index.py +0 -0
  185. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/guardian/findings.py +0 -0
  186. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/guardian/github_poster.py +0 -0
  187. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/guardian/martian.py +0 -0
  188. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/guardian/metrics.py +0 -0
  189. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/guardian/prompts.py +0 -0
  190. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/guardian/providers/__init__.py +0 -0
  191. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/guardian/providers/base.py +0 -0
  192. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/guardian/providers/gemini.py +0 -0
  193. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/guardian/providers/mistral.py +0 -0
  194. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/guardian/providers/ollama.py +0 -0
  195. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/guardian/recording.py +0 -0
  196. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/guardian/render.py +0 -0
  197. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/guardian/review_fingerprint.py +0 -0
  198. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/guardian/runner.py +0 -0
  199. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/pipeline.py +0 -0
  200. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/py.typed +0 -0
  201. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/query/analysis/__init__.py +0 -0
  202. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/query/analysis/analyzer.py +0 -0
  203. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/query/analysis/anomaly.py +0 -0
  204. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/query/analysis/cohesion.py +0 -0
  205. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/query/analysis/health.py +0 -0
  206. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/query/analysis/suggest_service.py +0 -0
  207. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/query/context/__init__.py +0 -0
  208. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/query/context/audit.py +0 -0
  209. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/query/context/context_service.py +0 -0
  210. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/query/context/prompt.py +0 -0
  211. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/query/context/snippet.py +0 -0
  212. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/query/drift/__init__.py +0 -0
  213. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/query/drift/_scc.py +0 -0
  214. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/query/drift/drift.py +0 -0
  215. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/query/drift/drift_service.py +0 -0
  216. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/query/drift/fingerprint.py +0 -0
  217. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/query/drift/fractal.py +0 -0
  218. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/query/drift/ontology_init.py +0 -0
  219. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/query/drift/quotient.py +0 -0
  220. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/query/drift/triads.py +0 -0
  221. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/query/engine.py +0 -0
  222. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/query/fqn.py +0 -0
  223. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/query/render/__init__.py +0 -0
  224. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/query/render/graph_json.py +0 -0
  225. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/query/render/mermaid.py +0 -0
  226. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/query/render/metrics.py +0 -0
  227. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/resolver/.gitkeep +0 -0
  228. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/resolver/__init__.py +0 -0
  229. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/resolver/engine.py +0 -0
  230. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/resolver/indices.py +0 -0
  231. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/resolver/symbols.py +0 -0
  232. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/resolver/uplift.py +0 -0
  233. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/storage/.gitkeep +0 -0
  234. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/src/cgis/storage/sqlite_store.py +0 -0
  235. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/integration/.gitkeep +0 -0
  236. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/self_parsing/.gitkeep +0 -0
  237. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/self_parsing/__init__.py +0 -0
  238. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/self_parsing/conftest.py +0 -0
  239. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/self_parsing/test_architecture.py +0 -0
  240. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/self_parsing/test_drift.py +0 -0
  241. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/self_parsing/test_fractal.py +0 -0
  242. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/self_parsing/test_init_ontology_roundtrip.py +0 -0
  243. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/self_parsing/test_self_parse.py +0 -0
  244. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/self_parsing/test_self_parse_ts.py +0 -0
  245. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/self_parsing/test_suggest.py +0 -0
  246. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/.gitkeep +0 -0
  247. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/conftest.py +0 -0
  248. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/guardian_stubs.py +0 -0
  249. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/review_path_inventory.txt +0 -0
  250. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test___main__.py +0 -0
  251. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_analyzer.py +0 -0
  252. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_audit.py +0 -0
  253. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_backfill_review_fingerprint.py +0 -0
  254. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_check_pytest_raises.py +0 -0
  255. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_cli.py +0 -0
  256. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_cohesion.py +0 -0
  257. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_context_service.py +0 -0
  258. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_di_acceptance.py +0 -0
  259. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_drift.py +0 -0
  260. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_drift_service.py +0 -0
  261. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_extractor_registry.py +0 -0
  262. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_fingerprint.py +0 -0
  263. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_fqn.py +0 -0
  264. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_fractal.py +0 -0
  265. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_gen_ideal_graph.py +0 -0
  266. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_generate_mcp_ref.py +0 -0
  267. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_graph_json.py +0 -0
  268. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_guardian_axes.py +0 -0
  269. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_guardian_bench.py +0 -0
  270. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_guardian_bench_script.py +0 -0
  271. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_guardian_calibrate.py +0 -0
  272. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_guardian_calibrate_script.py +0 -0
  273. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_guardian_chunked.py +0 -0
  274. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_guardian_chunker.py +0 -0
  275. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_guardian_collector.py +0 -0
  276. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_guardian_core.py +0 -0
  277. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_guardian_diff_index.py +0 -0
  278. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_guardian_findings.py +0 -0
  279. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_guardian_martian.py +0 -0
  280. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_guardian_martian_script.py +0 -0
  281. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_guardian_martian_union.py +0 -0
  282. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_guardian_metrics.py +0 -0
  283. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_guardian_mistral_sampling.py +0 -0
  284. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_guardian_ollama_sampling.py +0 -0
  285. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_guardian_ollama_truncation.py +0 -0
  286. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_guardian_poster.py +0 -0
  287. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_guardian_providers.py +0 -0
  288. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_guardian_providers_name.py +0 -0
  289. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_guardian_recording.py +0 -0
  290. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_guardian_render.py +0 -0
  291. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_guardian_review_script.py +0 -0
  292. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_guardian_runner.py +0 -0
  293. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_guardian_salvage.py +0 -0
  294. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_health_scorer.py +0 -0
  295. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_import_acceptance.py +0 -0
  296. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_mcp_server.py +0 -0
  297. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_mermaid.py +0 -0
  298. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_metrics.py +0 -0
  299. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_models.py +0 -0
  300. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_ontology_compliance.py +0 -0
  301. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_ontology_init.py +0 -0
  302. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_patterns_yaml.py +0 -0
  303. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_pipeline.py +0 -0
  304. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_prompt.py +0 -0
  305. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_python_extractor.py +0 -0
  306. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_quotient.py +0 -0
  307. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_resolver.py +0 -0
  308. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_resolver_indices.py +0 -0
  309. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_resolver_symbols.py +0 -0
  310. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_review_fingerprint_closure.py +0 -0
  311. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_review_fingerprint_contract.py +0 -0
  312. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_review_fingerprint_digest.py +0 -0
  313. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_review_fingerprint_record.py +0 -0
  314. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_snippet.py +0 -0
  315. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_sqlite_store.py +0 -0
  316. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_suggest_service.py +0 -0
  317. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_temperature_source.py +0 -0
  318. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_triads.py +0 -0
  319. {codegraph_brain-0.14.1 → codegraph_brain-0.15.0}/tests/unit/test_typescript_extractor.py +0 -0
  320. {codegraph_brain-0.14.1 → 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 \
@@ -230,3 +230,4 @@ guardian_metrics.jsonl
230
230
  *.db-wal
231
231
  *.db-shm
232
232
  .martian-workspace/
233
+ .guardian-recordings/
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "0.15.0"
3
+ }
@@ -1,5 +1,14 @@
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
+
3
12
  ## [0.14.1](https://github.com/zaebee/codegraph-brain/compare/codegraph-brain-v0.14.0...codegraph-brain-v0.14.1) (2026-08-16)
4
13
 
5
14
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: codegraph-brain
3
- Version: 0.14.1
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.14.1"
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
- judgements = await judge_all(skeptic[0], result.findings, recording.diff)
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, merged.findings, context.get("diff", ""), concurrency
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(skeptic_provider, merged.findings, skeptic_context["diff"])
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, result.findings, context.get("diff", ""), self.concurrency
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: