Commit Graph
1404 Commits
Author SHA1 Message Date
C0KERNEL cc0ee60c40 fix(export,cli): stamp graph provenance from the analysed repo, not the shell cwd (#2534 family) 2026-08-13 13:30:24 +01:00
Ousama Ben Younes a995921026 fix(js): shadow for...of / for...in loop bindings from indirect_call args (#2568 family) 2026-08-13 13:30:24 +01:00
rajashidattapy c1e41ea90e docs: refresh ARCHITECTURE.md module table + add a doc-parity test (keeps the #2558 'an' fix) 2026-08-12 20:57:39 +01:00
nelsondeleonc-source 39beeb9b4b docs: fix article typo (an extract_<lang>) in ARCHITECTURE.md 2026-08-12 20:56:28 +01:00
Redzwan Mutalib 28aaf20197 docs: document Windows test prerequisites in the README 2026-08-12 20:56:28 +01:00
Redzwan Mutalib 6b40338d96 docs: document CI parity checks in the README 2026-08-12 20:56:28 +01:00
rajashidattapy 5cc20a4811 test: skip the unreadable-dir detect test on non-POSIX / as root 2026-08-12 20:56:28 +01:00
rajashidattapy cdf1f65656 test: probe-and-skip symlink tests where symlink creation is unavailable (#2642) 2026-08-12 20:56:28 +01:00
rajashidattapy 243f3e32c1 test(hooks): stop argv mangling the shell-arg verdict test on Windows (#2126) 2026-08-12 20:56:28 +01:00
rajashidattapy 0df2a701a5 fix(paths): clear read-only bit before unlinking the atomic-write temp on Windows (#2622) 2026-08-12 20:56:28 +01:00
rajashidattapy 1aab181d15 test: Windows path portability in tests; llm emits POSIX source_file to the model (#2620) 2026-08-12 20:56:28 +01:00
safishamsiandClaude Opus 4.8 e4bfd2ad1a docs(changelog): add #2663/#2601/#2661/#2457/#2674 to the 0.9.41 section
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
v0.9.41
2026-08-12 14:22:46 +01:00
Arthuro0103 ee2fbf954b fix(benchmark): guard against a node with a None label (#2674) 2026-08-12 14:20:21 +01:00
rohit-jsfreaky 8be72ef7bf fix(js): stabilize unresolved local import target ids (#2457) 2026-08-12 14:20:21 +01:00
Ben Younes c285a94d45 fix(extract): resolve prefixed PHP use-imports to their target (#2661) 2026-08-12 14:20:21 +01:00
Ben Younes b645a3a7f1 fix(serve): suppress the query truncation banner when no nodes were cut (#2601) 2026-08-12 14:20:21 +01:00
Ben Younes 4730ed9abb fix(watch): refuse a shrink caused by an extractor failure during update (#2663) 2026-08-12 14:20:21 +01:00
safishamsiandClaude Opus 4.8 26c034a696 docs(changelog): add #2627/#2632/#2635/#2634 to the 0.9.41 section
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-11 20:11:19 +01:00
rohit-jsfreaky cd04bf1bf3 fix(csharp): extract members inside #if preprocessor blocks (#2634) 2026-08-11 20:09:59 +01:00
rajashidattapy bedf32e07c fix(wiki): count each incident edge once in the audit trail (#2635) 2026-08-11 20:09:59 +01:00
rajashidattapy c838df83c5 fix(cache): re-anchor CWD-relative source_file on warm cache hits (#2632) 2026-08-11 20:09:59 +01:00
rajashidattapy ce942e144f fix(extract): canonicalize source_file to POSIX separators (#2627) 2026-08-11 20:03:55 +01:00
safishamsiandClaude Opus 4.8 33283f5356 chore: bump to 0.9.41
Fixes: #2517 (JS/TS catch-binding indirect_call), #2434 (Cargo.toml
manifest), #2468 (scan-root vs parent .gitignore + NFC follow-up), #2482
(API rationale prompt).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-11 17:36:27 +01:00
safishamsiandClaude Opus 4.8 80ce81ef31 fix(detect): NFC-normalize the recomputed scan-root-relative path (#2468 follow-up)
The #2475 fix recomputed the match path against the explicit scan root but
skipped the _nfc() normalization rel_anchor gets, so an NFD-named path
could miss a parent ignore pattern under the #2544 NFC-matching regime.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-11 17:06:53 +01:00
Jaeung Jang 33d217c02f fix(extract): shadow JS/TS catch bindings from indirect_call args (#2568 family) 2026-08-11 16:54:35 +01:00
himanshupatro-334 cb06a7decc fix(detect): preserve explicit scan roots vs parent unanchored .gitignore (#2468) 2026-08-11 16:54:35 +01:00
Ben Younes 638d9e2c37 fix(ingest): recognize Cargo.toml as a package manifest (#2434) 2026-08-11 16:54:35 +01:00
himanshupatro-334 91e43c7678 fix(llm): add rationale guidance to the API extraction prompt (#2482) 2026-08-11 16:54:35 +01:00
safishamsiandClaude Opus 4.8 26128abf69 chore: bump to 0.9.40
Batch of correctness/determinism fixes (#2610/#2599, #2612, #2618, #2614,
#2582, #2467, #2544, #2493, #2568-followup, #2605, #2608, #2598).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
v0.9.40
2026-08-11 15:30:34 +01:00
safishamsiandClaude Opus 4.8 6926f7ac00 fix(export): make graph.html title marker-truncation take precedence (#2598 follow-up)
The #2600 title helper preferred a cwd-relative label, which still leaks
host/user path segments when the graph is built from a directory above the
project. Keep from the graphify-out / GRAPHIFY_OUT marker onward first
(portable in every case); fall back to cwd-relative only for a fully custom
output path with no marker.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-11 15:19:54 +01:00
michaelxer fe67768b93 fix(export): use portable path in graph.html document title
The <title> previously embedded str(output_path), so Windows absolute
host paths leaked into a tracked artifact (regression of #433, #2598).

Prefer a cwd-relative label, else keep from the graphify-out segment
onward, else the filename only.
2026-08-11 15:18:33 +01:00
Jaeung Jang d06bab0629 fix(extract): shadow a single unparenthesised arrow parameter from indirect_call args
tree-sitter gives an arrow with one unparenthesised parameter a `parameter`
field (singular) and no `parameters` list node, so `_js_local_bound_names`
never saw it: `x => sink(x)` bound nothing, and `x` read as a by-name
reference to any same-named callable in the corpus, fabricating an
indirect_call edge (INFERRED, 0.8).

The parenthesised form was always handled, so `(x) => …` and `x => …`
behaved differently. Same singular/plural trap as `catch_clause.parameter`.

Strictly subtractive on real code: +0/-88 indirect_call edges over 5,402
files of node_modules, +0/-98 over a 6,000-file mixed corpus.
2026-08-11 15:18:33 +01:00
rohit-jsfreaky 3c843bd0cc fix: skip Obsidian vault metadata directories 2026-08-11 15:18:33 +01:00
Bruno Santanna 5ffaaa606a fix(detect): normalize Unicode so accented ignore patterns match on macOS
An ignore rule naming a directory with an accent silently does nothing on
macOS, and the files it was meant to exclude get scanned anyway.

macOS (APFS/HFS+) returns filenames in NFD — "ç" comes back as "c" + U+0327
COMBINING CEDILLA — while editors write ignore files in NFC, where the same
"ç" is the single codepoint U+00E7. The two render identically and compare
unequal, so `fnmatch` never matches and the pattern is a no-op.

Found in a real repo: a `.graphifyignore` containing `Orçamento/` failed to
exclude that directory, and 9 client contract PDFs were picked up for semantic
extraction — i.e. queued to be sent to an LLM — despite an explicit rule
against it. The failure is silent: there is no warning, and the only symptom
is a file count that does not match what you expect. A user who does not
count would never know. That is what makes this worth fixing rather than
documenting: the rule appears to work.

Both sides are now normalized to NFC before matching, at three boundaries:
the pattern (in `_parse_gitignore_line`, so it covers .graphifyignore,
.gitignore and $GIT_DIR/info/exclude alike) and the two path forms used in
`_is_ignored` (`target.name` and the anchor-relative path).

NFC is already the form Linux and Windows produce, so this is a no-op there
and only repairs the macOS mismatch.

Tests: two regression tests cover both directions (NFC pattern vs NFD path on
disk, and the reverse); both fail before this change and pass after. A third
asserts ASCII patterns are unaffected, so the normalization cannot regress
existing behavior.

Full suite: 3833 passed. The 13 failures in tests/test_terraform.py are
pre-existing on a clean upstream checkout (optional tree_sitter_hcl not
installed) and unrelated to this change.
2026-08-11 15:18:33 +01:00
sean-soomgo 1fdd11fa76 fix(serve): resolve punctuated and non-ASCII node ids in _find_node (#2467)
`_find_node_tiers` builds two normalizations of the query: `term`, which
tokenizes on \w+ so punctuation becomes a space, and `norm_query`, which
keeps it. The exact tier compared the node id against `term` only, so
`term == nid_lower` was false for every id carrying punctuation, and
`norm_query` — which already held the right form, and is even one of the
two trigram needles — was never compared against the id at all. Comparing
`norm_query` to the folded id closes that half.

It does not reach ids carrying non-ASCII text. `_node_search_text`
indexed the id raw while every query path folds through
`_strip_diacritics`, which NFKD-decomposes. Hangul syllables decompose
into conjoining jamo, and jamo have combining class 0, so they survive
the combining-character filter: the needle's trigrams and the posting's
trigrams were disjoint, `_trigram_candidates` returned a candidate list
without the node, and it was dropped before any predicate ran. The
folded id is now part of the indexed text.

Both halves are additive. An id that resolved before resolves to the
same node; only ids that previously resolved to nothing can now resolve.
The folded field is appended, and only when the fold actually differs,
so field positions do not move and an all-ASCII graph indexes byte for
byte what it indexed before. Index build, median of 7 runs:

    graph                     trigrams   postings   build
    5k all-ASCII     before        1723     238876    96ms
    5k all-ASCII      after        1723     238876    97ms
    17k real         before       33442    2311784   998ms
    17k real          after       33490    2312462   990ms
    5k half-Hangul   before        1735     256381   116ms
    5k half-Hangul    after        1739     266381   124ms

The real graph is the 17269-node one measured below; 354 of its ids are
non-ASCII, so the index grows 0.03% and the build stays inside run-to-run
noise. The half-Hangul row is a deliberate worst case — every other node
id Korean — and even there the cost is paid once per graph load, on a
graph where id lookup previously returned nothing at all.

On a real 17269-node graph with Korean source filenames, every node id
fed back to itself, full population:

    id class                  total   before   after
    contains punctuation       2326        0    2326
    contains Hangul             354        0     354
    ASCII, no punctuation     14589    14589   14589

And every query that graph can produce — all 17269 ids plus all 16537
distinct labels — through `_find_node_tiers` on both variants in one
process: 31126 identical, 2680 that returned nothing before and resolve
now, 0 with a changed first result, 0 lost, 0 with a widened exact tier.
Every difference is a query that previously returned nothing.

Left alone deliberately: `_score_query` compares the id raw in the same
way, so `path` and query seeding still cannot take a punctuated id, and
the prefix tier also matches ids against `term` only. Both are behaviour
changes beyond this defect rather than part of it.

One note for the regression tests: the non-ASCII case needs a graph of
at least ~10 nodes. `_trigram_candidates` bails out to a full scan when
`min(present) > int(n * 0.10)`, so on a small synthetic graph the index
path is never taken and the test passes with the defect still present.
2026-08-11 15:18:33 +01:00
hjotha 36b47ba25d fix(export): stabilize graph JSON collection order 2026-08-11 15:18:33 +01:00
rajashidattapy 21e75cfae4 fix(normalize_id): address idempotency issues with Turkish identifiers and update related test cases 2026-08-11 15:18:33 +01:00
rajashidattapy d3d9ef8340 fix(normalize_id): ensure idempotency and valid character output by adjusting casefolding order 2026-08-11 15:18:33 +01:00
safishamsiandClaude Opus 4.8 4b76ee127f fix(extract): gate the #2551 partial-parse warning on plausible symbol loss (#2610, #2599)
The 0.9.37 #2551 warning fired on valid TypeScript/TSX: tree-sitter-typescript
sets root.has_error on tiny fully-recovered errors (a `&` in a JSX string
attribute; a semicolon-less `in_*` interface member) that still extract every
symbol. The warning now fires only when recovery plausibly cost symbols — the
file yielded <=1 node, or a materialized ERROR region spans more than one line —
so the genuine Kotlin one-line-body (#2551) and Luau (#2520) cases still warn
while valid TS goes silent. Warning also prints a root-relative path.

Thanks @Sid-AutoWisdom and @atlasplatformu-ai.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-11 15:18:12 +01:00
BackendDev e4d132b7da fix(go): resolve qualified symbols by import path
Prevent package-qualified calls and types from collapsing onto unrelated bare-name symbols. Preserve Go import evidence, resolve internal packages exactly, canonicalize external type stubs, and support incremental resolution context.

Investigation, implementation, and regression fixtures prepared with OpenAI Codex.
2026-08-11 14:40:01 +01:00
SinghAman21 5fd4ab650b fix(python): avoid crash resolving overdeep relative imports 2026-08-11 14:40:01 +01:00
rajashidattapy 717b7b464a fix(paths): implement cross-platform absolute path detection for stored paths 2026-08-11 14:40:01 +01:00
rajashidattapy 26245b0841 fix(cache): implement racily-clean guard for file hashing; enhance stat signature checks 2026-08-11 14:40:01 +01:00
safishamsiandClaude Opus 4.8 50556baaea fix(extract): affected traverses in-function dynamic imports; Python member-call gating; ObjC resolver arm; bump to 0.9.39 (#2584, #2586, #2589, #2591)
These land together because they are interleaved in extract.py/engine.py.

#2584 (PR #2588, thanks @phudayyy): the 0.9.38 dynamic-import dedupe keyed
only on target, so an in-function import() suppressed the file-level edge
affected follows. Dedupe now keys on the importing file, emitting one
file-level dynamic_import edge per file/target while keeping the call-site
edge.

#2586 / #2417 (PR #2586, thanks @EZZEASY): a Python member call on an
untyped receiver (x.get(...)) no longer binds by name to a same-named
module function. walk_calls now defers non-self/cls/super Python member
calls to the evidence-gated resolver; super().method() still resolves.
Known trade: same-file x = Thing(); x.method() loses its evidence-free
edge (precision over recall, per #2553).

#2589/#2591 resolver arm (in _resolve_objc_member_calls): the @protocol
exclusion and the self.field/_ivar receiver resolution (paired with the
objc.py extractor changes committed separately).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
v0.9.39
2026-08-10 18:07:20 +01:00
safishamsiandClaude Opus 4.8 ba8254b3ab fix(objc): protocol not a receiver type, category fold, property/ivar receivers (#2589, #2590, #2591)
#2589 (PR #2500): a @protocol declaration (labeled <Name>) is excluded from
the receiver-type index, so it no longer collides with a same-named class.
#2590 (PR #2501): a category/class-extension interface is keyed off the
base stem and folds into the base class instead of minting a duplicate
node.
#2591 (fresh): @property and ivar declarations are captured into a
per-class field-type table, and a message send to a self.field / _ivar
receiver resolves through it (bare field name only, so Foo.shared cannot
fabricate to a FooShared class). All hold the single-definition guard and
emit INFERRED.

Adapts PRs #2500/#2501 (thanks @xiongjianxu); #2591 fresh.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-10 18:06:50 +01:00
safishamsiandClaude Opus 4.8 978f87cb67 fix(watch): rebuild on a doc-only deletion batch so deleted docs are evicted (#2580)
graphify watch only rebuilt on a code-file event, so deleting only doc
files while watching flagged needs_update but evicted nothing until the
next code change. A batch containing any vanished path now triggers a full
reconcile rebuild (which evicts the deleted source); a surviving modified
doc still writes the needs-LLM flag. The general deleted-file leak was
already fixed in 0.9.10; this closes the live-watcher residual.

Thanks @angmeng.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-10 18:06:50 +01:00
safishamsiandClaude Opus 4.8 92274745ad fix(dedup): don't over-merge distinct same-file labels differing by a content word (#2576)
Fuzzy dedup compared same-file labels with prefix-weighted Jaro-Winkler, so
two distinct entities differing by one content word (asset contribution
flow vs asset consumption flow) cleared the threshold and one was lost. A
one-token difference is now judged on the differing tokens (any distinct
content word blocks; stopword/typo variants still merge), with a
same-length Damerau-Levenshtein typo escape. Genuine typo and
whitespace/case/punct variants still collapse. The #2532 collision path is
untouched.

Adapts PR #2587 (thanks @wilyan09007) with two hardening deltas.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-10 18:06:50 +01:00
safishamsiandClaude Opus 4.8 10ad921b42 fix: JS/Kotlin/Swift/SQL extractor correctness batch; bump to 0.9.38 (#2568, #2565, #2561, #2577, #2575)
These changes are interleaved across engine.py/extract.py by function, so
they land as one batch.

#2568 (thanks @imagineers-tyler): the 0.9.37 #2552 callback-body fix
unioned sibling closures' local names under the shared declaration, so a
local in one callback suppressed a real indirect_call in a sibling. Locals
are now scoped per body (keyed by body id, via walk_calls' extra_locals).
Restore-only, never fabricates; #2552 capture preserved.

#2565 (thanks @kskchaitanya1993): Kotlin property initializers — class,
top-level, companion, and `by lazy {}` — now seed call extraction, so
`val repo = createRepo()` produces a calls edge; literal initializers
produce none; FQ calls compose with the #2550 resolver.

#2561 (thanks @fakewaffle): Swift receiver typing now handles
`@Environment(Store.self)` (whitelisted; @Query/keypath/dotted skipped to
avoid a wrong edge) and in-corpus factory bindings via a marked concrete
return type; opaque/array/out-of-corpus returns stay unresolved.

#2577 (thanks @wilyan09007, PR #2579): the SQL extractor no longer emits a
reads_from edge to a CTE name. WITH names are scoped per query (a subquery
CTE no longer suppresses an outer real table of the same name), so a CTE
no longer mints a bare stub that binds to an unrelated same-named symbol.

#2575 (thanks @phudayyy, PR #2574): a dynamic `import('…')` inside a nested
function or at module scope now produces an edge, dynamic_import is
included in affected, and calls inside nested named functions are
collected; a dynamic import already captured as a deferred imports_from is
not double-counted.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
v0.9.38
2026-08-09 23:19:08 +01:00
safishamsiandClaude Opus 4.8 09a34ad87a fix(update,llm): retry failed extractions; surface claude-cli envelope errors; bump to 0.9.37 (#2543, #2554)
#2543 (adopts PR #2546, thanks @michaelxer): a failed extraction is no
longer stamped in the incremental manifest as up-to-date, so graphify
update retries it instead of skipping it forever; a manifest already
poisoned by the old behavior is healed on the next run; genuinely
unchanged files are not re-processed. Extended to the watch save_manifest
paths too.

#2554 (adopts PR #2555, thanks @annieyii): the claude-cli backend now
inspects the stdout envelope for an is_error result (e.g. a rate limit
returned with exit code 0) and raises it on both the zero and non-zero
exit paths, instead of parsing it as an empty success and bisecting
against a live rate limit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
v0.9.37
2026-08-08 23:37:49 +01:00
safishamsiandClaude Opus 4.8 cfc6a75c86 fix(extract): TS member-call gating + Kotlin grammar match (#2553, #2552, #2526, #2550, #2551)
TypeScript (#2553, #2552, thanks @Earthfreedom):
- _resolve_typescript_member_calls matched a receiver type by name alone
  and emitted EXTRACTED, so a third-party import could bind to an unrelated
  local class of the same name. It now requires the matched type to be
  same-file or imported by the caller's file, and tiers table-inferred
  receivers to INFERRED.
- calls inside a callback passed to another call (const h = wrapper(arrow))
  were never walked; the callback body is now walked and attributed to the
  declaration, through the same import-gated resolution so it cannot
  fabricate edges. The #2553 gate lands with #2552 by design.

Kotlin (#2526, #2550, #2551; adapts PR #2531, thanks @Mustaqeem66;
reports from @spaceBrownie and @thomasrengot-hub):
- match the bundled tree-sitter-kotlin 1.1.0 import node and resolve each
  import to the real target node (imports were silently dropped), so
  genuine calls promote to EXTRACTED via import evidence;
- a fully-qualified call com.example.Foo.bar() now produces a calls edge;
- a file the grammar cannot fully parse (e.g. one-line class C { val x })
  now warns instead of silently extracting nothing, and declarations
  recovered inside an error span keep their enclosing class.

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