Commit Graph
1507 Commits
Author SHA1 Message Date
rajarshidattapy ef282c19fa fix(extract): don't classify a deliberately-declined data JSON as failed (#2879)
A data-shaped JSON that extract_json intentionally declines (returning a `skipped`
marker, not an `error`) was counted as a failed extraction, so it was kept out of the
incremental manifest and re-processed on every run. Treat the `skipped` decline as a
valid empty outcome, disjoint from genuine failures (which still return `error` and are
still reported).
2026-08-20 15:41:07 +01:00
safishamsiandClaude Opus 4.8 b14b52e94e chore: bump to 0.9.47
Ships this cycle: the graph-correctness/UX batch — JS/TS factory-object methods
(#2745), stable graph.json field order (#2582), query names its graph (#2789),
ThinkingBlock-safe claude backend (#2697), manifest churn guard (#2838), C# primary
ctor refs (#2829), AST INFERRED rubric confidence_score (#2813), legacy numeric
confidence normalization; plus the small-fix batch — chunk-bisect on timeout (#2866),
symlink cache identity (#2832), sidecar cache root (#2787), driveless Windows path
guard (#2795), actionable partial-parse warning (#2788), and non-ASCII Obsidian tags
(#2862).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
v0.9.47
2026-08-19 14:11:10 +01:00
safishamsi 2acc62f38f fix(obsidian): make _obsidian_tag docstring a raw string (#2862)
The docstring documents the `\w` regex class; as a non-raw string that is an
invalid escape sequence (DeprecationWarning now, SyntaxError in a future Python).
Mark it raw.
2026-08-19 14:07:48 +01:00
abhay-codes07 4f104e6e1f fix(extract): make the partial-parse warning actionable, and stop citing closed #2551 (#2788)
The partial-parse warning ended every message with a hardcoded (#2551) — a Kotlin-specific
issue that is now closed — which misdirected reporters into recording unrelated failures as
already-tracked. Drop the citation and append real per-file data instead: the surviving
symbol count, distinguishing a total-loss file from a partially-recovered one.
2026-08-19 14:05:58 +01:00
abhay-codes07 e4b85d3b50 fix(hooks): a driveless rooted path is not cwd-relative on Windows (#2795)
The out-of-project read guard treated any non-absolute path as cwd-relative, but on
Windows a rooted-but-driveless path (`\foo\bar`) is not absolute yet resolves against
the current drive root, outside the project. Classify with a platform-correct predicate
(`not root and not drive` under the host path flavour) so the containment check is
reached. On POSIX the predicate reduces to `not is_absolute()`, so no behaviour changes
there.
2026-08-19 14:05:58 +01:00
hopstreax 26092ce5b1 fix: bisect extraction chunks on timeout (#2866)
A timeout on a multi-file extraction chunk failed the whole chunk. Route recognized
timeouts (subprocess.TimeoutExpired, SDK APITimeoutError, botocore read/connect
timeouts) through the same bounded bisection/merge path already used for
context-window-exceeded errors, so a single slow file no longer takes its chunk-mates
down. A single unsplittable file that times out is left unstamped and retried next run,
as before.
2026-08-19 14:05:58 +01:00
oleksii-tumanov dfdcf9e408 fix(cache): preserve symlink path identities (#2832)
file_hash salted the content digest with the resolved (symlink-collapsed) path, so a
symlink alias and its target hashed identically and one displaced the other from the
graph on a warm cache. Salt with the walked (lexical abspath) path relative to root
instead, giving aliases distinct keys; when the scan root itself is a symlink, derive
the leaf-relative salt from the lexical ancestor matching the resolved root. The
detect-layer containment guard (which resolves to block symlink escape) is untouched.
2026-08-19 14:05:58 +01:00
hopstreax c443ee7b3a fix(detect): respect cache root for converted sidecars (#2787)
Converted Office/Google-Workspace sidecars were written to `root/GRAPHIFY_OUT/converted`,
always anchored to the scanned source tree, so `graphify extract --out <dir>` against a
read-only or pinned checkout polluted the source tree. Route sidecar writes through the
cache root while keeping the content hash anchored to the scan root, so sidecar filenames
stay stable across checkouts.
2026-08-19 14:05:58 +01:00
josh-leung-KR 000c71a4a5 test(obsidian): cover non-ASCII community tags and colour-group parity (#2862)
Fails on the current sanitizer in 4 of 5 cases: Korean/Japanese/accented labels
lose their letters, distinct communities collapse to the same tag, and the
graph-view colour group queries a tag no note carries.
2026-08-19 14:05:58 +01:00
josh-leung-KR 4946c711ac fix(obsidian): keep non-ASCII letters in community tags (#2862)
_obsidian_tag stripped every non-ASCII character, so a Korean or Japanese community label collapsed to underscores and every note in that community carried the same tag. Python's \w is Unicode-aware, so switching the filter keeps letters from any script while still dropping spaces and punctuation.

Also route the .obsidian/graph.json colour-group query through the same sanitizer: it was built from the raw label, so on a non-ASCII label it queried a tag that no note carries.
2026-08-19 14:05:58 +01:00
safishamsi 56c1060158 test(query): assert the CLI query call site wires graph_path (#2789)
The header logic is unit-tested, but only a real subprocess run proves the CLI
query command actually passes the resolved graph path through — the wiring that
was the point of #2789. Cover both the under-CWD relative form and an explicit
--graph outside the CWD shown in full. Plus 0.9.47 changelog entry.
2026-08-18 22:38:47 +01:00
abhay-codes07 b8a8b4c4c9 fix(query): name the graph the answer came from (#2789)
The query header now leads with the graph file it opened and its node count,
shown relative to the CWD when the graph sits underneath it and absolute when it
does not — surfacing the case where a query run from a parent project silently
answers from the wrong corpus. graph_path is optional, so the header is
byte-identical for callers that do not pass it; the CLI query command and the MCP
query tool, which already resolve the path for querylog, now pass it.
2026-08-18 22:36:42 +01:00
safishamsi c0b46453d7 docs(changelog): add 0.9.47 entries for #2745 and #2582 2026-08-18 22:35:22 +01:00
safishamsi d0f0ac1c8d fix(js): dedup factory-object contains edge; cover arrow + bare-receiver (#2745)
The factory-object owner path emitted the `contains` edge inside the per-member
loop; add_node dedups on id but add_edge does not, so N methods assigned to one
object flooded the graph with N identical contains edges. Emit it once per owner.

Tests: assert a single contains edge across four methods, cover arrow-function
assignment (the dominant modern factory shape), and lock the negative case that a
non-object-literal receiver (`external.handler = fn`) is not captured.
2026-08-18 22:32:41 +01:00
rajanpanth 61228be167 fix(js): extract methods assigned to factory object APIs (#2745)
Preserve callable members assigned to a local object-literal factory API
(`const api = {}; api.foo = fn`), modeling the API object beneath its factory
and attaching the assigned functions as methods. The lazy owner-node minting
(only for identifiers proven to be object-literal bindings in the enclosing
function) avoids the #1077 config-object flood.

Partially addresses #2524 (the object-literal-method shorthand form
`return { foo() {} }` remains out of scope).
2026-08-18 22:29:47 +01:00
safishamsi 120c7d861f test(export): lock non-ASCII round-trip byte-identity for field-order fix (#2582)
Covers the ensure_ascii write path the field-order stabilization implicitly
relies on: a reordered node dict carrying escaped-unicode values must serialize
byte-identically across a read-rebuild.
2026-08-18 22:27:27 +01:00
C0KERNELandClaude Opus 4.8 b1f847d716 fix(export): stabilize graph.json field order across a read-rebuild round-trip (#2582)
node_link_data always appends the node key (id) last, so id sat mid-dict on a
cold build but last after a read-rebuild — churning graph.json key order with
no content change and defeating byte-stable caching. to_json now canonicalizes
each node/link dict (id / source,target,relation first, remaining keys sorted)
before serialization, so a load -> rebuild -> write round-trip is byte-identical.
Pure key permutation; values are untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-18 22:20:57 +01:00
safishamsiandClaude Opus 4.8 7047411678 test(claude): lock first-text-block (not concatenation) for the ThinkingBlock helper (#2697)
Adds the multiple-text-block test the deep-dive flagged: the helper must return
the FIRST text block (graphify's claude calls carry a single JSON payload, so
concatenating would corrupt it). Adds the CHANGELOG entry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-18 22:20:19 +01:00
mdshzb04andClaude Opus 4.8 55822b0399 fix(claude): read text blocks after a leading ThinkingBlock (#2697)
Extended thinking is on by default on current Claude models, so a response's
content[0] is a ThinkingBlock and the old content[0].text raised AttributeError
on the SDK claude backend. A shape-aware helper (mirroring the existing
_bedrock_response_text) skips leading non-text blocks and returns the first
text block; a thinking-only response falls back to the default. Only the two
SDK claude call sites are touched; the claude-cli JSON-envelope path is unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-18 22:19:29 +01:00
safishamsiandClaude Opus 4.8 97f2bb7540 test(detect): cover ast-kind no-op churn and corrupt-manifest write (#2838)
Adds the two cases the deep-dive flagged: the kind='ast' path (the literal
graphify update scenario) stays byte-identical on a no-op and updates on a real
edit; and a corrupt/unparseable existing manifest still gets written (the
byte-equality skip's except must never silently drop a real update). Also adds
the CHANGELOG entry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-18 18:09:59 +01:00
hopstreaxandClaude Opus 4.8 173a8814d2 fix(detect): prevent manifest timestamp churn on unchanged updates (#2838)
save_manifest unconditionally stamped a fresh 'seen' timestamp for every file
on every run, so a no-op graphify update rewrote all manifest entries and left
graphify-out/ permanently dirty (a trailing graph commit on every push). Now
the per-file 'seen' is preserved for genuinely unchanged entries, and the disk
write is skipped entirely when the serialized manifest is byte-identical to
what is already on disk; a real change still refreshes and persists.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-18 18:08:32 +01:00
safishamsiandClaude Opus 4.8 1eb356cde1 test(csharp): cover builtin-not-fabricated and struct primary-ctor (#2829)
Adds the two cases the deep-dive flagged: a built-in-typed primary-ctor param
(int count) must not fabricate a referenced node, and the branch's struct_declaration
claim is locked by a struct primary-ctor test. Also adds the CHANGELOG entry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-18 18:07:12 +01:00
brobl2008andClaude Opus 4.8 3e8ec33994 fix(csharp): walk primary-constructor parameters for references and calls (#2829)
A C# 12 primary constructor puts its parameters on the class/record declaration
itself, which the extractor never walked: class Holder(IDep dep) emitted no
references edge Holder->IDep, and because dep was never registered in the class
receiver table, a dep.Method() call inside the class was dropped too. Scan the
declaration's parameter_list, register each param name->type, and emit the
param type reference — mirroring the field/property handlers. Built-in and
type-parameter types are not fabricated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-18 16:30:16 +01:00
safishamsiandClaude Opus 4.8 7c10a961b0 docs(changelog): add the AST INFERRED rubric confidence_score entry (#2813)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-18 16:12:01 +01:00
abhay-codes07andClaude Opus 4.8 3bbf420bc3 fix(extract): give AST INFERRED edges a rubric confidence_score (#2813)
AST-emitted INFERRED edges landed at the rubric-forbidden 0.5 (or a hardcoded
0.8). Each AST INFERRED emission site now sets a discrete rubric score keyed to
the relation (uses -> 0.95 direct structural evidence, indirect_call and
unresolved cross-file calls -> 0.85), and the INFERRED write-time default moves
0.5 -> 0.55 so any score-less INFERRED edge is on the rubric set. EXTRACTED /
AMBIGUOUS tiers are unchanged; uses stays INFERRED (not promoted to EXTRACTED)
to keep audit percentages honest.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-18 16:02:02 +01:00
safishamsiandClaude Opus 4.8 74abbfa30c test(build): assert legacy-confidence normalization is idempotent; open 0.9.47
Adds the round-trip idempotency check the deep-dive flagged: after the first
load heals a legacy numeric confidence to INFERRED, reloading the persisted
graph stays silent with a stable score (the warning must not just move one run
later). Dates 0.9.46, opens 0.9.47.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-18 15:59:23 +01:00
Trantor-developsandClaude Opus 4.8 5ad221c3f4 fix(build): normalize legacy numeric edge confidences to INFERRED on load
A pre-enum graph.json stored a numeric edge confidence (a float) instead of a
string tag, which tripped the per-edge 'invalid confidence' validator warning on
every incremental reload. _fold_edge_aliases now moves a numeric confidence into
confidence_score (when none is present) and sets the tag to INFERRED — never
EXTRACTED, since a recovered legacy float is not structural provenance. Modern
string tags are untouched (no churn) and the raw float survives in
confidence_score.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-18 15:54:27 +01:00
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