Feat/resolver#3
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces the ResolverEngine class to resolve raw semantic edges into high-confidence resolved edges, along with corresponding unit tests. The feedback highlights several critical improvements: preventing class methods from overwriting global symbols in the lookup index, using removeprefix instead of replace to safely strip prefixes, and replacing fragile hardcoded string slicing with join to robustly reconstruct class fully qualified names (FQNs).
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces the ResolverEngine class to resolve raw semantic edges into high-confidence edges, along with corresponding unit tests. The review feedback highlights a critical issue regarding global symbol resolution: mapping global symbols directly by name to a single FQN string can cause collisions when multiple files define functions with the same name. The reviewer suggests storing a list of candidate FQNs and disambiguating them by preferring candidates that share the same file context as the source node.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces the ResolverEngine class to resolve raw semantic edges into high-confidence resolved edges, along with accompanying unit tests. The reviewer feedback suggests improving the robustness of method FQN resolution by lowering the expected part count threshold, and optimizing global symbol resolution from O(C) to O(1) by introducing a secondary index mapping file paths and names directly to their fully qualified names.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces the ResolverEngine class to resolve raw semantic edges into high-confidence resolved edges, along with corresponding unit tests. The review feedback highlights two key improvements: increasing self.num_parts to 3 to correctly validate the expected three-part FQN format for methods, and returning None instead of an arbitrary candidate when resolving ambiguous global symbols to avoid incorrect graph edges.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces the ResolverEngine class, which resolves raw semantic call edges into high-confidence resolved edges by indexing nodes and matching targets. It also includes unit tests to verify both direct and self-call resolution. The review feedback suggests several key improvements to enhance robustness and code quality: lowering self.num_parts to 2 to support FQNs without a file prefix, passing edge.file_path as a fallback to _resolve_global_call for better disambiguation, removing a redundant membership check on self.nodes, and utilizing Pydantic's model_copy(update=...) to cleanly copy and update Edge instances.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces the ResolverEngine class to resolve raw semantic edges into high-confidence resolved edges, along with corresponding unit tests. The feedback suggests improving the engine's robustness by reducing the hardcoded FQN parts threshold to support environments without file paths, indexing class nodes to resolve class instantiations, and adding a corresponding unit test to verify this behavior.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces the ResolverEngine class, which resolves raw semantic edges into high-confidence resolved edges by indexing nodes and performing a linking pass, accompanied by comprehensive unit tests. The feedback focuses on improving robustness and maintainability: specifically, normalizing file paths using os.path.normpath to ensure cross-platform compatibility, and simplifying FQN parsing by replacing hardcoded part counts with rpartition for cleaner and more resilient string manipulation.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
|
/gemini review |
|
There was a problem hiding this comment.
Code Review
This pull request introduces the ResolverEngine class to resolve raw semantic edges into high-confidence resolved edges, along with corresponding unit tests. Feedback highlights a potential issue where duplicate global symbols in the same file can silently overwrite each other in _file_global_symbols. Additionally, there is a discrepancy in the expected FQN format (colons vs. dots) between the resolver implementation and the model definition that could lead to unresolved method calls.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
…map, suffix priority - Flatten `import_list` node in _collect_imported_symbols to support `from X import (A, B)` parenthesized style (#1) - Map non-aliased dotted imports to local name: `import a.b.c` → {"a": "a"} so resolver reconstructs `a.b.c.foo()` correctly, not `a.b.c.b.c.foo()` (#2) - Check _suffix_map before strip-leading-segments in _map_to_node_fqn to prevent ambiguous strip from overriding precise src/-layout match (#3) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…lution (#13) (#48) * feat(extractor+resolver): Import Graph Linking — cross-file call resolution (#13) Closes #13. ## Extractor changes (python_extractor.py) - Creates a FILE node per file with import_map in metadata - Parses import_statement (import X, import X as Y) - Parses import_from_statement (from X import Y [as Z], relative imports) - Handles relative imports (leading dots) via _resolve_relative_module() - Wildcards (from X import *) are safely skipped - Emits structural IMPORTS edges from FILE node to base module ## Resolver changes (engine.py) - _file_imports index: normalized file_path → {alias: target_fqn} - _suffix_map index: suffix → [node_ids] (O(1) src/-layout lookup) - _map_to_node_fqn(): handles two prefix-mismatch directions: 1. Import has extra prefix: cgis.resolver.X → resolver.X (strip from import) 2. Node has extra layout prefix: cgis.pipeline.X → src.cgis.pipeline.X - _resolve_global_call: import map consulted before global symbol index ## Impact - 72.8% → 58.9% unresolved edges on this project's own graph - 492 edges newly resolved via import-based linking - Top previously-unresolved internal calls (pipeline.run, MermaidCompiler.compile) now link correctly via import map Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(lint): reduce complexity and fix PLC0415/E501 errors Refactor _process_import_from_statement into three focused helpers (_parse_relative_import, _collect_imported_symbols) to bring cyclomatic complexity from 14 to ~8. Move all inline imports to module level in test_pipeline.py and test_resolver.py. Shorten three over-length docstrings in test_python_extractor.py. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(extractor+resolver): handle parenthesized imports, dotted import map, suffix priority - Flatten `import_list` node in _collect_imported_symbols to support `from X import (A, B)` parenthesized style (#1) - Map non-aliased dotted imports to local name: `import a.b.c` → {"a": "a"} so resolver reconstructs `a.b.c.foo()` correctly, not `a.b.c.b.c.foo()` (#2) - Check _suffix_map before strip-leading-segments in _map_to_node_fqn to prevent ambiguous strip from overriding precise src/-layout match (#3) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(resolver+extractor): sonarqube complexity, import_map None guard, dots byte-len - Extract nested ternary in _resolve_global_call into if/elif/else — reduces cognitive complexity from 16 to 15 and fixes SonarQube nested-conditional alert - Guard import_map retrieval with `or {}` to handle explicit None in metadata - Count relative-import leading dots via byte length (sub.end_byte - sub.start_byte) instead of iterating sub.children, which can be empty for terminal leaf nodes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(resolver): extract _resolve_via_import_map to cut cognitive complexity Moves direct-import and module-prefixed-call resolution out of _resolve_global_call into a dedicated helper, bringing _resolve_global_call's cognitive complexity from 16 to ~8 (well within SonarQube's 15 limit). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove redundant `or 0` from getattr calls — default=0 already covers the missing-attribute case; `or 0` was misleadingly implying the default could return None - Log message: drop speculative "may be stale" clause, state fact only - Add test asserting ProviderUsage frozen=True raises ValidationError on mutation, so removing the flag would be caught by CI Skipped false positives: BLOCKER #1 (ProviderUsage IS already Pydantic), #2 (collect_graph_context already has -> str), #3 (logging paths is not non-determinism), MAJOR #4 (SQLite exceptions propagate naturally). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(guardian): token usage tracking and graph coverage metric (#95) - BaseProvider gains last_usage: ProviderUsage (prompt/completion/total) updated by each provider after generate_content - GeminiProvider reads response.usage_metadata; MistralProvider reads response.usage — both via getattr so absence is handled gracefully - ContextCollector tracks graph_stats {total, with_graph}; emits a warning when coverage is 0% (stale db or wrong ingest path) - guardian_review.py logs token counts and graph coverage after review Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(guardian): remove spurious async, append stats footer to report - test_provider_last_usage_defaults_to_zero: drop async keyword (no await inside, SonarQube S4719) - guardian_review.py: append model/token/graph footer to output file so the stats appear at the bottom of every PR comment, e.g.: > 🤖 mistral-large-latest · 12,450 prompt + 1,823 completion = 14,273 tokens · graph 0/3 files (0%) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(guardian): apply valid findings from guardian review - collector: rename total → total_changed in graph_stats for clarity - collector: add project_root to the 0% coverage warning log - tests: add test_graph_stats_empty_changed_files (was untested path) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(guardian): ProviderUsage → Pydantic frozen model Converts ProviderUsage from @DataClass to Pydantic BaseModel with frozen=True and @computed_field for total_tokens. Consistent with the project rule: shared data structures crossing module boundaries use Pydantic. CONTRIBUTING.md clarified: Pydantic for shared/public structures; pure-internal accumulators (local vars, loop state) exempt. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(guardian): apply real findings from third review - MINOR 6: total_tokens docstring now explains WHY (cost tracking), not just what it computes - MAJOR 3: add test asserting ProviderUsage.total_tokens works when prompt_tokens=0 (the asymmetric completion-only path was untested) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(guardian): apply valid findings from fourth review - Remove redundant `or 0` from getattr calls — default=0 already covers the missing-attribute case; `or 0` was misleadingly implying the default could return None - Log message: drop speculative "may be stale" clause, state fact only - Add test asserting ProviderUsage frozen=True raises ValidationError on mutation, so removing the flag would be caught by CI Skipped false positives: BLOCKER #1 (ProviderUsage IS already Pydantic), #2 (collect_graph_context already has -> str), #3 (logging paths is not non-determinism), MAJOR #4 (SQLite exceptions propagate naturally). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(tests): move pydantic import to top-level (PLC0415) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
BLOCKER #1: sort adjacency keys in _tarjan_scc for deterministic output order across runs (dict insertion order isn't stable after DB queries). BLOCKER #2: remove EXTENDS-based abstractness heuristic from _get_abstract_fqns. Marking the child of an EXTENDS edge as abstract is wrong (e.g. UserService(Base) is concrete). Only node metadata is_abstract is authoritative. MAJOR #2: add tests for raw_import: filtering and empty adj in Tarjan. BLOCKER #3, MAJOR #1, MINORs: not applied — #3 is a misread (external calls are already counted via fallback), #1 is pedantic, MINORs are edge cases / out-of-scope feature requests. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
BLOCKER #1: sort adjacency keys in _tarjan_scc for deterministic output order across runs (dict insertion order isn't stable after DB queries). BLOCKER #2: remove EXTENDS-based abstractness heuristic from _get_abstract_fqns. Marking the child of an EXTENDS edge as abstract is wrong (e.g. UserService(Base) is concrete). Only node metadata is_abstract is authoritative. MAJOR #2: add tests for raw_import: filtering and empty adj in Tarjan. BLOCKER #3, MAJOR #1, MINORs: not applied — #3 is a misread (external calls are already counted via fallback), #1 is pedantic, MINORs are edge cases / out-of-scope feature requests. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(analyzer): architectural anti-pattern detection (#69) Adds AnalyzerEngine with three detectors: - detect_cycles() — iterative Tarjan SCC on IMPORTS edges; each cycle emitted as CIRCULAR_DEPENDENCY anomaly with severity scaling by cycle length - detect_zone_of_pain() — Uncle Bob D-distance metric (afferent/efferent coupling + abstractness via EXTENDS/metadata); flags stable-concrete classes (D >= 0.70) - detect_god_objects() — classes with >= 10 methods AND >= 5 efferent targets flagged as GOD_OBJECT New CLI command: cgis analyze [--db graph.db] [--min-severity 0.5] [--format text|json] 18 unit tests covering Tarjan algorithm correctness, external-node filtering, threshold boundaries, and the full run() report. Also adds missing packages to pre-commit mypy additional_dependencies (rich, typer, tree-sitter*) so the hook has the same dependency set as the local venv. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(analyzer): address SonarQube alerts in anti-pattern detector - Replace chained startswith() with tuple argument - Extract _pop_scc() helper from _tarjan_step to reduce cognitive complexity from 16 to 9 (limit is 15) - Use pytest.approx() for floating-point equality assertion Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(analyzer): apply valid Guardian review findings BLOCKER #1: sort adjacency keys in _tarjan_scc for deterministic output order across runs (dict insertion order isn't stable after DB queries). BLOCKER #2: remove EXTENDS-based abstractness heuristic from _get_abstract_fqns. Marking the child of an EXTENDS edge as abstract is wrong (e.g. UserService(Base) is concrete). Only node metadata is_abstract is authoritative. MAJOR #2: add tests for raw_import: filtering and empty adj in Tarjan. BLOCKER #3, MAJOR #1, MINORs: not applied — #3 is a misread (external calls are already counted via fallback), #1 is pedantic, MINORs are edge cases / out-of-scope feature requests. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(analyzer): apply valid findings from second Guardian review MINOR-1: add inline rationale comments to all threshold constants. MAJOR-2: add explicit empty-graph tests for detect_zone_of_pain and detect_god_objects (run() already covers this via test_run_empty_graph, but focused tests are clearer). MAJOR-4: add self-calls test — methods calling siblings in the same class must NOT inflate efferent coupling (correct behavior confirmed). MAJOR-1: add test documenting that raw_call: targets count as efferent coupling (external calls are real dependencies). Skipped: BLOCKER-1 (annotations already present — hallucination), BLOCKER-2 (FUNCTION→class coupling is out of CLASS-metric scope), BLOCKER-3 (ID collision is edge case, same issue as prev review MINOR), MAJOR-3 (DECLARES alongside CONTAINS is safe fallback), MINORs 2-4. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(analyzer): apply valid findings from third Guardian review - Clarify _ZONE_OF_PAIN_DISTANCE comment: D > 0.7 AND I <= 0.3 - Add boundary tests: 9-method class not flagged (one below threshold), high-instability class (I≈0.33) not in Zone of Pain Skipped: all three BLOCKERs (two hallucinations + one misclassified), two MAJORs (hallucination + scope creep), MINORs 2-4. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(analyzer): document raw_call dedup in God Object efferent coupling Multiple calls to the same unresolved target count as one efferent entry (set semantics). Validates the 1/4 valid finding from Gemini Flash Lite review. Also restrict pre-commit mypy hook to src/ only — tests/ imports cgis as a local package not available in the hook's isolated virtualenv, matching what make type-check already does. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Guardian review findings #2 and #3: - test_ts_contains_edge_exists now verifies FILE→FUNCTION/METHOD/CLASS structural validity, not just len > 0 - test_pipeline_skips_test_files covers *.test.py and *.spec.py via the public pipeline interface (avoids SLF001 private-member access) - pre-commit mypy hook: add files: ^src/ (tests/ has no cgis install) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…files in pipeline (#107) * fix: use module_fqn for FILE node ID in TS extractor; skip test/spec files in pipeline - TS extractor: FILE node ID uses module_fqn instead of raw file_path - Pipeline: skip test/spec files (*.test.*, *.spec.*) - MCP server: register .ts/.tsx extractors - Self-parsing: TS test and conftest fixture for ui/src/ * fix: lint issues, skip TS tests when ui/src missing, ignore ui/ * test: strengthen CONTAINS assertion and add test-file skip coverage Guardian review findings #2 and #3: - test_ts_contains_edge_exists now verifies FILE→FUNCTION/METHOD/CLASS structural validity, not just len > 0 - test_pipeline_skips_test_files covers *.test.py and *.spec.py via the public pipeline interface (avoids SLF001 private-member access) - pre-commit mypy hook: add files: ^src/ (tests/ has no cgis install) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * security(guardian): scope contents:write to metrics-only job Previously contents:write was on the review job which also checks out and runs PR head code — unnecessarily broad. Split into two jobs: - review: contents:read (runs PR code, posts comment) - save-metrics: contents:write (checks out main only, pushes JSONL) Metrics file is passed between jobs via upload/download-artifact. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: move write permissions to job level, annotate _extractors - guardian.yml: permissions: {} at workflow level; pull-requests:write and contents:read scoped to review job only (Sonar alert fix) - pipeline.py: explicit class-level annotations for _extractors and _domains_config (Guardian finding #2) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: case-insensitive test file regex, NodeType enum in assertions Guardian review findings #2 and #4: - _TEST_FILE_PATTERN: add re.IGNORECASE for App.Test.tsx style variants - test_self_parse_ts.py: replace string literals with NodeType enum values for FILE/FUNCTION/METHOD/CLASS comparisons Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…tr-escape Addresses the colleague cross-review + gemini round 2 on #219. - depth default 1 (was 2): callers/callees lists are now *direct* by default, so the `_collect_*` data and the section notes are honest. `--depth N` still pulls transitive neighbours, but the note states the hop bound ("callers within N hops …") instead of mislabeling them "direct" (colleague 🔴 #1). - unresolved callees collected across the whole traversal, not just the focal node's own edges — symmetric with the resolved set at depth>1 (colleague 🟠 #2). - `_neutralize_closing_tags` now escapes only the closing tags THIS module emits (regex over source/context/class/domain/callers/callees), so TS/JSX `</div>` and generics reach the agent verbatim while injection is still blocked (colleague 🟡 #3). - `_escape_xml_attr` helper applied to every XML attribute (focal id/file, class name/file, domain ontology/domains) so `&`/`"`/`<`/`>` can't malform the tags (gemini MEDIUM ×4). - snippet: break on linecache EOF ("" sentinel) so a corrupt/huge end_line can't spin millions of empty reads; a blank middle line ("\n") is still preserved (gemini MEDIUM). CLI/MCP `cgis context` depth default → 1 to match. 8 new/updated tests, 908 pass, mypy strict, doc 99.6%. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
) * feat(query): GraphRAG prompt compiler — `cgis context <fqn>` (#19) New agent-facing context compiler: feeds an LLM a compact, XML-tagged package about a focal node (source snippet, enclosing class, domain boundary, callers, callees) in place of raw file dumps. - query/snippet.py: extract_snippet (linecache, graceful on missing/EOF) - query/prompt.py: pure compile_context — Hybrid XML, adaptive code-fence anti-injection, graceful degradation, L3 <domain> boundary block - query/context_service.py: orchestration — CALLS-filtered direct neighbours, INTERNAL-only (builtins/third-party dropped as noise), structural-parent class context, unresolved raw_call list - cli.py `cgis context` (clean stdout payload, stderr resolve notes, --source-root to locate snippets after `ingest ./src`) - mcp_server.py cgis_context tool (MCP_REFERENCE left to the autodoc sync) Mermaid is a visual format an LLM parses at several times the token cost, so adjacency is rendered as FQN+location bullet lists; only the focal node's source is inlined, never whole files. Dogfooded via cgis MCP + CLI (which caught two noise bugs: junk domain block from the structural ontology_class fallback, and builtin callees — both fixed). Incorporates @kehansama's two-layer-context insight: the <domain> block surfaces the focal node's architectural boundary so agents respect it during refactoring. Adaptive depth + centrality token-budgeting deferred to a follow-up. Closes #19 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(context): address #219 review — XML-close safety, path normalize, parent preference - prompt.py: neutralize `</` in source so a file containing `</source>`/`</context>` can't close the prompt's own XML tags (gemini SEC-HIGH). Surgical `</`-escape keeps `->` return arrows, generics and comparisons readable, unlike a blanket `<`/`>` escape (verified by dogfood: `def f() -> Node` stays intact). - context_service.py: normalize Windows backslash file_paths before locating the snippet so Windows-ingested graphs still read source on POSIX (gemini HIGH). - context_service.py: `_structural_parent` now skips parents missing from the store and prefers the enclosing CLASS over a FILE/MODULE so a method's class context is never lost to an earlier file-level edge (gemini MEDIUM + guardian). Rejected (guardian, empirically): the unresolved-callee `edge.target` "fix" — callees come from get_flow_graph (outgoing), so `edge.source == focus` is correct and a raw_call target is never the focus; and the domain-block claim — already gated on `focus.domains`, the finder reviewed a pre-fix hunk. 5 new regression tests. 902 pass, mypy strict, doc 99.6%. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(context): round-2 review — honest depth labels, scoped escape, attr-escape Addresses the colleague cross-review + gemini round 2 on #219. - depth default 1 (was 2): callers/callees lists are now *direct* by default, so the `_collect_*` data and the section notes are honest. `--depth N` still pulls transitive neighbours, but the note states the hop bound ("callers within N hops …") instead of mislabeling them "direct" (colleague 🔴 #1). - unresolved callees collected across the whole traversal, not just the focal node's own edges — symmetric with the resolved set at depth>1 (colleague 🟠 #2). - `_neutralize_closing_tags` now escapes only the closing tags THIS module emits (regex over source/context/class/domain/callers/callees), so TS/JSX `</div>` and generics reach the agent verbatim while injection is still blocked (colleague 🟡 #3). - `_escape_xml_attr` helper applied to every XML attribute (focal id/file, class name/file, domain ontology/domains) so `&`/`"`/`<`/`>` can't malform the tags (gemini MEDIUM ×4). - snippet: break on linecache EOF ("" sentinel) so a corrupt/huge end_line can't spin millions of empty reads; a blank middle line ("\n") is still preserved (gemini MEDIUM). CLI/MCP `cgis context` depth default → 1 to match. 8 new/updated tests, 908 pass, mypy strict, doc 99.6%. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(context): round-3 polish — whitespace-tolerant tag escape, drop walrus leak - prompt.py: `_OWN_CLOSING_TAG` now allows whitespace before `>` (`</source >`), the only injection variant the exact match missed (colleague non-blocking nit). Case stays sensitive — tags are lowercase and XML end-tags are case-sensitive, so `</SOURCE>` can't close `<source>` anyway. - context_service.py: `_structural_parent` rebuilt as a generator + filter comprehension instead of a `:=` walrus inside the list comp (which leaked `parent` to the function scope) — same behaviour, cleaner (gemini MEDIUM). 909 tests, mypy strict, doc 99.6%. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…t + low max-residual (#232 review) Colleague review: - #1 (semantic): fit band/residual now use DriftScorer.shape_residual (gate-FREE layered TV) so a well-shaped domain with a cycle reads gate_failed but is NOT banded 'no template fits' — the cycle is the gate's story. Ranking + init- ontology tolerance keep the full drift_score (round-trip #174 intact). - #2: _fit_quality returns None on an empty alphabet (profiles-but-no-patterns) — no more IndexError aborting drift. - #3: good-band cutoff clamped to min(0.25, max_residual) so --max-residual below 0.25 still bands near-perfect matches 'good'. - #4: coverage reuses a single get_all_nodes() (shared with the quotient build).
…t + low max-residual (#232 review) Colleague review: - #1 (semantic): fit band/residual now use DriftScorer.shape_residual (gate-FREE layered TV) so a well-shaped domain with a cycle reads gate_failed but is NOT banded 'no template fits' — the cycle is the gate's story. Ranking + init- ontology tolerance keep the full drift_score (round-trip #174 intact). - #2: _fit_quality returns None on an empty alphabet (profiles-but-no-patterns) — no more IndexError aborting drift. - #3: good-band cutoff clamped to min(0.25, max_residual) so --max-residual below 0.25 still bands near-perfect matches 'good'. - #4: coverage reuses a single get_all_nodes() (shared with the quotient build).
* feat(ontology): add funnel template — transpose of layered_dag (#186, #177) The single most common intra-domain archetype across 9 repos that the hand-authored 5-template alphabet omitted (funnel = layered_dagᵀ = {021U:0.5, 021C:0.5}). Adding it rebinds nothing — existing drift scores and ratchets are unchanged — but lets fit-quality (#177) measure against a transpose-closed alphabet. Bundled header + _ALPHABET pin updated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(drift): promote fit_templates to DriftScorer; iterate loaded alphabet (#177) Canonical distance-to-template ranking now on DriftScorer (iterates self._patterns → reflects whatever patterns.yaml declares, not a hardcoded list). init-ontology delegates — one source of truth for fit-quality (#177) and measure-then-label (#174). Direct tests prove the funnel shape ranks funnel nearest. * feat(drift): fit-quality (nearest template + residual + band) and coverage roll-up (#177) DriftReport.fit (FitQuality: nearest/runner-up template + residual + good|weak|none band) annotated in analyze_drift for profiled domains via the shared fit_templates ranking; band 'none' (residual > max_residual, default 0.45) is the 'no template fits' signal. DriftAnalysis.coverage lists graph prefixes no project_domain binds (drift's blind spot), reusing discover_domains. * feat(cli,mcp): fit column + no-template-fits/coverage roll-ups; --max-residual (#177) CLI drift gains a Fit column (nearest template + residual, banded), a 'no template fits' roll-up for band-none domains, and an unbound-code coverage section; --max-residual plumbs the cutoff. Notes now print below the table (uncramped by the new column). MCP cgis_drift gains max_residual + top-level coverage; per-report fit rides in asdict. * test: hoist fit fixtures to conftest; commit post-rebase formatting (#177) Sonar new-code duplication: the v2 fit patterns.yaml and the instar/triangle db builders were copied across test_drift_service/test_cli/test_mcp_server → shared fit_patterns_yaml/instar_db/triangle_db helpers in conftest (#211 dedup lesson). Also commits the ruff-format normalization the rebase conflict resolution left uncommitted (CI format-check fix). * refactor: Literal-typed fit band (drop type:ignore); hoist dom.fN literals to constants (#232 review) gemini: annotate band as Literal['good','weak','none'] so FitQuality construction is statically checked. Sonar: the dom.f1/f2/f3 node ids repeated across the conftest fit-db builders → _F1/_F2/_F3 constants. * fix(drift): band fit on gate-free shape residual; guard empty alphabet + low max-residual (#232 review) Colleague review: - #1 (semantic): fit band/residual now use DriftScorer.shape_residual (gate-FREE layered TV) so a well-shaped domain with a cycle reads gate_failed but is NOT banded 'no template fits' — the cycle is the gate's story. Ranking + init- ontology tolerance keep the full drift_score (round-trip #174 intact). - #2: _fit_quality returns None on an empty alphabet (profiles-but-no-patterns) — no more IndexError aborting drift. - #3: good-band cutoff clamped to min(0.25, max_residual) so --max-residual below 0.25 still bands near-perfect matches 'good'. - #4: coverage reuses a single get_all_nodes() (shared with the quotient build). * fix(drift): shape_residual returns None on no shape signal → fit=None (#232 review) A v1 profile (no layers), an empty census on both layers, or fully unresolved calls with no imports layer leaves total weight 0. Returning 0.0 banded 'good' — 'no signal to fit' masquerading as 'matches an archetype', the exact distinction #177 draws. shape_residual now returns None on total<=0 (covers all three sub-cases — broader than the per-cause guards suggested in review); _fit_quality maps it to fit=None. Regression: test_fit_none_when_no_shape_signal. --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>



No description provided.