codegraph-brain 0.7.3__tar.gz → 0.7.5__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 (256) hide show
  1. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/.github/workflows/autodoc.yml +7 -7
  2. codegraph_brain-0.7.5/.github/workflows/ci.yml +79 -0
  3. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/.github/workflows/guardian.yml +4 -4
  4. codegraph_brain-0.7.5/.github/workflows/pr-title.yml +51 -0
  5. codegraph_brain-0.7.5/.release-please-manifest.json +3 -0
  6. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/CHANGELOG.md +19 -0
  7. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/PKG-INFO +1 -1
  8. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/benchmarks/guardian/CURATION.md +66 -0
  9. codegraph_brain-0.7.5/benchmarks/guardian/pr-313.yaml +43 -0
  10. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/how-to/MCP_REFERENCE.md +6 -1
  11. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/specs/2026-07-31-finder-bug-class-taxonomy.md +90 -0
  12. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/pyproject.toml +1 -1
  13. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/api/mcp_server.py +73 -1
  14. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/guardian/findings.py +5 -1
  15. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/guardian/render.py +1 -0
  16. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/resolver/engine.py +1 -1
  17. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/resolver/indices.py +41 -22
  18. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/test_cohesion.py +3 -1
  19. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/test_guardian_chunker.py +3 -1
  20. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/test_mcp_server.py +151 -0
  21. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/test_mermaid.py +4 -2
  22. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/test_ontology_init.py +3 -1
  23. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/test_resolver_indices.py +57 -0
  24. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/uv.lock +1 -1
  25. codegraph_brain-0.7.3/.github/workflows/ci.yml +0 -43
  26. codegraph_brain-0.7.3/.release-please-manifest.json +0 -3
  27. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/.claude-plugin/marketplace.json +0 -0
  28. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/.github/workflows/release-please.yml +0 -0
  29. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/.gitignore +0 -0
  30. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/.pre-commit-config.yaml +0 -0
  31. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/.python-version +0 -0
  32. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/CLAUDE.md +0 -0
  33. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/CONTRIBUTING.md +0 -0
  34. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/LICENSE +0 -0
  35. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/Makefile +0 -0
  36. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/PRIVACY.md +0 -0
  37. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/README.md +0 -0
  38. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/benchmarks/guardian/pr-122.yaml +0 -0
  39. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/benchmarks/guardian/pr-140.yaml +0 -0
  40. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/benchmarks/guardian/pr-141.yaml +0 -0
  41. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/benchmarks/guardian/pr-142.yaml +0 -0
  42. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/benchmarks/guardian/pr-143.yaml +0 -0
  43. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/benchmarks/guardian/pr-144.yaml +0 -0
  44. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/benchmarks/guardian/pr-278.yaml +0 -0
  45. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/benchmarks/guardian/results.jsonl +0 -0
  46. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/data/.gitkeep +0 -0
  47. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/AUDIT.md +0 -0
  48. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/CASE_STUDY.md +0 -0
  49. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/architecture/HOW_IT_WORKS.md +0 -0
  50. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/architecture/ONTOLOGY.md +0 -0
  51. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/architecture/PATTERNS_AND_TRIADS.md +0 -0
  52. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/architecture/SELF_PORTRAIT.md +0 -0
  53. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/architecture/diagrams/pipeline_flow.mermaid +0 -0
  54. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/architecture/health_badge.json +0 -0
  55. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/assets/.gitignore +0 -0
  56. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/assets/cgis-app-avatar.png +0 -0
  57. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/assets/cgis-app-avatar.svg +0 -0
  58. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/assets/generate_avatar.py +0 -0
  59. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/examples/.gitkeep +0 -0
  60. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/how-to/AGENT_ONBOARDING.md +0 -0
  61. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/how-to/CLI_USAGE.md +0 -0
  62. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/lab-notes/2026-06-11-chunked-review-negative-result.md +0 -0
  63. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/ontology/.gitkeep +0 -0
  64. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/ontology/core.yaml +0 -0
  65. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/ontology/domains.yaml +0 -0
  66. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/ontology/patterns.yaml +0 -0
  67. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/ontology/tolerances.lock +0 -0
  68. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/specs/2026-06-09-domain-pattern-fingerprint-design.md +0 -0
  69. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/specs/2026-06-09-pattern-alphabet-motif-basis-design.md +0 -0
  70. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/specs/2026-06-10-guardian-sprint-design.md +0 -0
  71. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/specs/2026-06-11-fastapi-di-edges-design.md +0 -0
  72. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/specs/2026-06-11-guardian-chunked-review-design.md +0 -0
  73. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/specs/2026-06-11-guardian-chunker-design.md +0 -0
  74. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/specs/2026-06-11-mcp-drift-validate-fqn-design.md +0 -0
  75. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/specs/2026-06-11-resolver-split-design.md +0 -0
  76. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/specs/2026-06-11-symbol-import-edges-design.md +0 -0
  77. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/specs/2026-06-12-drift-empty-domains-design.md +0 -0
  78. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/specs/2026-06-12-gate-semantics-design.md +0 -0
  79. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/specs/2026-06-12-init-ontology-design.md +0 -0
  80. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/specs/2026-06-12-release-please-ci-design.md +0 -0
  81. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/specs/2026-06-13-suggest-packages-design.md +0 -0
  82. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/specs/2026-06-13-tangle-anti-pattern-design.md +0 -0
  83. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/specs/2026-07-29-guardian-skeptic-scoring-design.md +0 -0
  84. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/specs/2026-07-30-cgis-fractal-design.md +0 -0
  85. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/specs/2026-07-30-cgis-fractal-plan.md +0 -0
  86. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/specs/2026-07-30-chunk-source-filter-design.md +0 -0
  87. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/specs/2026-07-30-chunk-source-filter-plan.md +0 -0
  88. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/specs/2026-07-30-genai-client-close-design.md +0 -0
  89. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/specs/2026-07-30-genai-client-close-plan.md +0 -0
  90. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/specs/2026-07-30-guardian-precision-bench-design.md +0 -0
  91. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/specs/2026-07-30-guardian-precision-bench-plan.md +0 -0
  92. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/specs/2026-07-30-guardian-timeout-retry-design.md +0 -0
  93. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/specs/2026-07-30-guardian-timeout-retry-plan.md +0 -0
  94. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/specs/BLUEPRINT.md +0 -0
  95. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/specs/PRD.md +0 -0
  96. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/specs/TDD.md +0 -0
  97. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/specs/plans/2026-06-09-fingerprint-drift.md +0 -0
  98. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/specs/plans/2026-06-10-guardian-context-skeptic-inline.md +0 -0
  99. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/specs/plans/2026-06-10-guardian-structured-findings-bench.md +0 -0
  100. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/specs/plans/2026-06-10-motif-basis-part-b.md +0 -0
  101. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/specs/plans/2026-06-10-unified-pattern-alphabet.md +0 -0
  102. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/specs/plans/2026-06-11-fastapi-di-edges.md +0 -0
  103. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/specs/plans/2026-06-11-guardian-chunked-review.md +0 -0
  104. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/specs/plans/2026-06-11-guardian-chunker.md +0 -0
  105. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/specs/plans/2026-06-11-mcp-drift-validate-fqn.md +0 -0
  106. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/specs/plans/2026-06-11-resolver-split.md +0 -0
  107. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/specs/plans/2026-06-12-drift-empty-domains.md +0 -0
  108. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/specs/plans/2026-06-12-gate-semantics.md +0 -0
  109. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/specs/plans/2026-06-12-init-ontology.md +0 -0
  110. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/specs/plans/2026-06-12-release-please-ci.md +0 -0
  111. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/specs/plans/2026-06-12-symbol-import-edges.md +0 -0
  112. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/specs/plans/2026-06-13-suggest-packages.md +0 -0
  113. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/docs/specs/plans/2026-07-29-guardian-skeptic-scoring.md +0 -0
  114. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/main.py +0 -0
  115. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/plugin/.claude-plugin/plugin.json +0 -0
  116. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/plugin/.mcp.json +0 -0
  117. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/plugin/README.md +0 -0
  118. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/plugin/skills/cgis/SKILL.md +0 -0
  119. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/plugin/skills/ingest/SKILL.md +0 -0
  120. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/release-please-config.json +0 -0
  121. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/scripts/gen_ideal_graph.py +0 -0
  122. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/scripts/generate_health.py +0 -0
  123. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/scripts/generate_mcp_ref.py +0 -0
  124. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/scripts/generate_schema_docs.py +0 -0
  125. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/scripts/guardian_bench.py +0 -0
  126. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/scripts/guardian_review.py +0 -0
  127. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/scripts/inject_readme_graph.py +0 -0
  128. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/scripts/probe_closure_gap.py +0 -0
  129. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/scripts/probe_tier_ladder.py +0 -0
  130. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/__init__.py +0 -0
  131. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/__main__.py +0 -0
  132. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/api/.gitkeep +0 -0
  133. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/api/__init__.py +0 -0
  134. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/cli.py +0 -0
  135. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/core/.gitkeep +0 -0
  136. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/core/models.py +0 -0
  137. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/extractors/.gitkeep +0 -0
  138. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/extractors/_python_ast.py +0 -0
  139. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/extractors/_python_classes.py +0 -0
  140. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/extractors/_python_functions.py +0 -0
  141. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/extractors/_python_imports.py +0 -0
  142. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/extractors/_python_types.py +0 -0
  143. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/extractors/base.py +0 -0
  144. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/extractors/python_extractor.py +0 -0
  145. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/extractors/typescript_extractor.py +0 -0
  146. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/guardian/__init__.py +0 -0
  147. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/guardian/bench.py +0 -0
  148. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/guardian/chunked.py +0 -0
  149. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/guardian/chunker.py +0 -0
  150. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/guardian/collector.py +0 -0
  151. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/guardian/core.py +0 -0
  152. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/guardian/diff_index.py +0 -0
  153. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/guardian/github_poster.py +0 -0
  154. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/guardian/metrics.py +0 -0
  155. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/guardian/prompts.py +0 -0
  156. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/guardian/providers/__init__.py +0 -0
  157. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/guardian/providers/base.py +0 -0
  158. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/guardian/providers/gemini.py +0 -0
  159. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/guardian/providers/mistral.py +0 -0
  160. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/guardian/providers/ollama.py +0 -0
  161. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/guardian/recording.py +0 -0
  162. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/guardian/runner.py +0 -0
  163. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/guardian/skeptic.py +0 -0
  164. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/pipeline.py +0 -0
  165. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/py.typed +0 -0
  166. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/query/analysis/__init__.py +0 -0
  167. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/query/analysis/analyzer.py +0 -0
  168. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/query/analysis/anomaly.py +0 -0
  169. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/query/analysis/cohesion.py +0 -0
  170. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/query/analysis/health.py +0 -0
  171. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/query/analysis/suggest_service.py +0 -0
  172. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/query/context/__init__.py +0 -0
  173. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/query/context/audit.py +0 -0
  174. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/query/context/context_service.py +0 -0
  175. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/query/context/prompt.py +0 -0
  176. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/query/context/snippet.py +0 -0
  177. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/query/drift/__init__.py +0 -0
  178. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/query/drift/_scc.py +0 -0
  179. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/query/drift/drift.py +0 -0
  180. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/query/drift/drift_service.py +0 -0
  181. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/query/drift/fingerprint.py +0 -0
  182. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/query/drift/fractal.py +0 -0
  183. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/query/drift/ontology_init.py +0 -0
  184. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/query/drift/quotient.py +0 -0
  185. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/query/drift/triads.py +0 -0
  186. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/query/engine.py +0 -0
  187. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/query/fqn.py +0 -0
  188. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/query/render/__init__.py +0 -0
  189. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/query/render/graph_json.py +0 -0
  190. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/query/render/mermaid.py +0 -0
  191. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/query/render/metrics.py +0 -0
  192. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/resolver/.gitkeep +0 -0
  193. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/resolver/__init__.py +0 -0
  194. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/resolver/symbols.py +0 -0
  195. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/resolver/uplift.py +0 -0
  196. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/storage/.gitkeep +0 -0
  197. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/src/cgis/storage/sqlite_store.py +0 -0
  198. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/integration/.gitkeep +0 -0
  199. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/self_parsing/.gitkeep +0 -0
  200. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/self_parsing/__init__.py +0 -0
  201. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/self_parsing/conftest.py +0 -0
  202. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/self_parsing/test_architecture.py +0 -0
  203. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/self_parsing/test_drift.py +0 -0
  204. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/self_parsing/test_fractal.py +0 -0
  205. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/self_parsing/test_init_ontology_roundtrip.py +0 -0
  206. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/self_parsing/test_self_parse.py +0 -0
  207. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/self_parsing/test_self_parse_ts.py +0 -0
  208. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/self_parsing/test_suggest.py +0 -0
  209. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/.gitkeep +0 -0
  210. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/conftest.py +0 -0
  211. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/guardian_stubs.py +0 -0
  212. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/test___main__.py +0 -0
  213. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/test_analyzer.py +0 -0
  214. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/test_audit.py +0 -0
  215. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/test_cli.py +0 -0
  216. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/test_context_service.py +0 -0
  217. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/test_di_acceptance.py +0 -0
  218. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/test_drift.py +0 -0
  219. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/test_drift_service.py +0 -0
  220. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/test_fingerprint.py +0 -0
  221. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/test_fqn.py +0 -0
  222. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/test_fractal.py +0 -0
  223. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/test_gen_ideal_graph.py +0 -0
  224. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/test_generate_mcp_ref.py +0 -0
  225. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/test_graph_json.py +0 -0
  226. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/test_guardian_bench.py +0 -0
  227. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/test_guardian_chunked.py +0 -0
  228. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/test_guardian_collector.py +0 -0
  229. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/test_guardian_core.py +0 -0
  230. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/test_guardian_diff_index.py +0 -0
  231. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/test_guardian_findings.py +0 -0
  232. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/test_guardian_metrics.py +0 -0
  233. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/test_guardian_poster.py +0 -0
  234. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/test_guardian_providers.py +0 -0
  235. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/test_guardian_recording.py +0 -0
  236. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/test_guardian_render.py +0 -0
  237. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/test_guardian_runner.py +0 -0
  238. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/test_guardian_skeptic.py +0 -0
  239. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/test_health_scorer.py +0 -0
  240. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/test_import_acceptance.py +0 -0
  241. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/test_metrics.py +0 -0
  242. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/test_models.py +0 -0
  243. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/test_ontology_compliance.py +0 -0
  244. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/test_patterns_yaml.py +0 -0
  245. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/test_pipeline.py +0 -0
  246. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/test_prompt.py +0 -0
  247. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/test_python_extractor.py +0 -0
  248. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/test_quotient.py +0 -0
  249. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/test_resolver.py +0 -0
  250. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/test_resolver_symbols.py +0 -0
  251. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/test_snippet.py +0 -0
  252. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/test_sqlite_store.py +0 -0
  253. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/test_suggest_service.py +0 -0
  254. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/test_triads.py +0 -0
  255. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/test_typescript_extractor.py +0 -0
  256. {codegraph_brain-0.7.3 → codegraph_brain-0.7.5}/tests/unit/test_uplift.py +0 -0
