codegraph-brain 0.7.1__tar.gz → 0.7.2__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 (249) hide show
  1. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/.github/workflows/guardian.yml +1 -1
  2. codegraph_brain-0.7.2/.release-please-manifest.json +3 -0
  3. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/CHANGELOG.md +14 -0
  4. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/PKG-INFO +1 -1
  5. codegraph_brain-0.7.2/docs/lab-notes/2026-06-11-chunked-review-negative-result.md +132 -0
  6. codegraph_brain-0.7.2/docs/ontology/tolerances.lock +27 -0
  7. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/pyproject.toml +2 -1
  8. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/cli.py +116 -53
  9. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/query/drift/ontology_init.py +65 -0
  10. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_cli.py +121 -0
  11. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_ontology_init.py +70 -0
  12. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_patterns_yaml.py +79 -0
  13. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_triads.py +80 -0
  14. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/uv.lock +12 -1
  15. codegraph_brain-0.7.1/.release-please-manifest.json +0 -3
  16. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/.claude-plugin/marketplace.json +0 -0
  17. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/.github/workflows/autodoc.yml +0 -0
  18. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/.github/workflows/ci.yml +0 -0
  19. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/.github/workflows/release-please.yml +0 -0
  20. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/.gitignore +0 -0
  21. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/.pre-commit-config.yaml +0 -0
  22. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/.python-version +0 -0
  23. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/CLAUDE.md +0 -0
  24. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/CONTRIBUTING.md +0 -0
  25. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/LICENSE +0 -0
  26. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/Makefile +0 -0
  27. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/PRIVACY.md +0 -0
  28. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/README.md +0 -0
  29. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/benchmarks/guardian/CURATION.md +0 -0
  30. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/benchmarks/guardian/pr-122.yaml +0 -0
  31. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/benchmarks/guardian/pr-140.yaml +0 -0
  32. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/benchmarks/guardian/pr-141.yaml +0 -0
  33. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/benchmarks/guardian/pr-142.yaml +0 -0
  34. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/benchmarks/guardian/pr-143.yaml +0 -0
  35. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/benchmarks/guardian/pr-144.yaml +0 -0
  36. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/benchmarks/guardian/pr-278.yaml +0 -0
  37. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/benchmarks/guardian/results.jsonl +0 -0
  38. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/data/.gitkeep +0 -0
  39. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/AUDIT.md +0 -0
  40. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/CASE_STUDY.md +0 -0
  41. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/architecture/HOW_IT_WORKS.md +0 -0
  42. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/architecture/ONTOLOGY.md +0 -0
  43. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/architecture/PATTERNS_AND_TRIADS.md +0 -0
  44. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/architecture/SELF_PORTRAIT.md +0 -0
  45. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/architecture/diagrams/pipeline_flow.mermaid +0 -0
  46. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/architecture/health_badge.json +0 -0
  47. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/examples/.gitkeep +0 -0
  48. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/how-to/AGENT_ONBOARDING.md +0 -0
  49. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/how-to/CLI_USAGE.md +0 -0
  50. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/how-to/MCP_REFERENCE.md +0 -0
  51. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/ontology/.gitkeep +0 -0
  52. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/ontology/core.yaml +0 -0
  53. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/ontology/domains.yaml +0 -0
  54. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/ontology/patterns.yaml +0 -0
  55. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-06-09-domain-pattern-fingerprint-design.md +0 -0
  56. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-06-09-pattern-alphabet-motif-basis-design.md +0 -0
  57. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-06-10-guardian-sprint-design.md +0 -0
  58. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-06-11-fastapi-di-edges-design.md +0 -0
  59. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-06-11-guardian-chunked-review-design.md +0 -0
  60. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-06-11-guardian-chunker-design.md +0 -0
  61. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-06-11-mcp-drift-validate-fqn-design.md +0 -0
  62. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-06-11-resolver-split-design.md +0 -0
  63. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-06-11-symbol-import-edges-design.md +0 -0
  64. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-06-12-drift-empty-domains-design.md +0 -0
  65. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-06-12-gate-semantics-design.md +0 -0
  66. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-06-12-init-ontology-design.md +0 -0
  67. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-06-12-release-please-ci-design.md +0 -0
  68. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-06-13-suggest-packages-design.md +0 -0
  69. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-06-13-tangle-anti-pattern-design.md +0 -0
  70. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-07-29-guardian-skeptic-scoring-design.md +0 -0
  71. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-07-30-cgis-fractal-design.md +0 -0
  72. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-07-30-cgis-fractal-plan.md +0 -0
  73. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-07-30-chunk-source-filter-design.md +0 -0
  74. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-07-30-chunk-source-filter-plan.md +0 -0
  75. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-07-30-genai-client-close-design.md +0 -0
  76. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-07-30-genai-client-close-plan.md +0 -0
  77. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-07-30-guardian-precision-bench-design.md +0 -0
  78. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-07-30-guardian-precision-bench-plan.md +0 -0
  79. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-07-30-guardian-timeout-retry-design.md +0 -0
  80. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-07-30-guardian-timeout-retry-plan.md +0 -0
  81. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-07-31-finder-bug-class-taxonomy.md +0 -0
  82. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/BLUEPRINT.md +0 -0
  83. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/PRD.md +0 -0
  84. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/TDD.md +0 -0
  85. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/plans/2026-06-09-fingerprint-drift.md +0 -0
  86. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/plans/2026-06-10-guardian-context-skeptic-inline.md +0 -0
  87. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/plans/2026-06-10-guardian-structured-findings-bench.md +0 -0
  88. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/plans/2026-06-10-motif-basis-part-b.md +0 -0
  89. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/plans/2026-06-10-unified-pattern-alphabet.md +0 -0
  90. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/plans/2026-06-11-fastapi-di-edges.md +0 -0
  91. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/plans/2026-06-11-guardian-chunked-review.md +0 -0
  92. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/plans/2026-06-11-guardian-chunker.md +0 -0
  93. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/plans/2026-06-11-mcp-drift-validate-fqn.md +0 -0
  94. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/plans/2026-06-11-resolver-split.md +0 -0
  95. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/plans/2026-06-12-drift-empty-domains.md +0 -0
  96. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/plans/2026-06-12-gate-semantics.md +0 -0
  97. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/plans/2026-06-12-init-ontology.md +0 -0
  98. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/plans/2026-06-12-release-please-ci.md +0 -0
  99. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/plans/2026-06-12-symbol-import-edges.md +0 -0
  100. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/plans/2026-06-13-suggest-packages.md +0 -0
  101. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/plans/2026-07-29-guardian-skeptic-scoring.md +0 -0
  102. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/main.py +0 -0
  103. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/plugin/.claude-plugin/plugin.json +0 -0
  104. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/plugin/.mcp.json +0 -0
  105. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/plugin/README.md +0 -0
  106. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/plugin/skills/cgis/SKILL.md +0 -0
  107. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/plugin/skills/ingest/SKILL.md +0 -0
  108. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/release-please-config.json +0 -0
  109. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/scripts/gen_ideal_graph.py +0 -0
  110. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/scripts/generate_health.py +0 -0
  111. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/scripts/generate_mcp_ref.py +0 -0
  112. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/scripts/generate_schema_docs.py +0 -0
  113. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/scripts/guardian_bench.py +0 -0
  114. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/scripts/guardian_review.py +0 -0
  115. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/scripts/inject_readme_graph.py +0 -0
  116. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/scripts/probe_closure_gap.py +0 -0
  117. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/scripts/probe_tier_ladder.py +0 -0
  118. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/__init__.py +0 -0
  119. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/__main__.py +0 -0
  120. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/api/.gitkeep +0 -0
  121. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/api/__init__.py +0 -0
  122. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/api/mcp_server.py +0 -0
  123. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/core/.gitkeep +0 -0
  124. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/core/models.py +0 -0
  125. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/extractors/.gitkeep +0 -0
  126. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/extractors/_python_ast.py +0 -0
  127. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/extractors/_python_classes.py +0 -0
  128. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/extractors/_python_functions.py +0 -0
  129. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/extractors/_python_imports.py +0 -0
  130. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/extractors/_python_types.py +0 -0
  131. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/extractors/base.py +0 -0
  132. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/extractors/python_extractor.py +0 -0
  133. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/extractors/typescript_extractor.py +0 -0
  134. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/guardian/__init__.py +0 -0
  135. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/guardian/bench.py +0 -0
  136. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/guardian/chunked.py +0 -0
  137. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/guardian/chunker.py +0 -0
  138. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/guardian/collector.py +0 -0
  139. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/guardian/core.py +0 -0
  140. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/guardian/diff_index.py +0 -0
  141. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/guardian/findings.py +0 -0
  142. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/guardian/github_poster.py +0 -0
  143. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/guardian/metrics.py +0 -0
  144. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/guardian/prompts.py +0 -0
  145. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/guardian/providers/__init__.py +0 -0
  146. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/guardian/providers/base.py +0 -0
  147. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/guardian/providers/gemini.py +0 -0
  148. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/guardian/providers/mistral.py +0 -0
  149. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/guardian/providers/ollama.py +0 -0
  150. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/guardian/recording.py +0 -0
  151. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/guardian/render.py +0 -0
  152. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/guardian/runner.py +0 -0
  153. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/guardian/skeptic.py +0 -0
  154. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/pipeline.py +0 -0
  155. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/py.typed +0 -0
  156. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/query/analysis/__init__.py +0 -0
  157. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/query/analysis/analyzer.py +0 -0
  158. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/query/analysis/anomaly.py +0 -0
  159. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/query/analysis/cohesion.py +0 -0
  160. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/query/analysis/health.py +0 -0
  161. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/query/analysis/suggest_service.py +0 -0
  162. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/query/context/__init__.py +0 -0
  163. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/query/context/audit.py +0 -0
  164. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/query/context/context_service.py +0 -0
  165. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/query/context/prompt.py +0 -0
  166. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/query/context/snippet.py +0 -0
  167. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/query/drift/__init__.py +0 -0
  168. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/query/drift/_scc.py +0 -0
  169. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/query/drift/drift.py +0 -0
  170. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/query/drift/drift_service.py +0 -0
  171. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/query/drift/fingerprint.py +0 -0
  172. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/query/drift/fractal.py +0 -0
  173. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/query/drift/quotient.py +0 -0
  174. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/query/drift/triads.py +0 -0
  175. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/query/engine.py +0 -0
  176. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/query/fqn.py +0 -0
  177. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/query/render/__init__.py +0 -0
  178. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/query/render/graph_json.py +0 -0
  179. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/query/render/mermaid.py +0 -0
  180. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/query/render/metrics.py +0 -0
  181. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/resolver/.gitkeep +0 -0
  182. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/resolver/__init__.py +0 -0
  183. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/resolver/engine.py +0 -0
  184. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/resolver/indices.py +0 -0
  185. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/resolver/symbols.py +0 -0
  186. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/resolver/uplift.py +0 -0
  187. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/storage/.gitkeep +0 -0
  188. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/storage/sqlite_store.py +0 -0
  189. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/integration/.gitkeep +0 -0
  190. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/self_parsing/.gitkeep +0 -0
  191. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/self_parsing/__init__.py +0 -0
  192. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/self_parsing/conftest.py +0 -0
  193. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/self_parsing/test_architecture.py +0 -0
  194. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/self_parsing/test_drift.py +0 -0
  195. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/self_parsing/test_fractal.py +0 -0
  196. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/self_parsing/test_init_ontology_roundtrip.py +0 -0
  197. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/self_parsing/test_self_parse.py +0 -0
  198. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/self_parsing/test_self_parse_ts.py +0 -0
  199. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/self_parsing/test_suggest.py +0 -0
  200. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/.gitkeep +0 -0
  201. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/conftest.py +0 -0
  202. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/guardian_stubs.py +0 -0
  203. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test___main__.py +0 -0
  204. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_analyzer.py +0 -0
  205. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_audit.py +0 -0
  206. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_cohesion.py +0 -0
  207. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_context_service.py +0 -0
  208. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_di_acceptance.py +0 -0
  209. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_drift.py +0 -0
  210. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_drift_service.py +0 -0
  211. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_fingerprint.py +0 -0
  212. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_fqn.py +0 -0
  213. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_fractal.py +0 -0
  214. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_gen_ideal_graph.py +0 -0
  215. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_generate_mcp_ref.py +0 -0
  216. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_graph_json.py +0 -0
  217. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_guardian_bench.py +0 -0
  218. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_guardian_chunked.py +0 -0
  219. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_guardian_chunker.py +0 -0
  220. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_guardian_collector.py +0 -0
  221. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_guardian_core.py +0 -0
  222. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_guardian_diff_index.py +0 -0
  223. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_guardian_findings.py +0 -0
  224. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_guardian_metrics.py +0 -0
  225. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_guardian_poster.py +0 -0
  226. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_guardian_providers.py +0 -0
  227. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_guardian_recording.py +0 -0
  228. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_guardian_render.py +0 -0
  229. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_guardian_runner.py +0 -0
  230. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_guardian_skeptic.py +0 -0
  231. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_health_scorer.py +0 -0
  232. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_import_acceptance.py +0 -0
  233. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_mcp_server.py +0 -0
  234. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_mermaid.py +0 -0
  235. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_metrics.py +0 -0
  236. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_models.py +0 -0
  237. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_ontology_compliance.py +0 -0
  238. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_pipeline.py +0 -0
  239. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_prompt.py +0 -0
  240. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_python_extractor.py +0 -0
  241. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_quotient.py +0 -0
  242. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_resolver.py +0 -0
  243. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_resolver_indices.py +0 -0
  244. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_resolver_symbols.py +0 -0
  245. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_snippet.py +0 -0
  246. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_sqlite_store.py +0 -0
  247. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_suggest_service.py +0 -0
  248. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_typescript_extractor.py +0 -0
  249. {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_uplift.py +0 -0
@@ -67,7 +67,7 @@ jobs:
67
67
  uses: astral-sh/setup-uv@caf0cab7a618c569241d31dcd442f54681755d39 # v3
68
68
 
69
69
  - name: Install dependencies
70
- run: uv sync --group guardian
70
+ run: uv sync --extra guardian
71
71
 
72
72
  - name: Build code graph
73
73
  run: uv run cgis ingest ./src --output graph.db
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "0.7.2"
3
+ }
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.7.2](https://github.com/zaebee/codegraph-brain/compare/codegraph-brain-v0.7.1...codegraph-brain-v0.7.2) (2026-07-31)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **ci:** install guardian deps with --extra, not --group ([#300](https://github.com/zaebee/codegraph-brain/issues/300)) ([1da003d](https://github.com/zaebee/codegraph-brain/commit/1da003d811db8e60201693905a6f83c2fccf48cf))
9
+ * **cli:** escape data interpolated into Rich console output ([#298](https://github.com/zaebee/codegraph-brain/issues/298)) ([dd4943d](https://github.com/zaebee/codegraph-brain/commit/dd4943d0fea1100b47130fcb0790e940da83276a))
10
+ * **drift:** pin measured depth when a domain is shallower than its gate ([#297](https://github.com/zaebee/codegraph-brain/issues/297)) ([1ebb93a](https://github.com/zaebee/codegraph-brain/commit/1ebb93a888037af5ad333ddffc7896af61f6eb5e)), closes [#229](https://github.com/zaebee/codegraph-brain/issues/229)
11
+
12
+
13
+ ### Documentation
14
+
15
+ * add the chunked-review lab note (a negative result worth keeping) ([#299](https://github.com/zaebee/codegraph-brain/issues/299)) ([265fefd](https://github.com/zaebee/codegraph-brain/commit/265fefda7ee30ece692d13ea0bb171d99ac18bd9)), closes [#160](https://github.com/zaebee/codegraph-brain/issues/160)
16
+
3
17
  ## [0.7.1](https://github.com/zaebee/codegraph-brain/compare/codegraph-brain-v0.7.0...codegraph-brain-v0.7.1) (2026-07-31)
4
18
 
5
19
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codegraph-brain
3
- Version: 0.7.1
3
+ Version: 0.7.2
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
@@ -0,0 +1,132 @@
1
+ # Lab note: the chunked-review experiment — a negative result worth keeping
2
+
3
+ **Date:** 2026-06-11
4
+ **Experiment:** #154 slices 1–2 · **Code:** PR #157 (`6178bae`), PR #159 (`2030eb8`)
5
+ **Verdict:** gate FAILED 3/4 — feature stays behind a flag, off in production
6
+ **Specs:** [`2026-06-11-guardian-chunker-design.md`](../specs/2026-06-11-guardian-chunker-design.md) ·
7
+ [`2026-06-11-guardian-chunked-review-design.md`](../specs/2026-06-11-guardian-chunked-review-design.md)
8
+
9
+ The headline: the hypothesis survived contact with reality only partially, and the
10
+ honest negative result is the most valuable artifact.
11
+
12
+ ## The problem
13
+
14
+ Guardian (our in-repo LLM reviewer, replacing gemini-code-assist before its
15
+ 2026-07-17 sunset) reliably LGTMs large PRs. Baseline bench (6 replayed PRs with
16
+ hand-curated ground truth, 3 runs each): mean recall 0.22, but **recall = 0 on all
17
+ three largest PRs** (36–62K prompt tokens). Plan-2 ablations proved more context
18
+ doesn't help: feeding 94K tokens of full files still produced LGTMs.
19
+
20
+ Diagnosis: **attention dilution** — the finder skims, it doesn't read.
21
+
22
+ ## The hypothesis (#154)
23
+
24
+ If the finder can't read a big diff, give it small ones: split changed files into
25
+ connected components over the code graph (IMPORTS/CALLS from `cgis ingest`), run a
26
+ finder pass per chunk with a per-chunk context budget, merge, dedup, run ONE
27
+ skeptic pass, post one report. Each chunk = a small, complete world.
28
+
29
+ ## What we built
30
+
31
+ - **Slice 1 (PR #157, `6178bae`)** — `chunker.py`, pure logic. Per-file diff
32
+ splitting (rename/deletion/binary/git-quoted-path handling), union-find over
33
+ graph edges, test-pairing heuristic, deterministic ordering, honest degradation
34
+ to isolated chunks when the graph DB is missing.
35
+ - **Slice 2 (PR #159, `2030eb8`)** — wiring behind `GUARDIAN_FEATURES=chunked`.
36
+ `run_review_routed` entry point, per-chunk finder passes with per-chunk
37
+ `full_files` (120K char budget) + impact graph, out-of-chunk hallucination
38
+ filter with LLM-path-artifact normalization (`a/x.py` → `x.py`),
39
+ `(file,line,category)` dedup, single skeptic over finding-bearing chunks only,
40
+ `MAX_CHUNKS=8` cap with overflow merge, `cumulative_usage` token accounting
41
+ (which incidentally fixed a latent retry-loses-tokens bug).
42
+
43
+ Both slices SDD-executed (subagent implementer + spec reviewer + quality
44
+ reviewer), bench-gated by spec §7 **before** enabling anything in prod.
45
+
46
+ ## The gate (pre-registered, spec §7)
47
+
48
+ 1. mean recall (6 PRs) ≥ 0.27 — baseline + 0.05
49
+ 2. mean recall over large PRs {140, 143, 144} > 0 — the hypothesis must show where it matters
50
+ 3. mean noise ≤ 1.5/PR
51
+ 4. no PR drops > 0.05 below its baseline
52
+
53
+ ## The result: FAIL 3/4
54
+
55
+ 18 runs (6 PRs × 3, finder `gemini-2.5-flash`, skeptic `gemini-3.5-flash`), data in
56
+ `benchmarks/guardian/results.jsonl`:
57
+
58
+ | PR | chunks | recall base → chunked | noise base → chunked |
59
+ |---|---|---|---|
60
+ | 122 | 8 (cap) | 0.152 → 0.152 | 1.0 → **6.0** |
61
+ | 140 | 5 | 0 → **0.067** ✨ | 3.3 → 3.3 |
62
+ | 141 (noise probe) | 5 | 1.0 → 1.0 | 0.7 → 2.7 |
63
+ | 142 | 1 (degenerate) | 0.167 → 0 | 0 → 0 |
64
+ | 143 | 7 | 0 → 0 | 0 → 2.7 |
65
+ | 144 | 5 | 0 → 0 | 0.7 → 1.3 |
66
+
67
+ Mean recall 0.203 (❌ vs 0.27), large-PR recall 0.022 (✅ — but only PR 140
68
+ contributed), noise 2.67/PR (❌ vs 1.5), PR 142 regression (❌, with a variance
69
+ caveat: its baseline rested on one lucky 0.5 run out of three).
70
+
71
+ ## What we learned
72
+
73
+ 1. **Attention dilution is real but not the whole story.** Chunking produced the
74
+ first-ever nonzero recall on a large PR (140). But 143/144 stayed at zero across
75
+ 21 small, focused finder calls. Those ground-truth bugs are invisible to this
76
+ finder *regardless of context size* — a model/prompt capability ceiling, not a
77
+ context problem.
78
+ 2. **Chunking multiplies noise superlinearly.** Every chunk invents its own false
79
+ positives (122: 8 chunks → noise 6.0/run). A single skeptic pass over
80
+ concatenated chunk contexts can't compensate.
81
+ 3. **Pre-registered gates work.** Writing pass/fail criteria into the spec before
82
+ spending money made the no-go decision mechanical instead of motivated. The flag
83
+ stays OFF in prod; the code sleeps behind it, fully tested, waiting for a better
84
+ noise story.
85
+ 4. **Honest degradation pays off.** Every failure mode (no graph DB, flaky chunk,
86
+ unparsable finder output, dead skeptic) degrades to something useful instead of
87
+ crashing — which also made the bench itself survivable.
88
+
89
+ ## Where this goes next (slice 3 candidates, not committed)
90
+
91
+ - Noise mitigation: stricter per-chunk finder prompt, per-chunk findings cap,
92
+ skeptic hardening for chunked volume.
93
+ - Ensemble finder (#155): union of N runs matched *different* GT findings
94
+ (122: 0.09/0.09/0.18 → union ~0.27) — possibly a cheaper recall lever than chunking.
95
+ - Eyeball the 143/144 ground truths: if they need cross-file reasoning the finder
96
+ can't do, no amount of context plumbing will fix it.
97
+ - Quotient-level "architectural chunk" (domain-graph drift as its own review unit).
98
+
99
+ Total experiment cost: ~5 PLN of gemini billing for the slice-2 bench (1.03M prompt
100
+ tokens), on top of Plan-2's bench budget. Money well burned.
101
+
102
+ ## Reproducing these numbers
103
+
104
+ `benchmarks/guardian/results.jsonl` is committed, so the chunked side of this
105
+ experiment is re-derivable. Group rows by `pr`, take the 3 rows whose `features`
106
+ field contains `chunked` (all share `guardian_sha` `a866a2ac`), and average per PR:
107
+
108
+ | Figure | Note | Recomputed |
109
+ |---|---|---|
110
+ | mean recall (6 PRs) | 0.203 | 0.203 |
111
+ | mean noise / PR | 2.67 | 2.667 |
112
+ | large-PR recall {140,143,144} | 0.022 | 0.022 |
113
+
114
+ Every cell of the table's **chunked** column reproduces exactly.
115
+
116
+ **The `base` column does not.** No single `guardian_sha` slice of the
117
+ `features == ""` rows reproduces it, and the baseline headline of 0.22 mean recall
118
+ does not fall out of any of them either — the two candidate slices with exactly 3
119
+ runs per PR give 0.191 and 0.167, and the earliest slice gives 0.271 on uneven run
120
+ counts. The file has also been appended to since (there are July 2026 rows from
121
+ later work), so the working set used to compute the baseline in June is no longer
122
+ recoverable from it.
123
+
124
+ This is recorded rather than corrected: a lab journal is a historical record, and
125
+ silently restating the baseline would be worse than flagging it. The gate
126
+ conclusions do not rest on it — gates 1 and 3 failed on absolute values (0.203 <
127
+ 0.27, 2.67 > 1.5), and both reproduce. Gate 4 (per-PR regression vs baseline) is
128
+ the one that cannot be independently re-checked today.
129
+
130
+ ## Refs
131
+
132
+ #154, #155, #160 · PR #157, PR #159
@@ -0,0 +1,27 @@
1
+ # Drift-tolerance ratchet lock (#151) — generated from patterns.yaml, edited by hand.
2
+ #
3
+ # Project convention since #140/#141: drift_tolerance is a RATCHET. Values may
4
+ # only go DOWN as domains get healthier, never up to mask a regression.
5
+ #
6
+ # test_tolerances_never_exceed_the_lock enforces it: lowering a tolerance means
7
+ # updating this file in the same PR, which puts the change in the diff where a
8
+ # reviewer sees it. Raising one fails the suite.
9
+ #
10
+ # Deliberate re-baselining (e.g. the Part B re-baseline in #144) is allowed —
11
+ # update the lock and justify it in the PR description.
12
+
13
+ project_domains:
14
+ extraction: 0.40
15
+ resolution: 0.40
16
+ pipeline: 0.25
17
+ storage: 0.20
18
+ query: 0.20
19
+ components: 0.20
20
+ layout: 0.50
21
+ hooks: 0.15
22
+ store: 0.15
23
+ providers: 0.15
24
+ utils: 0.15
25
+
26
+ project_level:
27
+ cgis-project: 0.15
@@ -7,7 +7,7 @@ packages = ["src/cgis"]
7
7
 
8
8
  [project]
9
9
  name = "codegraph-brain"
10
- version = "0.7.1"
10
+ version = "0.7.2"
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"
@@ -81,6 +81,7 @@ dev = [
81
81
  "interrogate>=1.7.0",
82
82
  "ipdb>=0.13.13",
83
83
  "mypy>=2.1.0",
84
+ "networkx>=3.3",
84
85
  "pre-commit>=4.6.0",
85
86
  "pytest>=9.0.3",
86
87
  "pytest-asyncio>=0.24.0",
@@ -1,4 +1,22 @@
1
- """ "CLI to run pipeline."""
1
+ """ "CLI to run pipeline.
2
+
3
+ Rich markup convention (#148)
4
+ -----------------------------
5
+ Rich parses ``[...]`` in everything it renders, so any *data* reaching a console
6
+ sink must be wrapped in ``rich.markup.escape``. This applies to ``console.print``
7
+ f-strings, ``Table.add_row`` cells, and panel/tree labels alike.
8
+
9
+ The failure mode is silent: unescaped text is not styled, it *vanishes*. PR #144
10
+ shipped a line where ``[cgis-project]`` rendered as an empty string, so the
11
+ operator saw a blank where their domain name should have been.
12
+
13
+ wrong: console.print(f"[bold red]Not found:[/bold red] {path}")
14
+ right: console.print(f"[bold red]Not found:[/bold red] {escape(path)}")
15
+
16
+ Escape anything derived from config, the graph, the filesystem, CLI arguments, or
17
+ an exception message — names, FQNs, file paths, pattern names, ``{e}``. Literal
18
+ style tags you wrote yourself and pure numbers need no escaping.
19
+ """
2
20
 
3
21
  import dataclasses
4
22
  import json as _json
@@ -190,10 +208,12 @@ def ingest(
190
208
  pipeline = IngestionPipeline(extractors, domains_config=domains)
191
209
 
192
210
  if domains and not Path(domains).is_file():
193
- console.print(f"[bold red]❌ Domains config file not found:[/bold red] {domains}")
211
+ console.print(
212
+ f"[bold red]❌ Domains config file not found:[/bold red] {escape(str(domains))}"
213
+ )
194
214
  raise typer.Exit(code=1)
195
215
 
196
- console.print(f"[bold blue]🚀 Starting ingestion for:[/bold blue] {path}")
216
+ console.print(f"[bold blue]🚀 Starting ingestion for:[/bold blue] {escape(path)}")
197
217
 
198
218
  if incremental and output.endswith(".json"):
199
219
  console.print(
@@ -233,7 +253,7 @@ def ingest(
233
253
  console.print("[bold green]✅ Success![/bold green] Graph data ready.")
234
254
 
235
255
  except Exception as e:
236
- console.print(f"[bold red]❌ Error during ingestion:[/bold red] {e}")
256
+ console.print(f"[bold red]❌ Error during ingestion:[/bold red] {escape(str(e))}")
237
257
  raise typer.Exit(code=1) from e
238
258
 
239
259
 
@@ -272,16 +292,16 @@ def _resolve_cli_fqn(store: SQLiteStore, target: str, kind: str) -> str:
272
292
  resolution = resolve_fqn(store, target)
273
293
  if resolution.resolved is None:
274
294
  if resolution.candidates:
275
- console.print(f"[bold red]❌ Ambiguous FQN:[/bold red] {target}")
295
+ console.print(f"[bold red]❌ Ambiguous FQN:[/bold red] {escape(target)}")
276
296
  for candidate in resolution.candidates:
277
- console.print(f" [dim]- {candidate}[/dim]")
297
+ console.print(f" [dim]- {escape(candidate)}[/dim]")
278
298
  if resolution.truncated:
279
299
  console.print(_TRUNCATED_HINT)
280
300
  else:
281
- console.print(f"[bold red]❌ {kind} not found in graph:[/bold red] {target}")
301
+ console.print(f"[bold red]❌ {kind} not found in graph:[/bold red] {escape(target)}")
282
302
  raise typer.Exit(code=1)
283
303
  if resolution.via_suffix:
284
- console.print(f"[dim]Resolved '{target}' → '{resolution.resolved}'[/dim]")
304
+ console.print(f"[dim]Resolved '{escape(target)}' → '{escape(resolution.resolved)}'[/dim]")
285
305
  return resolution.resolved
286
306
 
287
307
 
@@ -368,7 +388,9 @@ def trace(
368
388
  """
369
389
  path = Path(db)
370
390
  if not path.is_file():
371
- console.print(f"[bold red]❌ Database not found:[/bold red] {db}. Run `ingest` first.")
391
+ console.print(
392
+ f"[bold red]❌ Database not found:[/bold red] {escape(db)}. Run `ingest` first."
393
+ )
372
394
  raise typer.Exit(code=1)
373
395
 
374
396
  allowed: frozenset[EdgeType] | None = None if show_structure else BEHAVIORAL_EDGE_TYPES
@@ -497,7 +519,9 @@ def impact(
497
519
  """
498
520
  path = Path(db)
499
521
  if not path.is_file():
500
- console.print(f"[bold red]❌ Database not found:[/bold red] {db}. Run `ingest` first.")
522
+ console.print(
523
+ f"[bold red]❌ Database not found:[/bold red] {escape(db)}. Run `ingest` first."
524
+ )
501
525
  raise typer.Exit(code=1)
502
526
 
503
527
  allowed: frozenset[EdgeType] | None = None if show_structure else BEHAVIORAL_EDGE_TYPES
@@ -562,14 +586,16 @@ def validate(
562
586
  """
563
587
  path = Path(db)
564
588
  if not path.is_file():
565
- console.print(f"[bold red]❌ Database not found:[/bold red] {db}. Run `ingest` first.")
589
+ console.print(
590
+ f"[bold red]❌ Database not found:[/bold red] {escape(db)}. Run `ingest` first."
591
+ )
566
592
  raise typer.Exit(code=1)
567
593
 
568
594
  try:
569
595
  with SQLiteStore(db) as store:
570
596
  stats = store.get_edge_stats()
571
597
  except Exception as e:
572
- console.print(f"[bold red]❌ Error reading database:[/bold red] {e}")
598
+ console.print(f"[bold red]❌ Error reading database:[/bold red] {escape(str(e))}")
573
599
  raise typer.Exit(code=1) from e
574
600
 
575
601
  def _pct(n: int) -> str:
@@ -600,7 +626,7 @@ def validate(
600
626
  top_table.add_column("count", justify="right", style="yellow")
601
627
  for target, count in stats.top_unresolved:
602
628
  name = target.removeprefix(RAW_CALL_PREFIX)
603
- top_table.add_row(name, str(count))
629
+ top_table.add_row(escape(name), str(count))
604
630
  console.print(top_table)
605
631
 
606
632
  threshold_pct = threshold * 100
@@ -637,7 +663,9 @@ def find(
637
663
  """
638
664
  path = Path(db)
639
665
  if not path.is_file():
640
- console.print(f"[bold red]❌ Database not found:[/bold red] {db}. Run `ingest` first.")
666
+ console.print(
667
+ f"[bold red]❌ Database not found:[/bold red] {escape(db)}. Run `ingest` first."
668
+ )
641
669
  raise typer.Exit(code=1)
642
670
 
643
671
  kinds = (kind.strip().upper(),) if kind and kind.strip() else ()
@@ -654,7 +682,12 @@ def find(
654
682
  table.add_column("FQN", style="yellow")
655
683
  table.add_column("Location", style="dim")
656
684
  for node in matches:
657
- table.add_row(node.name, node.type.value, node.id, f"{node.file_path}:{node.start_line}")
685
+ table.add_row(
686
+ escape(node.name),
687
+ node.type.value,
688
+ escape(node.id),
689
+ escape(f"{node.file_path}:{node.start_line}"),
690
+ )
658
691
  console.print(table)
659
692
 
660
693
 
@@ -675,14 +708,16 @@ def structure(
675
708
  """
676
709
  path = Path(db)
677
710
  if not path.is_file():
678
- console.print(f"[bold red]❌ Database not found:[/bold red] {db}. Run `ingest` first.")
711
+ console.print(
712
+ f"[bold red]❌ Database not found:[/bold red] {escape(db)}. Run `ingest` first."
713
+ )
679
714
  raise typer.Exit(code=1)
680
715
 
681
716
  # Normalize file path → FQN
682
717
  if "/" in target or "\\" in target or target.endswith(".py"):
683
718
  normalized = target.replace("\\", "/").removeprefix("./")
684
719
  target = file_path_to_module_fqn(normalized)
685
- console.print(f"[dim]→ FQN: {target}[/dim]")
720
+ console.print(f"[dim]→ FQN: {escape(target)}[/dim]")
686
721
 
687
722
  with SQLiteStore(db) as store:
688
723
  target = _resolve_cli_fqn(store, target, "Node")
@@ -696,7 +731,7 @@ def structure(
696
731
  typer.echo(_render_graph(output_format, target, nodes, edges))
697
732
  return
698
733
 
699
- console.print(f"[bold blue]📦 Structure of:[/bold blue] {target}\n")
734
+ console.print(f"[bold blue]📦 Structure of:[/bold blue] {escape(target)}\n")
700
735
  root_label = (
701
736
  f"[bold cyan]{target_node.type.value}[/bold cyan] [yellow]{target_node.id}[/yellow]"
702
737
  )
@@ -765,7 +800,9 @@ def analyze(
765
800
  """
766
801
  path = Path(db)
767
802
  if not path.is_file():
768
- console.print(f"[bold red]❌ Database not found:[/bold red] {db}. Run `ingest` first.")
803
+ console.print(
804
+ f"[bold red]❌ Database not found:[/bold red] {escape(db)}. Run `ingest` first."
805
+ )
769
806
  raise typer.Exit(code=1)
770
807
 
771
808
  with SQLiteStore(db) as store:
@@ -809,8 +846,8 @@ def analyze(
809
846
  f"severity=[bold {colour}]{a.severity_score:.2f}[/bold {colour}]"
810
847
  )
811
848
  for key, val in a.metrics.items():
812
- console.print(f" [dim]{key}:[/dim] {val}")
813
- console.print(f" [italic]💡 {a.refactoring_hint}[/italic]\n")
849
+ console.print(f" [dim]{escape(str(key))}:[/dim] {escape(str(val))}")
850
+ console.print(f" [italic]💡 {escape(a.refactoring_hint)}[/italic]\n")
814
851
 
815
852
 
816
853
  _DEFAULT_METRICS = "guardian_metrics.jsonl"
@@ -827,7 +864,7 @@ def guardian_rate(
827
864
  if updated:
828
865
  console.print(f"[green]✅ PR #{pr}: recorded {applied} applied findings.[/green]")
829
866
  else:
830
- console.print(f"[red]❌ No unrated entry found for PR #{pr} in {metrics}.[/red]")
867
+ console.print(f"[red]❌ No unrated entry found for PR #{pr} in {escape(metrics)}.[/red]")
831
868
  raise typer.Exit(code=1)
832
869
 
833
870
 
@@ -839,7 +876,7 @@ def guardian_stats(
839
876
  """Show Guardian review quality metrics trend."""
840
877
  reviews = load_reviews(Path(metrics))
841
878
  if not reviews:
842
- console.print(f"[yellow]No metrics found in {metrics}.[/yellow]")
879
+ console.print(f"[yellow]No metrics found in {escape(metrics)}.[/yellow]")
843
880
  raise typer.Exit
844
881
 
845
882
  reviews = reviews[-last:]
@@ -871,7 +908,7 @@ def guardian_stats(
871
908
  lgtm = "✅" if r.get("lgtm") else ""
872
909
  table.add_row(
873
910
  pr_str,
874
- str(r.get("model", "")),
911
+ escape(str(r.get("model", ""))),
875
912
  f"{tokens:,}",
876
913
  str(findings),
877
914
  applied_str,
@@ -962,8 +999,8 @@ def _render_drift_table(reports: list[DriftReport]) -> None:
962
999
  table.add_column("Status", justify="center")
963
1000
  for r in reports:
964
1001
  table.add_row(
965
- r.fqn_prefix,
966
- r.expected_pattern or "(hygiene)",
1002
+ escape(r.fqn_prefix),
1003
+ escape(r.expected_pattern or "(hygiene)"),
967
1004
  f"{r.drift_score:.2f}",
968
1005
  f"{r.tv_imports:.2f}" if r.tv_imports is not None else "—",
969
1006
  f"{r.tv_calls:.2f}" if r.tv_calls is not None else "—",
@@ -1029,11 +1066,13 @@ def drift(
1029
1066
  Exits with code 1 if any domain drift score meets or exceeds the critical threshold.
1030
1067
  """
1031
1068
  if not Path(db).is_file():
1032
- console.print(f"[bold red]❌ Database not found:[/bold red] {db}. Run `ingest` first.")
1069
+ console.print(
1070
+ f"[bold red]❌ Database not found:[/bold red] {escape(db)}. Run `ingest` first."
1071
+ )
1033
1072
  raise typer.Exit(code=1)
1034
1073
 
1035
1074
  if not Path(patterns).is_file():
1036
- console.print(f"[bold red]❌ Patterns file not found:[/bold red] {patterns}")
1075
+ console.print(f"[bold red]❌ Patterns file not found:[/bold red] {escape(patterns)}")
1037
1076
  raise typer.Exit(code=1)
1038
1077
 
1039
1078
  try:
@@ -1041,7 +1080,7 @@ def drift(
1041
1080
  db, patterns, max_drift=max_drift, profile=profile, max_residual=max_residual
1042
1081
  )
1043
1082
  except Exception as e:
1044
- console.print(f"[bold red]❌ Error during drift analysis:[/bold red] {e}")
1083
+ console.print(f"[bold red]❌ Error during drift analysis:[/bold red] {escape(str(e))}")
1045
1084
  raise typer.Exit(code=1) from e
1046
1085
 
1047
1086
  if output_format == DriftOutputFormat.JSON:
@@ -1123,17 +1162,19 @@ def init_ontology(
1123
1162
  ) -> None:
1124
1163
  """Propose a starter patterns.yaml from the measured graph (measure-then-label)."""
1125
1164
  if Path(out).exists() and not force:
1126
- console.print(f"[bold red]❌ {out} already exists[/bold red] — use --force to overwrite.")
1165
+ console.print(
1166
+ f"[bold red]❌ {escape(out)} already exists[/bold red] — use --force to overwrite."
1167
+ )
1127
1168
  raise typer.Exit(code=1)
1128
1169
  try:
1129
1170
  text = propose_ontology(db, margin=margin, min_nodes=min_nodes, depth=depth)
1130
1171
  except FileNotFoundError as e:
1131
- console.print(f"[bold red]❌ {e}[/bold red]")
1172
+ console.print(f"[bold red]❌ {escape(str(e))}[/bold red]")
1132
1173
  raise typer.Exit(code=1) from e
1133
1174
  Path(out).write_text(text)
1134
1175
  _render_init_summary(text)
1135
- console.print(f"[bold green]✅ Proposed ontology written to {out}[/bold green]")
1136
- console.print(f"Next: [cyan]cgis drift --db {db} --patterns {out}[/cyan]")
1176
+ console.print(f"[bold green]✅ Proposed ontology written to {escape(out)}[/bold green]")
1177
+ console.print(f"Next: [cyan]cgis drift --db {escape(db)} --patterns {escape(out)}[/cyan]")
1137
1178
 
1138
1179
 
1139
1180
  @app.command()
@@ -1163,7 +1204,9 @@ def context(
1163
1204
  """
1164
1205
  path = Path(db)
1165
1206
  if not path.is_file():
1166
- console.print(f"[bold red]❌ Database not found:[/bold red] {db}. Run `ingest` first.")
1207
+ console.print(
1208
+ f"[bold red]❌ Database not found:[/bold red] {escape(db)}. Run `ingest` first."
1209
+ )
1167
1210
  raise typer.Exit(code=1)
1168
1211
 
1169
1212
  err_console = Console(stderr=True)
@@ -1171,16 +1214,18 @@ def context(
1171
1214
  resolution = resolve_fqn(store, fqn)
1172
1215
  if resolution.resolved is None:
1173
1216
  if resolution.candidates:
1174
- err_console.print(f"[bold red]❌ Ambiguous FQN:[/bold red] {fqn}")
1217
+ err_console.print(f"[bold red]❌ Ambiguous FQN:[/bold red] {escape(fqn)}")
1175
1218
  for candidate in resolution.candidates:
1176
- err_console.print(f" [dim]- {candidate}[/dim]")
1219
+ err_console.print(f" [dim]- {escape(candidate)}[/dim]")
1177
1220
  if resolution.truncated:
1178
1221
  err_console.print(_TRUNCATED_HINT)
1179
1222
  else:
1180
- err_console.print(f"[bold red]❌ Node not found in graph:[/bold red] {fqn}")
1223
+ err_console.print(f"[bold red]❌ Node not found in graph:[/bold red] {escape(fqn)}")
1181
1224
  raise typer.Exit(code=1)
1182
1225
  if resolution.via_suffix:
1183
- err_console.print(f"[dim]Resolved '{fqn}' → '{resolution.resolved}'[/dim]")
1226
+ err_console.print(
1227
+ f"[dim]Resolved '{escape(fqn)}' → '{escape(resolution.resolved)}'[/dim]"
1228
+ )
1184
1229
  payload = build_context(store, resolution.resolved, depth=depth, source_root=source_root)
1185
1230
  typer.echo(payload)
1186
1231
 
@@ -1193,14 +1238,16 @@ def _render_metrics(report: ArchitectureReport) -> None:
1193
1238
  bottlenecks.add_column("In", justify="right", style="green")
1194
1239
  bottlenecks.add_column("Out", justify="right", style="yellow")
1195
1240
  for m in report.bottlenecks:
1196
- bottlenecks.add_row(m.node_id, m.node_type, str(m.in_degree), str(m.out_degree))
1241
+ bottlenecks.add_row(
1242
+ escape(m.node_id), escape(m.node_type), str(m.in_degree), str(m.out_degree)
1243
+ )
1197
1244
  console.print(bottlenecks)
1198
1245
 
1199
1246
  gods = Table(title="🏛️ God classes (top by declared members)")
1200
1247
  gods.add_column("Class", style="cyan")
1201
1248
  gods.add_column("Members", justify="right", style="red")
1202
1249
  for m in report.god_classes:
1203
- gods.add_row(m.node_id, str(m.out_degree))
1250
+ gods.add_row(escape(m.node_id), str(m.out_degree))
1204
1251
  console.print(gods)
1205
1252
 
1206
1253
  critical = Table(title="⭐ Critical nodes (top by PageRank — transitive importance)")
@@ -1213,7 +1260,11 @@ def _render_metrics(report: ArchitectureReport) -> None:
1213
1260
  # In/Out are over the same internal graph PageRank ran on; a high rank with
1214
1261
  # In=0 is a dangling-mass leaf artifact, not a real hub (#237).
1215
1262
  critical.add_row(
1216
- m.node_id, m.node_type, f"{m.page_rank:.4f}", str(m.in_degree), str(m.out_degree)
1263
+ escape(m.node_id),
1264
+ escape(m.node_type),
1265
+ f"{m.page_rank:.4f}",
1266
+ str(m.in_degree),
1267
+ str(m.out_degree),
1217
1268
  )
1218
1269
  console.print(critical)
1219
1270
 
@@ -1245,7 +1296,9 @@ def metrics(
1245
1296
  console.print("[bold red]❌ metrics supports --format text or json only.[/bold red]")
1246
1297
  raise typer.Exit(code=2)
1247
1298
  if not Path(db).is_file():
1248
- console.print(f"[bold red]❌ Database not found:[/bold red] {db}. Run `ingest` first.")
1299
+ console.print(
1300
+ f"[bold red]❌ Database not found:[/bold red] {escape(db)}. Run `ingest` first."
1301
+ )
1249
1302
  raise typer.Exit(code=1)
1250
1303
  try:
1251
1304
  with DuckDBAnalyzer(db) as analyzer:
@@ -1253,7 +1306,7 @@ def metrics(
1253
1306
  bottleneck_limit=limit, god_limit=limit, critical_limit=limit, exclude=exclude
1254
1307
  )
1255
1308
  except Exception as e: # duckdb missing, extension fetch, or a non-SQLite file
1256
- console.print(f"[bold red]❌ {e}[/bold red]")
1309
+ console.print(f"[bold red]❌ {escape(str(e))}[/bold red]")
1257
1310
  raise typer.Exit(code=1) from e
1258
1311
 
1259
1312
  if output_format == OutputFormat.JSON:
@@ -1271,16 +1324,20 @@ def _resolve_checkpoint(store: SQLiteStore, target: str) -> str:
1271
1324
  resolution = resolve_fqn(store, target)
1272
1325
  if resolution.resolved is None:
1273
1326
  if resolution.candidates:
1274
- err_console.print(f"[bold red]❌ Ambiguous checkpoint FQN:[/bold red] {target}")
1327
+ err_console.print(f"[bold red]❌ Ambiguous checkpoint FQN:[/bold red] {escape(target)}")
1275
1328
  for candidate in resolution.candidates:
1276
- err_console.print(f" [dim]- {candidate}[/dim]")
1329
+ err_console.print(f" [dim]- {escape(candidate)}[/dim]")
1277
1330
  if resolution.truncated:
1278
1331
  err_console.print(_TRUNCATED_HINT)
1279
1332
  else:
1280
- err_console.print(f"[bold red]❌ Checkpoint not found in graph:[/bold red] {target}")
1333
+ err_console.print(
1334
+ f"[bold red]❌ Checkpoint not found in graph:[/bold red] {escape(target)}"
1335
+ )
1281
1336
  raise typer.Exit(code=1)
1282
1337
  if resolution.via_suffix:
1283
- err_console.print(f"[dim]Resolved '{target}' → '{resolution.resolved}'[/dim]")
1338
+ err_console.print(
1339
+ f"[dim]Resolved '{escape(target)}' → '{escape(resolution.resolved)}'[/dim]"
1340
+ )
1284
1341
  return resolution.resolved
1285
1342
 
1286
1343
 
@@ -1347,7 +1404,9 @@ def audit(
1347
1404
  )
1348
1405
  raise typer.Exit(code=2)
1349
1406
  if not Path(db).is_file():
1350
- console.print(f"[bold red]❌ Database not found:[/bold red] {db}. Run `ingest` first.")
1407
+ console.print(
1408
+ f"[bold red]❌ Database not found:[/bold red] {escape(db)}. Run `ingest` first."
1409
+ )
1351
1410
  raise typer.Exit(code=1)
1352
1411
 
1353
1412
  with SQLiteStore(db) as store:
@@ -1434,12 +1493,14 @@ def suggest_packages_cmd(
1434
1493
  always exits 0 on success. Run `ingest` first.
1435
1494
  """
1436
1495
  if not Path(db).is_file():
1437
- console.print(f"[bold red]❌ Database not found:[/bold red] {db}. Run `ingest` first.")
1496
+ console.print(
1497
+ f"[bold red]❌ Database not found:[/bold red] {escape(db)}. Run `ingest` first."
1498
+ )
1438
1499
  raise typer.Exit(code=1)
1439
1500
  try:
1440
1501
  report = suggest_packages(db, prefix, with_calls=with_calls, min_q=min_q)
1441
1502
  except Exception as e:
1442
- console.print(f"[bold red]❌ Error during suggest-packages:[/bold red] {e}")
1503
+ console.print(f"[bold red]❌ Error during suggest-packages:[/bold red] {escape(str(e))}")
1443
1504
  raise typer.Exit(code=1) from e
1444
1505
 
1445
1506
  if output_format == SuggestOutputFormat.JSON:
@@ -1472,7 +1533,7 @@ def _render_fractal(reports: list[FractalReport]) -> None:
1472
1533
  entropy = "—" if rung.entropy is None else f"{rung.entropy:.2f}"
1473
1534
  name = rung.name if rung.live else f"{rung.name} [dim](no_signal)[/dim]"
1474
1535
  table.add_row(
1475
- name,
1536
+ escape(name),
1476
1537
  str(rung.groups),
1477
1538
  str(rung.triads),
1478
1539
  entropy,
@@ -1500,10 +1561,12 @@ def fractal(
1500
1561
  try:
1501
1562
  reports = analyze_fractal_db(db)
1502
1563
  except FileNotFoundError as e:
1503
- console.print(f"[bold red]❌ Database not found:[/bold red] {db}. Run `ingest` first.")
1564
+ console.print(
1565
+ f"[bold red]❌ Database not found:[/bold red] {escape(db)}. Run `ingest` first."
1566
+ )
1504
1567
  raise typer.Exit(code=1) from e
1505
1568
  except Exception as e:
1506
- console.print(f"[bold red]❌ Error during fractal analysis:[/bold red] {e}")
1569
+ console.print(f"[bold red]❌ Error during fractal analysis:[/bold red] {escape(str(e))}")
1507
1570
  raise typer.Exit(code=1) from e
1508
1571
 
1509
1572
  if output_format == FractalOutputFormat.JSON: