Summary
A JavaScript file that imports a named function through a path alias (import { fn } from "@/other.js") and calls it inside a method body gets a calls edge at graphify 0.8.11 and no calls edge at 0.9.53. The imports and imports_from edges for that file are gone at 0.9.53 as well. The nodes for both functions still exist, extraction reports success, and nothing warns.
The trigger is the alias specifier with no tsconfig.json or jsconfig.json on disk. Frameworks such as uni-app / HBuilderX resolve @/ to the project root natively and ship without either config file, so the alias never reaches _resolve_tsconfig_alias with a mapping and the specifier falls to the bare-import branch in extract.py (the "treat as external" path just below the #701 comment). 0.8.11 still emitted the calls edge in that situation, so this is a regression for those projects.
Versions and environment
- graphifyy 0.8.11 (edge present) and graphifyy 0.9.53 (edge absent), each in its own clean venv, Python 3.14.6, macOS 26.6.2
- Both runs:
graphify update <dir>, no LLM key set, tool printed "no LLM needed"
Minimal reproduction
Two files in an otherwise empty folder. No package.json, no tsconfig.json, no jsconfig.json.
adapter.js
export function enableBackgroundBle(options = {}) {
const timer = setInterval(() => {}, options.interval || 1000);
return () => clearInterval(timer);
}
client.js
import { enableBackgroundBle } from "@/adapter.js";
export class BleClient {
async createBLEConnection({ forceRefresh = false } = {}) {
this._cleanup = enableBackgroundBle({ interval: forceRefresh ? 500 : 1000 });
return true;
}
}
Run:
graphify update "$PWD"
python3 -c "
import json; g=json.load(open('graphify-out/graph.json'))
for l in g['links']: print(l['source'], '--'+l['relation']+'-->', l['target'], l.get('confidence'))"
Actual
graphify 0.8.11 (5 nodes, 5 edges):
client_js --imports_from--> adapter_js EXTRACTED
client_js --contains--> ..._client_bleclient EXTRACTED
..._client_bleclient --method--> ..._client_bleclient_createbleconnection EXTRACTED
..._client_bleclient_createbleconnection --calls--> ..._adapter_enablebackgroundble EXTRACTED
adapter_js --contains--> ..._adapter_enablebackgroundble EXTRACTED
graphify 0.9.53 (5 nodes, 3 edges):
adapter --contains--> adapter_enablebackgroundble EXTRACTED
client --contains--> client_bleclient EXTRACTED
client_bleclient --method--> client_bleclient_createbleconnection EXTRACTED
Expected
At 0.9.53, at least the edge 0.8.11 produced:
client_bleclient_createbleconnection --calls--> adapter_enablebackgroundble
Ideally also client --imports--> adapter_enablebackgroundble and client --imports_from--> adapter, which 0.9.53 does emit when the import is relative (below).
What isolates it
Same two files, three variations, all at 0.9.53:
| Variation |
calls edge |
imports edges |
from "@/adapter.js", no config file |
absent |
absent |
from "./adapter.js", no config file |
present |
present |
from "@/adapter.js" plus a jsconfig.json with "paths": { "@/*": ["./*"] } |
present |
present |
So the alias resolver works when it has a mapping. The regression is the fallback when it has none: 0.8.11 resolved the call by name and 0.9.53 does not.
Impact on a real corpus
On a JS/Vue project of roughly 17.5k nodes that uses @/ throughout and has no tsconfig/jsconfig, rebuilding from 0.8.11 to 0.9.53 took distinct cross-file calls pairs from 482 to 192; 404 of the old 482 have no counterpart in the new graph. Same-file calls pairs rose over the same rebuild (3,955 to 5,201), so this is specific to cross-file resolution. graphify path answers that routed through one of those edges at 0.8.11 now return "No directed path found", and --undirected finds nothing either.
Not a direction artifact
I checked this against the _src/_tgt handling from #563: export.py:347 in 0.9.53 pops the markers and writes source/target in the true order, and the written graph.json carries directed: false with no markers. The edge above is not flipped; it is absent from the link list entirely.
Related
A possible direction: when an alias specifier has no tsconfig/jsconfig mapping and the last path segment matches exactly one on-disk source file in the scanned tree, resolve to it instead of marking it external; or keep the 0.8.11 name-based calls fallback for unresolved bare specifiers, marked INFERRED.
Summary
A JavaScript file that imports a named function through a path alias (
import { fn } from "@/other.js") and calls it inside a method body gets acallsedge at graphify 0.8.11 and nocallsedge at 0.9.53. Theimportsandimports_fromedges for that file are gone at 0.9.53 as well. The nodes for both functions still exist, extraction reports success, and nothing warns.The trigger is the alias specifier with no
tsconfig.jsonorjsconfig.jsonon disk. Frameworks such as uni-app / HBuilderX resolve@/to the project root natively and ship without either config file, so the alias never reaches_resolve_tsconfig_aliaswith a mapping and the specifier falls to the bare-import branch inextract.py(the "treat as external" path just below the#701comment). 0.8.11 still emitted thecallsedge in that situation, so this is a regression for those projects.Versions and environment
graphify update <dir>, no LLM key set, tool printed "no LLM needed"Minimal reproduction
Two files in an otherwise empty folder. No
package.json, notsconfig.json, nojsconfig.json.adapter.jsclient.jsRun:
Actual
graphify 0.8.11 (5 nodes, 5 edges):
graphify 0.9.53 (5 nodes, 3 edges):
Expected
At 0.9.53, at least the edge 0.8.11 produced:
Ideally also
client --imports--> adapter_enablebackgroundbleandclient --imports_from--> adapter, which 0.9.53 does emit when the import is relative (below).What isolates it
Same two files, three variations, all at 0.9.53:
callsedgeimportsedgesfrom "@/adapter.js", no config filefrom "./adapter.js", no config filefrom "@/adapter.js"plus ajsconfig.jsonwith"paths": { "@/*": ["./*"] }So the alias resolver works when it has a mapping. The regression is the fallback when it has none: 0.8.11 resolved the call by name and 0.9.53 does not.
Impact on a real corpus
On a JS/Vue project of roughly 17.5k nodes that uses
@/throughout and has no tsconfig/jsconfig, rebuilding from 0.8.11 to 0.9.53 took distinct cross-filecallspairs from 482 to 192; 404 of the old 482 have no counterpart in the new graph. Same-filecallspairs rose over the same rebuild (3,955 to 5,201), so this is specific to cross-file resolution.graphify pathanswers that routed through one of those edges at 0.8.11 now return "No directed path found", and--undirectedfinds nothing either.Not a direction artifact
I checked this against the
_src/_tgthandling from #563:export.py:347in 0.9.53 pops the markers and writessource/targetin the true order, and the written graph.json carriesdirected: falsewith no markers. The edge above is not flipped; it is absent from the link list entirely.Related
callsedge #3346 (TS: calls through namespace or renamed imports resolve on the identifier, not the binding)callsedge is dropped for aliased imports when the modules live in a subdirectory #2943 (Python: aliased module import losescallsin a subdirectory)extract.py:6514) does not apply? #2837 (question about the JS/TS import gate)A possible direction: when an alias specifier has no tsconfig/jsconfig mapping and the last path segment matches exactly one on-disk source file in the scanned tree, resolve to it instead of marking it external; or keep the 0.8.11 name-based
callsfallback for unresolved bare specifiers, marked INFERRED.