Skip to content

Commit 9bf5a59

Browse files
authored
Fix logical partial-family impact traversal (#5072)
* Fix logical partial-family impact traversal (#5060) * Address adversarial review findings for #5060 * Preserve stale partial-family ambiguity (#5060)
1 parent ef9d7d2 commit 9bf5a59

14 files changed

Lines changed: 966 additions & 86 deletions

DEVELOPER_GUIDE.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2407,6 +2407,8 @@ the query token.
24072407

24082408
Practical consequence: `impact <ClassName>` on a class-like symbol returns the heuristic file-dependency-hint fallback (with metadata edges) when no member-level callers exist, whereas default `callers <ClassName>` returns only executable edges. Both are correct under their own contracts; counts will not match. To reconcile, run `references <ClassName> --kind attribute` (or `annotation`), or pass an explicitly supported non-default kind to `callers` / `callees`, to surface edges that the default call graph intentionally drops.
24092409

2410+
For a single C# logical partial family on a current reference-identity contract, impact resolution precedes the physical-file ambiguity guard. `ResolveImpactDefinitions` retains the representative output plus a separately bounded set of every physical family symbol ID and path. `GetTransitiveCallers` queries that ID set as one SQL union root, so candidate rows, callers, cycle nodes, and converging paths are deduplicated before BFS accounting; file-hint fallback resolves member names across every retained family path and excludes all declaration files from dependents. Physical family expansion uses `DefaultImpactPartialFamilyMemberBudget`, reported through `partial_family_member_*`, and never sets the ordinary result `truncated` / `truncated_reason` fields. A stale reference-identity contract keeps symbol-root scope and preserves physical multi-file ambiguity because legacy traversal cannot guarantee the family ID/path union. When the family expansion is capped, CLI count JSON marks the count degraded and non-authoritative, and MCP count-only returns `total: null`. Only equal logical partial keys may enter this path; multiple logical keys, including unrelated same-name namespace or language symbols, retain the established ambiguity result.
2411+
24102412
`impact --json` and MCP `impact_analysis` expose zero-result diagnostics as structured routing fields. `zero_result_reason` remains the compact terminal reason; `impact_failure_chain` lists failed preconditions or traversal states in order, using values such as `definition_not_found`, `callable_filter_fails`, `multiple_definitions`, `multiple_definition_files`, `graph_unavailable`, `depth_requested_zero`, and `no_callers`. `suggestion_type` classifies the prose `suggestion` as `resolution`, `traversal`, or `precondition`. CLI `impact --strict` exits with `FeatureUnavailable` when the chain contains a resolution or precondition failure, but still treats a genuine `no_callers` traversal result as success.
24112413

24122414
`definition --json` and MCP `definition` results may include `disambiguator` for C# definitions when existing symbol metadata can distinguish otherwise identical names. Current values include `overload(...)` for method signatures, `partial-class` / `partial-struct` / `partial-interface`, and `extension-method-on(<receiver>)`. Languages without overload or receiver metadata omit the field.
@@ -6105,6 +6107,8 @@ before/after 合計と理由別の affected / removed 件数を加える。汎
61056107

61066108
実運用上の帰結: クラスのようなシンボルに対する `impact <ClassName>` は、member-level の caller が存在しない場合 heuristic file-dependency-hint fallback (metadata エッジを含む) を返し、一方の既定 `callers <ClassName>` は実行可能 edge だけを返す。両方とも個々の契約上は正しいが、件数は一致しない。差分を埋めるには `references <ClassName> --kind attribute`(または `annotation`)を使うか、`callers` / `callees` に明示的に対応する非既定 kind を渡し、既定 call graph が意図的に落としている edge を確認する。
61076109

6110+
reference-identity contract が current な単一の C# 論理 partial family では、impact resolution を物理ファイルの ambiguity guard より先に行います。`ResolveImpactDefinitions` は代表出力に加え、全物理 family の symbol ID / path を独立した上限付き集合として保持します。`GetTransitiveCallers` はその ID 集合を単一 SQL union root として問い合わせるため、candidate row、caller、cycle node、収束 path を BFS の計数前に重複排除します。file-hint fallback は保持した全 family path から member 名を解決し、全 declaration file を依存先候補から除外します。物理 family 展開には `DefaultImpactPartialFamilyMemberBudget` を使い、`partial_family_member_*` で報告し、通常の result 用 `truncated` / `truncated_reason` fields は立てません。reference-identity contract が stale の場合は legacy traversal のため symbol-root metadata を維持します。family 展開が上限に達した count は CLI JSON で degraded / non-authoritative とし、MCP count-only では `total: null` を返します。この経路へ入るのは論理 partial key が同一の場合だけで、無関係な同名 namespace / language symbol を含む複数 logical key は従来どおり ambiguity result を維持します。
6111+
61086112
`impact --json` と MCP `impact_analysis` は、0 件診断を structured routing field として返します。`zero_result_reason` は端末向けの短い理由のまま残し、`impact_failure_chain` は `definition_not_found`、`callable_filter_fails`、`multiple_definitions`、`multiple_definition_files`、`graph_unavailable`、`depth_requested_zero`、`no_callers` などの失敗前提や traversal 状態を順序付きで列挙します。`suggestion_type` は prose の `suggestion` を `resolution`、`traversal`、`precondition` に分類します。CLI `impact --strict` は chain に resolution / precondition failure が含まれる場合は `FeatureUnavailable` で終了しますが、真正な `no_callers` traversal 結果は成功として扱います。
61096113

61106114
`definition --json` と MCP `definition` の結果は、既存の symbol metadata で同名定義を区別できる C# 定義に対して `disambiguator` を含む場合があります。現行値は method signature 用の `overload(...)`、`partial-class` / `partial-struct` / `partial-interface`、`extension-method-on(<receiver>)` です。overload や receiver metadata を持たない言語ではこの field を省略します。

TESTING_GUIDE.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,7 @@ Use `docs/test-doc-maintenance-plan.md` before moving oversized suites or adding
267267
Deps JSON summary output and json-graph summary rejection share one SQL graph fixture.
268268
Dependency-cycle coverage must prove that the graph budget is independent of the display limit, SCC ranking remains stable when the page size grows, opaque cursors return the next ranked component, mismatched cursor filters fail closed, and graph-budget exhaustion marks totals as non-authoritative.
269269
Impact-cycle coverage must distinguish consecutive same-display-name symbols by canonical source/target IDs, verify their structured shortest-path identities, keep unresolved upstream callers and non-unique resolved overload groups out of only the canonical cycle graph, avoid guessed IDs for ambiguous path roots, aggregate mixed target identities without undercounting, and retain controls for direct singleton recursion and multi-node cycles.
270+
Partial-family impact coverage must keep declarations, implementations, duplicate downstream callers, a member-to-member cycle, an unrelated same-name symbol, and a small instance-scoped family budget in one fixture family. Assert logical-root identity and physical evidence separately, prove stale reference identity does not claim logical traversal, and prove family-member truncation leaves ordinary traversal truncation unset while making count output non-authoritative (`total: null` for MCP). Fixtures that need production-equivalent partial grouping must pass `familyScopeKey` where applicable and stamp both C# hotspot-family and reference-identity readiness.
270271
Exact-symbol dependency coverage reuses the C# source-candidate fixture to prove that a `--limit 1` query can select a symbol beyond the unfiltered candidate window. The repository-scale guard builds 10,001 candidate symbols, runs on non-Windows .NET 8, and keeps query-only work within 2 seconds and 32 MiB of current-thread allocation; fixture construction stays outside the measured interval. Windows retains the lightweight functional pushdown coverage but omits this scale fixture because its bounded full-suite session already runs close to the one-hour timeout.
271272
References stale-SQL-contract count and result envelopes share one downgraded graph fixture.
272273
Callers and callees stale-SQL-contract result envelopes share one downgraded graph fixture.
@@ -1356,6 +1357,7 @@ dotnet test --filter "FullyQualifiedName~GitHelperTests"
13561357
filterなしの`languages --json` catalog coverageはcommandを1回だけ実行し、canonical language辞書を1つ構築して、extension、alias、extraction、graph、gap、guidance、exact-filenameの各contractをまとめて検証してください。言語coverageの拡張でcatalog discoveryとserializationを繰り返さないようにします。
13571358
option風literalのnamed-query escapingは、definition、graph、symbols、files、inspect、impact command全体で1つのindexed Probe fixtureを再利用してください。
13581359
impact cycle の回帰 coverage では、同じ表示名が連続する別 symbol を正規 source/target ID で区別し、構造化 shortest-path identity を検証し、未解決の上流 caller と一意でない resolved overload group を正規 cycle graph からだけ除外し、曖昧な path root に推測 ID を付けず、複数 target identity を過少計上せず集約するとともに、直接 singleton 再帰と複数 node cycle の control を維持してください。
1360+
partial-family impact coverage は declaration、implementation、重複する downstream caller、member 間 cycle、無関係な同名 symbol、小さい instance-scoped family budget を 1 つの fixture family にまとめてください。論理 root identity と物理 evidence を別々に assert し、stale な reference identity が logical traversal を名乗らないこと、family-member truncation が通常の traversal truncation を立てず count output を non-authoritative(MCP は `total: null`)にすることを確認します。本番同等の partial grouping が必要な fixture は必要に応じて `InsertIndexedFile` に `familyScopeKey` を渡し、C# hotspot-family と reference-identity の readiness を両方 stamp してください。
13591361
複数 named-query の output coverage は、compact projection、rich JSON 互換性、query ごとの limit / truncation、UTF-8 byte cap に1つの indexed fixture を再利用し、serializer mode を直接比較できるようにしてください。
13601362
共通 bounded-response coverage は、definition、find、status、hotspots、references、callers / callees、impact、map 全体で1つの graph-ready databaseを再利用してください。cursor と UTF-8 byte-budget の境界 case は別の最小 multi-row fixture にまとめ、family parity のために indexing setup を重複させないでください。outline は、長い signature と Unicode を含む深い階層の focused fixture 1つを使い、最後の改行を含む正確な byte 境界、欠落や重複のない cursor 全 page 走査、最小 budget の diagnostic、上限なし出力の非変更を確認してください。regression coverage では alias と read-only batch dispatch、明示的な definition body projection、inactive な impact collection、authoritative な総件数を持つ map section の row 単位 pagination も確認してください。
13611363
adversarial な bounded-response coverage では、parser failure の byte cap、impact definition page の offset、既存 map compact section、map shape control の競合、compact と明示 body の組み合わせ、profile / verbose control record の抽出も固定してください。

USER_GUIDE.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -823,6 +823,25 @@ dependencies are intentionally part of the graph. Legacy indexes stored those
823823
reads as `call`; they remain readable and keep their historical inclusive behavior
824824
until re-indexed.
825825

826+
When the reference-identity contract is current and every matching C# declaration belongs to one logical partial family, `impact`
827+
uses the family's stable `partial_family_id` as one traversal root and walks the
828+
union of all physical member identities. Callers, file hints, and shortest paths
829+
reached through more than one member are deduplicated; the representative
830+
definition still carries `family_members` as physical evidence. An unrelated
831+
same-name type or member remains ambiguous and is never folded into that family.
832+
With `--with-paths`, the logical root's `path_details` node carries the
833+
representative `definition_path` plus `partial_family_id`.
834+
JSON and MCP responses identify this mode with
835+
`traversal_root_scope: "logical_partial_family"` and
836+
`traversal_partial_family_id`. The `partial_family_member_*` fields report the
837+
eligible physical root count, limit, omitted count, and truncation independently
838+
from the normal result/traversal `truncated` fields. A stale identity contract keeps
839+
`traversal_root_scope: "symbol"` and omits the family-root metadata because the
840+
legacy traversal cannot guarantee the physical-ID union. When the separate family
841+
root limit is reached, CLI count JSON also reports `degraded: true` and
842+
`authoritative_count: false`, while MCP count-only output reports `total: null`;
843+
ordinary traversal `truncated` remains unchanged.
844+
826845
On a current index, cycle detection follows the resolved source/target symbol IDs
827846
on real directed edges. Two distinct methods with the same display name are not a
828847
cycle, while direct recursion is reported as a singleton cycle. JSON caller rows
@@ -4314,6 +4333,22 @@ member / value read を `member_read` として保存し、callers / callees / i
43144333
`call` として保存しているため、引き続き読み取り可能で、再 index するまでは従来の
43154334
inclusive な挙動を維持します。
43164335

4336+
reference-identity contract が current で、一致する C# declaration がすべて 1 つの論理 partial family に属する場合、`impact` は
4337+
安定した `partial_family_id` を 1 つの traversal root として使い、全物理 member identity
4338+
の和集合を辿ります。複数 member から到達する caller、file hint、shortest path は重複排除し、
4339+
代表 definition の `family_members` には物理 evidence を残します。無関係な同名 type / member
4340+
は曖昧なままで、この family へ統合しません。JSON / MCP response はこの mode を
4341+
`traversal_root_scope: "logical_partial_family"` と `traversal_partial_family_id` で示します。
4342+
`--with-paths` では論理 root の `path_details` node に代表 `definition_path` と
4343+
`partial_family_id` も含めます。
4344+
`partial_family_member_*` fields は物理 root 候補の件数、上限、省略件数、truncation
4345+
を通常の result / traversal 用 `truncated` fields とは独立して報告します。identity contract
4346+
が stale の場合、legacy traversal は物理 ID の和集合を保証できないため
4347+
`traversal_root_scope: "symbol"` のままとし、family-root metadata を省略します。独立した
4348+
family root 上限に達した count JSON は CLI で `degraded: true` と
4349+
`authoritative_count: false`、MCP count-only で `total: null` を返しますが、通常の traversal
4350+
用 `truncated` は変更しません。
4351+
43174352
current index では、cycle 判定は実在する有向辺の解決済み source/target symbol ID を
43184353
辿ります。表示名が同じ別 method は cycle にせず、直接再帰は singleton cycle として
43194354
報告します。JSON の caller row は `caller_symbol_id` と、callee を一意に解決できる場合は
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
category: fixed
3+
issues:
4+
- 5060
5+
affected:
6+
- src/CodeIndex/Database/DbReader.GraphQueries.cs
7+
- src/CodeIndex/Models/QueryResults.cs
8+
- src/CodeIndex/Cli/QueryCommandRunner.Dependencies.cs
9+
- src/CodeIndex/Mcp/McpToolHandlers.ImpactAnalysis.cs
10+
- tests/CodeIndex.Tests/DbReaderImpactTests.cs
11+
- tests/CodeIndex.Tests/QueryCommandRunnerImpactTests.cs
12+
- tests/CodeIndex.Tests/QueryCommandRunnerTests.cs
13+
- tests/CodeIndex.Tests/golden/impact.json
14+
- tests/CodeIndex.Tests/McpServerTests.cs
15+
- tests/CodeIndex.Tests/McpServerToolsCallTests.cs
16+
- USER_GUIDE.md
17+
- DEVELOPER_GUIDE.md
18+
- TESTING_GUIDE.md
19+
---
20+
21+
## English
22+
23+
- **Impact analysis now traverses one logical partial family as a single root (#5060; follow-up to #4309, #4566, and #3932)** — On current reference identity, C# partial declarations and implementations contribute their union of physical graph identities without triggering `multiple_definition_files`, including genuine caller-free partial methods; callers, file hints, cycles, and converging paths are deduplicated while definitions retain physical-member evidence. Stale identity preserves physical multi-file ambiguity, and unrelated same-name symbols remain on safe ambiguity paths. Machine-readable output reports the separately bounded family-member expansion without conflating it with ordinary traversal truncation, and capped count output is explicitly non-authoritative.
24+
25+
## 日本語
26+
27+
- **impact analysis が 1 つの論理 partial family を単一 root として traversal するよう修正しました (#5060#4309#4566#3932 の follow-up)** — current な reference identity では、caller が存在しない真正な partial method を含め、C# の partial declaration / implementation が物理 graph identity の和集合を提供し、`multiple_definition_files` で停止しません。definition に物理 member evidence を残しつつ、caller、file hint、cycle、収束 path を重複排除します。stale identity は物理的な multi-file ambiguity を維持し、無関係な同名 symbol も安全な ambiguity 経路を保ちます。machine-readable output は上限付き family-member 展開を通常の traversal truncation と混同せず個別に報告し、上限に達した count output は明示的に non-authoritative とします。

0 commit comments

Comments
 (0)