@@ -28,15 +28,15 @@ jobs:
28
28
  enable-cache: true
29
29
 
30
30
  - name: Install CGIS
31
- run: uv sync
31
+ run: uv sync --frozen
32
32
 
33
33
  - name: Self-ingest core engine
34
- run: uv run cgis ingest ./src --output /tmp/self_graph.db
34
+ run: uv run --frozen cgis ingest ./src --output /tmp/self_graph.db
35
35
 
36
36
  - name: Generate pipeline flow diagram
37
37
  run: |
38
38
  mkdir -p docs/architecture/diagrams
39
- uv run cgis trace "cgis.pipeline.IngestionPipeline.run" \
39
+ uv run --frozen cgis trace "cgis.pipeline.IngestionPipeline.run" \
40
40
  --db /tmp/self_graph.db \
41
41
  --depth 1 \
42
42
  --format mermaid \
@@ -44,21 +44,21 @@ jobs:
44
44
  > docs/architecture/diagrams/pipeline_flow.mermaid
45
45
 
46
46
  - name: Compute graph health badge
47
- run: uv run python scripts/generate_health.py --db /tmp/self_graph.db
47
+ run: uv run --frozen python scripts/generate_health.py --db /tmp/self_graph.db
48
48
 
49
49
  - name: Inject graph into README.md
