The query skill was split across two fragments so no platform got both
capabilities: Claude had the vocab/IDF query-expansion step but no fallback if
the CLI was unavailable; every other platform had the inline NetworkX fallback
but the weaker raw-question matcher. Merge into one unified query reference +
stub (Step 0 expansion -> CLI traversal -> inline NetworkX fallback, plus
path/explain inline) shipped to all hosts. Remove the query_variant enum, its
toml field, and the _CLI_ONLY_QUERY_HEADINGS coverage-audit exemption. Re-render
all skill artifacts and re-bless expected/. skillgen check/audit-coverage/
monolith-roundtrip/schema-singleton all pass. Refs #1325.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Same-named types in different packages left implements/inherits edges stuck on
bare shadow stubs, isolating the real interface (_rewire_unique_stub_nodes only
fixes the globally-unique case). New _resolve_java_type_references pass uses each
referencing file's import statements (+ package decl) to build an FQN->def index,
re-points dangling implements/inherits/imports edges to the exact definition, and
drops the orphaned stub. External/stdlib imports stay unresolved (correct). Runs
after id-disambiguation so target ids are final. Java-scoped; other _extract_generic
languages share the same bare-name fallback and remain a follow-up.
Adds tests/test_java_type_resolution.py (simple, ambiguous-by-import, build-survival).
Refs #1318.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adopts the approach from #1330 (thanks @duncan-daydream) on top of the v0.8.40
Swift import fix: _import_swift returns (id,label) module pairs, the extractor
materializes a type=module anchor node per import, and _disambiguate_colliding_node_ids
exempts type=module nodes so the same module imported from N files collapses to
one shared node (enables reverse traversal "what imports CoreKit"). The --no-cluster
writer now dedupes nodes by id and edges to match the clustered build_from_json path.
Replaces the interim _import_label/synthesize_import_module_nodes mechanism.
Adds tests/test_swift_import_resolution.py (cross-file collapse, build survival)
and dedupe_nodes coverage. Refs #1327, #1330.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bump version to 0.8.40; document this release in CHANGELOG (PowerShell .psm1
indexing, Swift import survival, no-cluster edge dedup, custom OpenAI/Anthropic
endpoints, JS/TS assignment-form extraction, community-name + --graph fixes,
four production bug fixes, perf, security CI); add .psm1 to the README
supported-extensions table.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- #1315: add .psm1 to CODE_EXTENSIONS + _DISPATCH so PowerShell modules are indexed
- #1327: synthesize a module node for Swift import targets (new LanguageConfig
flag synthesize_import_module_nodes) so imports edges survive build.py pruning;
strengthen the Swift dangling-edge test to also assert edge targets
- #1317: dedupe parallel edges by (source,target,relation) in the --no-cluster
and incremental update write paths so edge counts are deterministic and
`update` is idempotent
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extract JS/TS this.X=, exports.X=, prototype, class arrow fields, and function expressions (closes#1322). Validated locally against v8: full suite 2069 passed.
export.py: to_json now accepts community_labels and writes community_name onto
each node. Previously cluster-only wrote labels only to GRAPH_REPORT.md,
graph.html, and .graphify_labels.json — graph.json stored only the numeric cid,
so query/MCP showed blank or numeric community values (#1305).
__main__.py: pass community_labels=labels to to_json in cluster-only path.
explain command now prefers community_name over raw numeric community field.
serve.py: query and get_node read paths prefer community_name over community,
with fallback so old graphs without the field still work. Adds --graph flag as
an alias for the positional argument in graphify-mcp/_main(), fixing
"unrecognized arguments: --graph" for users following the documented pattern
shared by every other graphify subcommand (#1304).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
extract.py: clamp ProcessPoolExecutor max_workers to 61 on Windows (issue #1298).
Python's ProcessPoolExecutor hard-caps at 61 on Windows via WaitForMultipleObjects;
>61-core machines crashed on AST extraction. Clamp applied after all input paths
(auto-compute, GRAPHIFY_MAX_WORKERS, --max-workers) to cover all three.
build.py: skip ghost-merge when two AST nodes share (basename, label) key (issue #1257).
When same-named symbols appear in same-named files across directories (e.g. two
render() in two index.ts), last-writer-wins produced an arbitrary canonical node
and mis-pointed all edges. Now tracked in _loc_collisions; ambiguous keys are
skipped in Pass 2, leaving the ghost intact rather than merging into the wrong node.
__main__.py: ignore OSError on unreadable .graphify_version probes (issue #1299).
On restricted-permission installs or network mounts, .exists()/.read_text() raised
PermissionError and crashed every graphify query/explain/path call at startup.
All three FS probes now wrapped in try/except OSError: return.
prs.py: resolve claude.cmd on Windows in prs.py claude-cli backend (issue #1288).
The _call_llm and _call_claude_cli paths were already fixed; prs.py had the same
bare ["claude", ...] call that fails on Windows npm installs with WinError 2.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
stat+MD5 across all files now fans out over a thread pool instead of running sequentially — I/O-bound work releases the GIL so this scales with available disk concurrency.
Eliminates O(depth) generator-frame chain overhead. Each leaf's value previously propagated through 26+ yield-from frames; now a single stack loop handles the full traversal.
Replaced O(n) linear scan `next((n for n in candidates if n["id"] == neighbor_id))` with O(1) dict lookup via pre-built `candidates_by_id`. Also pre-caches `_norm()` results in `norm_cache` to avoid recomputing per inner iteration.
For a 36k-file codebase (~100k high-entropy candidates) this reduces the pass-2 loop from O(n^2*B) (~30–100B iterations) to O(n*B), eliminating the multi-minute CPU hang after AST extraction completes.
Adds complete Persian translation (README.fa-IR.md) with RTL formatting and updates main README language selector to include Persian between Arabic and Italian.
JS/TS symbol resolution only handled named imports. A default-exported
symbol (`export default class Foo`) imported as `import Foo from './foo'`
produced only a file→file `imports_from` edge; the class node received no
incoming symbol edge. On codebases that default-export most classes
(NestJS services/helpers/models, etc.) those symbols looked like isolated
leaf nodes, and `graphify affected "<Class>"` / `explain` reported no
callers.
Record default imports with imported_name="default", register a "default"
export for `export default <class|function|identifier>`, and let the
existing exported-origin resolver wire the `imports` edge — which also
resolves calls made through the local binding, even when it is renamed
(`import Bar from './foo'; new Bar()`). `export { X as default }` was
already handled via the export-clause path; anonymous defaults
(`export default class {}`) have no resolvable symbol and stay file-level.
Adds regression tests for default-export class/function/identifier import
resolution and renamed-binding call resolution.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>