1480 Commits
Author SHA1 Message Date
safishamsiandClaude Opus 4.8 558df6d57d chore: bump to 0.9.46
Ships: Common Lisp extractor; the graph-correctness batch (edge-relation
collapse #2803, orphaned-import sweep #2807, hyperedge member rewire #2805,
non-UTF-8 ignore #2798, id-less hyperedge #2775, Go case-only #2779,
.graphify_root validation #2603, #2694 part 2); the fresh-contributor batch
(markdown node_kind #2533, post-checkout skip #2421, underscore query #2473,
Java annotation refs #2426); and the normalize_id caseless-stability fix.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
v0.9.46
2026-08-17 19:21:39 +01:00
safishamsiandClaude Opus 4.8 0929519ec1 fix(ids): make normalize_id caseless-stable via a casefold+NFKC fixpoint loop
casefold and NFKC do not commute and neither is a fixpoint of the other, so a
single NFKC(casefold(...)) pass left normalize_id(s) != normalize_id(s.casefold())
for some combining-mark sequences (e.g. Greek ypogegrammeni U+0345 + a combining
accent): pre-casefolding turned U+0345 into iota, which NFKC then composed with
the accent into a form the single pass never saw. Iterate casefold-then-NFKC to
a bounded fixpoint (casefold first, on the raw input) so the result is stable
regardless of prior casefolds. No churn: letter/digit-bearing ids and every
CONTRACT_CASE are byte-identical; idempotency, word-only, and the Turkish (#2614)
cases still hold. Adds a deterministic regression pin so the fix does not rely on
hypothesis re-drawing the codepoints.

This was a pre-existing latent bug (present in released 0.9.45), surfaced by the
hypothesis property test; ids.py was untouched by the PRs landed alongside it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-17 19:00:46 +01:00
safishamsiandClaude Opus 4.8 356b50df6f test(java): make the annotation no-dup test effective + add string/enum guard (#2426)
The no-dup test asserted on _edge_labels (a set), so count()==1 was trivially
true whether or not the dedup ran. Count raw edge occurrences (normalized
labels) so a regressed dedup — @Uses({X, X}) emitting 2 edges — actually fails
the test. Adds a guard that string/enum annotation arguments (@RequestMapping(
"/x"), @Retention(RetentionPolicy.RUNTIME)) do not fabricate type refs. Adds
the CHANGELOG entry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-17 18:42:21 +01:00
oleksii-tumanovandClaude Opus 4.8 eadef76a68 fix(extract): emit Java annotation type references (#2426)
Java annotation extraction read only the annotation's name field, dropping
class-literal arguments (@Repeatable(RubricsFor.class), @Uses({A.class,B.class}))
and annotation-member return types (RubricFor[] value()), so a container
annotation became a disconnected island. Emit references edges for both, with
qualified-identity preservation; string/enum annotation arguments are not
treated as type refs, and JDK annotations resolve to sourceless stubs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-17 18:33:35 +01:00
safishamsiandClaude Opus 4.8 4be19990aa test(serve): add over-match guard for underscore token splitting (#2473)
The fix broadens seeding (splitting user_service into user + service), so add
the negative test the deep-dive flagged: an unrelated single-token node must not
out-rank the node matching the full multi-token query. (Note: _search_tokens
does not split camelCase, so the fix covers underscore/hyphen, not camelCase.)
Adds the CHANGELOG entry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-17 18:30:50 +01:00
nadiadatepe-engandClaude Opus 4.8 236ed9e548 fix(serve): treat _ as a token separator so underscore queries match hyphenated labels
_search_tokens split on \w+, which counts _ as a word char but not -, so a
query spelled with underscores stayed one un-matchable token while a hyphenated
label tokenized into parts. Splitting on [^\W_]+ makes both sides tokenize
consistently, so `graph_first_guard` matches `graph-first-guard`. Both query
and label route through _search_tokens, keeping the two sides symmetric.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-17 18:01:01 +01:00
safishamsiandClaude Opus 4.8 355b6628b4 test(hooks): replace static string-match with a runtime same-head test (#2421)
The PR's test only asserted the guard line exists in the source — the exact
static-assertion anti-pattern #2126/#2641 removed for providing zero coverage.
Replace it with a runtime test that runs the real emitted post-checkout script
under sh up to the guard (with a sentinel, before the launch): same-head skips,
different-head falls through, file-checkout skips at the earlier guard. Adds the
CHANGELOG entry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-17 17:53:43 +01:00
nothariharanandClaude Opus 4.8 5c88af7617 fix(hooks): skip post-checkout rebuild when PREV_HEAD equals NEW_HEAD (#2421)
git checkout -b <new> reports a branch switch (flag=1) but passes the same SHA
for prev and new HEAD, so the post-checkout hook fired a full changed_paths-less
rebuild — the most expensive graphify op — on essentially every new branch. Add
a POSIX-sh guard that exits 0 when PREV_HEAD == NEW_HEAD; a real branch switch
(PREV != NEW) still rebuilds.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-17 17:47:56 +01:00
safishamsiandClaude Opus 4.8 e7cb399ac3 test(markdown): cover PyYAML-absent fallback and heading id-stability (#2533)
Adds the two gaps the deep-dive flagged: a direct test of
_parse_frontmatter_fallback (the PyYAML-absent path, previously unexercised)
and an assertion that heading ids stay _make_id(stem, title) regardless of
frontmatter/node_kind (pins no id-churn). Also adds the CHANGELOG entry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-17 17:47:16 +01:00
evanthomasgeldersandClaude Opus 4.8 cf4dac387f feat(markdown): tag page vs heading nodes and extract frontmatter
Adds an additive node_kind ("page"|"heading") attribute so a docs corpus can
distinguish the page node from its heading nodes, and parses leading YAML
frontmatter onto the page node via the bounded sanitize_metadata (values become
capped attributes, not graph nodes). Also fixes a leading YAML `#` comment in
frontmatter being extracted as an H1. Node ids/labels/file_type are unchanged,
so existing markdown graphs are not re-keyed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-17 17:21:01 +01:00
safishamsiandClaude Opus 4.8 7765d610d8 fix(commonlisp): mint sourceless stubs for import targets so edges don't dangle
The extractor emitted `imports` edges to _cl_id(mod_name) with no node created,
so a :use/require edge dangled (verified: sample.lisp left 'cl' and 'alexandria'
as edgeless targets) and never resolved to an in-corpus defpackage. Mint a
sourceless stub for each import target (the established cross-file pattern):
edges now have real targets, the corpus rewire collapses a stub onto a unique
in-corpus defpackage of the same name (:use :mylib -> the real package), and an
external one (cl) persists as a clean leaf. origin_file is stripped before
persist. Adds a no-dangling-edges regression test. Also adds the CHANGELOG entry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-17 16:08:55 +01:00
fadeandClaude Opus 4.8 038ada6f0f feat(commonlisp): add Common Lisp .lisp/.cl/.lsp/.asd extractor (optional tree-sitter-commonlisp extra)
tree-sitter-commonlisp-backed extractor for packages, classes, functions,
methods, generics, macros, variable definers, and same-file calls. Handles the
grammar's dedicated defun node (defun/defmacro/defmethod/defgeneric via
defun_header) and the generic list_lit + leading-symbol forms (defvar/defclass/
defpackage), recurses into wrapper macros (eval-when/progn) and reader
conditionals, and maps CL operator chars (= < > ? ! + *) to readable id
suffixes. Wired into detect/extract dispatch, the extractor registry, a
[commonlisp] optional extra, and the README; ships a fixture and a 23-test suite
behind an importorskip guard.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-17 16:05:33 +01:00
safishamsiandClaude Opus 4.8 c44a6cf7f1 cleanup(serve): drop dead <=budget sub-branch in the overshoot notice (#2784)
The `if len(output) <= char_budget: return output` inside the cut_count==0
block sits within `if len(output) > char_budget`, so it can never be true.
Remove it and note why the branch is reached only when nodes fit but edges
overrun. Also adds the CHANGELOG entry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-17 15:41:59 +01:00
AromalBiju1andClaude Opus 4.8 7d01244a03 fix(serve): honest overshoot notice when edges push a complete answer over budget (#2784)
An unconditional early return once no whole node line was cut returned the full
edge section unchecked, so a query could emit ~6x the requested budget with no
indication — and the truncation banner advised raising the budget, the exact
trigger. When the node set fits but appending edges overruns the budget, prepend
an honest 'complete answer over budget' notice (real counts, estimated vs
requested tokens) without truncating edges, preserving the #2601 completeness
guarantee. The genuinely-fits case is byte-identical.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-17 15:40:56 +01:00
safishamsiandClaude Opus 4.8 7722e23900 fix(detect): decode a UTF-16-BOM ignore file instead of latin-1 garbage (#2798)
The #2799 fallback (utf-8 -> host codepage -> latin-1) turned a BOM'd UTF-16
ignore file (what PowerShell Set-Content / Notepad 'Unicode' write) into
NUL-laden mojibake via latin-1, so its rules matched nothing. Detect the
UTF-16 BOM and decode as utf-16 before the latin-1 fallback. Adds an
end-to-end UTF-16 exclusion test and a direct no-NUL-garbage test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-17 15:40:19 +01:00
abhay-codes07andClaude Opus 4.8 7d855bbc98 fix(detect): stop silently dropping rules from a non-UTF-8 ignore file (#2798)
Ignore files were read with errors="ignore", so a mis-encoded byte in a rule
(e.g. a cp1252 accented directory name) was deleted, turning the pattern into
one that matches nothing — an exclusion that silently failed open. Both
rule-read sites now decode UTF-8-BOM first, then fall back to the host codepage
and latin-1 (never raising, never dropping bytes), with a one-time warning; the
rule survives intact instead of being truncated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-17 15:36:14 +01:00
safishamsiandClaude Opus 4.8 5929edf984 test(dedup): pin chained-collapse and kept-single-member for hyperedge rewire (#2805)
Adds the two cases the deep-dive flagged: a chained collapse (a_old, a_mid -> a)
lands directly on the final survivor (the whole fix rests on the union-find
remap being fully flattened), and a hyperedge collapsing to one distinct member
is kept, not dropped (pinning the deliberate sub-two-member policy). Adds the
CHANGELOG entry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-17 15:35:53 +01:00
abhay-codes07andClaude Opus 4.8 0d145fe110 fix(dedup): rewire hyperedge members onto survivors instead of dropping them (#2805)
Node dedup rewired edge endpoints to survivors but never remapped hyperedge
members, so a member naming a merged-away node silently vanished from the
rebuilt graph (the group shrank with no dangling reference). deduplicate_entities
now rewires hyperedge member ids through the same union-find survivor map the
edges use, de-duplicating members within each hyperedge; id-less/malformed
hyperedges pass through untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-17 15:34:16 +01:00
safishamsiandClaude Opus 4.8 795102cc28 test(build): pin shrink-guard safety and shared-stub survival for orphan sweep (#2807)
Documents the target-only invariant that makes a single sweep pass sufficient,
and adds two tests: the sweep does not trip the #479 shrink guard (swept
source-less orphans count as explained loss), and a stub still referenced by a
surviving file is not swept. Also adds the CHANGELOG entry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-17 15:33:34 +01:00
abhay-codes07andClaude Opus 4.8 ab73657432 fix(build): sweep external-import nodes a prune leaves orphaned (#2807)
An external-import stub (source_file: "") is only ever an edge target of the
file that imported the symbol. When prune_sources removes that file, the stub
is stranded at degree 0 but no stale-node path reaches it (they all key on
source_file), so it accumulates in the node count, GRAPH_REPORT and exports.
build_merge now sweeps source-less degree-0 nodes that the prune just orphaned,
guarded against nodes that were already isolated beforehand so real content is
never touched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-17 15:26:44 +01:00
safishamsiandClaude Opus 4.8 01921dbc35 test(build): assert metadata survives demotion + unknown-relation is specific; open 0.9.46 (#2803)
Adds a numeric-metadata-preserved test (surviving specific edge keeps its own
weight/confidence, not the demoted generic's) and an unknown-relation test
(a non-denylisted relation is treated as specific in either arrival order, so
the denylist can't drift into an ordering). Dates 0.9.45, opens 0.9.46.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-17 15:25:39 +01:00
abhay-codes07andClaude Opus 4.8 a359b7f39b fix(build): keep the specific relation when an edge pair collapses (#2803)
When two edges connect the same (source, target) pair with different relations,
the simple-graph collapse was last-write-wins by alphabetical relation, so a
specific `calls` was systematically overwritten by a generic `references`/
`uses` — and `references` is not in the callflow filter, so those call sites
dropped out of the call graph. A generic relation can no longer clobber a
stored non-generic one; the outcome is now order-independent and the edge count
is unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-17 15:24:13 +01:00
safishamsiandClaude Opus 4.8 0738af373a chore: bump to 0.9.45
Ships: id-less hyperedge load tolerance (#2775), per-platform version stamp
completing #2694, Go case-only symbol collision (#2779), and .graphify_root
anchor validation (#2603).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
v0.9.45
2026-08-16 21:12:56 +01:00
safishamsiandClaude Opus 4.8 f0345da28b harden(watch): document cwd anchor fallback, align bucket tuple, add tests (#2603)
Two small hardening tweaks on top of #2778: comment that in the hook path
Path.cwd() is the load-bearing anchor rescue (project_root == watch_root ==
the bad marker), and add hyperedges to the _anchors_stored_sources bucket
tuple to match the sibling loop. Adds a deletion-still-evicts test (the anchor
validation must not over-preserve) and an incremental==cold id-parity test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-16 19:22:06 +01:00
catpotdandClaude Opus 4.8 6f05172521 fix(watch): validate .graphify_root before anchoring stored sources on it (#2603)
A graph built on a subfolder stores source_file relative to the repo root but
the skill writes .graphify_root as the absolute subfolder, so an incremental
rebuild anchored stored paths under the wrong root, judged every unchanged
source deleted, and collapsed the graph. The anchor is now adopted only if the
stored sources actually resolve under it, falling back to the build root / cwd
otherwise, so a stale subfolder marker can no longer evict the whole corpus.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-16 18:36:22 +01:00
safishamsiandClaude Opus 4.8 05e1e4c01d test(go): cover method-level and all-exported case-only collisions (#2779)
Adds the two cases the deep-dive flagged as untested: a receiver-based method
collision (Get/get on the same type) and the all-salted branch where no member
is the unique exported one (Run/RUN). Also adds the CHANGELOG entry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-16 18:35:46 +01:00
catpotdandClaude Opus 4.8 1e68a7aa76 fix(go): keep both halves of a case-only symbol collision (#2779)
Node ids casefold (deliberately, for AST/LLM/builder parity), but Go is
case-sensitive and uses case for visibility: exported `Run` and unexported
`run` in one file collapsed to one id, so the second was dropped by add_node
and a local `run()` call phantomed to a same-named symbol in another package.
The Go extractor now salts the non-canonical half of a same-file case-only
collision (exported keeps the stable plain id), so both survive and the
in-file resolver binds the unexported call locally. ids.py / normalize_id are
untouched, so the id contract holds and non-Go corpora are unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-16 18:34:46 +01:00
safishamsiandClaude Opus 4.8 7addee1159 test(install): assert stale untouched platform still warns end-to-end (#2694)
Adds the end-to-end assertion the version-stamp fix was really about: after
installing one platform, a different stale platform actually emits the
staleness warning on stderr (the behavior the over-stamping suppressed). Adds
the CHANGELOG entry closing #2694.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-16 18:34:11 +01:00
Ousama Ben YounesandClaude Opus 4.8 a59a4e812e fix(install): stamp .graphify_version only for the platform being installed (#2694)
install() called _refresh_all_version_stamps(), which stamped every installed
platform's .graphify_version with the current version WITHOUT rewriting that
platform's skill content — so a genuinely stale platform was stamped current
and its staleness warning was suppressed. Removes that bogus refresh; each
platform's stamp now advances only when its own content is (re)written via
_copy_skill_file, so a not-reinstalled stale platform correctly still warns.
Completes #2694 (part 1 shipped in 0.9.44 via #2753).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-16 18:30:13 +01:00
safishamsiandClaude Opus 4.8 0524982346 test(export): cover many/empty id-less hyperedges; open 0.9.45 (#2775)
Adds the multi-id-less case (the real corpus had 183/234 id-less) and the
empty-string-id case (falsy, treated as id-less). Dates the released 0.9.44 and
opens the 0.9.45 section.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-16 18:28:53 +01:00
Ousama Ben YounesandClaude Opus 4.8 ae8d059231 fix(export): tolerate id-less persisted hyperedges in attach_hyperedges (#2775)
The semantic extractor emits hyperedges with no id and build.py persists them
verbatim, so a prior graph.json can contain id-less hyperedges. Seeding the
dedup set with a hard h["id"] raised KeyError: 'id' on every incremental
re-extract, silently failing the whole graph load. Guard the comprehension with
h.get("id"), symmetric with the incoming-set guard already below it; id-less
entries are retained in the graph, id-bearing dedup is unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-16 18:27:34 +01:00
safishamsiandClaude Opus 4.8 4fca621532 chore: bump to 0.9.44
Ships the accumulated 0.9.44 fixes: OCaml qualified-call fix; cross-file uses
attribution (#2652), nested JS functions incl. arrow idioms (#2653), bash
source-path resolution + traversal gate (#2596), wiki verbatim links (#2597),
export MAX_PATH budgeting (#2655), external-import & function-expression
indirect_call shadows (#2757/#2752), lazy CommonJS requires (#2700), affected
absolute-seed root (#2706), cpp doctest TEST_CASE recovery (#2594), git-tracked
gitignored files (#2759), CLAUDE_CONFIG_DIR always-on install (#2694), and
committed viz-node-limit config (#2760).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
v0.9.44
2026-08-15 22:10:43 +01:00
safishamsiandClaude Opus 4.8 c389b3ee75 fix(hooks): baked viz limit must yield to a per-run override; status resilient (#2760)
Two fixes on top of #2768: (1) the baked line was an unconditional
export GRAPHIFY_VIZ_NODE_LIMIT="<n>", which clobbered an explicit
GRAPHIFY_VIZ_NODE_LIMIT=... git commit; use the ${VAR:-<n>} default form
(mirroring GRAPHIFY_MAX_WORKERS) so the per-run value wins, and widen the
status drift regex to read the new form (still accepting the old bare one).
(2) A malformed .graphifyrc turned the read-only hook status into a traceback;
status now catches the parse error, warns, and continues. Adds precedence and
malformed-status tests. Also adds the CHANGELOG entry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-15 22:00:53 +01:00
hopstreaxandClaude Opus 4.8 dc895a2f7c fix(hooks): persist viz node limit in project config (#2760)
Adds a committed .graphifyrc (viz_node_limit=<int>) that `graphify hook install`
reads and bakes into the generated post-commit/post-checkout hooks, so a
project-wide viz node limit is shared via version control and survives hook
regeneration instead of needing a hand-edit that the next --force clobbers.
`hook status` reports the value and flags drift.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-15 21:55:47 +01:00
safishamsiandClaude Opus 4.8 65b33b0e62 test(install): cover CLAUDE_CONFIG_DIR always-on registration (#2694)
Adds the coverage the fix lacked (the shared home sandbox deletes
CLAUDE_CONFIG_DIR, so it must be set explicitly): env set -> registration lands
in $CLAUDE_CONFIG_DIR/CLAUDE.md and the default ~/.claude profile is untouched;
env unset -> unchanged ~/.claude/CLAUDE.md with the tilde skill ref. Also adds
the CHANGELOG entry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-15 21:54:36 +01:00
AromalBiju1andClaude Opus 4.8 4910dccc4a fix(install): honor CLAUDE_CONFIG_DIR in always-on CLAUDE.md registration
The always-on registration wrote `~/.claude/CLAUDE.md` unconditionally, so
installing from a Claude profile relocated via CLAUDE_CONFIG_DIR mutated the
DEFAULT profile instead — the same fix _platform_skill_destination already
applies to the skill-copy path, missed for this path. The global branch now
writes to $CLAUDE_CONFIG_DIR/CLAUDE.md with a matching skill reference; env
unset/blank still falls back to ~/.claude. Refs #2694 (part 1; the version-stamp
half is unaddressed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-15 21:49:01 +01:00
safishamsiandClaude Opus 4.8 c2a676d215 fix(js): also shadow generator function-expression bindings; lock .tsx coupling (#2752)
Closes the gap #2752's title implies: a generator function EXPRESSION
(function*(k){}) parses as generator_function, which was in neither
_JS_DESCEND_TYPES nor the JS/TS function_boundary_types, so it still fabricated
an indirect_call to a same-named callable. Adds generator_function to both.
Adds a generator-FE regression test and a .tsx test that locks the
_TSX_CONFIG-by-reference coupling (untested before). Full JS/TS indirect_call
/calls regression re-run green (812 passed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-15 20:11:13 +01:00
imagineers-tylerandClaude Opus 4.8 3512585ed0 fix(js): shadow untracked function-expression bindings from indirect_call args
An inline/nested function expression's own params and locals were not folded
into the indirect_call shadow set, because function_expression was missing from
the JS/TS function_boundary_types even though walk_calls' closure-descend branch
already handles it. A bare reference to such a param, passed as a call argument,
fabricated an INFERRED indirect_call to an unrelated same-named callable. Adding
function_expression to the boundary sets routes it through the same shadow path
arrows use. Same family as the catch/arrow/loop/external-import (#2757) shadows.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-15 19:22:02 +01:00
safishamsiandClaude Opus 4.8 3460500fff perf(detect): skip the git ls-files probe when no .gitignore contributes (#2759)
#2766 ran `git ls-files` on every scan of a git repo. Gate it on .gitignore
actually contributing patterns beyond the explicit (.graphifyignore/--exclude)
set — with no .gitignore in play nothing can be gitignore-dropped, so the
tracked-file exemption is moot and an ordinary corpus pays no subprocess cost.
Adds a spy test asserting the probe is skipped without a .gitignore and runs
once with one. Also adds the CHANGELOG entry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-15 19:20:34 +01:00
NithishandClaude Opus 4.8 95ad0adcaa fix(detect): preserve Git-tracked files matched by a .gitignore pattern (#2759)
A file that is git-tracked but also matches a .gitignore pattern (a committed
file later added to .gitignore, or a force-added one) was silently dropped from
the corpus, even though git never un-tracks such a file — so the graph lost
real, shipping source. detect now runs `git ls-files` once per scan, keeps a
set of tracked-file identity keys plus their ancestor directories (so a
git-only-ignored parent dir is not pruned before the walk reaches the tracked
file below it), and exempts tracked paths from .gitignore. .graphifyignore and
--exclude stay authoritative, and a non-git corpus is unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-15 19:09:57 +01:00
safishamsiandClaude Opus 4.8 e6b0efb66b fix(cpp): guard empty-normalize collapse for punctuation-only test names (#2594)
The #2751 recovery minted node ids via _make_id(stem, name); a punctuation-only
name (TEST_CASE("***")) normalizes to empty, collapsing onto the bare file-stem
id — colliding with the file namespace and swallowing every later such test
under one id via seen_ids (#1899). Detect the collapse and fall back to a stable
line-positional id so each stays distinct. Adds a regression test (also covers
SCENARIO) and the CHANGELOG entry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-15 17:24:35 +01:00
Ousama Ben YounesandClaude Opus 4.8 54b3b78955 fix(cpp): recover doctest/Catch2 string-named TEST_CASE nodes (#2594)
doctest/Catch2 register tests with a string-literal name (TEST_CASE("...")),
which tree-sitter-cpp parses as an ERROR node, so every test function was
silently dropped. Adds a line-anchored regex fallback that appends one code
node per top-level TEST_CASE/TEST_CASE_TEMPLATE/SCENARIO plus a contains edge
from the file node, mirroring the Groovy Spock fallback.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-15 17:17:29 +01:00
safishamsiandClaude Opus 4.8 6f1cbe5fda test(affected): cover outside-root clean-miss and non-out-dir graph layout (#2706)
Adds the two gaps the deep-dive flagged: an absolute seed outside the derived
root must miss cleanly (not resolve to a same-basename in-root node), and a
--graph pointing at a graph.json NOT under graphify-out uses the else gp.parent
fallback root. Also adds the CHANGELOG entry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-15 17:16:53 +01:00
Ousama Ben YounesandClaude Opus 4.8 243a1801f1 fix(affected): resolve an absolute-path seed via the graph-derived root (#2706)
affected anchored an absolute-path seed to Path.cwd(), so running it from
anywhere but the repo root made relative_to(cwd) raise and the query fell
through unmatched, silently returning nothing. It now derives the repo root
from the graph's own location (<root>/graphify-out/graph.json) and anchors the
seed there, so an absolute seed resolves regardless of cwd. Composes with the
#2707 relative-seed fix (root defaults to cwd for other callers).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-15 17:12:16 +01:00
safishamsiandClaude Opus 4.8 e5d662eb63 test(js): cover dynamic-require skip and no-double-count for lazy requires (#2700)
Adds the two invariants the deep-dive flagged: require(variable) must not
fabricate an edge, and a module-scope require still yields exactly one
imports_from edge (guards the module-vs-body pass partition against future
double-counting). Also adds the CHANGELOG entry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-15 16:46:04 +01:00
rajanpanthandClaude Opus 4.8 259bb6acfb fix(js): retain lazy CommonJS require dependencies (#2700)
A require(...) written inside a function body (the canonical lazy-require idiom
for breaking circular deps) produced no edge at all, while the identical require
at module scope resolved as EXTRACTED imports_from/imports edges. walk_calls now
feeds nested require declarations to the same _require_imports_js routine,
attributing the edge to the enclosing callable; dynamic require(var) is still
skipped and no bare local node is minted (#1077 scope guard).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-15 16:44:34 +01:00
safishamsiandClaude Opus 4.8 94c2050f93 test(js): cover external-import-shadow vs same-named local callable (#2757)
Adds the collision case the fix must survive: a name both imported externally
and defined as a callable in another corpus file. The external use must not
fabricate a cross-file indirect_call while a genuine local by-name reference
still binds. Also adds the CHANGELOG entry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-15 16:43:11 +01:00
phudayyyandClaude Opus 4.8 ceeafb05ce fix(js): an import from outside the corpus shadows indirect_call resolution (#2757)
An identifier bound by an import whose target resolves OUTSIDE the scanned
corpus (e.g. a lucide-react icon) is now shadowed within the file, so using it
as a value no longer fabricates an INFERRED indirect_call onto an unrelated
same-named callable elsewhere in the corpus. The internal-vs-external decision
is delegated to the existing import resolver, so a relative/in-corpus import
still resolves to its real target. Same shadow family as #2241/#2568/#2685.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-15 16:42:12 +01:00
safishamsiandClaude Opus 4.8 774b12fe9e docs(readme): add YouTube channel badge to the social links
Adds a YouTube badge linking to https://www.youtube.com/@graphifylabs in both
the header badge row and the footer social block, alongside Discord/LinkedIn/X.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-15 16:39:46 +01:00
safishamsiandClaude Opus 4.8 9e2387f3e8 fix(ocaml): don't bind a qualified external call to a same-named local def
A qualified call `M.f` where `M` is not a module defined in the same file is
an external-library call (e.g. Hardcaml's `Reg_spec.create`, `Scope.create`).
Resolving it by bare last name bound it to a same-named local `let f`,
producing a false `calls` edge and a `create -> create` self-loop when the
caller was that local `f`. Now: track locally-defined module names; a
qualified call whose root module is not local and whose bare name collides
with a local def is kept as a distinct external target (stub labelled by the
full path), so it neither self-loops nor collapses onto the local def.
Unqualified calls and calls into a locally-defined module still resolve
locally, and cross-file `Geo.area` still collapses onto another file's `area`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-14 21:09:32 +01:00