Add record_declaration to the Java class types so a record becomes a first-class
type node instead of an isolated file node, and add object_creation_expression
to call types with a dedicated branch that reads the callee from the `type`
field (new Foo() has no `name` field), so `new Foo(...)` emits a calls edge to
the constructed type.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A .graphifyignore made graphify skip that directory's .gitignore entirely, so a
file excluded only by .gitignore (including neutrally-named secrets the
sensitive-file heuristic misses) got indexed into the graph and could leak into
committed graph artifacts. Read .gitignore first and .graphifyignore last so
their patterns merge and graphifyignore negations still win.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add changelog entries for the Swift cross-file (#1356), update prune (#1361),
obsidian canvas (#1324), and edge source_file backfill (#1279) fixes that
shipped without changelog notes, and a README troubleshooting entry for the
LLM JSON-truncation warnings and how to reduce them.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ollama/openai/deepseek/kimi set max_tokens in their backend config, but the
openai-compat dispatch read only max_completion_tokens (which only gemini
defines), so their output silently capped at the 8192 fallback and truncated
deep-mode JSON. Read either key and give the openai config an explicit cap;
GRAPHIFY_MAX_OUTPUT_TOKENS still overrides.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Three pass-2 guards (mirrored in the --dedup-llm pair collection): block merges
when labels' embedded numbers differ as zero-padding-insensitive multisets;
block cross-file merges of file-anchored rationale/document nodes (same-file
still merges); and score cross-file long labels on plain Jaro instead of
Jaro-Winkler so the prefix bonus can't fabricate merges of shared-prefix but
token-divergent entities (jest-native vs react-native), while genuine cross-file
duplicates still clear Jaro and same-file near-duplicates keep Jaro-Winkler.
Co-Authored-By: van4oza <van4oza@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Semantic/LLM edges occasionally omit source_file, which build only normalized
when already present, so the field reached graph.json empty and downstream
validation flagged it. Backfill from the source (then target) node in
build_from_json and in the --no-cluster raw-write path, which bypasses it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
to_canvas built cards solely by iterating communities, so a graph with no
community data (--no-cluster builds, or a missing analysis sidecar) wrote the
empty 32-byte {"nodes":[],"edges":[]} shell while notes rendered fine. Fall back
to one synthetic community covering every node so the canvas reflects the graph.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The /graphify --update runbook called build_merge with absolute prune_sources
but no root=, so _norm_source_file never relativized them to match the graph's
relative source_file values. Nothing was pruned and changed/deleted files left
ghost nodes that compounded on every incremental update. Fix the shared skillgen
fragment to pass root='INPUT_PATH' and re-render all platform artifacts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Capture property/field initializer constructor calls, build a per-file Swift
type table from property/parameter declarations, and add a member-call
resolution pass that types the receiver and emits an edge only when the type
name resolves to exactly one definition. Additive and INFERRED-only; the
is_member_call drop and the #543/#1219 god-node guards stay intact.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Harden HTML export against U+2028/U+2029 script-breakout XSS + two crash-on-adversarial-input fixes (non-dict LLM JSON, _extract_parallel IndexError). Validated: full suite 2107 passed, HTML export smoke clean. Thanks @mistic96.
build_merge: prune a re-extracted file's stale nodes/edges before merge instead of accumulating (fixes#1283, #1285). Validated: full suite 2107 passed. Thanks @RelywOo.
Index PowerShell .psd1 manifests + emit Import-Module/dot-source edges (closes#1331). Builds on the shipped .psm1 support. Validated: full suite 2107 passed, 18 new tests. Thanks @geektan123.
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.