AST file nodes were ID'd from the full relative path plus extension
(match_script_pipeline_step_py) while semantic subagents follow the
{parent_dir}_{stem} spec (script_pipeline_step), so every file split
into two disconnected ghost nodes.
Fix at the single remap chokepoint in extract(): file node IDs and all
edge endpoints already funnel through the #502 relative-path remap, so
changing that remap to emit _file_node_id (one parent dir, no extension)
converts the node and every referencing edge together - Python, TS, Lua,
C and bash import edges all stay connected. symbol_resolution pre-computes
the canonical form directly (bypassing the remap) so it is synced too.
Per-site conversion (as attempted in #1038/#1065) orphans edges because
it moves the node without the edge targets; the chokepoint approach
avoids that entirely.
Backward compat for existing graphs: graphify extract --force, as the
skill.md spec already documents.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
TS 5.0 allows `"extends": ["./a", "./b"]`. _read_tsconfig_aliases called
`extends.startswith("@")` directly, so an array raised
`AttributeError: 'list' object has no attribute 'startswith'`. _safe_extract
caught it and skipped the WHOLE file — and since alias resolution fires on any
path-aliased import, every TS file using `@/`-style imports in a repo with
array-extends tsconfig was dropped, yielding an empty graph.
Normalize `extends` to a list (str -> [str], list kept, else []), process
entries in order (later overrides earlier, current config's paths override all
parents per TS semantics), and skip scoped npm configs (`@...`) per entry as
before.
Adds regression test test_tsconfig_array_extends_alias_resolves_existing_ts_file.