2148 Commits
Author SHA1 Message Date
safishamsiandClaude Opus 4.8 f765dcb341 release: 0.9.79
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
v0.9.79
2026-10-06 22:37:19 +01:00
safishamsiandClaude Opus 4.8 c0024cfeab fix(watch): exclude graph.json schema metadata from the topology compare
0.9.78's #4167 added schema_version/graphify_version under graph.json's
"graph" key, but the candidate topology built from node_link_data(G) has no
such metadata, so _canonical_topology_for_compare judged every incremental
update a topology change and needlessly re-clustered and re-rendered. Strip the
two metadata keys before comparing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-06 22:37:10 +01:00
safishamsiandClaude Opus 4.8 397d025f7b release: 0.9.78
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
v0.9.78
2026-10-06 20:55:39 +01:00
Mahaboob pasha 9d3ccfc99c fix(analyze): keep edge direction in graph_diff
graph_diff keyed undirected edges by their sorted endpoints, so a call
that reversed (a->b became b->a) dropped out of the --update summary.
When both snapshots carry _src/_tgt and they differ, the edge is now
reported as one removed and one new edge, with source/target taken from
the markers. Graphs without the markers diff as before.

The skill's diff step loaded the old graph.json with a plain
node_link_graph call, which drops the stored direction, so it now uses
paths.load_node_link_graph (#4066/#4088). Skills regenerated with
skillgen, and the aider/devin monolith round-trip allows the changed
lines.

Closes #4067

Co-Authored-By: Grok Bot <noreply@x.ai>
(cherry picked from commit 917fab9fbb)
2026-10-06 20:55:02 +01:00
nikhil2004andClaude Opus 4.8 3ba28369fb fix(extract): preserve C++ classes behind export macros (#3661, #3648)
C++ projects using module export macros (class MYLIB_EXPORT Foo {}) lost the
class and its inherits edge because tree-sitter-cpp fails to parse the macro and
error-recovers over the declaration. Blank the ALL-CAPS macro token before
parsing, preserving byte length and newlines so offsets and ids stay stable;
guarded so legitimate all-caps class names, 'final', range-for, and brace-init
are never touched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-06 20:54:55 +01:00
rohit-jsfreakyandClaude Opus 4.8 be528e110e fix(extract): decode UTF-16 and non-UTF-8 source files before parsing (#4146, #4145)
tree-sitter reads UTF-8, but extractors handed it raw file bytes, so a UTF-16
file yielded zero nodes and a cp1252 name like CaféOrder was truncated. Decode
via a shared reader that returns UTF-8 bytes unchanged (ids/offsets/cache keys
for working files are byte-identical) and re-encodes UTF-16/UTF-32/non-UTF-8
using a deterministic cp1252->latin-1 fallback. The semantic whole-file and
slice readers now share the same decoder.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-06 20:54:45 +01:00
rohit-jsfreakyandClaude Opus 4.8 51c05e7beb fix(watch): keep placeholder nodes for never-scanned files on incremental rebuilds (#4161, #4160)
On an incremental reconcile the corpus sweep read a never-scanned referenced
path (unresolved dynamic import target, .sln/.csproj reference) as a deleted
source and evicted the placeholder node a referrer minted, dropping the
unchanged referrer's preserved cross-file edges. Skip eviction for such
referenced-but-unscanned paths (purely additive; never causes a new eviction),
with deletion-evidence guards so genuinely deleted files are still reaped.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-06 20:54:35 +01:00
oleksii-tumanovandClaude Opus 4.8 84d0ec76d9 fix(powershell): resolve member calls by receiver owner (#4163)
Replaces the name-only member-call resolver with receiver-proven binding:
$this.M() binds to the enclosing class only when its name is unique, and
[Type]::M() only to a single unambiguous local class; value/cast/dynamic/
duplicate/external receivers fall through to raw_calls. Fixes the same-name-
method caveat from the 0.9.77 member-call work (#3992).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-06 20:54:25 +01:00
rohit-jsfreaky 1d38e84a91 perf(watch): compute each stored source path's identity once per update
_reconcile_existing_graph asks _StoredSourcePaths.identity() for every
node, edge and hyperedge, several times per item. On graphify's own repo
that is ~150k calls for under 1k distinct files, each rebuilding Path
objects and an abspath. The answer depends only on the stored path and
roots fixed in __init__, so cache it (and in_watch_root) per instance.

Reconcile step on this repo: 10.3 s -> 2.7 s. graph.json is byte-identical
with and without the change for a full build and for an incremental
update with an edited, a deleted and an added file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B8gwEpjajiWHVW3c9smuKf
(cherry picked from commit 1360728f0f)
2026-10-06 20:54:15 +01:00
rohit-jsfreaky 0083ee78e5 fix(python): keep missing relative-import targets out of the checkout path
A relative import of a module with no file behind it (`from .absent
import x`) built its target from the attempted absolute path, so the id
became e.g. `c_users_<user>_..._pkg_absent_py`. The root-relative id
remap never rewrites it (there is no node to anchor on), so the checkout
location and the OS username ended up in graph.json and every clone got
a different id.

Use the dotted module name relative to the scan root instead
(`pkg_absent`), the id an unresolved absolute import of the same module
already gets, so `from .absent` and `from pkg.absent` now meet on one
node. An import that climbs above the scan root falls back to a `ref`
id. Imports that resolve, and attempted paths that exist, are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B8gwEpjajiWHVW3c9smuKf
(cherry picked from commit 8cdbb0ea4f)
2026-10-06 20:54:15 +01:00
Mahaboob pasha ac467b0c00 fix(csharp): resolve calls through casts and interface-typed properties (#3797)
((IStore)_obj).Save() and (_obj as IStore).Save() produced no calls edge:
the receiver capture didn't handle a parenthesized cast, so the call never
reached the receiver-typed resolver. Use the cast's type as the receiver so
it resolves like IStore.Save().

IStore Store => ...; Store.Save() bound to the concrete Store.Save() because
a capitalised receiver was looked up as a type name first. A field, property
or local of that name shadows the type, so try the recorded receiver_type
first and fall back to the type name.

Co-Authored-By: Grok Bot <noreply@x.ai>
(cherry picked from commit 2d26dad2ab)
2026-10-06 20:54:15 +01:00
rohit-jsfreaky f50b79ffc5 fix(js): keep unresolved require() targets out of the checkout path
A CommonJS require() of a relative module with no file behind it resolved
to the attempted absolute path, and the target id plus every destructured
symbol under it were minted from that path. The checkout location and the
OS username ended up in node ids, so the same repo gave a different
graph.json in every clone.

#2457 fixed this for static imports only; require() goes through
_require_imports_js. Apply the same rule there: a resolved path that is
not a file is unresolved and gets the stable ref id.

The two destructured/member require tests asserted symbol edges into
cjs_require.js's missing ./foundation and ./helpers, i.e. those
absolute-path ids. They now run the fixture next to real modules, so
they still check one imports edge per binder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B8gwEpjajiWHVW3c9smuKf
(cherry picked from commit 5759ab1331)
2026-10-06 20:54:15 +01:00
rohit-jsfreaky 32034eccf5 fix(csharp): make namespace scope ids independent of line endings
C# namespace scope ids (the scope_chain / scope_id node metadata a
namespace-scoped `using` is matched against) were `s{start_byte}`. A
CRLF checkout (Windows with core.autocrlf) has one more byte per line
than an LF checkout of the same commit, so the two wrote different
metadata into graph.json: on Newtonsoft.Json's 242 source files, 3,096
of 4,352 nodes (71%) differed by line endings alone.

Use row:column, which is the same for both and just as unique. The ids
are only compared within one file, so resolution is unchanged: all
11,627 edges are identical before and after, and the nodes only differ
in the id text (identical once each id is mapped to its scope).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B8gwEpjajiWHVW3c9smuKf
(cherry picked from commit ffd855a6fa)
2026-10-06 20:54:15 +01:00
smngvlkz 251722cf17 Add graph.json schema version metadata
(cherry picked from commit 4fbf544949)
2026-10-06 20:54:15 +01:00
Huanyi Xie fb8b98fb52 fix(r): preserve symbol-only binding identity
Keep operator scopes separate from their containing file and ordinary normalized identifiers, including nested bindings.

Co-Authored-By: OpenAI Codex <noreply@openai.com>
(cherry picked from commit dc43c7c646)
2026-10-06 20:54:15 +01:00
rohit-jsfreaky e82d97d9e6 fix(dm): key .dmf element ids on the window name, not its node id
extract_dmf minted each element id from its window's node id, which
already embeds the file stem. The stem is absolute at extraction time and
extract()'s id remap only rewrites the leading copy, so the nested copy
kept the checkout path and the OS username inside every element id.

Use the window name instead. Element ids no longer depend on where the
repo is checked out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B8gwEpjajiWHVW3c9smuKf
(cherry picked from commit 3d9021fab7)
2026-10-06 20:54:15 +01:00
safishamsiandClaude Opus 4.8 5c7b84792f release: 0.9.77
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
v0.9.77
2026-10-05 20:13:41 +01:00
brunovima83 031c58ee18 fix(skill): run Step B3 when every semantic file is cached
The split-host runbook told the agent to skip to Part C when every doc,
paper and image hit the semantic cache. Part C reads
.graphify_semantic.json unconditionally and only the Step B3 merge writes
it, so a second run on an unchanged mixed corpus failed with
FileNotFoundError; writing an empty file instead dropped every cached node.

The all-cached case now skips B1/B2 but still runs B3. Step B0 also clears
.graphify_chunk_*.json before dispatch: B3 merges every chunk on disk, and a
leftover from an interrupted run would otherwise be merged as fresh.

The aider and devin monoliths keep the old sentence; they are pinned to the
v8 baseline by --monolith-roundtrip.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit bab1bb2775)
2026-10-05 20:12:34 +01:00
Mr-Neutr0n 2ce4a20764 Fix #3690: benchmarks: code-intelligence result inaccessible; BENCHMARK
Signed-off-by: Mr-Neutr0n <64578610+Mr-Neutr0n@users.noreply.github.com>
(cherry picked from commit c7bbf6c834)
2026-10-05 20:12:26 +01:00
akshit af83bc00aa test: cover untouched good-first regressions
(cherry picked from commit cd49739c92)
2026-10-05 20:12:06 +01:00
Huanyi Xie 3f712fe309 fix(build): honor disabled same-file semantic deduplication
Pass dedup=False into graph construction so repeated labels do not collapse distinct non-AST identities. Preserve existing AST and document-file twin reconciliation. Fixes #4019.

Assisted-by: OpenAI Codex (GPT-6)
(cherry picked from commit 1f3805f16e)
2026-10-05 20:11:30 +01:00
Huanyi Xie 28675a191c fix(rust): omit anonymous constants from graph symbol identity
Assisted-by: OpenAI Codex (GPT-6)
(cherry picked from commit 99159f362c)
2026-10-05 20:11:30 +01:00
Huanyi Xie c3d54bc52b fix(extract): keep cross-drive diagnostic paths from aborting extraction
Assisted-by: OpenAI Codex (GPT-6)
(cherry picked from commit 8a98287926)
2026-10-05 20:11:30 +01:00
rajatnagda45 76576865b6 fix(powershell): record method calls ($this.M(), [Type]::M())
The call walker only visited `command` nodes, so every method invocation
was dropped from the call graph. A method call parses as an
invokation_expression (callee in the member_name child, args in
argument_list); a bare property read ($this.Name) is a separate
member_access node and correctly produces no call.

Handle invokation_expression and resolve the member name against a
method-only label map so a member call binds to a method, never to a free
function that merely shares the name (fail-closed on unknown receivers).

Fixes #3992

(cherry picked from commit 1e6a9029e4)
2026-10-05 20:11:30 +01:00
raditha dissanayake 5eeb037929 fix(export): preserve stored edge direction when reloading graph.json (#4066)
graph.json is written "directed": false and carries direction in arc order
(#563), or in _src/_tgt markers on legacy files (#2309). A plain undirected
node_link_graph load re-orders endpoints by node-list position, so every
link whose target precedes its source in the node list came back reversed
(79% of links on graphify's own graph).

query, path/explain, affected, merge-graphs and the MCP server already
restore direction. The remaining reloaders did not:

- export html / neo4j / falkordb (cypher.txt and --push) / obsidian canvas
- graph.html re-rendered by update, watch and the git hooks
- the git merge driver, which wrote the reversal into the committed
  graph.json, even when merging three identical copies

Add paths.restore_arc_direction, the inline idiom query and merge-graphs
already use, and apply it in load_node_link_graph and the export loader.
to_cypher, to_canvas and push_to_neo4j/falkordb now read _src/_tgt as
to_html already does. The merge driver writes direction back into arc
order before saving, as to_json does, so the committed file keeps the
post-#563 convention.

global add has the same bug and is fixed separately.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 5a461c99d7)
2026-10-05 19:40:13 +01:00
harshaygadekar 79b7a8450f fix(detect): scope incremental manifest anchoring to prevent false deletions (#3785)
(cherry picked from commit 1c161995bf)
2026-10-05 19:40:13 +01:00
Sourya Prabaharan 674338b924 fix(extract): terminate trailing Astro script comments
Preserve subsequent script blocks and byte offsets when a script ends in a line comment. Fixes #4072.

Co-authored-by: Codex <noreply@openai.com>
(cherry picked from commit fc48f659c4)
2026-10-05 19:40:12 +01:00
Huanyi Xie b182659519 test(cpp): distinguish union methods from data fields
Assisted-by: OpenAI Codex (GPT-6)
(cherry picked from commit 0a7c77cb63)
2026-10-05 19:40:12 +01:00
Huanyi Xie 5ac6a3c934 test(extract): preserve cross-language JVM inheritance
Assisted-by: OpenAI Codex (GPT-6)
(cherry picked from commit 1575f867f2)
2026-10-05 19:40:12 +01:00
Huanyi Xie 9a1d8c06ca test(kotlin): isolate annotation class literal boundaries
Assisted-by: OpenAI Codex (GPT-6)
(cherry picked from commit 42dc0a7fee)
2026-10-05 19:40:12 +01:00
Jarvis-J-Jacob d07c91cba9 test(csharp): assert bare this?.M() null-conditional call resolves
#3976 made recv?.M() resolve through the receiver-typed path but only
tested field receivers. Add a test that `this?.Save()` inside a class
resolves to the enclosing class's Save() like `this.Save()`, and not to
an unrelated class's Save().

Fixes #4077

(cherry picked from commit 6ce8348026)
2026-10-05 19:40:12 +01:00
harshaygadekar 5c5ce7fb53 fix(js): extract methods on exported objects to resolve CommonJS/ESM calls (#3778)
(cherry picked from commit 65f2da519f)
2026-10-05 19:40:12 +01:00
Huanyi Xie d2b5e19682 fix(php): resolve builtin-named methods on known self receivers
Assisted-by: OpenAI Codex (GPT-6)
(cherry picked from commit aca5651bc8)
2026-10-05 19:40:12 +01:00
Mahaboob pasha 1f078a3ee9 fix(extract): don't emit L1 for zero-byte files
A 0-byte file has no line 1, so the file node's source_location=L1 was an
anchor that can never resolve. Leave it None (how other location-less file
nodes already look) in the shared tree-sitter path, and keep the node: an
empty __init__.py is still a module other files import.

Closes #4041

Co-Authored-By: Grok Bot <noreply@x.ai>
(cherry picked from commit 29597adec8)
2026-10-05 19:40:11 +01:00
Mahaboob pasha ee292729ae feat(scala): extract class-level context bounds
`class Foo[A: Ordering]` (and the same on case classes, traits and enums)
now emits a `references` edge with the `type_bound` context from the type
to each bound, reusing the method-level path from #2046.

Closes #4080

Co-Authored-By: Grok Bot <noreply@x.ai>
(cherry picked from commit d1257efb7d)
2026-10-05 19:40:11 +01:00
CLOCKZW 438a050e08 test(elixir): assert defguardp private guards are extracted
#4027 taught the Elixir extractor defmacro, defmacrop, defguard and
defguardp, but the regression test only exercised the first three. Add a
defguardp clause to the fixture and assert its node is extracted, so
dropping defguardp from the extractor's keyword list fails this test.

Closes #4076

Co-Authored-By: Claude <noreply@anthropic.com>
(cherry picked from commit 345b66b01f)
2026-10-05 19:40:04 +01:00
Jarvis-J-Jacob 9f6c0b9721 test(zig): assert a tagged-union nested-struct payload's fields are not minted
#4050 emits a node + case_of edge per `union(enum)` variant but
deliberately does not recurse into a variant's nested-struct payload.
Extend the tagged-union test to pin that: `rectangle: struct { w, h }`
is minted as a variant, while its `w`/`h` fields are not, and
`rectangle` owns no case_of edges of its own.

Fixes #4074

Co-Authored-By: Claude <noreply@anthropic.com>
(cherry picked from commit 994bb2e534)
2026-10-05 19:40:04 +01:00
MalikHaroonKhokhar eb283ca58b test(pascal): cover the valued enumerated form (#4071)
Enum extraction (#4056) is only tested with the plain form,
`TColor = (clRed, clGreen, clBlue)`. The valued form nests an explicit
ordinal under declEnumValue -> defaultValue, beside the value's identifier,
so a change to how the value's name is read could leak the literal into the
label, or emit it as a node, without any test noticing.

Add a test for `TColor = (clRed = 1, clGreen = 2)` asserting the type node,
a `case_of` edge to each bare value name and to nothing else, and that
neither literal becomes a node.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 1b05d1593c)
2026-10-05 19:40:04 +01:00
rajatnagda45 e4c48bf97e fix(objc): extract C-style enum declarations
Objective-C is an object-oriented extractor that already emits @interface,
@implementation and @protocol type nodes, but plain `enum` and
`typedef enum` definitions were dropped entirely: both the enum type and all
of its enumerators were invisible in the graph.

Handle `enum_specifier` (and the `typedef enum { ... } Name;` form, where the
name is the typedef alias), emitting a type node plus a node and `case_of` edge
per enumerator — the same shape every other language with enums uses (Java
#1719, C++ #3939, Swift). A nameless enum and a bodyless `enum X y;` reference
are skipped so no phantom node is minted.

NS_ENUM/NS_OPTIONS is deliberately out of scope: it is a macro call that
tree-sitter-objc parses into an ERROR subtree, so there is no clean AST to
read. Documented in the test.

Add regression tests for the plain, typedef, and skip cases.

(cherry picked from commit 2d30c413d3)
2026-10-05 19:40:03 +01:00
rajatnagda45 e444d5ac37 fix(sql): link a trigger to the function it executes
A trigger exists to run a routine, but the create_trigger branch only linked
the trigger to its ON table and ignored the EXECUTE FUNCTION/PROCEDURE clause
entirely. The routine a trigger fires — its whole reason to exist — was never
represented in the graph.

Register function/procedure node ids in a routine_nids map (mirroring
table_nids) and, in create_trigger, read the object_reference after EXECUTE and
emit an `executes` edge to that routine. Resolution is name-based against
routines defined in the same file (a trigger's function is declared before it);
when the routine is not in this file the edge is skipped rather than guessing a
cross-file target or minting a bare-name stub that could never rewire onto a
`name()` function node.

Add regression tests for the resolved case and the fail-closed case.

(cherry picked from commit ea39929459)
2026-10-05 19:40:03 +01:00
rajatnagda45 282e2bbc56 fix(powershell): link enum members with case_of instead of contains
A PowerShell enum member is a discriminant case, not a declared field, so it
should hang off its enum with a case_of edge like every other language that
has enums (Java #1719, C#, Swift, Rust, VB.NET). PowerShell was routing enum
members through the same contains path used for class members.

The relation is not cosmetic: case_of targets are excluded from constructor
binding, so an enum member named like a type could previously be mistaken for
one.

Update the existing enum test to expect case_of and add a focused regression
test asserting enum members get case_of (and not contains) while the file
still contains the enum type via contains.

(cherry picked from commit 011dda9b0f)
2026-10-05 19:40:03 +01:00
rajatnagda45 4f0eb679bf fix(solidity): link enum values with case_of instead of contains
A Solidity enum value is a discriminant case, not a declared member, so it
should hang off its enum with a case_of edge like every other language that
has enums (Java #1719, C#, Swift, Rust, VB.NET). Solidity was the lone
remaining outlier, routing enum values through the same contains path used
for struct fields.

The relation is not cosmetic: case_of targets are excluded from constructor
binding, so an enum value named like a type could previously be mistaken for
one. Struct fields keep contains, since a field is a real declaration.

Add a regression test asserting enum values get case_of (and not contains)
while a struct field keeps contains.

(cherry picked from commit fdd99502d7)
2026-10-05 19:40:03 +01:00
safishamsiandClaude Opus 4.8 b57cf9108e docs: add GraphGuild community-program banner
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-05 17:46:04 +01:00
safishamsiandClaude Opus 4.8 35adf432b9 docs(readme): shrink the Translations section to a one-liner
The top-of-README language switcher already lists every language; keep
the bottom section as a short pointer to docs/translations/ and the
PR-to-contribute call, instead of duplicating the full table.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-04 22:28:19 +01:00
safishamsiandClaude Opus 4.8 61f610d550 docs(readme): add a Translations section
List all 32 README languages in a dedicated Translations section (a
#translations anchor), like other large OSS READMEs, with a pointer to
docs/translations/ for improving or adding one.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-04 22:19:38 +01:00
safishamsiandClaude Opus 4.8 0010e25d0d release: 0.9.76
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
v0.9.76
2026-10-04 21:02:57 +01:00
Syed Fahad c37be6169c chore(packaging): point the PyPI homepage at graphify.com
The PyPI page listed the GitHub repo as both Homepage and Repository, so
nothing on PyPI linked to the project's website or docs. Homepage is now
graphify.com, docs.graphify.com is listed as Documentation, and Repository
still points at GitHub.

(cherry picked from commit 25a73bcaa6)
2026-10-04 21:02:36 +01:00
safishamsiandClaude Opus 4.8 76d3b88aee docs: correct two stale resolver comments
Follow-ups on #3975 and #4043:
- the PHP language-construct set cited _GO_PREDECLARED_FUNCS, which does
  not exist; the real analog is _LANGUAGE_BUILTIN_GLOBALS.
- _EXTERNAL_STUB_RELATIONS said `calls` to an external target is always
  suppressed (#3156); after #4043 an imported module does receive a
  `calls` edge, so clarify the actual invariant.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-04 20:41:11 +01:00
Alex Tumanov 140cb590e7 test(extract): preserve isolation with external module calls
Require exact coarse calls to unresolved absolute imports while retaining
package and cross-directory local target isolation.

AI-Assisted-By: OpenAI Codex
(cherry picked from commit e1eeede9b5)
2026-10-04 20:40:34 +01:00
Alex Tumanov 33edd89b43 fix(extract): preserve Python import bindings across sibling cases
Use existing plain-import provenance to distinguish separate and same-line from-imports. Reject type aliases and dynamic namespace references, and protect changed and unchanged external callers through incremental CLI extraction.

Fixes #3793

AI-Assisted-By: OpenAI Codex
(cherry picked from commit 5e240c8bd3)
2026-10-04 20:40:34 +01:00