Commit Graph
1593 Commits
Author SHA1 Message Date
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 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
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
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
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
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 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
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
safishamsiandClaude Opus 4.8 de35292406 fix(cli): derive the --help platform list from _PLATFORM_CONFIG + add a drift guard (#4655)
#4655 added amp/copilot/kilo/kimi to the hand-maintained --help install
line but still omitted antigravity-windows, so it disagreed with
_print_install_usage. Derive the list from the same
[*_PLATFORM_CONFIG, "gemini", "cursor"] expression the install usage
uses so the two cannot drift, and add test_help_lists_every_install_platform
to catch any future platform that is not advertised.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-22 13:16:25 +01:00
madhusudanpatnaik c0ac375b11 fix(extract): keep the checkout path out of ObjC category ids
An ObjC category (`@interface Foo (Cat)` in `Foo+Cat.h`) keys its class
node and methods off the base stem `Foo`, so they fold into `Foo.h`'s
class (#1556). extract() turns an id minted from an absolute input path
into the repo-relative form only when it starts with its own file's stem
prefix (`.../Foo+Cat`), so these ids kept the absolute form. A category
on a class outside the corpus (`NSString (Extra)`) got a node id with
the checkout path in it, and so did the methods of a category on an
in-corpus class. graph.json then carried the path (#502), two checkouts
of one repo gave different graphs, and an incremental rebuild in a moved
or copied checkout added duplicate nodes.

The remap now also registers a category file's base-stem prefix, after
the file's own prefixes. On SDWebImage, AFNetworking and Kingfisher,
334 / 51 / 17 ids carried the checkout path; now 0, two checkouts give
identical graphs, and absolute and relative input agree. Every edge,
compared by label and file, is unchanged. Through the CLI, SDWebImage's
graph drops 125 duplicate category nodes (a leaked copy beside the clean
copy reached through its include/ symlink) and one false reference.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 11fe66a3cf)
2026-10-22 13:13:03 +01:00
Ziyu Dong b9f95739ca fix(serve): accept the bracketed Host header of an IPv6 HTTP bind
`--transport http --host ::1` put the bare literal into the DNS-rebinding
allowlist (`::1`, `::1:8080`), but clients reaching http://[::1]:8080
send `Host: [::1]:8080`, so every request was rejected with 421 and the
server was unusable on an IPv6 loopback bind. Bracket an IPv6 literal
before building the allowlist; foreign Host headers are still rejected.

(cherry picked from commit 831b25703c)
2026-10-22 13:12:57 +01:00
Ziyu Dong b324c34f78 fix(ingest): never write a text capture through an existing entry
Binary captures pick their filename with lstat so they never replace an
existing directory entry (#4393). Text captures (webpages, tweets, arXiv)
still used Path.exists(), which follows symlinks: a dangling symlink named
like the capture reported "free" and write_text() followed it out of the
corpus directory. The loop also stopped at 999 numbered names and then
overwrote the last one. Share the lstat-based selection between both
paths.

(cherry picked from commit 3e483ca077)
2026-10-22 13:12:56 +01:00
Amar Kumar Thakur 0aa256f95f fix(codex): quote a spaced exe path in the PreToolUse hook command
The claude and gemini hooks (since #522/#1987) double-quote a resolved graphify path containing spaces, but _install_codex_hook embedded the resolver output raw. On a checkout path like 'D:/Project V2/.venv/Scripts/graphify.EXE', every shell that runs the hook (Git Bash on Windows to cmd.exe to POSIX sh) word-splits the command at the first space: the guard can never fire, and #2165's subcommand-parsing test splits at the same space and fails.

Mirror _claude_pretooluse_hooks/_gemini_hook with the same two-line guard: bare-name (project-scoped, #3129) installs are unaffected, as is any absolute path without spaces. test_codex_hook_command_is_a_real_cli_subcommand now parses hook commands with shlex.split instead of naive .split(), and a new regression test installs all three hosts under a spaced resolver output, proves argv[0] survives intact, and re-verifies uninstall recognition of the quoted entry.

(cherry picked from commit 87e997d3a0)
2026-10-22 13:12:56 +01:00
lab 1207 ae5493c182 fix(extract): a require binding does not shadow the required class
A \const X = require('./m')\ binding is an import, not a module-scope data
binding. The node it produced in the importing file shadowed the class the
required file exports, so \
ew X()\ bound to the local node and the
required class got no incoming edge.

(cherry picked from commit 66bb69114a)
2026-10-21 19:33:10 +01:00
ayushcodes10 85537d9c01 Add regression tests for the member call defer fix
Covers the issue's own repro, a delegating wrapper that no longer
self loops and an unrelated same named sibling that no longer steals
the edge, plus three controls: a bare unqualified call still
resolves by name, this dot calls still bind through the caller's own
class, and a TypeScript typed receiver still resolves through its
declared type. A fourth control checks that a factory object's own
sibling methods still call each other, the one legitimate case that
keeps resolving locally instead of deferring.

(cherry picked from commit 49c645d2ff)
2026-10-21 19:33:01 +01:00
Jay Hemnani cc488b9076 fix(manifest_ingest): read only a POM's own dependencies
_parse_pom collected every <dependency> in the file with
".//dependencies/dependency", including <dependencyManagement> entries,
which only pin versions, and the <dependencies> of build plugins. Each
became a depends_on edge, so a parent POM that manages its modules'
versions depended on every module.

Read <dependencies> under <project> and under <profiles><profile>, where
Maven takes a project's dependencies from.

(cherry picked from commit 534177d1ea)
2026-10-21 19:33:01 +01:00
Rishi Gupta 2b07311b0d test(security): reject URLs with embedded credentials
(cherry picked from commit 09494cb720)
2026-10-21 19:32:46 +01:00
Rishi Gupta 52dea94b01 test(security): cover malformed URL ports
(cherry picked from commit 57d6217281)
2026-10-21 19:32:19 +01:00
Rishi Gupta 8f6ad0f61e test(security): cover hostless HTTP URLs
(cherry picked from commit fad73d94d9)
2026-10-21 19:32:11 +01:00
safishamsiandClaude Opus 4.8 b90b9df794 test: account for #3987 Step B3.5 block in the B3-merge skill tests
The partial-read coverage check (#3987) adds a python block between the
Step B3 merge and Part C. Two skill-path tests that landed separately in
the 0.9.9x skill work hard-code the block count in that range (3) and
then execute every block in it. Bump the expected count to 4; the
behavioral assertions (token sums, merged node sets, stale-chunk
clearing) still hold because B3.5 only writes an empty
.graphify_partial.txt on the all-cached and codex-merge paths.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-20 16:56:09 +01:00
nothariharan 8a982c4a92 fix(skill): a partly-read file is no longer a silent, permanent loss
The Read tool returns at most 2000 lines per call and reports no error when it
truncates, so a subagent that read only the head of a long file still writes a
well-formed chunk. Step B3 treated "chunk exists and parses" as success, and
because build_merge replaces a re-extracted file's nodes, the unread tail was
not merely missed: it was deleted, the run reported success, and the manifest
recorded the file as done so no later --update retried it. On a 4165-line file
that left 103 nodes covering 31% of the document, with 90 of 131 entries gone
and the manifest treating it as finished.

Three changes, in the fragments, so all thirteen disk-dispatching hosts pick
them up from one edit:

The extraction spec (verbose and compact) now states the cap and forbids
emitting nodes for a partly-read file, and asks the subagent to report a
top-level "coverage" map of file -> lines read. A short count is recoverable; a
missing entry is indistinguishable from a complete read, which is why the
machine check needs the prompt to ask.

Step B1 bounds a chunk by total lines as well as file count (20-25 files AND
~6000 lines), and gives any file past 2000 lines a chunk to itself. File count
alone was only half the mechanism: a subagent holding 25 files also dilutes
attention, which is how a 590-line plan came back with zero nodes.

Step B3.5 compares each reported coverage against the file's real length, names
what was short, and hands the list to the two places that decide whether a file
is ever looked at again: save_semantic_cache(partial_source_files=...) stamps
those entries partial, which load_cached treats as a miss, and
_stamped_manifest_files(partial_source_files=...) leaves them unstamped so
detect_incremental re-queues them. Both are the existing contract the library
path already uses for a truncated LLM response (#933, #1950), so the skill and
library paths now recover the same way. Nodes already found for those files are
kept; the next run's replace-on-re-extract swaps them for a complete set.

The check is fail-safe in the direction that loses nothing: no coverage reported
means no file is flagged, and a flagged file is re-read rather than trusted.
Codex collects subagent results in memory with no per-chunk files on disk, so it
gets the prompt-layer fix but not the Step B3.5 gate; its inline extraction has
no chunk file to compare against.

Tests execute the emitted python instead of matching its source (the #2126/#2641
convention), including a round trip through the real semantic cache: the
partly-read file must come back as uncached, while the fully-read file still
hits. Reverting the fix makes that round trip return an empty uncached list -
the file served from cache forever, which is the bug.

Fixes #2225

(cherry picked from commit 0b6a1868af)
2026-10-20 15:40:39 +01:00
56a0329b31 fix(query): only refuse an EXACT-name ambiguity, not a search keyword (#4517)
The ambiguous-name refusal added in #4517 routed through _resolve_single_node,
whose winning tier can be a prefix or substring match, so a bare search keyword
that merely substring-matched several labels was wrongly refused: `query "client"`
returned EXIT 3 because it is a substring of `AlphaClient`/`BetaClient`. That
narrowed query's keyword-search semantics.

Adds _query_name_refusal, which refuses only when the term EXACTLY names several
nodes (the source_exact/exact tiers) -- #4511's `.nonce()` case -- and otherwise
falls through to scoring. The `query` and `--seed` paths use it; explain/path/
get_node keep _resolve_single_node, whose prefix/substring tiers are correct for a
direct node lookup. Adds a regression test that a substring keyword still searches.

Co-Authored-By: shivampal46011 <shivampal46011@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-20 14:57:06 +01:00
shivampal46011 8ff7b39899 Refuse an ambiguous name in graphify query
query guessed a node when the same name existed in several files, and an ambiguous --seed still exited 0. explain and path already refuse that lookup. query now returns the same candidate list and exits 3, and a --seed that matches nothing exits 4.
2026-10-20 14:53:31 +01:00
Rudy CelekliandClaude Opus 5.5 f000104ba9 fix(detect): classify extensionless scripts whose shebang names a versioned interpreter
_shebang_interpreter returned the interpreter exactly as written, and both
detect's _SHEBANG_CODE_INTERPRETERS and extract's _SHEBANG_DISPATCH list
only the generic names. An extensionless CLI starting with
#!/usr/bin/env python3.12, #!/usr/bin/python2.7 or #!/usr/bin/lua5.4 was
classified as None and silently left out of the scan. Map a version
suffix on a known interpreter back to that interpreter (python3.12 ->
python3, lua5.4 -> lua) in the shared parser, so detection and extractor
dispatch both see it. Unknown names are left as they are.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 11:28:07 +01:00
Rudy CelekliandClaude Opus 5.5 bf3aac0e28 fix(detect): warn when a PDF cannot be read instead of returning "" silently (#4527)
extract_pdf_text wrapped the whole PdfReader loop in except Exception:
return "", so a malformed or encrypted PDF looked exactly like a PDF with
no text and dropped out of the graph with no trace. Keep returning ""
(callers rely on it), but print a warning naming the file and the error
first. Warn once per path, since detect() and the semantic pass both read
the same file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 11:28:05 +01:00