Skip to content

Feat/resolver#3

Merged
zaebee merged 9 commits into
mainfrom
feat/resolver
Jun 6, 2026
Merged

Feat/resolver#3
zaebee merged 9 commits into
mainfrom
feat/resolver

Conversation

@zaebee

@zaebee zaebee commented Jun 6, 2026

Copy link
Copy Markdown
Owner

No description provided.

@zaebee zaebee self-assigned this Jun 6, 2026
@zaebee zaebee added the enhancement New feature or request label Jun 6, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/cgis/resolver/engine.py Outdated
Comment thread src/cgis/resolver/engine.py Outdated
Comment thread src/cgis/resolver/engine.py Outdated
Comment thread src/cgis/resolver/engine.py Outdated
Comment thread src/cgis/resolver/engine.py Outdated
@zaebee

zaebee commented Jun 6, 2026

Copy link
Copy Markdown
Owner Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/cgis/resolver/engine.py Outdated
Comment thread src/cgis/resolver/engine.py Outdated
Comment thread src/cgis/resolver/engine.py Outdated
Comment thread src/cgis/resolver/engine.py Outdated
@zaebee

zaebee commented Jun 6, 2026

Copy link
Copy Markdown
Owner Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/cgis/resolver/engine.py Outdated
Comment thread src/cgis/resolver/engine.py Outdated
Comment thread src/cgis/resolver/engine.py
Comment thread src/cgis/resolver/engine.py Outdated
@zaebee

zaebee commented Jun 6, 2026

Copy link
Copy Markdown
Owner Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/cgis/resolver/engine.py Outdated
Comment thread src/cgis/resolver/engine.py Outdated
@zaebee

zaebee commented Jun 6, 2026

Copy link
Copy Markdown
Owner Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/cgis/resolver/engine.py Outdated
Comment thread src/cgis/resolver/engine.py Outdated
Comment thread src/cgis/resolver/engine.py Outdated
Comment thread src/cgis/resolver/engine.py
Comment thread src/cgis/resolver/engine.py Outdated
@zaebee

zaebee commented Jun 6, 2026

Copy link
Copy Markdown
Owner Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/cgis/resolver/engine.py Outdated
Comment thread src/cgis/resolver/engine.py Outdated
Comment thread tests/unit/test_resolver.py
@zaebee

zaebee commented Jun 6, 2026

Copy link
Copy Markdown
Owner Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/cgis/resolver/engine.py
Comment thread src/cgis/resolver/engine.py Outdated
Comment thread src/cgis/resolver/engine.py Outdated
Comment thread src/cgis/resolver/engine.py Outdated
Comment thread src/cgis/resolver/engine.py
Comment thread src/cgis/resolver/engine.py Outdated
@zaebee

zaebee commented Jun 6, 2026

Copy link
Copy Markdown
Owner Author

/gemini review

@sonarqubecloud

sonarqubecloud Bot commented Jun 6, 2026

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/cgis/resolver/engine.py
Comment thread src/cgis/resolver/engine.py
@zaebee zaebee merged commit cc8a115 into main Jun 6, 2026
1 check passed
@zaebee zaebee deleted the feat/resolver branch June 6, 2026 07:58
zaebee added a commit that referenced this pull request Jun 7, 2026
…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>
zaebee added a commit that referenced this pull request Jun 7, 2026
…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>
zaebee added a commit that referenced this pull request Jun 9, 2026
- 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>
zaebee added a commit that referenced this pull request Jun 9, 2026
* 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>
zaebee added a commit that referenced this pull request Jun 9, 2026
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>
zaebee added a commit that referenced this pull request Jun 9, 2026
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>
zaebee added a commit that referenced this pull request Jun 9, 2026
* 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>
zaebee added a commit that referenced this pull request Jun 9, 2026
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>
zaebee added a commit that referenced this pull request Jun 9, 2026
…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>
zaebee added a commit that referenced this pull request Jun 12, 2026
…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>
zaebee added a commit that referenced this pull request Jun 12, 2026
)

* 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>
zaebee added a commit that referenced this pull request Jun 13, 2026
…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).
zaebee added a commit that referenced this pull request Jun 13, 2026
…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).
zaebee added a commit that referenced this pull request Jun 13, 2026
* 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant