export const { auth, handlers, signIn, signOut } = NextAuth(config)
is NextAuth version five's own documented boilerplate, and
createNavigation from next intl follows the exact same shape. The
extractor collapsed the whole pattern into one combined node, id
joining every destructured name and label the literal pattern
syntax, so a single name import elsewhere could never find a node
named just auth and the edge, and any call resolved through it,
dangled. Each shorthand and renamed property in the pattern now
becomes its own node sharing the statement's line, exactly what the
export would have produced had it been written as N separate
statements. A renamed property exports under its key, since that is
what an importer binds by, never its local alias. A rest element or
a default valued entry does not correspond to one discrete exported
name and is skipped rather than guessed at.
The split only applies to an actually exported statement. An
unexported destructure of the identical shape, const doWork equals
require of lib being the common one, is a local import binding, not
a module export, and giving it a bare named node in the importing
file would collide with the real definition it only imports, turning
a clean resolution into a false ambiguous name. Caught this against
the existing suite before it ever reached a commit. Fixes#2604's
class 2.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit b1614da58a)
Confirms the reconstructed prefix set for an ASCII path is byte for
byte identical to what the old ASCII only regex produced, so the fix
changes behavior only for paths the old code was already getting
wrong.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit 97bd1f37f0)
Runs deduplicate_entities on the defining and referencing Korean
node pair in both arrival orders and asserts the defining file own
node survives either way, matching the concrete case the issue
reports: a page whose filename equals the concept name losing its own
node to a page that merely mentions it in passing.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit 852c61098f)
Confirms the layer above _id_prefixes actually benefits from the
fix: a node whose id and source_file both encode a Korean path is
recognized as defining that id, and a node sharing that id from a
different file is correctly recognized as merely referencing it.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit 7d15c1bb26)
Confirms the exact repro shape from the issue: the reconstructed
prefix set for a Korean file path preserves every segment instead of
collapsing them to nothing, matching what the file's own id is
actually minted as.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit ab4bde5413)
Fixes issue 3352.
_id_prefixes reimplemented id segment slugification with its own
ASCII only regex, so any path segment made of non Latin characters
(Korean, CJK, Cyrillic and so on) collapsed to nothing instead of
being preserved. _defines_id, the only real caller, uses these
prefixes to recognize when a node's own source_file is the file its
id encodes, and _collision_rank already implements the definer wins
rule this issue asks for. With the wrong prefixes, a node minted from
a non ASCII path never matched, so a genuine defining page lost the
definer wins tiebreak and the survivor fell through to plain arrival
order, exactly the symptom this issue reports.
Swapping to normalize_id, the same recipe every real extractor mints
an id with, makes the reconstructed prefixes match the ids that are
actually minted for such a file.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit e618f332f6)
Toward issue 3352: _id_prefixes reimplements id slugification with
its own ASCII only regex instead of the canonical normalize_id every
real extractor uses to mint an id, so it silently drops non Latin
characters. This adds the import with no behavior change yet; the
swap lands in the next commit.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit 237f02d135)
A bare require("mod") with no assignment - the dominant idiom in
Neovim configs - never reached the import handler: only
variable_declaration was in the Lua import dispatch set, so
require(...) used as a standalone statement produced no edge at
all. Detect the require call ahead of the generic import_types
check instead of widening it to function_call, which would have
swallowed every plain call and skipped the walker's recursion into
its arguments (e.g. a require() nested inside a callback passed to
vim.keymap.set).
(cherry picked from commit 2a208bbbb0)
A bare `require("mod")` statement with no assignment produces no imports
edge, while `local x = require("mod")` does. Cover the bare form in the
flattened and lua/config layouts, with and without parentheses, plus
guards for the callback recursion a fix must preserve and for method-style
calls that must not fabricate an edge.
(cherry picked from commit c87586c343)
Covers the same file case, the doubly nested repro shape from the
report itself, confirms the number 1740 named enclosing function
case is unaffected, and documents the direct import collision gap
called out in the fix commit as a pin rather than a silent
assumption.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit 7983186f2f)
Number 1740 fixed calls inside an anonymous callback by attributing
them to the enclosing named function, but that only works when a
named function actually encloses it. it("...", async () => { ... })
has no such enclosing function anywhere, and that shape hits test
files hardest since it is the idiomatic way to write one. The
original report already named the intended fallback, attribute to
the module node when no named function encloses the call, but that
half was never built. A topmost closure argument to a module level
call statement now gets its own function_bodies entry keyed by the
file node, mirroring the existing fix for this.X assignments found
in the exact same statement shape, so the ordinary call walking
machinery, including its own descent into further nested closures,
picks it up from there. Toward #3124.
Known gap left open, described in the new test module's own
docstring: a callee that is both directly imported by the same file
and only called from a module level anonymous closure still does not
resolve, because the cross file resolution pass dedupes purely by
source and target regardless of relation, so the file node's own
import edge to that target pre-empts the calls edge. Fixing that
dedup properly reaches far beyond this one case and is left for a
separate change.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit 8a0367c505)
An unset command hook defaults to Claude Code's 600 second timeout,
so a single wedged guard, a stuck filesystem or a hung subprocess,
stalls the surrounding tool call for ten minutes on Bash, Grep, Read
and Glob, the four highest frequency tools an agent uses. The guard
itself measures around 170 milliseconds warm, so both generated
entries now carry a 10 second timeout, generous headroom over the
measured cost while still two orders of magnitude below the unset
default. Toward #3314.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit fd08017d22)
TOKENS.md / tokens.md read as design-token reference docs, not credential
dumps, unlike singular "token.md" or another keyword's bare plural
("secrets.md"), which still exclude. The .dart/.ts design-token source
files from the original report are already covered by the existing
graphable-source exemption; this closes the remaining prose-note gap.
Fixes#3527
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A6anVY9hgp5ph5qLXRmc2p
(cherry picked from commit 64e85524ef)
- chinese extra: pin jieba-py from 3.12 (was 3.14); jieba's invalid escapes are
a hard error on 3.12+ and broke graphify.serve import.
- serve.py: suppress the tokenizer SyntaxWarning by category so -W error can't
escalate it; catch SyntaxError as a fallback.
- hooks.py: harden the #3492 rebuild-root guard for 3.13, whose resolve() no
longer raises on a symlink loop — require a real directory.
Verified full suite green on both 3.10 (5628) and 3.13 (5627).
The #3490 lock predated its own setuptools>=83 / pypdf / yt-dlp floor bumps, so
a frozen sync resolved setuptools 82.0.1 and the no-isolation wheel-build test
failed. Regenerate the lock so it honors the floors. Also update the windows
skill test: the troubleshooting item is now 'Skip Leiden' (graspologic ->
graspologic-native gating), which #3490 changed in the fragment but not the test.
The workspace `exports` resolver reduced a condition object to a single target,
so a package that splits its runtime source from its declarations never
produced a cross-package edge. Reported in #3487; the exports map itself is
present and works, the defect is in how one is chosen.
`_EXPORT_CONDITION_PRIORITY` listed `types` ahead of `require` and `default`.
`types` is declaration-only and normally points into `dist/`, which is build
output and not part of the corpus, so the resolver returned a path that is not
on disk and stopped there — `_package_entry_candidates` returned `[candidate]`
as soon as any condition matched, so the `default` source beside it was never
tried and the import became a dangling `ref_*` external.
That is not the order the map was added with. #1308 specifies
`source -> import -> module -> default -> require -> types`, and the commit
that implemented it says `default` is consulted LAST so a matching
`import`/`module`/`svelte` condition wins; the tuple shipped with `types` in
the middle instead.
Three changes, all on the workspace `exports` path:
- `types` moves to the end of `_EXPORT_CONDITION_PRIORITY`, restoring the
specified order. `require`/`default` keep their relative order, so the
behaviour pinned by `test_workspace_subpath_export_default_consulted_last`
is unchanged.
- `_resolve_export_targets` keeps every condition target in preference order
instead of returning the first, so a target that is not on disk falls
through to the next condition rather than taking the import down with it.
- `react-native` is honoured only for an importer that is itself native, and
each target is tried alongside its platform-suffixed siblings
(`Icon.tsx` -> `Icon.web.tsx` / `Icon.native.tsx`), which is how a bundler
reaches a platform split whose `exports` target names the unsuffixed path.
Importer platform comes from whole path segments, so `apps/mobile/src` is
native while `packages/web-utils` is not web.
`_resolve_export_target` keeps its signature and its semantics for the
`package.json` `imports` path, and platform expansion applies only to the
workspace `exports` path, so relative imports elsewhere are untouched.
Behaviour note for review: when every `exports` target escapes the package
directory, resolution still falls through to the bare-path fallback exactly as
before. The first revision of this change swallowed that fallback, which the
containment guard made reachable, and a test now pins it. The `"."` branch
additionally gains the containment guard its subpath sibling already had; that
is the only change here beyond the reported defect, and such a package
previously resolved outside its own directory.
Verified: same 29 pre-existing failures before and after, byte-identical
failure sets (`diff` clean; unrelated to this path — non-regular-file, watch
and uninstall-scope tests on Windows). Passing goes 5500 -> 5507, the seven
tests added here. The fixture from #3487 resolves its four previously-dropped
imports and leaves the three control cases unchanged; focused run 109 passed;
`ruff check` clean; all five `tools.skillgen` CI-parity checks OK.
(cherry picked from commit c704a5b2f2)
_js_top_level_function_bodies scanned only direct program children, so a
top-level `export function f(){}` / `export const g = () => {}` — wrapped in
an export_statement — was skipped and the calls inside it never became `uses`
facts. Only the use-fact path consults the per-file import-alias table, so an
aliased-import call (`import { bar as baz }; baz()`) produced no calls edge:
the plain-name global resolver has no `baz` to match, and the alias mapping
that would have pointed it at `bar` was never exercised. Unwrapping a
non-re-export export_statement to its inner function/arrow declaration treats
exported and non-exported functions identically, so the alias table resolves
the call to the real definition.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JJbLfztSxm2tH5cJwBbe9q
(cherry picked from commit 7d98266538)
detect() converts Office files and Google Workspace shortcuts into
markdown sidecars under a converted subdirectory of the output dir,
then ran the same scan ignore check on the sidecar's own path. The
documented gitignore advice puts the whole output dir inside a
gitignored tree, so that check rejected the tool's own output for
the same reason a user would gitignore it, and the source document
vanished from the corpus with nothing recorded about why. The check
exists to keep user files out of the scan, not to filter output this
same pass just produced from an already admitted source, so a
sidecar under the converted directory is now exempt from it. Fixes
#3504.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit 900b376d39)
graphify extract has printed this since #1692, but the update and
watch rebuild path never did, so a corpus with no extractor for its
language, Lean 4 in the reported case, rebuilt successfully with
those files completely absent and nothing said about it. Prints the
same wording from the extract command, and threads the count into
the local detection summary the rebuild path already builds for
GRAPH_REPORT.md, which previously dropped it. Fixes#3511.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit 120405673f)
detect() already returns every file it saw but could not classify, no
supported extension or shebang, but nothing read it: a corpus mostly
in an unsupported language got the same well covered verdict as one
that was actually extracted, silently. Adds an Unclassified line to
the Corpus Check section naming the count and the biggest offending
extensions. Toward #3511.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit f08e50534d)
Replacing a path with itself, if the initial os.replace call ever
fails for that call, crashed: the fallback backs dst up by renaming
it aside before landing the new content, and when src and dst are
the same path that rename moves src out from under itself, so the
final unlink of src then raised FileNotFoundError. Not reachable
through any current caller, every one passes a distinct temp file,
but cheap to guard against directly. Found by the graphify review
bot on PR 3509.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit 5b108e1a1f)
The fallback removed whatever sat at the destination and then
renamed the new content into place as two separate steps, so a
failure in the second step left nothing at that path at all, worse
than the original write it was trying to protect. Now renames the
existing destination aside as a backup instead of unlinking it, and
renames the backup straight back if the final rename fails, so a
mid swap failure restores the original rather than losing it. Found
by the graphify review bot on PR 3509.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit b56c77a107)
os.replace atomically swaps whatever sits at the destination, a
symlink included, by replacing the link itself. install.py relies on
exactly that for its managed skill symlinks, per #3286. The
fallback's plain shutil.copy2 does the opposite when the destination
is a symlink: opening it for writing follows the link and overwrites
whatever it points at instead of the link. Copies to a fresh temp
file beside the destination first, then swaps the destination out
from under it with an unlink plus rename, matching replace's own
behavior and, as a side effect, shrinking the window where a reader
could see a half written file down to two fast metadata operations
rather than however long the copy itself takes. Found by the graphify
review bot on PR 3509.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit 39effa0c28)
The GraphML writer replaced its temp file directly with no Windows
fallback of any kind, so a mid size export could hit the same class
of failure as the cache and install paths.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit ab2bde0956)
The AST extraction cache writes its own temp file and replace, a
separate code path from the shared atomic writer, and only caught
PermissionError. This was the exact write named in the report: worker
failed for a file with WinError 17 while replacing a cache entry
under the output directory's ast cache. Also covers the stat index
write, which had no Windows fallback at all.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit 7638d47565)
os.replace can fail on Windows even when the temp file and the
destination are in the same directory on the same drive: WinError
17, "cannot move to a different disk drive", maps to a plain OSError
in Python rather than PermissionError, so the existing copy then
delete fallback for WinError 5 and 32 never caught it. Adds
os_replace_with_fallback, checking both PermissionError and winerror
17, and points the shared atomic writer at it instead of its own
inline PermissionError only version. Toward #3508.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit 32cbf1f83e)
Path.resolve() raises RuntimeError, not OSError, when a symlink chain
loops back on itself, so the #3265 in repo check let that propagate
up instead of falling back to the repo top like every other bad
marker value. Since a fork or PR can commit an actual symlink loop
alongside a crafted .graphify_root value, an untrusted checkout could
still turn the guard itself into a failure path. Both rebuild bodies
now catch RuntimeError alongside OSError. Found by the graphify
review bot on PR 3492.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit 96d48c76b5)
Executes the shipped snippet directly (extracted from the rebuild
body text) rather than a hand copy that could quietly drift from it,
covering both the rejected case (a marker pointing outside the repo)
and the case the guard must not break (a subdirectory scoped root).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit 5976f7122a)
Team setup documents committing the graphify output directory, and
.graphify_root lives inside it, so its contents are checkout
controlled. Both generated git hooks read that file and pass its
value straight to _rebuild_code with no bound, so a value planted
there by a forked PR could point the rebuild, and therefore what it
reads and what it writes back into that same committed directory, at
a location outside the repository the hook runs in. Both rebuild
bodies now only adopt the saved root when it resolves inside the
working tree the hook is running from, falling back to the repo top
otherwise. Fixes#3265.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit 31aa4d4e9d)
A native Rust or C++ label containing "::" could be hijacked by the
new path::Symbol resolution when the substring before "::" happens to
match some unrelated file elsewhere in the graph, for example an
extensionless file literally named the same as a Rust module. The
query would then resolve to a node in that unrelated file instead of
the node the literal label actually names. Path scoped resolution now
only runs when the raw, unsplit label does not already have a literal
exact match among real node labels, since a genuine label essentially
never equals a whole path plus symbol string verbatim. Found by the
graphify review bot on PR 3491.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit 642f62a0e1)
The prior retry suggestion, a bare repo relative path, resolves to the
file node rather than the symbol, so following it never actually
disambiguates anything. Now that path::symbol exists, point both the
explain CLI and the shared MCP resolver at it, using a generic
placeholder rather than one of the rival paths so the message stays
the same regardless of node iteration order.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit c18f3d0a2c)
A bare symbol name that exists in multiple files is ambiguous, and the
only suggested retry (a bare repo relative path) resolves to the file
node itself, never the symbol, so it could not actually disambiguate
anything. This adds a path::Symbol form that restricts the label match
to nodes defined in that file, wired into _find_node_tiers ahead of
the ordinary tiers so it is available to explain, get_node and
get_neighbors alike. Fixes#3485.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit 84a2da2769)