50
50
  run: |
51
- uv run python scripts/inject_readme_graph.py \
51
+ uv run --frozen python scripts/inject_readme_graph.py \
52
52
  --db /tmp/self_graph.db \
53
53
  --fqn "cgis.pipeline.IngestionPipeline.run" \
54
54
  --depth 1 \
55
55
  --path-prefix "src/"
56
56
 
57
57
  - name: Generate MCP reference manual
58
- run: uv run python scripts/generate_mcp_ref.py
58
+ run: uv run --frozen python scripts/generate_mcp_ref.py
59
59
 
60
60
  - name: Sync schema docs from source
61
- run: uv run python scripts/generate_schema_docs.py
61
+ run: uv run --frozen python scripts/generate_schema_docs.py
62
62
 
63
63
  - name: Open PR with generated docs
64
64
  uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7
@@ -0,0 +1,79 @@
1
+ name: Continuous Integration
2
+
3
+ on:
4
+ push:
5
+ branches: [ main, dev, "feat/**", "fix/**", "docs/**" ]
6
+ pull_request:
7
+ branches: [ main ]
8
+
9
+ jobs:
10
+ python-ci:
11
+ name: Python Verification
12
+ runs-on: ubuntu-latest
13
+ # Job level, not step level: a step's own `env` block is not reliably visible
14
+ # to that same step's `if`, and a condition that silently never fires is a
15
+ # gate that looks like protection and is not.
16
+ env:
17
+ SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
18
+ steps:
19
+ - name: Checkout Code
20
+ uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
21
+ with:
22
+ # Full history: the scanner computes new-code metrics by diffing against
23
+ # the base branch, and a depth-1 clone makes it log
24
+ # WARN Could not find ref 'main' in refs/heads, refs/remotes…
25
+ # which silently degrades every new_* metric, new_coverage included.
26
+ fetch-depth: 0
27
+
28
+ - name: Setup uv
29
+ uses: astral-sh/setup-uv@caf0cab7a618c569241d31dcd442f54681755d39 # v3
30
+ with:
31
+ enable-cache: true
32
+
33
+ - name: Lockfile in step with pyproject
34
+ # This is the only step that validates the lockfile. `--frozen` below stops
35
+ # uv rewriting uv.lock, but it does not check that the lock is current —
36
+ # that is `--locked`. Keeping the check explicit gives a clearer failure
37
+ # than a sync error, and it still runs first so an unfrozen uv could never
38
+ # repair the lock ahead of it.
39
+ run: uv lock --check
40
+
41
+ - name: Install Dependencies
42
+ run: uv sync --frozen --group dev
43
+
44
+ - name: Ruff Format Check
45
+ run: uv run --frozen ruff format --check .
46
+
47
+ - name: Ruff Lint
48
+ run: uv run --frozen ruff check .
49
+
50
+ - name: Interrogate (≥90% docstring coverage)
51
+ run: uv run --frozen interrogate -v --fail-under 90 src/cgis
52
+
53
+ - name: Mypy (strict)
54
+ run: uv run --frozen mypy src
55
+
56
+ - name: Pytest
57
+ # xml alongside term-missing: the console form is for a human reading the
58
+ # log, the XML is the only form SonarCloud can import.
59
+ run: uv run --frozen pytest --cov=cgis --cov-report=term-missing --cov-report=xml
60
+
61
+ - name: SonarQube Cloud scan
62
+ # Inert until SONAR_TOKEN exists, so this can land before the migration.
63
+ # Coverage cannot be imported by Automatic Analysis at all — see #317 —
64
+ # so the token must be added only AFTER Automatic Analysis is switched
65
+ # off in the SonarCloud UI, or the two modes conflict.
66
+ if: env.SONAR_TOKEN != ''
67
+ uses: SonarSource/sonarqube-scan-action@22918119ff8e1ca75a623e15c8296b6ea4fbe28f # v8.2.1
68
+ with:
69
+ # Passed as args rather than a committed sonar-project.properties:
70
+ # SonarCloud treats that file as a signal to disable Automatic
71
+ # Analysis, which would break the working setup the moment this
72
+ # merges rather than when the migration is actually performed.
73
+ args: >
74
+ -Dsonar.projectKey=zaebee_codegraph-brain
75
+ -Dsonar.organization=zaebee
76
+ -Dsonar.sources=src,scripts
77
+ -Dsonar.tests=tests
78
+ -Dsonar.python.version=3.12
79
+ -Dsonar.python.coverage.reportPaths=coverage.xml
@@ -67,10 +67,10 @@ jobs:
67
67
  uses: astral-sh/setup-uv@caf0cab7a618c569241d31dcd442f54681755d39 # v3
