Commit Graph
2533 Commits
Author SHA1 Message Date
safishamsiandClaude Opus 4.8 2a0cacb60c docs(changelog): open 0.9.96 with the install-security, type-alias, csharp-overload, tree, and other fixes
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-23 18:16:39 +01:00
safishamsiandClaude Opus 4.8 18e52c4bb8 build(cache): bump schema to 25 for the type-alias, C# overload, and package.json-link extraction changes
Covers #4578/#4579/#4580/#4581 (Python/Go/C++/Rust aliases + Rust union),
#4591 (#4584, C# overload nodes), and #4572 (#4569, package.json dep link).
Each PR shipped a stale 20->21 bump against an older base; this is the single
reconciled bump on top of HEAD's 24.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-23 18:15:41 +01:00
Deepjyoti SarmahandClaude Opus 4.8 e76019466d fix(json_config): link a package.json dependency's manifest entry to its ref node (#4569, #4572)
Applied from PR #4572 (authorship preserved); stale cache-schema bump dropped for one combined bump.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-23 18:15:25 +01:00
Mpasha17andClaude Opus 4.8 87151e40ac fix(csharp): give each method overload its own node and bind calls by argument count (#4584, #4591)
Applied from PR #4591 (authorship preserved); stale cache-schema bump dropped for one combined bump.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-23 18:15:24 +01:00
madhusudanpatnaikandClaude Opus 4.8 14514b37f6 fix(rust): extract type aliases and union items as type nodes (Refs #4285, #4581)
Applied from PR #4581 (authorship preserved); stale cache-schema bump dropped for one combined bump.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-23 18:15:22 +01:00
madhusudanpatnaikandClaude Opus 4.8 3bab26798e fix(cpp): extract namespace-scope using/typedef aliases as type nodes (#4580)
Applied from PR #4580 (authorship preserved); stale cache-schema bump dropped for one combined bump.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-23 18:15:21 +01:00
madhusudanpatnaikandClaude Opus 4.8 8cfda7f1ce fix(go): extract type A = B aliases as type nodes (#4579)
Applied from PR #4579 (authorship preserved); stale cache-schema bump dropped for one combined bump.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-23 18:15:20 +01:00
madhusudanpatnaikandClaude Opus 4.8 291248ef49 fix(python): extract explicit type aliases as type nodes (#4578)
Applied from PR #4578 (authorship preserved); the PR's stale cache-schema
bump is dropped in favour of one combined bump for the alias cluster.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-23 18:15:06 +01:00
Neal 44079cbd9b fix(detect): skip binary media word counting
Co-authored-by: Codex <noreply@openai.com>
(cherry picked from commit 62663244fb)
2026-10-23 18:12:48 +01:00
omsenjalia 73876cbdc7 fix(cache): read and write AST cache entries via extended-length paths on Windows
Under a deep root, the cache entry
<root>/graphify-out/cache/ast/v<ver>-s<schema>-g<grammars>/<64 hex>.json,
and mkstemp's even longer <hash>.<random>.tmp temp name, overrun Windows
MAX_PATH. save_cached raised FileNotFoundError and `graphify extract`
failed with "AST extraction failed" (e.g. any project under the default
%TEMP% of a long user name).

Move detect._os_path (the \?\ helper from #1655) to paths.os_long_path
so cache.py can share it, and use it for the entry and temp file in
save_cached and load_cached. detect._os_path stays as an alias. Keys are
unaffected: _normalize_path still strips the prefix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit abf9c9f6ab)
2026-10-23 18:12:47 +01:00
AromalBiju1 cbcd8dd601 fix(tree): don't count untagged nodes as a repo when choosing merged layout
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
(cherry picked from commit a751df5f89)
2026-10-23 18:12:46 +01:00
AromalBiju1 97f18cb7b0 fix(tree): group merged graphs by repo tag so repos get their own roots
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
(cherry picked from commit 873795a05b)
2026-10-23 18:12:46 +01:00
m.fotohi 8a19705383 fix(install): use unique temp paths to prevent symlink attacks during skill install
(cherry picked from commit b2d3ffec7a)
2026-10-23 18:12:46 +01:00
safishamsiandClaude Opus 4.8 53d3d80346 fix(deps): declare pyyaml as a base dependency
The 0.9.95 YAML structural extractor (#4706) imports PyYAML, but it was
only a transitive dependency, so a fresh `pip install graphifyy` produced
zero nodes from .yaml/.yml files (the extractor degrades gracefully but
silently). manifest_ingest and markdown also use it via guarded imports.
Declare it like tree-sitter-json so the feature works out of the box.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-23 16:16:07 +01:00
safishamsiandClaude Opus 4.8 ae2b453b7d chore: bump version to 0.9.95
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-23 16:14:52 +01:00
safishamsiandClaude Opus 4.8 4f99094579 docs(changelog): 0.9.95 — add the security, affected, build, cross-repo, hooks, objc, and sql fixes
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-23 16:13:31 +01:00
safishamsiandClaude Opus 4.8 3b213769f0 fix: bump cache schema to 24 (#4705, #4677), clean recovered SQL labels, add security tests
- Bump _AST_CACHE_SCHEMA 23 -> 24: #4705 (ObjC NS_CLOSED_ENUM) and #4677 (SQL
  CREATE TABLE ERROR-node recovery) both change extraction output, and neither
  PR included the bump, so a warm ObjC/SQL cache would serve stale results.
- #4677: display the recovered table label through _clean_ident (consistent
  with the routine/trigger recoveries) while keeping the id keyed off the raw
  name so FK edges still resolve to it.
- Add regression tests the #4708/#4709/#4710 security PRs shipped without
  (multicast/unspecified SSRF block, Obsidian tag injection, Google shortcut
  frontmatter escaping).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-23 16:12:55 +01:00
08raviyadav e69cc9d89e Recover CREATE TABLE statements dropped into ERROR nodes by unsupported column defaults (fixes #4613)
(cherry picked from commit d496e9113e)
2026-10-23 16:11:21 +01:00
Jason Daniel 8a29ab62cb feat(objc): extract NS_CLOSED_ENUM declarations as enums (#4401)
Co-Authored-By: Claude <noreply@anthropic.com>
(cherry picked from commit c0d1001005)
2026-10-23 16:11:20 +01:00
Dextheking1 fedc4e82ed google_workspace: use full YAML scalar escaping for shortcut frontmatter
(cherry picked from commit 9907005e93)
2026-10-23 16:11:11 +01:00
Dextheking1 bf48d2b83e export: sanitize obsidian frontmatter tag values
(cherry picked from commit 420cc5c2be)
2026-10-23 16:11:10 +01:00
Dextheking1 02f62e769d security: block multicast and unspecified IPs in _ip_is_blocked
(cherry picked from commit cd96fbea26)
2026-10-23 16:11:09 +01:00
dherreraj 5c3c47ebea fix(hooks): run the rebuild from the checkout's top level (#3258)
The rebuild bodies read graphify-out/ and its .graphify_root relative to
the cwd, but git does not always start hooks at the top level. With
GIT_DIR exported and no GIT_WORK_TREE (a nested git call from another
hook or tool), git takes the caller's cwd as the work tree and
`git rev-parse --show-toplevel` echoes that subdirectory. A stray
graphify-out/ left there by an earlier run then roots the rebuild at the
subdirectory; for .claude/worktrees/ that walks every linked worktree.
The #3265 guard rejects a `..` marker, but its fallback is the same cwd.

Add a shared _TOPLEVEL_ANCHOR snippet right after _WORKTREE_GUARD in all
four hooks. In the standard layout it cds to the parent of the absolute
common .git dir; with GIT_WORK_TREE or a separate git dir it keeps git's
own --show-toplevel. GIT_DIR is rewritten to its absolute form so later
git calls still resolve. The block already runs in a subshell, so the cd
does not leak into chained hooks.

(cherry picked from commit e6193ab816)
2026-10-23 16:11:08 +01:00
xiongjianxu d709f67941 fix(cross-repo): bind ObjC split decl/impl across repos to its definition
An ObjC class whose @interface(.h) and @implementation(.m) live in
different repositories surfaces as two same-named candidate type nodes
that _merge_decl_def_classes folds within a repo but cannot across the
boundary (its id embeds the directory, #1556). The cross-repo member-call
pass then tripped the single-definition guard (len(candidates) != 1) and
dropped the call edge even though exactly one candidate DEFINES the
selector.

Scoped to lang == "objc": when more than one same-named candidate
survives, union their members and bind the selector to the sole candidate
that DEFINES it (a method node with definition_file set, or a .m/.mm
source). Two candidates that both define it are two unrelated classes and
stay ambiguous, so the guard still catches the collision it was meant to.
Other languages, the C# resolver path, and the single-candidate path are
unchanged.

Co-Authored-By: Claude Code <noreply@anthropic.com>
(cherry picked from commit a82a375c2d)
2026-10-23 16:11:07 +01:00
nothariharan 7598c5dcee fix(build): resolve a raw graph's import endpoints through the alias index (#4670)
`extract --no-cluster` and the clustered path disagreed on where a
framework-style `#import <Kit/ToolEvent.h>` lands. The clustered path
re-pointed the edge at the header's real file node (`kit_event_toolevent`);
the raw path minted an external stub for the header's bare basename
(`toolevent`) and left the edge on it, so the two write paths of one
extractor produced different graphs for the same corpus.

The raw path only had a normalization map. Normalization cannot bridge
`toolevent` and `kit_event_toolevent` - they are not the same string
modulo case and punctuation - so re-pointing was impossible. The
clustered path resolves it through the pre-migration alias index, which
maps a file's old-stem spellings onto its canonical id (#1504/#1509).

Move that index into a shared `_alias_index` helper and use it in both
paths, so the clustered and the raw write path resolve an endpoint the
same way. The raw path now re-points endpoints BEFORE it mints stubs, so
an import that names a real file is no longer frozen onto a stub minted
for its unresolved spelling; only a genuinely external import
(`<Foundation/Foundation.h>`) still mints one (#2873).

An alias is committed only when exactly one node claims it, so two files
sharing a bare stem stay ambiguous and the import keeps its stub rather
than binding to whichever same-stem file was seen first.

Tests: two unit tests over `finalize_raw_graph_endpoints` (an old-stem
alias resolves to the real file node; an ambiguous alias stays
unresolved) and one end-to-end test that `extract --no-cluster` and the
clustered path agree on the import target. The new tests fail without
this change.

(cherry picked from commit acbec5f5c3)
2026-10-23 16:11:06 +01:00
Deepjyoti-Sarmah fb90065380 test: affected <path> on a shared basename with a second L1 node (#4698)
Pins the resolver against the node shapes #2032 and the rationale pass
produce, plus end-to-end runs of the issue's repro and the comment
control that was never broken.

(cherry picked from commit 3b04706956)
2026-10-23 16:11:06 +01:00
Deepjyoti-Sarmah 7aae85ec6e fix: resolve affected <path> for disambiguated file labels (#4698)
A path query matches every node in the file, so resolution ends in
_prefer_file_node. Two of its passes compare the label to the bare
basename, which #2032's disambiguation rewrote to a unique path suffix
(alpha/hooks.py); the third pass trusts L1 uniqueness, which a module
docstring (a rationale node on line 1) or a one-line top-level
definition also occupies.

Accept a label that names its own source_file, using the
_is_file_node_label predicate build/analyze already share. Runs before
the L1 fallback, so extra L1 nodes no longer decide the result.

(cherry picked from commit b80626ed3b)
2026-10-23 16:11:06 +01:00
safishamsiandClaude Opus 4.8 d67b5a0faf docs(changelog): 0.9.95 — add the YAML structural extraction feature (#4706)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-23 15:55:13 +01:00
safishamsiandClaude Opus 4.8 1834b3e703 fix(yaml): handle multi-document streams, gate data YAML, bump cache schema (#4706)
Three fixes on top of #4706's YAML extractor:
- Multi-document YAML (`---` separators) returned zero nodes because it used
  yaml.safe_load, which accepts only one document; k8s manifests, Helm output,
  and CI pipelines (the headline use cases) were silently empty. Use
  safe_load_all and give each document a distinct node (labelled by kind+name
  for k8s) so two docs in one file do not merge their keys.
- Add a _is_config_yaml gate mirroring extract_json's _is_config_json: a
  top-level data array, an OpenAPI/Swagger spec, a JSON-schema-in-YAML, and a
  flat all-scalar bundle (i18n) are skipped as data instead of minting up to
  500 orphan nodes. A mapping with any nested value, a recognized config name,
  or a .github/workflows path is still walked.
- Bump _AST_CACHE_SCHEMA 22 -> 23 (the PR reclassifies .yaml/.yml and adds a
  new extractor, so cached YAML must re-extract).
- Tests: multi-doc, data/OpenAPI/array skip, the safe_load ACE guard, and caps.
- Clearer missing-PyYAML hint (pip install pyyaml).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-23 15:54:58 +01:00
nothariharan 9c5d56f28f test(watch): use the markdown language alias for the document override
The rcfile language table maps 'markdown' -> .md; 'md' is not a recognized
alias, so the override was ignored and the watch test never saw a document.

(cherry picked from commit c1890f376a)
2026-10-23 15:51:09 +01:00
nothariharan e4d8235378 test(yaml): add structural extraction coverage and update document tests (#2214)
- Add tests/test_yaml_extraction.py (classification, key nodes, contains edges,
  stable ids, scalar/empty documents).
- Drop .yaml/.yml from the oversized-document slicing parametrization: they are
  code now, not documents sent to the semantic pass.
- Point the language-override lifecycle test at .md so it still exercises a
  document override (yaml is no longer a document).

(cherry picked from commit b69b32e2f1)
2026-10-23 15:51:09 +01:00
nothariharan 6a21696caa fix(detect/extract): structurally extract .yaml/.yml config files (#2214)
YAML files were classified as documents, so code-only / AST-only runs
(graphify update) produced zero nodes from them. Move .yaml/.yml into the
code path like .json: add a PyYAML-based structural extractor (extract_yaml)
registered in _DISPATCH, add the suffixes to CODE_EXTENSIONS, drop them from
DOC_EXTENSIONS and the splittable-text set.

Deterministic ids derived from the file path; top-level and nested keys become
nodes with contains edges, capped at 500 pairs / depth 6 to avoid exploding on
data-heavy YAML.

(cherry picked from commit 2e5565a684)
2026-10-23 15:51:09 +01:00
safishamsiandClaude Opus 4.8 d4f3cf7f51 docs(changelog): open 0.9.95 with the module-import over-promotion fix (#4707)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-23 15:39:02 +01:00
nothariharan a8ce86f02e test(extract): pin #2945 module-import over-promotion regression
Adds a regression test that fails on the pre-fix resolver: a caller that
imports a module for a different symbol and calls a same-named function must
keep the edge INFERRED, never EXTRACTED.

(cherry picked from commit 7d41be8891)
2026-10-23 15:38:48 +01:00
nothariharan ac4b5e33dd fix(extract): don't promote module-imported same-named calls to EXTRACTED (#2945)
When an ambiguous bare call resolves to a candidate solely because the
caller imported the candidate's source module (not the symbol itself), the
edge was promoted to EXTRACTED, falsely certifying a same-named symbol that
was never actually imported. Keep the resolution (for graph connectivity) but
mark it INFERRED. Symbol-level (from X import Y) imports still promote to
EXTRACTED.

(cherry picked from commit e73ca4e4a7)
2026-10-23 15:38:48 +01:00
safishamsiandClaude Opus 4.8 c588b72d93 chore: bump version to 0.9.94
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
v0.9.94
2026-10-22 17:06:40 +01:00
safishamsiandClaude Opus 4.8 c3dad319ee docs(changelog): 0.9.94 — add the SQL, markdown, mcp, cross-repo, and ObjC fixes
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-22 13:50:44 +01:00
Deepjyoti-Sarmah b8d9f12385 fix: fold SQL table stubs across files and bind schema-qualified names
The stub rewire skipped dotted labels, but a SQL table is schema.name,
so dotted labels from .sql files now count as definitions. SQL stubs
that share a normalized name and bind to nothing fold into one node
(the smallest id, so file order doesn't matter). .sql joins the
language families, so a SQL read no longer binds to a same-named
definition in another language.

(cherry picked from commit 55bc540bbe)
2026-10-22 13:49:38 +01:00
Deepjyoti-Sarmah 8e8a7c3808 test: one node per SQL table across files
A table no file defines, read from N files, becomes N per-file stub
nodes, and a schema-qualified read never binds to its definition in
another file. A SQL read of `Orders` also binds to a Python class
Orders. Guards cover same-file binding and different schemas.

(cherry picked from commit 1dfc710497)
2026-10-22 13:49:38 +01:00
Deepjyoti-Sarmah 9ce1404719 fix(sql): end the statement at GO batch separators
GO is a client-tool command, not SQL, and tree-sitter-sql has no rule
for it. Before parsing, each GO [n] line found on the masked text
(comments and strings blanked) becomes `;` plus spaces, so byte
offsets and line numbers don't move. A go line after a comma, or
before a comma or `)`, is a column and stays.

AST cache schema 20 -> 21 so cached .sql extractions re-run.

(cherry picked from commit 2293c31d79)
2026-10-22 13:49:33 +01:00
Deepjyoti-Sarmah 01715accd5 test(sql): GO batch separators without a semicolon
A GO line with no `;` before it is read as an identifier, so the next
statement lands in an ERROR node and its table is lost. Guards cover
`;`-terminated batches, GO inside a block comment, a column named go,
and GO after a keyword line such as SET NOCOUNT ON.

(cherry picked from commit f00ecc42c9)
2026-10-22 13:48:29 +01:00
Deepjyoti-Sarmah 518bd3f066 fix(sql): strip T-SQL brackets and read routine bodies (#4635)
tree-sitter-sql has no T-SQL bracket identifiers and no create_procedure
node, so bracket names were mangled and procedures only got a node from
the regex recovery, never an edge. Labels and lookup keys now drop the
brackets (ids are unchanged). Each recovered routine's body, up to the
next CREATE/ALTER, a GO line or EOF, is scanned on the masked text:
FROM/JOIN give reads_from, and INSERT/UPDATE/DELETE/MERGE/SELECT INTO
give writes_to. Alias updates resolve to their table; CTEs, variables
and temp tables are skipped.

AST cache schema 20 -> 21 so cached .sql extractions re-run.

(cherry picked from commit 70f521dba9)
2026-10-22 13:48:14 +01:00
Deepjyoti-Sarmah 608722208d test(sql): T-SQL bracket names, procedure reads and writes (#4635)
Bracket names like [dbo].[Orders] come out as the label `dbo].[Orders`,
stored procedures get no reads_from edges in any dialect, and
INSERT/UPDATE/DELETE/MERGE make no edge. Guards cover aliases, CTEs,
temp tables, variables, comments, strings and body boundaries.

(cherry picked from commit a2a666c6d7)
2026-10-22 13:47:26 +01:00
safishamsiandClaude Opus 4.8 7a0b031088 docs(serve): correct _resolve_single_node docstring to name explain (#4618)
The landed docstring listed only the get_node/get_neighbors MCP tools,
but the CLI `explain` command also routes node lookup through this
resolver (cli.py), so a substring-only near-miss now refuses there too.
Name explain in the docstring so the shared surface is accurate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-22 13:47:19 +01:00
nothariharan c6d4c25135 fix(mcp): a substring near-miss is no longer the answer (#3917)
`get_node` / `get_neighbors` share `_resolve_single_node`, which takes the
first node of the winning tier. The substring tier matches a query anywhere
inside a label, so `get_node(label="MailController")` on a codebase with no
`MailController` returned `EmailController` as if it were the exact symbol,
with no hint that the name does not exist.

The resolver now splits the two fuzzy tiers by intent. A prefix match still
resolves, because the caller started typing the real name (`email` ->
`EmailController`). A substring-only match is a search hit, not the symbol
the caller named, so it returns `No exact match for '<query>'` followed by
up to five closest names with their source location, plus a `path::symbol`
retry example. `get_neighbors` inherits the same answer through the shared
resolver, so both tools report one lookup the same way (ADR-0001).

Exact, source-exact and prefix resolution, the ambiguity refusals, the
dotted/Python and `path::symbol` forms and scoring are unchanged. The other
near-miss case in the issue (`get_node(label="edit")` with several `.edit()`
methods) already refused as Ambiguous under ADR-0001; tests cover both.

Tests: the issue's MailController repro, prefix shorthand, exact-over-substring
precedence, the five-candidate cap.

(cherry picked from commit d3875f4f1c)
2026-10-22 13:47:04 +01:00
xiongjianxu 775ddb9d5a fix(cross-repo): point a cross-repo call edge at its call site, not the caller's header
A cross-repo `calls` edge took its `source_file` from the caller node
(an ObjC method node is minted at its `@interface` in the `.h`) while its
`source_location` is the call-site line, recorded in the `.m`. Pairing the
two pointed `source_file:source_location` into the wrong file — often past
the end of a short header.

The park payload now carries the call-site file alongside the line, and
`link_cross_repo_member_calls` prefers it, falling back to the caller
node's file for pre-#3152 graphs. The parked path is relativized in the
same extract-time pass that relativizes node source_files, so it never
leaks the absolute checkout path into graph.json.

Co-Authored-By: Claude Code <noreply@anthropic.com>
(cherry picked from commit 3281d42492)
2026-10-22 13:46:54 +01:00
xiongjianxu 3a4beec195 Stop re-queuing declined-by-design code files every incremental (#2879)
The #2543 heal re-queues any manifest-stamped code file that has a
registered extractor but zero nodes in graph.json, treating it as a
poisoned stamp. A file the extractor declines by design (data JSON such
as .xcassets Contents.json, #1224/#2879) is exactly that shape — stamped,
zero nodes — yet legitimate, so the heal re-queued it on every incremental
run forever.

extract() now surfaces the declined files (skipped_sources), save_manifest
marks their row ast_empty: true (preserved across unchanged incrementals,
dropped once a file starts producing nodes), and the heal skips rows
carrying the marker. A pre-fix manifest has no marker, so such a file is
re-queued exactly once, re-stamped with the marker, and left alone after.

Co-Authored-By: Claude Code <noreply@anthropic.com>
(cherry picked from commit c930591f08)
2026-10-22 13:46:54 +01:00
xiongjianxu 6e6d5dbbfd fix(extract): resolve ObjC member calls when a class splits across header/impl dirs
`_merge_decl_def_classes` folds an @interface/@implementation pair into one node
only when both live in the SAME directory (the id embeds the directory path,
#1556). A class whose `.h` sits in `include/` and `.m` in `src/` therefore mints
two same-labelled nodes that never merge, so `[w render]` saw two type-def
candidates, tripped the member-call resolver's single-definition god-node guard
(`if len(type_defs) != 1: continue`), and emitted NO edge -- the same corpus
resolved fine when the pair shared a directory. Header/impl directory splits are
common layout, so this hit ordinary code.

Replace the three per-arm god-node bails with a shared `_resolve_member_target`
that unions the candidates' members and binds the selector to the sole candidate
that DEFINES it (an @implementation in `.m`/`.mm`, or a merged node carrying
`definition_file`). A header-only node is a bare declaration and does not count,
so the split pair resolves to its one real definition. Two unrelated same-named
classes that each define the selector still yield two definitions and stay
ambiguous -- the guard holds for the genuinely-ambiguous case it was meant for.

Co-Authored-By: Claude Code <noreply@anthropic.com>
(cherry picked from commit 510934c686)
2026-10-22 13:46:53 +01:00
nothariharan a287c0795a fix(markdown): keep owner evidence when the edge already exists
The review bot flagged a real hazard in the owner-evidence bookkeeping: a
mention whose `references` edge is already in the graph returned early, so
the document never recorded that it cites the target. A later bare method
mention in the same document then lost its owner and was dropped.

`_add` now records the cited target before it decides whether the edge has
to be written, so the evidence survives an edge that is already there.

The pipeline cannot reach that state today (the resolver is handed a fresh
edge list, and the incremental context only carries contains/method/
inherits/imports edges), so this is hardening rather than a fix for a
reachable failure. The regression test drives the resolver directly with a
pre-existing class edge.

(cherry picked from commit 37efe877cf)
2026-10-22 13:46:52 +01:00
nothariharan 0620458b04 fix(markdown): a bare method name needs its class cited (#4609)
`resolve_markdown_mentions` linked any bare, parenthesis-less code span to
the corpus's single `_callable` carrying that label, and methods qualify.
A prose word in a code span (`redis` the server, `data`, `message`) then
became `heading --references--> SomeClass.word()` at INFERRED 0.95, on a
method that has nothing to do with the sentence. On the reporter's corpus
44 of 1,906 mention edges were words of this kind.

A bare single-name span is now resolved in three passes. Explicit evidence
(a cited path, a dotted qualifier chain, or a trailing `()`) resolves as
before. Bare mentions that do not name a method keep resolving, and their
targets are recorded per document. A bare mention that names a method is
kept only when its owner class is already cited in the same document, so
`redis` no longer links to `Cache.redis` while `successWithWarnings` next
to `HandlerResult` still does. Ambiguity, built-ins and the case-sensitive
label match are unchanged.

Tests cover the issue repro, the owner-cited case, the `()` case, a free
function, and the cross-document guard.

(cherry picked from commit 6bba91fc65)
2026-10-22 13:46:52 +01:00