feat(godot): static graph fixes — res:// paths, ClassName.member resolution & qualified queries, impact input, reasons.target#103
Merged
Conversation
…, and orphan detection
GDScript extends/get_node("res://…") references store reference_name WITH the
res:// prefix, while .tscn/.tres refs are mapped to project-relative at
extraction. normalize_rel only converts backslashes, so the three unresolved-ref
comparison sites failed for res://-prefixed refs: an existing extends target was
falsely dangling, a res:// ref never matched a project-relative changed path in
impact, and a resource referenced only via a res:// ref was falsely orphaned.
Add a scoped strip_res_prefix helper and apply it at the three sites
(find_dangling_references, resource_impact unresolved match, referenced_resource_paths).
normalize_rel is left unchanged (6 shared callers). The resolved-edge branch is
untouched (file:-node edges are already project-relative).
… node's file A GDScript `DamageCalculator.calc_skill_damage(...)` call is an unresolved Calls ref `DamageCalculator.calc_skill_damage`; the target Function lives in the same file as the `class_name DamageCalculator` node but never resolved, so callers/impact/affected missed all static calls to global helper classes. Add a GodotResolver step: when a Calls ref is shaped `<Class>.<member>` and the receiver names a real GDScript Class node, resolve to the Function named `<member>` in that class's file (conf 0.9, framework). Keyed only on persisted kind/name/file_path — no extraction/schema/golden change. Deterministic (lexicographic file tie-break, ambiguity left unresolved) and static-only (lowercase/instance receivers never match a Class node). Runs before the autoload roster.
…plan
The raw --impact value was passed straight to resource_impact and then wrapped
by res_path (format!("res://{}")), so a res:// value became res://res://, an
OS-absolute path leaked into the plan, and ./ prefixes were kept. All produced
wrong or empty verify plans.
Add normalize_impact_input: strip a leading res:// first (so a res:// value is
never read as absolute), then a leading ./ or .\, convert backslashes, and if
the result is an absolute path under the project root make it project-relative
(an absolute path outside the root passes through, yielding an empty impact
rather than an error). All input forms now resolve to one project-relative path
and verifyPlan emits a single valid res:// entry.
Each verifyPlan.reasons entry now carries the target path it refers to, so a consumer can tell which changed resource each reason row is about without cross-referencing the impact list. Investigated the reported resolved-edge subkind loss on a real Godot project: ext_resource and autoload refs both surface edgeSubkind correctly via resource_impact's unresolved branch, and the only resolved references edges legitimately carry no subkind. No genuine subkind loss exists, so no resolver/graph change is made beyond the reasons.target field.
…ct/query GDScript source calls are written `ClassName.static_func(...)`, and users naturally paste that qualified form into callers/impact/query. But a `class_name X` global is not stacked during extraction, so its methods store name == qualified_name == the short member name and no dotted node exists — the qualified query matched nothing while the short name worked. Add a CLI-lookup fallback (mirrors the T2 resolver): when the normal search finds no exact-name match and the query is a single-dotted `<Recv>.<member>` whose receiver names a GDScript class node, resolve to the Function named <member> in that class's file. Deterministic (files sorted+deduped), static-only (receiver must be a real GDScript class node), and lookup-only — no extraction, schema, or golden change. callers/impact/query now return the same result for the qualified form as for the short name.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes 4 WorldFlipper-reported Godot static-graph issues plus a follow-up qualified-name query gap, all verified on a real Godot project. No extraction / node-id / schema / golden change; resolver + graph-query + CLI only.
fix(godot)— stripres://at the three Godot path-match sites (find_dangling_references,resource_impactunresolved loop,referenced_resource_paths) soextends "res://…"/get_node("res://…")refs stop false-reporting as dangling and match in impact/orphan.normalize_rel(6 callers) left unchanged.feat(godot)— resolve GDScriptClassName.member(...)static calls to theFunctionnamedmemberin the class node's file (resolver-only, deterministic, static-only).callers/impactnow see these edges.fix(godot)— normalizeaudit --impactinput (res://first →./→\→/→ absolute-under-root relativize, else pass-through). All input forms equivalent; no moreres://res://.feat(cli)— addverifyPlan.reasons[].target; investigated the resolved-edge subkind gap and confirmed it is already covered (ext_resource/autoload carryedgeSubkindvia the unresolved branch).feat(godot)—callers/impact/queryaccept the qualifiedClassName.memberform (a natural paste from GDScript source), returning the same result as the short name. CLI-lookup fallback mirroring T2; no extraction change.Verification
make cigreen locally (fmt + clippy + full workspace test + guardrail).cargo test -p codegraph-benchgreen (golden byte-stable).git diff reference/empty;Cargo.toml/versions untouched; nocodegraph-extract/codegraph-corechange.extends "res://…";callers/impactresolveDamageCalculator.calc_skill_damage; the 4--impactinput forms are equivalent;reasons[].targetpresent; qualified-name queries equal short-name queries.