68
68
 
69
69
  - name: Install dependencies
70
- run: uv sync --extra guardian
70
+ run: uv sync --frozen --extra guardian
71
71
 
72
72
  - name: Build code graph
73
- run: uv run cgis ingest ./src --output graph.db
73
+ run: uv run --frozen cgis ingest ./src --output graph.db
74
74
 
75
75
  - name: Restore metrics from data branch
76
76
  run: |
@@ -91,7 +91,7 @@ jobs:
91
91
  GUARDIAN_SKEPTIC: ${{ vars.GUARDIAN_SKEPTIC }}
92
92
  GUARDIAN_SKEPTIC_MODEL: ${{ vars.GUARDIAN_SKEPTIC_MODEL }}
93
93
  run: |
94
- uv run python scripts/guardian_review.py \
94
+ uv run --frozen python scripts/guardian_review.py \
95
95
  --output guardian_report.md \
96
96
  --db graph.db \
97
97
  --pr "$PR_NUMBER" \
@@ -219,7 +219,7 @@ jobs:
219
219
  git show origin/data/guardian-metrics:guardian_metrics.jsonl \
220
220
  > guardian_metrics.jsonl
221
221
 
222
- uv run cgis guardian-rate "$PR_NUMBER" "$APPLIED" \
222
+ uv run --frozen cgis guardian-rate "$PR_NUMBER" "$APPLIED" \
223
223
  --metrics guardian_metrics.jsonl
224
224
 
225
225
  if git ls-remote --exit-code origin data/guardian-metrics > /dev/null 2>&1; then
