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.
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/.github/workflows/guardian.yml +1 -1
- codegraph_brain-0.7.2/.release-please-manifest.json +3 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/CHANGELOG.md +14 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/PKG-INFO +1 -1
- codegraph_brain-0.7.2/docs/lab-notes/2026-06-11-chunked-review-negative-result.md +132 -0
- codegraph_brain-0.7.2/docs/ontology/tolerances.lock +27 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/pyproject.toml +2 -1
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/cli.py +116 -53
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/query/drift/ontology_init.py +65 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_cli.py +121 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_ontology_init.py +70 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_patterns_yaml.py +79 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_triads.py +80 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/uv.lock +12 -1
- codegraph_brain-0.7.1/.release-please-manifest.json +0 -3
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/.claude-plugin/marketplace.json +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/.github/workflows/autodoc.yml +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/.github/workflows/ci.yml +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/.github/workflows/release-please.yml +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/.gitignore +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/.pre-commit-config.yaml +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/.python-version +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/CLAUDE.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/CONTRIBUTING.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/LICENSE +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/Makefile +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/PRIVACY.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/README.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/benchmarks/guardian/CURATION.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/benchmarks/guardian/pr-122.yaml +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/benchmarks/guardian/pr-140.yaml +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/benchmarks/guardian/pr-141.yaml +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/benchmarks/guardian/pr-142.yaml +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/benchmarks/guardian/pr-143.yaml +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/benchmarks/guardian/pr-144.yaml +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/benchmarks/guardian/pr-278.yaml +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/benchmarks/guardian/results.jsonl +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/data/.gitkeep +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/AUDIT.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/CASE_STUDY.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/architecture/HOW_IT_WORKS.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/architecture/ONTOLOGY.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/architecture/PATTERNS_AND_TRIADS.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/architecture/SELF_PORTRAIT.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/architecture/diagrams/pipeline_flow.mermaid +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/architecture/health_badge.json +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/examples/.gitkeep +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/how-to/AGENT_ONBOARDING.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/how-to/CLI_USAGE.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/how-to/MCP_REFERENCE.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/ontology/.gitkeep +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/ontology/core.yaml +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/ontology/domains.yaml +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/ontology/patterns.yaml +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-06-09-domain-pattern-fingerprint-design.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-06-09-pattern-alphabet-motif-basis-design.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-06-10-guardian-sprint-design.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-06-11-fastapi-di-edges-design.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-06-11-guardian-chunked-review-design.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-06-11-guardian-chunker-design.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-06-11-mcp-drift-validate-fqn-design.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-06-11-resolver-split-design.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-06-11-symbol-import-edges-design.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-06-12-drift-empty-domains-design.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-06-12-gate-semantics-design.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-06-12-init-ontology-design.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-06-12-release-please-ci-design.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-06-13-suggest-packages-design.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-06-13-tangle-anti-pattern-design.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-07-29-guardian-skeptic-scoring-design.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-07-30-cgis-fractal-design.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-07-30-cgis-fractal-plan.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-07-30-chunk-source-filter-design.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-07-30-chunk-source-filter-plan.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-07-30-genai-client-close-design.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-07-30-genai-client-close-plan.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-07-30-guardian-precision-bench-design.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-07-30-guardian-precision-bench-plan.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-07-30-guardian-timeout-retry-design.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-07-30-guardian-timeout-retry-plan.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/2026-07-31-finder-bug-class-taxonomy.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/BLUEPRINT.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/PRD.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/TDD.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/plans/2026-06-09-fingerprint-drift.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/plans/2026-06-10-guardian-context-skeptic-inline.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/plans/2026-06-10-guardian-structured-findings-bench.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/plans/2026-06-10-motif-basis-part-b.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/plans/2026-06-10-unified-pattern-alphabet.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/plans/2026-06-11-fastapi-di-edges.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/plans/2026-06-11-guardian-chunked-review.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/plans/2026-06-11-guardian-chunker.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/plans/2026-06-11-mcp-drift-validate-fqn.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/plans/2026-06-11-resolver-split.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/plans/2026-06-12-drift-empty-domains.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/plans/2026-06-12-gate-semantics.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/plans/2026-06-12-init-ontology.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/plans/2026-06-12-release-please-ci.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/plans/2026-06-12-symbol-import-edges.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/plans/2026-06-13-suggest-packages.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/docs/specs/plans/2026-07-29-guardian-skeptic-scoring.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/main.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/plugin/.claude-plugin/plugin.json +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/plugin/.mcp.json +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/plugin/README.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/plugin/skills/cgis/SKILL.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/plugin/skills/ingest/SKILL.md +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/release-please-config.json +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/scripts/gen_ideal_graph.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/scripts/generate_health.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/scripts/generate_mcp_ref.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/scripts/generate_schema_docs.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/scripts/guardian_bench.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/scripts/guardian_review.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/scripts/inject_readme_graph.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/scripts/probe_closure_gap.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/scripts/probe_tier_ladder.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/__init__.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/__main__.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/api/.gitkeep +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/api/__init__.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/api/mcp_server.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/core/.gitkeep +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/core/models.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/extractors/.gitkeep +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/extractors/_python_ast.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/extractors/_python_classes.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/extractors/_python_functions.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/extractors/_python_imports.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/extractors/_python_types.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/extractors/base.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/extractors/python_extractor.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/extractors/typescript_extractor.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/guardian/__init__.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/guardian/bench.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/guardian/chunked.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/guardian/chunker.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/guardian/collector.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/guardian/core.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/guardian/diff_index.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/guardian/findings.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/guardian/github_poster.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/guardian/metrics.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/guardian/prompts.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/guardian/providers/__init__.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/guardian/providers/base.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/guardian/providers/gemini.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/guardian/providers/mistral.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/guardian/providers/ollama.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/guardian/recording.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/guardian/render.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/guardian/runner.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/guardian/skeptic.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/pipeline.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/py.typed +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/query/analysis/__init__.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/query/analysis/analyzer.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/query/analysis/anomaly.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/query/analysis/cohesion.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/query/analysis/health.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/query/analysis/suggest_service.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/query/context/__init__.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/query/context/audit.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/query/context/context_service.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/query/context/prompt.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/query/context/snippet.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/query/drift/__init__.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/query/drift/_scc.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/query/drift/drift.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/query/drift/drift_service.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/query/drift/fingerprint.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/query/drift/fractal.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/query/drift/quotient.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/query/drift/triads.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/query/engine.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/query/fqn.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/query/render/__init__.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/query/render/graph_json.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/query/render/mermaid.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/query/render/metrics.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/resolver/.gitkeep +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/resolver/__init__.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/resolver/engine.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/resolver/indices.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/resolver/symbols.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/resolver/uplift.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/storage/.gitkeep +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/src/cgis/storage/sqlite_store.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/integration/.gitkeep +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/self_parsing/.gitkeep +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/self_parsing/__init__.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/self_parsing/conftest.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/self_parsing/test_architecture.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/self_parsing/test_drift.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/self_parsing/test_fractal.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/self_parsing/test_init_ontology_roundtrip.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/self_parsing/test_self_parse.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/self_parsing/test_self_parse_ts.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/self_parsing/test_suggest.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/.gitkeep +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/conftest.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/guardian_stubs.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test___main__.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_analyzer.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_audit.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_cohesion.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_context_service.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_di_acceptance.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_drift.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_drift_service.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_fingerprint.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_fqn.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_fractal.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_gen_ideal_graph.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_generate_mcp_ref.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_graph_json.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_guardian_bench.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_guardian_chunked.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_guardian_chunker.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_guardian_collector.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_guardian_core.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_guardian_diff_index.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_guardian_findings.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_guardian_metrics.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_guardian_poster.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_guardian_providers.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_guardian_recording.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_guardian_render.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_guardian_runner.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_guardian_skeptic.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_health_scorer.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_import_acceptance.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_mcp_server.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_mermaid.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_metrics.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_models.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_ontology_compliance.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_pipeline.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_prompt.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_python_extractor.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_quotient.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_resolver.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_resolver_indices.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_resolver_symbols.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_snippet.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_sqlite_store.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_suggest_service.py +0 -0
- {codegraph_brain-0.7.1 → codegraph_brain-0.7.2}/tests/unit/test_typescript_extractor.py +0 -0
- {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 --
|
|
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
|
|
@@ -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.
|
|
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.
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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:
|