Commit Graph
2485 Commits
Author SHA1 Message Date
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 b19eefa8f0 docs(changelog): 0.9.94 — add the ObjC-id, hook-quoting, IPv6, ingest-symlink, and --help fixes
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-22 13:17:22 +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
Ziyu Dong dd45dfc2b0 fix(cli): list amp and every --platform value in graphify --help
The top-level help left out the `amp install` / `amp uninstall`
subcommands, and its `install --platform` list omitted copilot, kilo and
kimi, although all of them are accepted (`_PLATFORM_CONFIG`) and
documented in the README platform table. `graphify install` with a bad
platform already prints the full list, derived from `_PLATFORM_CONFIG`;
only the hand-written banner had drifted.

(cherry picked from commit 6480046271)
2026-10-22 13:14:03 +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
safishamsiandClaude Opus 4.8 c7f16f9426 docs(changelog): open 0.9.94 with the URL-security and extraction fixes
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-21 19:35:13 +01:00
safishamsiandClaude Opus 4.8 0e7cf1ec4b fix(js): narrow the require-binding guard to literal require callees (#4634)
#4634 suppressed the module-scope node for a require binding so it no
longer shadows the required class. But its guard used
_find_require_call(value), which matches ANY identifier(...) call, so it
also dropped real `const x = factory()` nodes (e.g. express(), NextAuth(),
wrapper()) and regressed #2604/#2552 (10 test failures). Add
_js_value_is_require, which name-checks the callee is literally `require`
the same way _require_imports_js already does, and gate the binding on it.

Bump _AST_CACHE_SCHEMA 20 -> 21: the require-binding change alters
extraction output (drops the shadow node, redirects the calls edge).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-21 19:34:27 +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
ayushcodes10 74bed49f46 Defer a JS/TS member call instead of guessing by name
A member call whose receiver is neither this nor self reached the
per file bare name map, the same way an unqualified call does. A
delegating wrapper like history.appendFrame(entry) inside a function
also named appendFrame then bound to itself, an EXTRACTED self loop,
and the same map could just as easily bind it to any other same
named function in the file instead of the receiver's own method.

Every such member call now defers, exactly like it already does for
Python, Java and C#. The deferred call still reaches the receiver
typed resolver, which binds it correctly when the receiver's type is
known, and otherwise leaves it unresolved rather than guessing. A
plain call with no receiver, and this dot calls resolved through the
caller's own class, are both untouched.

One case keeps resolving locally rather than through that resolver,
since the resolver only sees whole file state and could still bind
it to a same named member on an unrelated object: a method assigned
onto a local factory object calling a sibling through that very
object, such as store.get() from inside store.set. The receiver name
there reconstructs the caller's own owner id exactly, so it is read
straight off the owner rather than guessed.

(cherry picked from commit 24b1f28fd4)
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 822fbb04c2 fix(security): reject embedded URL credentials
(cherry picked from commit 2fac78e8ed)
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 53bfe14613 fix(security): reject malformed URL ports
(cherry picked from commit 55a4fbcddc)
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
Rishi Gupta 724a184bdd fix(security): reject HTTP URLs without a hostname
(cherry picked from commit 6f4782affa)
2026-10-21 19:32:11 +01:00
Syed Fahad 847a93f3ca docs: use discord.gg/graphify for the community invite
README, translations, support docs, and the GitHub templates were handing out a coded invite. It still joins the same server. Use the vanity URL.

(cherry picked from commit 7331198fc4)
2026-10-21 16:22:06 +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>
v0.9.93
2026-10-20 16:56:09 +01:00
safishamsiandClaude Opus 4.8 5942ff8281 chore: bump version to 0.9.93
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-20 15:56:54 +01:00
safishamsiandClaude Opus 4.8 2f4d9af25d docs(changelog): 0.9.93 — add the skill partial-read recovery (#3987)
Land #3987 (@nothariharan, reported by @Ryanm218): on the skill
extraction path a long file a subagent only skimmed was cached as
complete and its unread tail silently dropped on the next replace-on-
re-extract. The chunk coverage self-report is now checked against each
file's real line count; a short file is cached partial and left
unstamped so --update retries it, reusing the library's existing
partial_source_files contract (#933, #1950).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-20 15:43:03 +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
safishamsiandClaude Opus 4.8 b667aa9aa1 docs(changelog): 0.9.93 — add the query ambiguity-refusal fix (#4517)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-20 14:57:31 +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
safishamsiandClaude Opus 4.8 8379689697 docs(changelog): 0.9.93 — five more community fixes (detect, markdown, cli, serve)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-20 11:33:26 +01:00
safishamsiandClaude Opus 4.8 0d90ba83de build(cache): bump schema to 20 for the markdown wikilink-resolution change
#4561 changes extract_markdown's output (vault-relative wikilinks now resolve to
the real page), and markdown extraction is AST-cached, so a .md file cached under
schema 19 would serve stale unresolved links on re-extract until the file itself
changed. Bump the schema so the first build after upgrading re-resolves markdown
links. (The other batch-3 fixes -- cli, serve, PDF-warning, shebang detection --
do not change cached per-file AST output.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-20 11:29:25 +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
Deepjyoti Sarmah a31f6d83f2 fix: same-named files from two repos are not one file in explain and path (#4512)
The same-file check used the bare source_file, so web/dup.py and ml/dup.py read as one file, with bare line numbers and a hint that could not tell them apart.

A file is now its repo plus source_file. A repo is shown only when the candidates span more than one repo, so a graph with no repo attribute reads as before. When the path is shared across repos, the hint says to use the full node id.
2026-10-20 11:28:05 +01:00
berce ffaf5c820d fix(markdown): resolve wikilinks that start with the scan root's own folders (#4559)
Obsidian writes wikilink paths from the vault root, so scanning a folder
below it meets links like [[Corp/wiki/Fox]] from inside Corp/. The
vault-wide fallback matched them as suffixes of root-relative paths,
which never succeeds, and the references edge was dropped. When nothing
matches, drop the longest leading run of the scan root's own folders
and match the rest exactly from the root.
2026-10-20 11:28:04 +01:00
berce 5f4cccf0e7 fix(cli): leave installed skills alone on version and help (#4545)
_run_cli refreshed stale skills and old OpenCode plugins before the
--version/--help early returns, so checking what was installed rewrote
SKILL.md, left a .bak and restamped .graphify_version. Skip both
refreshes for version, help and `<cmd> --help`; the stale-skill warning
still runs, since it only reads. Free-text commands keep refreshing.
2026-10-20 11:28:03 +01:00
safishamsiandClaude Opus 4.8 848acdfcde docs(changelog): 0.9.93 — eight more community fixes (extraction, export, prs)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-20 11:00:22 +01:00
8b32528802 test(python): module-call exact-name tests, incl. a cross-module negative
Carries the tests that belong with the module-call exact-name fix (#4577): the
fix landed via the PR's follow-up perf commit, which did not include the test
file. Adds the PR author's five cases (exact-name function/class resolution,
aliased import, fold-only fail-closed) plus a negative cross-module case proving a
call never binds to a same-named child of an UNRELATED module -- the one headline
risk of re-bucketing by exact name.

Co-Authored-By: madhusudanpatnaik <madhusudanpatnaik@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-20 10:55:51 +01:00
safishamsiandClaude Opus 4.8 374adfadab fix(extract): also redact opaque Bearer tokens; guard against over-redaction
Follow-up to #4566. Adds an opaque (non-JWT) `Bearer <token>` shape to the
credential redactor so `Authorization: Bearer <opaque>` is redacted (the JWT form
was already caught by the eyJ pattern); the word "Bearer" is kept, only the token
is replaced. Adds an over-redaction regression test proving a 40-char git SHA, a
sha256 digest and a UUID pass through untouched, so the redactor never mangles the
ordinary high-entropy identifiers that appear in real rationale labels.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-20 10:54:35 +01:00
safishamsiandClaude Opus 4.8 3d20f14d38 build(cache): bump schema to 19 for the Dart/C++ extraction-output changes
This batch's Dart typedef (#4582), C++ Metal/CUDA qualifier (#4554, #4550) and
extern-"C" header-routing (#4526) fixes all change extraction output, so a repo
cached under schema 18 would be served stale output on re-extract. The Dart PR's
own 17->18 bump collided with the already-landed 0.9.93 schema-18 note and was
dropped; the C++ and extern-"C" PRs did not bump at all. One global bump to 19
covers the whole batch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-20 10:53:37 +01:00
rajatnagda45 e90a4b5dcf fix(cpp): don't mint sourceless god nodes for Metal/CUDA GPU qualifiers
Metal (.metal) and CUDA (.cu/.cuh) are C++-derived GPU languages that route
through the C++ extractor, but their qualifiers are not part of the C++ grammar:

  - Metal address-space / function qualifiers: `device`, `constant`,
    `threadgroup`, `thread`, `kernel`, `vertex`, `fragment`, ...
  - CUDA execution-space / memory qualifiers: `__global__`, `__device__`,
    `__host__`, `__shared__`, `__constant__`, `__forceinline__`, ...

tree-sitter-cpp parses a leading qualifier as the declaration's
`type_identifier` (the real type is pushed into an ERROR node), and
`_cpp_collect_type_refs` emitted it as a type reference. Each qualifier then
became a sourceless, corpus-shared god node that every shader/kernel
`references` (e.g. `kernel`/`device`/`constant` on sample.metal,
`__global__`/`__device__`/`__host__` on sample.cu, the CUDA ones colliding on
ids `global`/`device`/`host`). That is a provenance violation — a node with no
source_file — and the hubs also pollute god-node rankings in analyze.

Skip a fixed per-extension set of dialect qualifier keywords in the C++
type-ref collector, keyed by file extension so plain `.cpp`/`.cc`/`.h` files
are untouched (there these words are legal identifiers and still resolve as
real user types). Genuine user types in the shader/kernel (the `Vec3` struct)
are preserved.

Documented limitation: `constant Foo* p` buries the real `Foo` type inside the
ERROR node, so `Foo` is not recovered; this change only removes the bogus
qualifier node.
2026-10-20 10:53:16 +01:00
0xkaushik-aiandClaude Sonnet 5.5 44bd623a2e fix(extract): redact credential-shaped text from rationale labels
Docstrings and `# WHY:`/`# NOTE:` comments become rationale node labels,
which are persisted verbatim to graph.json, graph.html, GRAPH_REPORT.md and
the AST cache. A token pasted into a comment therefore ended up in the
artifacts the README tells users to commit and share.

Add security.redact_secrets() (shape-based: vendor-prefixed tokens, PEM
private-key headers, JWTs, URL passwords) and apply it in
_shorten_rationale_label(), before truncation so a cut can't leave a partial
token. That helper is shared by the Python and JS/TS rationale extractors.

Related to #2781 (document text sent to LLM backends unredacted); this
covers only the comment/docstring -> graph path.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-20 10:53:04 +01:00
madhusudanpatnaikandClaude Opus 5.5 ecf9a81c39 perf(dart): skip the typedef scans in files that declare none
The typedef step scanned every file twice (the `=` and legacy forms),
and the variable/method passes compiled-cache-looked-up the typedef guard
on every match, so a typedef-free test file ran ~3-10% slower than v8.
One substring test skips both scans when the file never says `typedef`,
and the guard is precompiled: bloc's 616 files now run at v8's speed or
better.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HrXx9K7CikYRNMqWg99y8E
2026-10-20 10:52:47 +01:00
madhusudanpatnaikandClaude Opus 5.5 c8d087ab16 perf(python): look exact names up in an index in the module-call arm
Filtering the children that share a folded key on every call made N
calls to N same-key names O(N^2) (2,000 case-variant calls: 1.7 s vs
0.8 s on v8). The exact-name index is built with the folded one, so the
lookup is O(1); pydantic's graph is identical to the scanning version.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HrXx9K7CikYRNMqWg99y8E
2026-10-20 10:51:29 +01:00
bekaandClaude Opus 5.5 3c1754591a fix(extract): route extern "C" headers to the C++ extractor
A C-API header guarded by `#ifdef __cplusplus extern "C" {` has no
class/namespace/template/:: marker, so `_is_cpp_header` left it on
extract_c. The C grammar rejects the linkage block and the header
extracts zero symbols (e.g. whisper.cpp's include/whisper.h and
include/parakeet.h), dropping the project's public API from the graph.

Add `extern "C"` to `_CPP_HEADER_MARKERS`. ObjC sniffing keeps priority.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 10:50:37 +01:00
Rudy CelekliandClaude Opus 5.5 ffa5a6b051 fix(export): export GraphML from a graph loaded as a MultiGraph
Since #4500, graph.json carries "multigraph": true whenever it holds
parallel edges (imports and calls between the same pair, say), and
graphify export loads it as a MultiGraph. to_graphml's attribute
coercion indexed edges as H.edges[u, v], which needs a key on a
MultiGraph, so graphify export graphml crashed with "not enough values to
unpack" and wrote nothing. Iterate edges(data=True) instead, which works
for both graph types and keeps every parallel edge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 10:50:37 +01:00
Rudy CelekliandClaude Opus 5.5 8d39c75d02 fix(prs): read StatusContext state when classifying CI
gh's statusCheckRollup mixes CheckRun entries (status/conclusion) with
StatusContext entries, the commit statuses external CI such as Jenkins or
CircleCI posts, which carry only a state. _parse_ci never read state, so a
failing external CI showed as NONE, or as SUCCESS beside a passing check
run, and the PR was classified READY instead of CI-FAIL in pr list,
triage and the MCP PR tools. Fold state into the conclusions, with ERROR
as a failure and PENDING/EXPECTED as pending.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 10:50:36 +01:00
Rudy CelekliandClaude Opus 5.5 8b06e7033c fix(prs): keep a default branch name that contains a slash
The git symbolic-ref fallback in _detect_default_branch kept only the last
path segment of refs/remotes/origin/<branch>, so a default branch named
release/2.x came back as 2.x and every PR targeting it was classified
WRONG-BASE. Strip the refs/remotes/origin/ prefix instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 10:50:35 +01:00
safishamsiandClaude Opus 4.8 9939411608 docs(changelog): 0.9.93 — seven community extraction and build fixes
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-20 10:40:39 +01:00
safishamsiandClaude Opus 4.8 56dbc3e391 build(cache): record the extractor-output changes in the schema-18 bump
The Kotlin typealias (#4553), C# delegate (#4552), and Swift macro (#4551) fixes
in this batch change extraction output (new node kinds), so a repo cached under
schema 17 would be served stale, stub-laden output on re-extract. The Rust fix
(#4567) already bumped _AST_CACHE_SCHEMA to 18; the schema is global, so that one
bump invalidates stale entries for the whole batch. This only extends the
schema-18 comment to document the three extractor changes it also covers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-20 10:36:00 +01:00