@@ -0,0 +1,51 @@
1
+ name: PR Title
2
+
3
+ # The repository squash-merges, so the PR title becomes the commit subject that
4
+ # release-please parses. A title it does not recognise is dropped from the
5
+ # changelog silently — no warning, no failure. That has now happened twice:
6
+ # #297 ("drift trust batch: …", a descriptive title) and #313
7
+ # ("security(mcp): …", a type that does not exist), the second of which would
8
+ # have shipped a security fix invisibly.
9
+ on:
10
+ pull_request:
11
+ branches: [ main ]
12
+ # `edited` matters: without it, fixing a rejected title would not re-run
13
+ # the check. Kept in a separate workflow from ci.yml so retitling does not
14
+ # replay the whole test suite.
15
+ types: [ opened, edited, reopened, synchronize ]
16
+
17
+ permissions: {}
18
+
19
+ jobs:
20
+ conventional-title:
21
+ name: Conventional commit title
22
+ runs-on: ubuntu-latest
23
+ steps:
24
+ - name: Check the title release-please will parse
25
+ env:
26
+ # Never interpolate an untrusted title straight into `run:` — it is
27
+ # attacker-controlled text. Through the environment it is just data.
28
+ PR_TITLE: ${{ github.event.pull_request.title }}
29
+ run: |
30
+ set -euo pipefail
31
+ # Types release-please understands. Anything else is dropped from the
32
+ # changelog without complaint, which is the failure being prevented.
33
+ TYPES='feat|fix|docs|chore|style|refactor|perf|test|build|ci|revert'
34
+ if printf '%s' "$PR_TITLE" | grep -qE "^($TYPES)(\([a-z0-9._/-]+\))?!?: .+"; then
35
+ printf '::notice::PR title is a conventional commit.\n'
36
+ exit 0
37
+ fi
38
+ printf '::error::PR title is not a conventional commit: %s\n' "$PR_TITLE"
39
+ cat >&2 <<'MSG'
40
+
41
+ This repository squash-merges, so the PR title becomes the commit
42
+ subject release-please reads. An unrecognised type is dropped from the
43
+ CHANGELOG silently.
44
+
45
+ Expected: <type>(<optional scope>): <description>
46
+ Types: feat fix docs chore style refactor perf test build ci revert
47
+
48
+ Note there is no `security` type — use `fix` with a scope:
49
+ fix(mcp): validate db_path before cgis_ingest creates a database
50
+ MSG
51
+ exit 1
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "0.7.5"
3
+ }
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.7.5](https://github.com/zaebee/codegraph-brain/compare/codegraph-brain-v0.7.4...codegraph-brain-v0.7.5) (2026-08-01)
4
+
5
+
6
+ ### Documentation
7
+
8
+ * **spec:** record what changed for [#258](https://github.com/zaebee/codegraph-brain/issues/258) — fixture, second lever, pre-registered gate ([#322](https://github.com/zaebee/codegraph-brain/issues/322)) ([fca8da8](https://github.com/zaebee/codegraph-brain/commit/fca8da8120a7542a71c875d9a960df615f59594e))
9
+
10
+ ## [0.7.4](https://github.com/zaebee/codegraph-brain/compare/codegraph-brain-v0.7.3...codegraph-brain-v0.7.4) (2026-08-01)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * **mcp:** validate db_path before cgis_ingest creates a database ([#313](https://github.com/zaebee/codegraph-brain/issues/313)) ([2c5499b](https://github.com/zaebee/codegraph-brain/commit/2c5499b8fb7f0d40a03144417c3210900371d499)), closes [#312](https://github.com/zaebee/codegraph-brain/issues/312)
16
+
17
+
18
+ ### Documentation
19
+
20
+ * auto-sync architecture graph and MCP reference ([#314](https://github.com/zaebee/codegraph-brain/issues/314)) ([7d5b666](https://github.com/zaebee/codegraph-brain/commit/7d5b66669f9dfa40e2fc86dd59a4baf492a9342e))
21
+
3
22
  ## [0.7.3](https://github.com/zaebee/codegraph-brain/compare/codegraph-brain-v0.7.2...codegraph-brain-v0.7.3) (2026-07-31)
4
23
 
5
24
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codegraph-brain
3
- Version: 0.7.3
3
+ Version: 0.7.5
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
@@ -206,3 +206,69 @@ Fixed later by human-directed review as #283 (`b2ae97b`), which is the
206
206
  Precision is unaffected: `matched` stays 0, so the entry still scores `0/10`. The
207
207
  addition supplies a recall signal — a real **0/1** where the empty ground truth
208
208
  gave a vacuous 1.0.
209
+
210
+ ## pr-313 (added 2026-08-01, #315) — first security fixture
211
+
212
+ `security(mcp): validate db_path before cgis_ingest creates a database`.
213
+
214
+ Provenance is stronger than the rest of the corpus: neither entry was mined from
215
+ review dialogue. Both were **reproduced by execution** on the pre-fix code and
216
+ re-run after the fix.
217
+
218
+ - `dangling-symlink-write-primitive` (110-121, major, **security**, `fix-commit`
219
+ `8bcda77`). A `.db` symlink whose target does not exist passes every check —
220
+ `is_file()` is False for a dangling link, so the magic-byte test never runs —
221
+ and SQLite then follows it and creates the target. Demonstrated by writing a
222
+ SQLite header into a non-existent `.ssh/authorized_keys`; refused afterwards.
223
+ - `oserror-escapes-the-tool` (114-124, minor, `logic`, `fix-commit` `72c34eb`).
224
+ The guard runs before `cgis_ingest`'s own try/except and `Path.is_dir()`,
225
+ `.stat()`, `.open()` propagate `OSError`, so a `PermissionError` on the parent
226
+ crashes the MCP tool. Demonstrated with a `chmod 000` parent.
227
+
228
+ ### Why `head` is `2d79671` and not the final pull head
229
+
230
+ Standard policy, restated because this PR has four commits: `2d79671` is the
231
+ snapshot the first review ran on and the only one where **both** findings are
232
+ present. The later three are the fixes.
233
+
234
+ ### Overlapping ranges are deliberate
235
+
236
+ The two ranges overlap on 114-121. A reviewer can legitimately anchor the
237
+ symlink bypass either at the unresolved `Path(db_path)` (110) or at the
238
+ `is_file()` test where it slips through (121), and the OSError leak sits across
239
+ the whole probe block. Matching is greedy per entry, so one prediction still
240
+ consumes only one entry.
241
+
242
+ ### Scoring the historical runs would be unfair
243
+
244
+ Three reviews ran over this PR, but on **different commits** — guardian started
245
+ at 23:38:58, after `72c34eb` had already fixed the OSError defect, so it never
246
+ had a shot at that entry:
247
+
248
+ | reviewer | commit reviewed | findings | GT available | GT hit |
249
+ |---|---|---|---|---|
250
+ | gemini (1st) | `2d79671` | 1 | 2 | 1 (OSError) |
251
+ | guardian | `72c34eb` | 5 | 1 | 0 |
252
+ | gemini (2nd) | `c90bbf8` | 2 | 1 | 1 (symlink) |
253
+
254
+ A replay at `head` gives every reviewer both findings, which is the point of
255
+ pinning the review head. The table above must not be read as guardian missing
256
+ two.
257
+
258
+ ### A refuted claim that is deliberately absent
259
+
260
+ Guardian's `multi-dot suffix` finding on this PR was a false positive — it
261
+ asserted `graph.tar.db` should be rejected, when ending in `.db` is exactly the
262
+ stated policy, and its suggested `path.suffixes` fix would have broken a
263
+ legitimate `my.project.db`. Per the policy above, refuted claims are omitted
264
+ from ground truth entirely rather than filed as `ambiguous`, so that precision
265
+ failures stay observable. Recorded here because the cross-family skeptic
266
+ **confirmed** it (`Verified by gemini-2.5-flash`) — the cleanest example yet of
267
+ a skeptic passing a systematic reasoning error (#246).
268
+
269
+ ### `category: security`
270
+
271
+ New value, added with this fixture (`findings.py`). The finder prompt does not
272
+ yet offer it — teaching the finder to emit it is #258 and needs bench
273
+ validation. Scoring is unaffected: `_entry_accepts` compares file and line
274
+ range, never category.
@@ -0,0 +1,43 @@
1
+ # PR 313 — security(mcp): validate db_path before cgis_ingest creates a database.
2
+ # See CURATION.md. First security fixture in the corpus (#315, taxonomy gap #258).
3
+ #
4
+ # Provenance is unusually strong: both findings were REPRODUCED BY EXECUTION on
5
+ # the pre-fix code and re-run after the fix, not mined from review dialogue.
6
+ #
7
+ # head = review head (the snapshot the first review ran on). The three later
8
+ # commits on that branch are the fixes, so replaying the final pull head would
9
+ # make both findings unfindable — CURATION.md erratum on spec §3.1.
10
+ #
11
+ # The two ranges overlap on 114-121. That is inherent rather than sloppy: a
12
+ # reviewer can legitimately anchor the symlink bypass either at the unresolved
13
+ # `Path(db_path)` (110) or at the `is_file()` test where it slips through (121),
14
+ # and the OSError leak sits across the whole probe block. Matching is greedy per
15
+ # entry, so one prediction still consumes only one entry.
16
+ pr: 313
17
+ base: 411f8c5894b6
18
+ head: 2d79671547d4
19
+ findings:
20
+ - id: dangling-symlink-write-primitive
21
+ file: src/cgis/api/mcp_server.py
22
+ lines: [110, 121]
23
+ severity: major
24
+ category: security
25
+ summary: >
26
+ db_path is validated unresolved, so a `.db` symlink whose target does not
27
+ exist passes every check — is_file() is False for a dangling link, so the
28
+ magic-byte test never runs — and SQLite then follows it and creates the
29
+ target. Demonstrated: a symlink to a non-existent .ssh/authorized_keys was
30
+ created as a SQLite database (header b'SQLite format 3\x00').
31
+ source: fix-commit
32
+ - id: oserror-escapes-the-tool
33
+ file: src/cgis/api/mcp_server.py
34
+ lines: [114, 124]
35
+ severity: minor
36
+ category: logic
37
+ summary: >
38
+ The guard runs before cgis_ingest's own try/except, and Path.is_dir(),
39
+ .stat() and .open() all propagate OSError, so a PermissionError on the
40
+ parent directory escapes the MCP tool as a crash instead of a message the
41
+ agent can act on. Demonstrated with a chmod 000 parent directory.
42
+ source: fix-commit
43
+ ambiguous: []
@@ -176,9 +176,14 @@ Show the structural layout (CONTAINS/DECLARES) of a module or class.
176
176
  Scan a local directory, extract all symbols, resolve links, and build the graph DB.
177
177
 
178
178
  Use this to initialise or refresh the code knowledge graph for a project.
179
- Paths are normalised relative to the workspace root so the database is
179
+ Node FQNs are normalised relative to the workspace root so the graph is
180
180
  portable across machines.
181
181
 
182
+ ``db_path`` must name a database — it has to end in ``.db``, ``.sqlite`` or
183
+ ``.sqlite3``, live in a directory that already exists, and not point at an
184
+ existing file that is not a SQLite database. cgis will not create parent
185
+ directories.
186
+
182
187
  By default the ingest is **incremental**: only changed/new files are
183
188
  re-scanned, and the summary reports both what changed this run and the
184
189
  whole-graph total. Set ``full_rebuild=True`` to re-scan every file and
@@ -153,3 +153,93 @@ Steps 2–4 need a provider key, which is why they are not done here.
153
153
  instructions (we have no such capability in the finder).
154
154
  - Copying rule text verbatim — the source is Apache-2.0 Go/agentic tooling for a
155
155
  different stack; #258 asks for the taxonomy only.
156
+
157
+ ---
158
+
159
+ # Addendum — 2026-08-01: the experiment is now runnable, and a second intervention appeared
160
+
161
+ Three things changed after this document was written. None of them changes the
162
+ conclusion above (**still nothing shipped to `prompts.py`**), but two of them
163
+ change what the deciding experiment should measure.
164
+
165
+ ## 1. The top gap now has a fixture
166
+
167
+ When the table above was written, the Security-Sensitive Code row was **absent**
168
+ with no ground truth to test it against; only Resource Management (pr-278) had
169
+ one. `benchmarks/guardian/pr-313.yaml` (#315) now carries a real security defect:
170
+
171
+ dangling-symlink-write-primitive (major, category: security)
172
+
173
+ A `.db` symlink whose target does not exist defeats a path guard —
174
+ `is_file()` is False for a dangling link, so the magic-byte check never runs and
175
+ SQLite creates the target. Reproduced by execution, not mined from dialogue.
176
+
177
+ `category: security` was added to `Category` with that fixture. The finder prompt
178
+ does **not** offer it — that is this document's subject and still unshipped.
179
+ Scoring is unaffected: `_entry_accepts` compares file and line range, never
180
+ category.
181
+
182
+ So the experiment can now measure both evidenced gaps rather than one.
183
+
184
+ ## 2. A second, better-evidenced intervention: exculpating guards
185
+
186
+ Production runs on 2026-07-31 produced a failure mode this document does not
187
+ consider, because it is not a missing class — it is a defect in the classes that
188
+ already exist.
189
+
190
+ On PR #297 the finder returned **12** "Unvalidated dictionary access" findings
191
+ against code that validates with `isinstance` one line above the use. On #313 it
192
+ returned "floating-point comparison may be flaky" on a line carrying an explicit
193
+ `+ 1e-9`. Both trace to focus-area wording that names a *shape* and never names
194
+ the guard that makes an instance a non-finding:
195
+
196
+ > **Unvalidated external data** — … used as a `dict`/… **without first checking
197
+ > its type or presence**
198
+
199
+ The finder matches the shape and does not check whether the guard is present.
200
+
201
+ This matters for sequencing: **adding classes to a checklist that already
202
+ over-fires multiplies noise.** The precision fix should be measured first, or at
203
+ least separately — otherwise a recall gain from new classes and a noise change
204
+ from the rewrite arrive in the same number and neither can be attributed.
205
+
206
+ It is also the cheaper experiment. It needs no new fixture: if adding an
207
+ exculpating clause to each existing focus area lowers `noise` without lowering
208
+ `matched` on the current corpus, that is the whole result.
209
+
210
+ ## 3. Replay already exists
211
+
212
+ This document says steps 2–4 "need a provider key". True, but worth stating
213
+ plainly for whoever picks this up: the *machinery* is not missing.
214
+ `uv run --frozen python scripts/guardian_bench.py --replay-finder <recording.json>`
215
+ freezes a finder set and judges it, and `load_finder_recording` strips prior
216
+ verdicts on read so arms stay comparable.
217
+ (#302 was filed claiming this was absent and has been closed as redundant.)
218
+
219
+ The only blocker is credentials.
220
+
221
+ ## Pre-registered gate
222
+
223
+ Writing pass/fail criteria before spending money is what made the chunked-review
224
+ no-go mechanical rather than motivated (#160). Same discipline here.
225
+
226
+ **Arm A — exculpating-guard rewrite** (no new classes):
227
+ 1. mean `noise` per PR drops by ≥ 25% against the frozen baseline
228
+ 2. `matched` does not drop on any PR
229
+ 3. pr-141, the noise probe, does not regress
230
+
231
+ **Arm B — class checklist** (security, concurrency, resource, error-handling;
232
+ compact named classes, no per-class few-shot, per #247):
233
+ 1. pr-278 `gemini-client-never-closed` OR pr-313 `dangling-symlink-write-primitive`
234
+ is matched at least once in three runs — the hypothesis must show where it matters
235
+ 2. no PR drops more than 0.05 below its baseline recall
236
+ 3. mean noise does not exceed the Arm A result
237
+
238
+ Ship each arm only on its own gate. If Arm B passes only with Arm A applied,
239
+ say so explicitly rather than reporting the combined delta.
240
+
241
+ ## Still out of scope
242
+
243
+ Unchanged from above, plus: do not run Arm B before Arm A has a number. The
244
+ float few-shot regression happened because two effects were entangled in one
245
+ measurement.
@@ -7,7 +7,7 @@ packages = ["src/cgis"]
7
7
 
8
8
  [project]
9
9
  name = "codegraph-brain"
10
- version = "0.7.3"
10
+ version = "0.7.5"
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"
@@ -85,20 +85,92 @@ def _render_subgraph(
85
85
  return f"❌ Unknown format '{output_format}'. Use 'mermaid' or 'json'."
86
86
 
87
87
 
88
+ #: Names cgis will create a database under. Everything else is refused, so an
89
+ #: agent cannot be talked into materialising `~/.ssh/authorized_keys` (#312).
90
+ _DB_SUFFIXES = frozenset({".db", ".sqlite", ".sqlite3"})
91
+
92
+ #: First 16 bytes of any SQLite file.
93
+ _SQLITE_MAGIC = b"SQLite format 3\x00"
94
+
95
+
96
+ def _reject_db_path(db_path: str) -> str | None:
97
+ """Return a refusal message for an unusable ``db_path``, or None if it is fine.
98
+
99
+ ``cgis_ingest`` is the only MCP tool that creates its database — the other
100
+ twelve refuse a path that does not already exist, which is guard enough for
101
+ them. It needs a different one: it reads untrusted repository content and is
102
+ then told by the same agent where to write, so ``db_path`` is attacker-
103
+ reachable in a way the read-only tools' paths are not.
104
+
105
+ Refuses an unexpected suffix, a missing parent directory (creating a tree is
106
+ never wanted), a directory target, and an existing file that is not a
107
+ database. The last is belt-and-braces — SQLite already declines to open a
108
+ non-database — but it fails with a message that says why.
109
+
110
+ Everything is judged on the **resolved** path. A dangling symlink is the
111
+ bypass this guard exists to stop: ``attack.db`` pointing at a target that
112
+ does not exist yet makes ``is_file()`` return False, every check passes, and
113
+ SQLite creates the target. Resolving first means the suffix rule applies to
114
+ where the write actually lands. (A symlink to an *existing* non-database was
115
+ already refused by the magic-byte check.)
116
+
117
+ The filesystem probes are wrapped: this runs *before* ``cgis_ingest``'s own
118
+ try/except, and ``Path.resolve()``, ``Path.is_dir()`` and friends propagate
119
+ ``OSError`` (a ``PermissionError`` on the parent, or a symlink loop), which
120
+ would escape the tool as a crash instead of a message the agent can act on.
121
+ """
122
+ try:
123
+ path = Path(db_path).resolve()
124
+ except OSError as exc:
125
+ return f"❌ Refusing db_path '{db_path}': path is inaccessible ({exc})."
126
+ # Case-insensitive: `.DB` carries the same intent, and on a case-insensitive
127
+ # filesystem it is literally the same file.
128
+ if path.suffix.lower() not in _DB_SUFFIXES:
129
+ allowed = ", ".join(sorted(_DB_SUFFIXES))
130
+ return f"❌ Refusing db_path '{db_path}': name must end in one of {allowed}."
131
+ try:
132
+ if path.is_dir():
133
+ return f"❌ Refusing db_path '{db_path}': it is a directory."
134
+ if not path.parent.is_dir():
135
+ return (
136
+ f"❌ Refusing db_path '{db_path}': parent directory does not exist. "
137
+ "cgis will not create one."
138
+ )
139
+ if path.is_file() and path.stat().st_size > 0:
140
+ with path.open("rb") as fh:
141
+ if fh.read(len(_SQLITE_MAGIC)) != _SQLITE_MAGIC:
142
+ return (
143
+ f"❌ Refusing db_path '{db_path}': existing file is not a SQLite database."
144
+ )
145
+ except OSError as exc:
146
+ return f"❌ Refusing db_path '{db_path}': path is inaccessible ({exc})."
147
+ return None
148
+
149
+
88
150
  @mcp.tool()
89
151
  def cgis_ingest(project_path: str, db_path: str = _DEFAULT_DB, full_rebuild: bool = False) -> str:
90
152
  """Scan a local directory, extract all symbols, resolve links, and build the graph DB.
91
153
 
92
154
  Use this to initialise or refresh the code knowledge graph for a project.
93
- Paths are normalised relative to the workspace root so the database is
155
+ Node FQNs are normalised relative to the workspace root so the graph is
94
156
  portable across machines.
95
157
 
158
+ ``db_path`` must name a database — it has to end in ``.db``, ``.sqlite`` or
159
+ ``.sqlite3``, live in a directory that already exists, and not point at an
160
+ existing file that is not a SQLite database. cgis will not create parent
161
+ directories.
162
+
96
163
  By default the ingest is **incremental**: only changed/new files are
97
164
  re-scanned, and the summary reports both what changed this run and the
98
165
  whole-graph total. Set ``full_rebuild=True`` to re-scan every file and
99
166
  overwrite the database from scratch — use this to drop nodes for files that
100
167
  were deleted or renamed, which an incremental run leaves behind.
101
168
  """
169
+ refusal = _reject_db_path(db_path)
170
+ if refusal is not None:
171
+ logger.warning("MCP ingest refused db_path", db=db_path)
172
+ return refusal
173
+
102
174
  pipeline = IngestionPipeline(_EXTRACTORS)
103
175
  try:
104
176
  with SQLiteStore(db_path) as store:
@@ -5,7 +5,11 @@ from typing import Literal
5
5
  from pydantic import BaseModel, Field
6
6
 
7
7
  Severity = Literal["critical", "major", "minor"]
8
- Category = Literal["logic", "contract", "tests", "types", "ontology"]
8
+ # "security" exists so curated ground truth can name an exploitable defect (#315).
9
+ # The finder prompt does not yet offer it as a choice — teaching the finder to
10
+ # emit it is #258, and needs bench validation. Scoring is unaffected either way:
11
+ # bench matching compares file and line range, never category.
12
+ Category = Literal["logic", "contract", "tests", "types", "ontology", "security"]
9
13
  Verdict = Literal["confirmed", "refuted", "uncertain"]
10
14
 
11
15
 
@@ -11,6 +11,7 @@ _CATEGORY_LABEL = {
11
11
  "tests": "Test Coverage",
12
12
  "types": "Type Safety",
13
13
  "ontology": "Ontology",
14
+ "security": "Security",
14
15
  }
15
16
 
16
17
 
@@ -126,7 +126,7 @@ class ResolverEngine:
126
126
 
127
127
  def _ensure_virtual_node(self, target: str, virtual_nodes: dict[str, Node]) -> None:
128
128
  """Create a virtual boundary node for target if it is not already in the graph."""
129
- if target not in self._index.nodes and target not in virtual_nodes:
129
+ if not self._index.has_node(target) and target not in virtual_nodes:
130
130
  virtual_nodes[target] = self._make_virtual_node(
131
131
  target, self._index.classify_fqn(target)
132
132
  )