Commit Graph
2300 Commits
Author SHA1 Message Date
rohit-jsfreaky 1704777e39 perf(csharp): resume past the \$/@ prefix run so malformed files stay linear (#3376)
(cherry picked from commit 7f0fd6ddfe)
2026-10-12 22:37:55 +01:00
rohit-jsfreaky 709bba316b fix(csharp): extract past a verbatim interpolated string ending in an escaped quote (#3376)
(cherry picked from commit 1524a6370d)
2026-10-12 22:37:54 +01:00
N. Hariharan d2c1592eab fix(install): preserve line endings of CLAUDE.md, AGENTS.md and siblings (#4360)
#3741 stopped the always-on and Gemini writers from translating line endings
on write (newline=""), but every one of these paths still READ through
Path.read_text, which opens in universal-newline mode and folds CRLF to LF
before the block is merged. A later write then re-emitted the platform
default, so a read-modify-write normalised the WHOLE file: on Windows an LF
file gained CRLF on every line, on Linux a CRLF file lost its CR.

Install and uninstall now go through one shared read/write pair
(_read_keep_newline / _write_keep_newline): the file's own dominant ending is
detected on read, the section helpers keep working on bare-LF text, and the
ending is restored on write with newline="". A file with no prior convention
is written LF, matching the other writers. This covers claude_install,
_agents_install, codebuddy_install, gemini_install/_uninstall,
vscode_install/_uninstall, _strip_graphify_md_section, _agents_uninstall,
codebuddy_uninstall and _remove_claude_skill_registration, plus the two
#3741 paths so the read side can no longer drift from the write side.

Adds a round-trip test per writer: an LF file and a CRLF file in, same
endings out, and install-then-uninstall returns the exact original bytes.

(cherry picked from commit df5f032a62)
2026-10-12 22:37:54 +01:00
N. Hariharan c3d537ae4a fix(install): remove only graphify's own hook entries, keep others (#4359)
Hook install/uninstall identified graphify's hooks with
`"graphify" in str(group)` over the whole group dict. That removed a user's
own command sharing graphify's `hooks` array (with the whole group), and any
unrelated group whose command path merely contained the word "graphify" (e.g.
a script under `graphify-notes/`). Uninstall rewrote the settings file with no
backup, and `_strip_graphify_hook` raised AttributeError on a non-dict entry.

Match the individual `hooks[]` entries instead. A graphify entry is a command
hook whose command invokes graphify's own `hook-guard`/`hook-check` subcommands
(current installs) or references `graphify-out/` (the pre-#522 inline bash
guard). Only those entries are removed; a group is dropped only when nothing is
left in it, and non-dict/non-list shapes are passed through. Applies to the
Claude, CodeBuddy, Codex and Gemini install and uninstall filters, keeping them
in step. Uninstall now goes through `_write_settings_with_backup`, so it keeps
a rolling `.graphify-bak` like install already did.

Adds tests covering a user command sharing graphify's group, an unrelated group
whose path contains "graphify", a non-dict entry, and the legacy-bash replacement
plus idempotency for each host.

(cherry picked from commit 41310c256d)
2026-10-12 22:37:54 +01:00
Mpasha17 804b7933cd fix(serve): refuse the dotted-symbol same-file tie in _tier_refusal (#4366)
Moves the check out of _resolve_single_node so the path endpoint
(CLI path / MCP shortest_path) refuses pkg.mod.run the same way
get_node and explain do, instead of taking the first hit.

Co-Authored-By: Grok Bot <grok-bot@x.ai>
(cherry picked from commit 77f473437b)
2026-10-12 22:37:54 +01:00
Mpasha17 a3d7165df5 fix(serve): resolve dotted Python symbols by module path (#4366)
pkg.mod.func, pkg.mod.func() and the pkg.mod:func entry-point form now
map onto the path::symbol lookup (pkg/mod.py or pkg/mod/__init__.py,
suffix-matched so src/ layouts work). A function and same-named method
in that module are refused as a tie instead of picking one.

Co-Authored-By: Grok Bot <grok-bot@x.ai>
(cherry picked from commit 8f6451af9f)
2026-10-12 22:37:54 +01:00
rajatnagda45 149f55979a fix(go): drop generic type parameters from signature type references
A `[T any]` type parameter is a local binding, not a corpus symbol, but
`_go_collect_type_refs` appended the bare `T`/`R` identifiers used in a
function signature, struct field, or interface method as type references and
ran them through the sourceless-stub path. Every generic function/type in the
corpus then collapsed its `T` onto one bare `t` node — a god node that
pollutes god-node rankings, since single-letter type parameters are not in the
analyzer's builtin-noise filter. TS, C#, and Java already exclude their type
parameters.

A new `_go_type_param_names` helper collects the names a declaration binds
(its own `[T, R]` list and, for a method, the receiver's `Box[T]` arguments —
Go methods reuse the receiver's parameters), and `_go_collect_type_refs` skips
them. Genuine user types and the generic container head (the `Box` in a
`Box[T]` return) still reference.

(cherry picked from commit 434eb689c8)
2026-10-12 22:37:54 +01:00
rajatnagda45 6dbd10a311 fix(extract): drop Swift Self type annotation from references
A `-> Self` / `: Self` annotation names the enclosing type, not a corpus
symbol, but `_swift_collect_type_refs` appended the `Self` type_identifier
as a type reference and ran it through the sourceless-stub path. That
fabricated a node with the bare id `self` that every `Self`-returning
method in the corpus collapses onto — a god node, the same keyword-type
pitfall Rust's and Python's `Self` were fixed for.

`Self` is now skipped in both the `user_type` and bare `type_identifier`
arms. Genuine user types and the `Int`/`String` builtin type hubs are
unaffected.

(cherry picked from commit c4fb250b3b)
2026-10-12 22:37:54 +01:00
rajatnagda45 dafdbec33b fix(extract): drop Python Self type annotation from references
A `-> Self` / `: Self` / `typing.Self` annotation (PEP 673) names the
enclosing class, not a corpus symbol, but the Python type-ref pass treated
the bare identifier `Self` as a type reference and ran it through the
sourceless-stub path. That fabricated a node with the bare id `self` that
every `Self`-annotated method in the corpus collapses onto — a god node,
the same keyword-type pitfall Rust's `Self` was fixed for.

`Self` now sits with the other typing special forms in the type-container
set, so it is skipped the way `None`/`Ellipsis`/`ClassVar` already are;
this also covers the `typing.Self` attribute spelling. Dropping it matches
how the `-> "ClassName"` self forward-ref is already left unresolved. A
genuine user return type is unaffected.

(cherry picked from commit b67f05408e)
2026-10-12 22:37:54 +01:00
rajatnagda45 c36516988e fix(extract): stop Python property-accessor decorators minting stub nodes
`@x.setter` / `@x.getter` / `@x.deleter` are the property-descriptor
protocol, not references to corpus symbols named setter/getter/deleter.
The Python decorator-edge pass (#2154) ran `ensure_named_node` on the
decorator's head symbol, so every property with a setter/deleter/getter
redefinition fabricated a sourceless stub node and a false `references`
(context=decorator) edge onto it — the same provenance pollution the
builtin-decorator noise list already guards against for `@property`.

`_python_is_property` already recognizes the `.getter` form for the
read-semantics; the decorator-edge path now mirrors it and skips any
`.setter`/`.getter`/`.deleter` decorator. Imported decorators reached
through an attribute (`@app.route`) are unaffected.

Regression test asserts no decorator edge and no sourceless stub for a
property with setter and deleter accessors.

(cherry picked from commit 2dc8972c67)
2026-10-12 22:37:54 +01:00
safishamsiandClaude Opus 4.8 7536dc0665 chore(release): 0.9.87
Eighteen community pull requests: PHP/C#/Python/TypeScript/Julia
extraction fixes, retrieval and watch hardening, skill and benchmark
correctness. The AST cache schema is bumped (C# type_path, Python
property reads), so the first build after upgrading re-extracts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
v0.9.87
2026-10-12 14:55:03 +01:00
JensD-git d85cb85654 fix(csharp): keep id-sharing and qualified-base partials out of the merge (#4363)
Follow-ups from the automated review of the first commit:

- Two same-named nested types under different outers in one file share one
  node id. That node took the first declaration's type_path and could join
  the other outer's halves; it now gets no type_path and is not merged.
- The shadowed-base step drops only a placeholder spelled with the bare
  name. A base written qualified (Other.IFoo), or through a using alias of
  that name, can be a different type that only shares the simple name, and
  keeps its edge.
- Pin that receiver-qualified calls (base.M(), this.M(), Type.M(), obj.M())
  keep their targets: they leave the cross-file pass as member calls before
  the own-type binding.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 37ec971f16)
2026-10-12 14:50:32 +01:00
JensD-git 371cb47e83 fix(csharp): merge nested and generic partial types by their full type path (#4363)
The partial merge grouped halves by (namespace, label) and skipped nested
types, because nested ids omit the enclosing type. A nested partial type split
across files stayed split, and `Box` / `Box<T>` partials were folded into one
node although they are distinct types (#4249).

- The C# extractor records each partial type's path inside its namespace,
  enclosing types first, each with its arity (`Outer`1.Inner`), as
  metadata.type_path; the merge keys on (assembly, namespace, type_path).
- The cross-file call pass binds a C# unqualified call to the one candidate
  that is a member of the caller's own type, so a call into another half of a
  partial type is not lost to a same-named method elsewhere (#4252 does this
  in-file only). Ties fall through unchanged.
- After C# type resolution, a merged partial type drops a placeholder base
  that sits next to a real base of the same name; one half resolving a sibling
  nested base and the other not left two bases, and base-chain lookups failed.
- _AST_CACHE_SCHEMA 8 -> 9, so cached C# results without type_path are
  re-extracted.

Fixes #4363.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 92d9197787)
2026-10-12 14:50:32 +01:00
rikurunico c7bc5fe4c0 fix(php): route ALL scoped calls through the PHP resolver, not just uppercase receivers
engine.py's defer guard only deferred a scoped_call_expression into raw_calls
when member_receiver[:1].isupper() (or is_this_field_call/_csharp_defer), so
self::, static::, parent::, $var::, and lowercase-first class receivers fell
through to the pre-existing per-file bare-name path instead of reaching
_resolve_php_member_calls. That path is last-write-wins across the whole file
and can mis-bind a same-named method on an unrelated class.

Tag scoped_call_expression with is_scoped_call and add it to the defer
condition unconditionally, so every `::` call is deferred to raw_calls and
resolved by the dedicated resolver regardless of receiver casing or sigil.

Also tighten the self::/static:: and $var:: fixtures with a same-named decoy
method on another class, so they actually prove enclosing-class scoping
and no-fabrication instead of passing on a bare-name coincidence.

Addresses review feedback on PR #3874.

(cherry picked from commit 801c2a2dfd)
2026-10-12 14:50:04 +01:00
rikurunico b0fd76a61c fix(php): handle case-insensitivity and namespace qualification in static-call resolution
Addresses 3 findings from the automated review on PR #3874:

- Fully-qualified scopes (\App\Models\User::method() or
  App\Models\User::method()) were left parked as unresolved: the class
  lookup keyed on the full scope text, which never matches a class
  node's own unqualified label. Now normalizes to the last namespace
  segment before lookup.
- Method names were matched case-sensitively even though PHP method
  calls are case-insensitive; a call spelled with different case than
  the declaration silently produced no edge. The lookup key is now
  lowercased on both sides.
- A lowercase-first class name (legal PHP, just non-PSR) fell through
  the `receiver[:1].isupper()` gate into "dynamic scope" and was
  dropped entirely. The gate is now `not receiver.startswith("$")`,
  since a `$var::` scope is the only case actually requiring the
  declared-type table this resolver doesn't have.

(cherry picked from commit 7ed0e1d70a)
2026-10-12 14:49:34 +01:00
rikurunico 2d37da18c9 fix(php): resolve Class::method() static calls to the actual method
scoped_call_expression (`Helper::format()`) used the scope text as the
callee name instead of the method name, so the shared cross-file
bare-name pass bound the call to whatever node shared the class's
label - almost always the class definition - never the actual method.
It also skipped member-call handling entirely, so the edge went
through the import-evidence confidence gate: same-namespace PHP
classes never need a `use` import for each other, so every
same-namespace static call was stuck at INFERRED 0.85 even though the
receiver class is named explicitly in source.

Capture the real method name as the callee and the scope as the
receiver, mark it a member call, and add a dedicated PHP resolver
(mirroring the existing Java/Swift/C++ member-call resolvers) that
binds self::/static:: to the enclosing class, parent:: through a
single inherits edge, and Class:: to the class's own method - all
EXTRACTED, since the receiver is named explicitly in source. A
dynamic scope ($var::method()) has no declared-type table to resolve
against and is left unresolved rather than guessed.

Fixes #3872

(cherry picked from commit 2ec460168b)
2026-10-12 14:49:34 +01:00
smngvlkz 169e470ce0 fix(watch): don't drop full rebuilds when the lock is busy (#4274)
(cherry picked from commit 6ad54959bd)
2026-10-12 14:49:29 +01:00
jnrod03-rgb 2aad748f37 fix(skillgen): pass root= to extract() in Step 3 Part A (#3727)
extract() uses cache_root as the fallback anchor for source_file and node ids
when root is not given (#1941). Part A called it with cache_root alone. That is
harmless while the cache sits in the scan root, but the two parameters look
alike and the block gets adapted: pointing only the cache somewhere else
re-anchored every code node. Same node count, every id renamed, every cached
edge into code left dangling - the node-count shrink guard cannot see it.

Pass root=Path('INPUT_PATH') next to cache_root, as the CLI already does, and
say in the block which of the two anchors ids. The rendered call is behaviour
preserving as written; it only stops being a trap when the cache is relocated.

The aider/devin monoliths call extract(code_files) with neither argument and
are left untouched.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit efe15d792c)
2026-10-12 14:49:29 +01:00
rohit-jsfreaky a5e71809f9 fix(benchmark): count the tokens of the answer query_graph returns
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B8gwEpjajiWHVW3c9smuKf
(cherry picked from commit 687cedd407)
2026-10-12 14:49:28 +01:00
oleksii-tumanov 7ff507b1c6 fix: honor Claude CLI model env during labeling
Use GRAPHIFY_CLAUDE_CLI_MODEL for plain Claude CLI completions when callers do not pass an explicit model. Preserve explicit-model precedence and the CLI default when the setting is absent.

AI assistance: OpenAI Codex was used to help implement and validate this change.

(cherry picked from commit 109107e407)
2026-10-12 14:49:28 +01:00
Mahaboob pasha 8435b91c0d feat(html): add a light/dark/system theme toggle to graph.html (#3894)
Co-Authored-By: Grok Bot <noreply@x.ai>
(cherry picked from commit 95dd5397cd)
2026-10-12 14:49:28 +01:00
SrijanSriv aa7837960b fix(update): let a removed import stub go when the total grows
An auto-minted external node is rebuilt from import edges. Dropping one
while code grows is not a lost rationale node, so the update writes
without --force.
2026-10-12 13:02:57 +01:00
SrijanSriv be04997e9d fix(export): keep an integer node id that is still in the graph
The semantic check only treated a string id as present, so a second
to_json of nodes 1, 2, and 3 refused the same graph.
2026-10-12 13:02:57 +01:00
SrijanSriv 7bfd59ce2b fix(update): refuse a saved semantic id when the total grows
Compare saved semantic ids with the new graph instead of the node total.
A sourceless stub cannot fill a missing id, and a code rebuild is not a
deletion. A legacy id that build_from_json rewrites to its canonical
stem is the same node, so cluster-only still writes.
2026-10-12 13:02:57 +01:00
SrijanSriv 3050270639 test(update): refuse a lost rationale id when the node total grows
graphify update accepts any new node list that is at least as long as
the saved one, and a rebuilt code file was treated as a reason to drop
the rationale node on that file. A deleted code file must still write.

The #1116 fixture nodes are marked AST so a removed code symbol stays a
code deletion.
2026-10-12 13:02:35 +01:00
SrijanSriv 355c2b538b test(export): refuse a lost rationale id when the node total grows
A larger total used to make to_json accept a write that dropped a saved
rationale id. The new stub keeps the semantic count flat, so a count
check would miss the same loss.
2026-10-12 13:02:35 +01:00
rohit-jsfreakyandClaude Opus 5.5 1723546eec fix(python): count x.prop += v as a read of the property
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B8gwEpjajiWHVW3c9smuKf
2026-10-12 13:02:22 +01:00
rohit-jsfreakyandClaude Opus 5.5 d244be4c97 feat(python): link reads of a @property to its getter
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B8gwEpjajiWHVW3c9smuKf
2026-10-12 13:02:22 +01:00
rohit-jsfreakyandClaude Opus 5.5 68a45aeb7b fix(ts): reach a #name-labeled node from c.name() only when the class declares a public name
Review finding: with the `#` guard gone, `c.run()` on a class that declares only
`#run` (its public `run` inherited from a base) landed on the private method. The
extractor now marks a member node `_ts_public_twin` when its class declares both
`#name` and a public `name` (method or property, e.g. hono's `newResponse` arrow
property), and the resolver only lets a public call reach a `#`-labeled node that
carries the mark. The mark is on the node so a changed-files rebuild, which reads
unchanged files from graph.json, still sees it (forwarded in watch.py and cli.py).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B8gwEpjajiWHVW3c9smuKf
2026-10-12 13:01:47 +01:00
rohit-jsfreakyandClaude Opus 5.5 ad88f5bc70 fix(ts): keep a public call's edge when foo and #foo share one node
The extractor gives a class's `foo()` and `#foo()` the same node id, labeled after
whichever is declared first. Skipping a `c.foo()` match whose label starts with `#`
therefore dropped a real edge when the private member came first (hono's
`c.newResponse()`, whose public arrow property has no node of its own). A call
written `c.foo()` can only mean a public member, so keep the edge to that node.
Only the first commit of #4233 shipped in 0.9.81; this is its second one.

Covered for both declaration orders. vs 0.9.84: hono +3 edges, nothing removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B8gwEpjajiWHVW3c9smuKf
2026-10-12 13:01:47 +01:00
safishamsiandClaude Opus 4.8 afd4fa55c6 test(prs): use a now-relative date so the PR-dict test can't go STALE
The #3829 fixture hardcoded updatedAt, which crosses the 14-day staleness
window over time and flips the asserted status from APPROVED to STALE. Compute a
recent date instead.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-12 13:01:40 +01:00
himanshupatro-334 5036d2de23 test: isolate PR default branch detection 2026-10-12 13:01:09 +01:00
himanshupatro-334 3fd8d3ec5d fix: scope PR graph impact to requested PR 2026-10-12 13:01:09 +01:00
ayarodionovandClaude Opus 5.5 2fc8e9c94a fix(julia): resolve module-qualified functor types, never fall back to the variable
The functor branch picked the type from the typed expression's identifier and
parametrized-type children. A module-qualified type (`Mod.Box`) is a
`field_expression`, so `(b::Mod.Box)(x)` fell back to the variable `b`, and
`(::Mod.Box)(x)` / `(b::Mod.Box{T})(x)` were dropped. Read the last child (the
type) through a new `_functor_type_name`, which unwraps parametric and
qualified names and returns None for anything else.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 13:01:09 +01:00
ayarodionovandClaude Opus 5.5 fac1491665 fix(julia): name parametric functors by their type, not the variable
`(b::Box{T})(x)` puts the type in a `parametrized_type_expression`, so taking
the last identifier in the typed expression picked the variable `b` and
emitted a bogus `b#call` node, split from `Box#call`. Read the last operand
through `_type_name_of`, which already unwraps parametric heads.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 13:01:09 +01:00
shazeb d611afbee9 fix(skill): stop --update from printing the whole corpus
The incremental step dumped every file path into the agent context even
though later steps read the sidecar from disk. Print a short summary
and leave the full result in .graphify_incremental.json.

(cherry picked from commit d23d1c618a)
2026-10-12 13:01:00 +01:00
Mahaboob pasha 07f3d0fc3c fix(csharp): type a var from the called method's return type (#4266)
Co-Authored-By: Grok Bot <noreply@x.ai>
(cherry picked from commit 816dcf5927)
2026-10-12 13:01:00 +01:00
devkancheti4-design d3f8687c00 fix(query): order the per-term guaranteed seeds by match strength (#4357)
_pick_seeds appended the per-term guaranteed seeds (#1445) in sorted(best_seed_by_term), alphabetical term order,
and the renderer prints seeds first, so a weak match on an early-alphabet term led a specific term's exact match.
_score_query now returns term_order: the per-term winners by their existing key (-singleton score, which carries
the term's IDF weight, then -degree, label length, node id), the term breaking ties. _query_graph_text passes it to
_pick_seeds. The same seeds are picked and the order is deterministic; without term_order the sorted order is kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 7bcbc60ac1)
2026-10-12 13:01:00 +01:00
safishamsiandClaude Opus 4.8 c8d769f95c test(dedup): stamp _origin on the shrink-guard fixture (post-#3110)
#3110 makes the semantic tier carry _origin:"semantic" explicitly, so a real
graph.json's semantic nodes are no longer tier-guessed from the L-shape of
source_location. This fixture wrote a baseline graph with no _origin, which
backfilled to AST and mixed with the re-extracted (now semantic) nodes, so the
pair no longer merged and the shrink guard had nothing to catch. Stamp the
fixture nodes semantic to reflect what real post-#3110 graphs contain; the guard
correctly fires again.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-12 12:29:39 +01:00
abhay-codes07andClaude Opus 4.8 4fff67d219 fix(skill): stamp _origin on the semantic tier so update cannot delete it (#2843)
build._is_ast_tier reads _origin when present and otherwise guesses the tier
from the shape of source_location (L<line> means AST). extract() stamps its AST
items _origin:"ast", but nothing stamped the semantic side, so a semantic node
whose location happened to look like "L12" read as AST — and the next
`graphify update` deleted it along with the re-extracted code file it seemed to
belong to. Part C, extract_corpus_parallel, and the CLI merge now setdefault
_origin:"semantic" on every semantic node/edge before the merge, so a semantic
item is never mistaken for an AST one. AST items (stamped "ast") are untouched;
setdefault preserves any existing stamp.

Landed from PR #3110. Skill artifacts regenerated fresh from the updated
fragments (not the PR's stale committed blobs); all five skillgen validators
pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-12 12:18:15 +01:00
9c66fd30dc feat(config): declare languages for ambiguous extensions via .graphifyrc (#2961)
An ambiguous extension (`.inc`, `.h`, TS-family, ...) is hard-mapped to one
global default; when that is wrong the file does not error — it parses as the
wrong language and yields a few incidental nodes, so the graph looks populated
while the real symbols are missing. A project can now declare what an extension
means via `language.<ext>=<language|.ext>` lines in `.graphifyrc` (which already
holds `viz_node_limit`). The declaration flows through file classification,
extractor selection, grammar/resolution, imports, watch, and the AST cache
(salt + manifest language field + incremental invalidation), is thread-local
per scan root, and is forwarded to the worker pool. Default detection is
byte-for-byte unchanged when no declaration is present.

Landed from PR #3789 (squashed), which carried @abhay-codes07's original #3075
forward with reviewed follow-ups. Authorship credited to both contributors.

Co-Authored-By: Andre Brait <andrebrait@gmail.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-12 12:16:17 +01:00
41418864aa fix(hook): bound the soft read/search nudge per session (#3435)
The read/search nudge was re-injected on every qualifying Read/Glob/Grep/Bash
call with no dedup or session cap (issue #3435 measured ~8,348 injections,
~651k tokens, for 339 graphify calls), and an agent that just ran
`graphify query` was still nudged on its very next read. `_nudge_allowed`
now gates all three soft-nudge sites with a per-session + per-agent counter
(first nudge always fires; later ones suppressed while the query stamp is
fresh; hard cap via GRAPHIFY_HOOK_NUDGE_CAP, default 5), with 24h GC and
fail-open preserved. Subagents get independent budgets. The strict deny path
is unchanged.

Landed from PR #3443 (squashed). The README strict-mode note is omitted here
because that prose moved to docs.graphify.com in the README trim.

Co-Authored-By: Andre Brait <andrebrait@gmail.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-12 12:15:19 +01:00
safishamsiandClaude Opus 4.8 a5ba60ddf6 release: 0.9.86
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
v0.9.86
2026-10-11 21:17:07 +01:00
Andre Brait ceff3e215d fix: write graph.json as one line of compact JSON (#3413)
graph.json was pretty-printed (indent=2), so every PR that commits a
rebuilt graph reports tens of thousands of changed lines. GitHub's PR
line counts ignore .gitattributes (-diff, binary), so only the file's
content can fix it: one line of compact JSON is +1 -1.

Every writer of a project graph.json now emits separators=(",", ":")
with no indent: to_json, the `update` raw/no-cluster candidate text,
prune, merge-driver, merge-graphs, `extract --no-cluster` and the global
graph. Readers are unaffected.

`update` compares parsed data, not bytes, so an existing indented graph
would have been "unchanged" forever. It is now rewritten compact once,
then left alone.

Co-Authored-By: Claude <noreply@anthropic.com>
(cherry picked from commit 4c31235c37)
2026-10-11 21:07:15 +01:00
Krish Garg c68d167ee4 fix(rust): expand grouped use declarations into import bindings
Give each binding of a use tree the import edge that v8 produces for
its equivalent plain use declaration. Preserve aliases, path comments,
separator whitespace, nested prefixes and self/glob binding behavior.

Keep the old group edge as call evidence until the call pass finishes;
exclude per-binding edges from that pass, then remove private markers.
Plain imports and all call edges retain their current-v8 behavior.
AST cache schema 6 -> 7 rejects results written before expansion.

Replace the earlier broad resolver in #4135 with this syntax-only fix.
The resolver is preserved on local branch wip/rust-use-resolver.

On current v8, all 30 new tests and the full 6642-test suite pass.
Fresh ripgrep builds have 864 import edges instead of 247, with every
call unchanged. Existing name-based import rewiring can still produce
wrong matches, just as it does for equivalent plain declarations.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Codex <noreply@openai.com>
(cherry picked from commit 2669ee4f11)
2026-10-11 21:07:09 +01:00
oleksii-tumanov 5d92f1ec08 feat: support a sampling seed for built-in Ollama requests
AI-Assistance: OpenAI Codex
(cherry picked from commit 4907979e77)
2026-10-11 21:06:27 +01:00
Tivonsico 4e98b51c5c docs: distinguish local CLI guidance from MCP instructions
Co-Authored-By: OpenAI Codex <noreply@openai.com>
(cherry picked from commit cac64a0c22)
2026-10-11 21:06:10 +01:00
Andre Brait 51bd59731a docs(always-on): say large graph.json diffs in pull requests are expected
A committed graphify-out/graph.json is rewritten whole on every build, so
its pull-request diffs are large by design. Assistants reviewing such PRs
flagged them as churn and proposed skipping the rebuild or its hooks. The
Claude, AGENTS.md and Gemini blocks now say the diff is expected.

(cherry picked from commit e6cf17c171)
2026-10-11 21:06:10 +01:00
Vibhanshu Mishra 7a8dfff3db fix(codex): preserve semantic chunks through B3 merge
Persist validated Codex extraction responses as chunk files so the shared B3 merge preserves semantic nodes and edges.

Fail before cache and graph updates when extraction responses are missing or invalid.

Add regression coverage for chunk merging, cached reruns, and failure handling.

Fixes #4311

Co-authored-by: OpenAI Codex <codex@openai.com>
(cherry picked from commit da5850f0ac)
2026-10-11 21:06:10 +01:00
Mahaboob pasha bcf9624477 feat(scala): link classes to typeclasses in derives clauses (#2048)
`case class Point(x: Int) derives Show, Eq` (and the same on enums) now
emits a `references` edge with the `derives` context from the type to each
derived typeclass, next to the class-level context bounds it resembles. A
qualified name (`derives cats.Show`) links by its last segment.

Co-Authored-By: Grok Bot <noreply@x.ai>
(cherry picked from commit e6ccd59f20)
2026-10-11 21:06:10 +01:00