mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-18 13:53:01 +08:00
a192df40f96cd3fee5bc86100e5555eeac9316a7
1327 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
a192df40f9 |
fix(telemetry): count late index uploads, treat usage as live ingest, purge before catch-up (#2333) (#2356)
Three bugs in the self-hosted telemetry services (telemetry-worker/ and
telemetry-dashboard/), reported against
|
||
|
|
6c61dd74ca |
fix(vbnet): link Shared fields and properties read through their class (#2305) (#2355)
VB.NET emitted a reference for calls only, so a value read or write through a type name -- `AppSession.SessionId`, `AppSession.CurrentUser = "demo"`, `Logger.Level` -- linked nothing, and `codegraph callers` on the field, the property and the class came back empty. The reporter's central session class showed 6 callers against ~460 referencing files. - Extraction (TS only; VB.NET is not kernel-routed): the static-member pass now covers VB.NET. A value read through a Capitalized name is sent as one `references` ref, `Name.Member`, the receiver kept as a call's is. Namespace roots (`System`, `Microsoft`, `My`, `Global`) and the built-in type keywords (`String.Empty`) are not sent. - Resolution (vbnet-receivers.ts matchVbMemberRead, ahead of the framework, import and name strategies): the receiver is looked up with #2351's VB.NET scoping, case-insensitively. A local, parameter, field, property or Module member of that name holds a value and links nothing; one typed as the type of its own name ("Color Color") reads that type. Otherwise the class, Module, Structure, Interface or Enum the namespaces, Imports and aliases around the read see is the type: the read links the member it declares or inherits (field, property, constant, event, Enum value, nested type; Shared before a same-named instance member, never the member the read is in) and, from outside the type, the type itself. A method named this way is a call made without parentheses (`calls`) unless AddressOf / NameOf only names it. - `AppSession.Items(0)`, an index into a Shared field that VB.NET writes as a call, now reads the field and the type instead of linking nothing. Contributor PR #2321 (@ChrisPrapas) had the right idea -- enable the static-member pass for VB.NET with a member-level ref -- and its test scenarios are adopted. Its resolution is replaced: it name-matched the receiver's bare name and checked a member only against the receiver's spelling. Ported onto main it passes its own tests, but binds a parameter `appSession As OtherSession` to AppSession's members, misses import aliases, and adds 8,652 / 13,554 edges on SCrawler / staxrip, about 2,000 per repo binding a receiver to another class's same-named member (a plugin form's local `Dim CONTAINER_MAIN` went to another form's control field). Verification: new vbnet-shared-member-refs.test.ts (7 tests) fails 6/7 on main and passes; the other VB.NET suites pass; tsc and build clean; the issue's repro lists Consumer.Run and Consumer2.Describe for SessionId, CurrentUser and AppSession. Validation, main vs this branch: nodes unchanged, 0 edges removed, no self-loops. SCrawler 14,893 -> 17,437 edges, staxrip 26,647 -> 32,568: Enum values 803 / 1,448, Shared fields and properties 363 / 1,459, the type read through 1,374 / 2,879, Shared functions called without parentheses 3 / 126, AddressOf / NameOf 1 / 9. Spot-checked against source: per-site SiteSettings, import aliases, escaped `[Date]` / `[New]`, nested enums, "Color Color" fields; designer `Point.Empty` and staxrip's AutoCrop duplicates stay unlinked. Index time within noise; value reads that link nothing are kept for re-resolution like failed calls (db 22.6 -> 28.9 MB, 28.3 -> 35.8 MB). Not covered: reads through Me / MyBase, unqualified reads, instance reads through a typed variable, Module variables as receivers, namespace-qualified receivers and With blocks. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: ChrisPrapas <129202926+ChrisPrapas@users.noreply.github.com> |
||
|
|
69ee3b9a85 |
fix(ui): fall back past a port Windows refuses with EACCES, and say why (#2299) (#2353)
On Windows, a port another program holds exclusively (SO_EXCLUSIVEADDRUSE)
or one inside an excluded port range fails listen() with EACCES rather than
EADDRINUSE. The `codegraph ui` port fallback only moved on after EADDRINUSE,
so it stopped at the first such port. The error then blamed the POSIX
privileged-port rule ("Ports below 1024 usually need elevated privileges")
for ports like 4747 or 49912. Confirmed on Windows 11: a .NET listener on
0.0.0.0:P with ExclusiveAddressUse makes Node's listen(P, '127.0.0.1') fail
with EACCES.
Fix:
- shouldTryNextPort moves past EADDRINUSE on every platform. It moves past
EACCES at any port on Windows, which has no privileged ports, and from
1024 up elsewhere.
- Below 1024 off Windows, EACCES is the privileged-port rule, and the next
port falls under it too. The walk stops there with the message it always
gave, rather than trying 20 privileged ports.
- describeBindFailure takes the platform:
- Windows names the exclusive hold or reserved range and how to list the
ranges (netsh int ipv4 show excludedportrange protocol=tcp).
- POSIX keeps the privileged-port text below 1024. Above it, it says the
system refused permission.
- A walk that ran out says "in use or reserved" (Windows) or "in use or
not allowed" when any port was refused rather than in use.
- Both helpers take the platform as a parameter, like browserOpenCommand,
so the tests check every platform's answer on any host.
This builds on PR #2319 by @sx4im, which advanced on EACCES, used the
Windows wording and added the issue's listen-spy test. That PR advanced on
EACCES on every platform and printed "Windows refused port N ... netsh" on
Linux and macOS too. On POSIX it would also walk 20 privileged ports and
end with "Ports 80-99 are all in use or reserved". This change restricts
both to where they are true. The changelog entry goes in the viewer-launch
file, because the viewer is not released yet.
Verification:
- __tests__/ui-server.test.ts gains three kinds of test:
- The issue's end-to-end test: port N is taken and N+1 is refused through
a listen spy, so the server must land past N+1 and serve 200.
- A pinned-port message test.
- Unit tests of the decision and the messages for win32, linux and darwin.
- Results on __tests__/ui-server.test.ts:
- Main source: 9 failed / 40 passed. The walk fails with "Not allowed to
listen on port 57423. Ports below 1024 usually need elevated privileges".
- With the fix: 49 passed / 2 skipped (existing POSIX-gated tests).
- Built CLI on Windows, with 0.0.0.0:<port> held exclusively:
- `codegraph ui` while 4747 is held: main exits 1 with the privileged-port
message. With the fix it serves on 4748 (GET / 200).
- `codegraph ui --port <held>`: prints the Windows message.
- A 3-port walk over held ports reports "in use or reserved".
- Re-listening on the same server after a real EACCES works on Node 22.22
and 24.16.
- tsc --noEmit is clean and npm run build succeeds. The 20 viewer test
files pass (439 passed, 5 skipped).
- Full suite: 5732 passed, 10 failed, 319 skipped. The 10 failures are
timeouts and EBUSY teardown under load in function-ref,
git-index-currency, index-daemon-rebuild and rust-self-owner. All four
files pass when run alone.
Issue #2299 reported by @ijbranch.
Co-authored-by: Saim Shafique <msaimshafique4664@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
26e8488aed |
fix(windows): the kernel-parity sweep loads the engine as file:// URLs (#2352)
scripts/kernel-parity.mjs handed import() a path from path.join(). On Windows that is `C:\...`, which Node's ESM loader rejects with ERR_UNSUPPORTED_ESM_URL_SCHEME, so the sweep could not run there at all and contributors kept patched scratch copies. The three engine imports now go through pathToFileURL(...).href, as the agent-eval probe scripts already do. On macOS and Linux an ordinary path resolves to the same file:// URL as before. Checked on Windows 11 after npm ci, npm run build and scripts/build-kernel.sh: the unmodified script threw at its first import; with the fix, the C# fixtures (3/3), src/search (4/4), all of src/ as TypeScript (261/263, 2 deferred to wasm) and every fixture language (42/42) run to the summary line and exit 0. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c9944147bb |
fix(vbnet): resolve receiver calls by declared type; guard .NET names, bare calls and ties (#2351)
VB.NET's resolver guessed a receiver call's target from the receiver's name whenever no class matched it, without any of the guards C# has. Indexing Structure members (#2345) gave those guesses new targets: 23 SCrawler `xxxFile.Delete()` calls on PersonalUtilities' SFile went to a nested struct's `TempFileConversion::Delete`, and staxrip's main app sent 90 calls to its AutoCrop tool's duplicate `ColorHSL`. - Declared types (new src/resolution/vbnet-receivers.ts): a receiver's `As` type decides the call (local, parameter, field, property, `For Each`, `As New`, ...). The candidates are that type's member, one it inherits, or an extension method written for it. A type from outside the project gets no link. - .NET standard method names (`Add`, `Contains`, `Clear`, `Dispose`, `ToString`, ...) on an untyped receiver need a receiver named after the owner, as in C#. VB.NET matches them case-insensitively. - A bare call, or one on `Me`, reaches the enclosing class, what it inherits, or a Module. A nested type is matched by its bare name only from inside its owner. Types resolve through enclosing namespaces, `Imports` (aliases included) and the caller's project. A tie between equally good guesses goes to the caller's file, then its project, then the nearer directory, or gets no link. - vbReceiverOf reads the name at or after the reference's column, so `Me.Size = New System.Drawing.Size(...)` is read through `System.Drawing`, not `Me`. Before/after on main (nodes unchanged): SCrawler 15,092 -> 14,893 edges, staxrip 28,002 -> 26,647. Of the non-containment edges removed, 855 / 2,802 are the same link with new resolver metadata. The rest: - .NET names on untyped receivers no longer guessed: 196 / 1,378. 731 of staxrip's are StringBuilder `sb.Append` calls that had gone to `LogBuilder::Append`. - Other receiver guesses dropped: 86 / 103, including the SFile.Delete calls. - Bare calls out of scope: 38 / 834. 590 of staxrip's are `New Point(...)` that had gone to a class nested in `ButtonEx`; others are WinForms `Refresh()`, `Focus()` and `Activate()`. - Retargeted: 227 / 532, e.g. 208 bare `Add(...)` calls in encoder classes now reach the inherited `CommandLineParams::Add`. 121 / 958 call sites resolve that did not before, e.g. `cms.Add` -> ContextMenuStripEx and `td.AddButton` -> TaskDialog. Designer -> Size links fall from 5 / 1 to 0; designer -> Add stays 0. Known trade-off, shared with C#'s guard: about 31 staxrip calls like `switches.Join(BR)` that may be the project's own `MiscExtensions` extension methods are no longer linked. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
355afc70b1 |
fix(csharp): walk field and property initializers; a constant never names a type (#2337) (#2348)
* fix(csharp): walk field and property initializers as the member Field declarators and property `= initializer` values were skipped by both extractors, so `private readonly ILogger _log = LogManager.GetLogger(typeof(X));` linked nothing, and a method or class used only from an initializer looked unused. - tree-sitter.ts / csharp.rs: walk each C# field declarator whole with its field on the stack, and every property `value:` (not only `=> expr`) with the property on the stack. Calls, instantiations, static reads, lambda bodies and fn-ref candidates (varinit included) belong to the member; the fn-ref scan skips the walked subtrees, so each candidate is captured once. Attributes stay unwalked. - A target-typed `new()` that is the initializer instantiates the declared type (`List<Foo> _items = new();` -> List). In bodies it stays invisible, as before. - name-matcher.ts: a .NET type position never names a `constant` (the kind `const` / `static readonly` fields get). Walking initializers exposed `new Version(5, 18)` binding to a `const string Version` and `static readonly Meter Meter = new(...)` instantiating itself. Kernel/wasm parity: 0 diffs on serilog, Newtonsoft.Json and jellyfin, and full-index dumps byte-identical between the two arms. No EXTRACTION_VERSION bump: #2345 already moved it to 28 this release. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(changelog): credit the report and contributor behind the constant change The type-position change here is what issue #2337 reports (`new Station { … }` unresolved beside a `private const string Station`) and the same one-line change as @drakeo338's #2339. Verified on the issue's own three-file repro: the `instantiates` edge to `Demo.Core::Station`, missing on main, is present with this branch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
0c7d0f7d42 |
perf(resolution): remove the Python indexing regressions since 1.6.2 — graph byte-identical (#2332) (#2344)
Indexing CPython took about three times as long on 1.6.2 as at |
||
|
|
dea076fd1e |
fix(mcp,extraction): exit a daemon whose install changed; never store a grammar-load failure (#2335, #2336) (#2346)
* fix(mcp,extraction): a daemon whose install changed exits; grammar-load failures are never stored (#2335, #2336) A daemon that outlived an upgrade loaded grammars lazily from an install that no longer existed, and stored every file its watcher re-indexed as a zero-node row under the file's new content hash: no hash-based sync ever revisited those rows, and `codegraph status` called the index up to date. - The daemon checks its own package.json every 30 s (CODEGRAPH_DAEMON_INSTALL_CHECK_MS, 0 turns it off) and exits once the file is gone or carries another version, so the next session starts a daemon from the current install. - A result whose grammar failed to load (`parser_error`) is never stored: the file keeps its previous data and the next sync or index retries it, and a row an older engine stored that way is re-indexed even though its hash matches. The tag-based CFML path reports a missing grammar with the same code. - A launcher whose own install was deleted no longer crashes on the spawn error of the daemon it tried to start. - `codegraph status` and `status --json` (filesNeedingReindex, filesWithParseErrors) report indexed files whose symbols are missing although their content is current (#2336), and `files --json` carries each file's recorded errors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(mcp): a session whose own install changed never claims the writer lock (#2335) A daemon now exits once its install is upgraded or removed, and nobody holds the writer slot for a successor at that point. Its sessions run the same replaced code and serve themselves in-process the moment it goes; one that found the slot free claimed it as its writer even though its engine could not load (a real npm upgrade: "Cannot find module './sqlite-adapter'"), and so kept a daemon from the current install from starting. Such a session now falls back read-only and says to restart it. Also adds the CHANGELOG entries for the install check and grammar-load failures (#2335) and for the status report (#2336). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
511d86e94d |
fix(mcp): replace a daemon left running by an older install (#2335) (#2343)
A daemon keeps running the code it started with. The 1.6.1 daemon in #2335 outlived an upgrade, kept loading grammars from the install the upgrade removed and wrote every file it re-indexed as empty, while every session from the new install served itself in-process, read-only, because the old daemon held the writer lock. Daemons from before this change cannot notice an upgrade, so the launcher acts: - A launcher that finds a daemon of an older plain X.Y.Z release stops it the way `codegraph daemon` does (daemon.pid and the socket hello must agree; SIGTERM, SIGKILL only if it still answers) and starts one from its own install. It finds the daemon by its hello on the launcher's own socket, or by the project lock when it listens elsewhere: on Windows, daemons before #2278 named their pipe after the root as typed. A daemon of the same, a newer, a prerelease or an unknown version is never touched, so two installs cannot take turns stopping each other's. - The writer slot never falls free on the way. The launcher swaps writer.pid to itself (mode `handover`) before the signal, clears the old daemon's leftovers while it holds the slot, and the daemon it spawns takes the slot over (CODEGRAPH_DAEMON_HANDOVER). Sessions of the old daemon that fall back in-process find the slot held and serve reads only, instead of claiming it with the removed install's code. A daemon that does not exit gets the slot back, and a successor outwaits a racing candidate that took daemon.pid first. Validated on Windows with real npm upgrades from the published 1.6.1 and 1.6.2 to this build packed as 1.6.3, with the old session calling throughout. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
489b4749c7 |
fix(vbnet,csharp): index every Structure member; walk property bodies and VB initializers (#2345)
VB.NET's grammar tags each member of a `Structure` as its own `body`
field, so extractAggregate's field lookup saw only the first member.
SCrawler's UserMedia indexed none of its members, and a structure that
opened with a nested enum indexed only the enum. Aggregates now resolve
their body the way classes and enums do (resolveBody, which VB maps to
the declaration itself). Objective-C, the only other struct language
with that hook, is unchanged.
Property bodies were never walked: the propertyTypes branch set
skipChildren after extractProperty, so calls, instantiations and reads
in a VB `Get`/`Set` block, `= initializer` or `As New`, and in a C#
accessor body or `=> expr`, were lost.
- propertyBodies() names the parts of a property that run code (VB: the
whole declaration, as its methods are walked; C#: each accessor's body
and an arrow value), walked with the property on the stack. The
candidates-only fn-ref scan skips the walked subtrees by node id, so a
function value is captured once, from the property. C# `= initializer`s
stay unwalked, as C# field initializers are.
- The C# kernel mirrors it (property_bodies, scan skip list). The parity
suite and a new micro pass; a serilog sweep is 209/214 byte-identical
with 0 diffs (5 deferred for parse errors).
- VB field declarators (`= expr`, `As New T`) and Custom Event
AddHandler/RemoveHandler/RaiseEvent blocks are walked as their field or
event.
Before/after (nodes / edges): SCrawler 9,950 -> 10,427 / 14,336 -> 15,092;
staxrip 13,440 -> 13,644 / 24,120 -> 28,002, most of it its
`Property X As New NumParam With {...}` settings; serilog edges
6,642 -> 6,654. All 23 removed edges are the same call sites re-resolved
by VB's name-guessing resolver now that struct members are candidates:
10 wrong edges dropped, 9 correct ones now guessed wrong, 4 wrong either
way. Designer -> Add/Size edge counts are unchanged.
EXTRACTION_VERSION 27 -> 28.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
6560052a6f |
docs(changelog): promote [Unreleased] into [1.6.2]
[skip ci] Auto-generated by Release workflow.v1.6.2 |
||
|
|
0f0af7bf22 |
release: 1.6.2 (#2318)
Bumps package.json and ui/package.json (the component library is versioned with the engine; ui-package.test.ts pins it) to 1.6.2, and the matching version fields in package-lock.json — the top level, packages[""] and the ui workspace, whose entry was still at 1.6.0. Only those three fields change; `npm ci` accepts the lock. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
217190680d |
fix(telemetry): recover from the D1 10 GB outage; dashboard starts on today (#2317)
The telemetry database hit D1's 10 GB cap on 2026-08-11 and refused nearly every write for seven weeks: the dashboard kept showing Aug 9 as the latest day, and its activation query (a cohort join over raw events, ~55 s per week) stalled D1's single query lane until every panel failed. Root cause was the client: usage counters were aggregated per process and appended on exit, so every `serve` launch, CLI command and prompt hook uploaded its own `count: 1` line - 30.3M of the 30.8M event rows. - Client: merge count lines per (day, kind, name, client) on append, stale-claim recovery (which also bypassed the size cap) and send. - Ingest: usage counters ADD into usage_daily, one row per machine x day x tool (migration 0003), so storage no longer depends on upload frequency. - Rollup: reads usage from usage_daily, folds legacy usage rows out of events in 50k-row transactions, maintains machine_first_seen.first_index_day (0002), catches up on days a failed run missed, purges usage_daily with retention. 0004 drops the now-unused events_machine_day index. - Dashboard: ranges end today; rolled-up series return null past the rollup's last day instead of a phantom zero; no panel reads raw events (activation reads first_index_day); min/max lookups stay index-only; one retry on transient 5xx; banners when ingest stalls or the rollup falls behind. - scripts/backfill-rollup.sh re-runs the rollup over a day range. Already applied in production: DB 10 GB -> 467 MB, all usage counts kept, rollups current through 2026-10-02. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
dbb3d63cb1 |
fix(mcp): wait for a stopping daemon, and for the proxy's fallback engine (#2311) (#2316)
* fix(mcp): wait for a stopping daemon, and for the proxy's fallback engine (#2311) Now that QueryPool.destroy() waits up to 15 s for a query worker still starting up (#2307), two callers gave up too early: - stopDaemonAt() gave a daemon 3 s after SIGTERM, then re-probed its identity and reported it still running once the daemon had closed its socket, so a daemon waiting on a starting worker made `codegraph index` abort. A daemon that has closed its socket but still holds its lock is now given 17 s more to exit; nothing is signalled during that wait. - The proxy's in-process fallback called engine.stop() and exited on the next line. It now exits once its engine, and any engine still being retired after a handback to the daemon, has stopped, with a 20 s backstop. Once shutdown starts it ignores client input and starts no new engine. Extracted from bompus/codegraph@cc87ec34da, a fork merge commit that cannot be cherry-picked whole; only these four files are taken, unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(mcp): a stopping daemon is waited for whether it closed its socket or released its lock (#2311) Builds on the previous commit: - stopDaemonAt: one rule after the first 3 s. If the daemon's identity can be re-proven (same lock, socket answers), escalate to SIGKILL as before. Otherwise, whether it has stopped answering its socket or already let go of its lock, it is partway through its own shutdown: signal nothing more and wait, bounded, for the PID to exit. The previous commit reported a daemon that had released its lock but not yet exited as still running, a race at the end of every slow shutdown. The cleanup that follows compares the lock with the one signalled, so a successor's is kept. On Windows, where SIGTERM is TerminateProcess, the same wait covers a termination still settling. - The grace is WORKER_START_SETTLE_MS + 2 s and the proxy's backstop WORKER_START_SETTLE_MS + 5 s, so they move with the settle wait they cover. stopDaemonAt takes `shutdownGraceMs` so a test can bound it. - The proxy says so on stderr when its backstop fires. Tests: the daemon fixture's setup and cleanup are shared, and the fixture process is always killed on cleanup (a failing run used to leak it). New: a daemon that released its lock mid-shutdown is waited for; a daemon that never finishes stopping is still reported after the grace and never SIGKILLed; the proxy waits for an engine being retired at shutdown; and client input arriving once shutdown has begun starts no engine. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Aaron Queen <bompus@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
ad60dccd04 |
test(windows): the windowsHide guard fails closed on spawns it can't trace (#2312) (#2315)
the watcher's `git config --get core.excludesFile` call. That report is against 1.6.1; the call (and the `ls-files --exclude-standard` one next to it) already runs with windowsHide on main since #2280, which has not been released yet. An audit of every child_process call in src/ found nothing else missing it: 31 call sites, 29 hidden, 2 documented exceptions (macOS-only vm_stat; the detached browser opener). What #2280's AST guard could not see is how the next regression would slip in. It only followed static named/namespace imports and passed silently on anything else, so each of these shipped green with no windowsHide: - child_process loaded by a default import, `import = require`, `require()`, `import()`, or a re-export - a spawner aliased or passed as a value (`const run = execFileSync`, `promisify(execFile)`, `const { spawn } = cp`) - options in a `let`, or a later spread that may set windowsHide back - an options parameter of an exported wrapper (callers in other files), a wrapper used as a callback, or a wrapper call that omits the options The guard now fails closed on all of them, and a second describe pins each shape (plus the compliant ones it must keep passing) so the guard itself can't go blind again. Also credit #2312 on the existing changelog entry and drop the duplicate copy of that entry #2280 left under [Unreleased]. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
2965b7a349 |
docs(changelog): ready [Unreleased] for 1.6.2; release verify waits up to 5 min (#2314)
- Adds the Highlights block and groups the 164 entries the way 1.6.1's notes are: New Features for the newly supported routes and newly indexed code, Fixes under sub-headings (Windows; the background server and CLI; indexing; across languages; then one per language family) so a skimmer finds their area. Duplicates left by merges are folded together (the Windows console window entry twice; two overlapping Vapor closure-route entries; three Windows crash entries into one). No entry's wording is otherwise changed. - The release workflow's "Verify every package is actually on the registry" gave each package 60 s. 1.6.1 published all seven packages fine, but the first took longer than that to appear and the run went red. It now waits up to 5 minutes per package. No viewer entries: those stay in docs/viewer-launch-changelog.md, and `codegraph ui` stays behind CODEGRAPH_UI=1 (checked on a fresh build: absent from --help, refused with the "not in this release yet" message). Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
70fe0ecc52 |
fix(windows): helper threads never end mid-start or with GC marking in flight (#2309)
On Windows a worker thread that ends - terminate(), its own process.exit(), or the process exiting - while V8's concurrent marker is marking its heap crashes the whole process (exit 3221225477 / 0xC0000005, no error, no dump). Bisected on the Windows 11 ARM64 VM: --no-concurrent-marking alone stops it (18/480 -> 0/480; no other concurrency flag does), but it costs about a third of indexing speed, so CodeGraph avoids the dangerous moments instead: - src/worker-teardown.ts: owners terminate a worker only once it has started (its first message, posted after its modules load - mid-load is by far the worst moment), and a worker about to exit runs a full collection first, which finishes any marking in flight (gc reached at runtime, never global). - Resolver pool / store writer: the worker collects before exiting on 'close' (the normal path - main 21/480 crashed on the VM, now 2/1200), and the 5 s close fallback never terminates a worker still starting. - codegraph_status change counts: the worker posts 'loaded' after its heavy require and collects before answering; a deadline answers unknown at once but terminates only once loaded (3/720 -> 0/1440), and MCPEngine.stop() waits for measurements in flight. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
3d86bcc037 |
fix(windows): the query pool never terminates a worker still starting up (#2307)
Terminating a worker thread while it is still loading its modules can crash the whole process on Windows (exit 3221225477 / 0xC0000005, no error, no dump). QueryPool.destroy() terminated every worker at once, and the daemon destroys its pool whenever it stops - a session that ends within a few seconds of starting had its eager worker in exactly that state. MCPEngine.stop() didn't even wait for the pool before the daemon called process.exit(). - QueryPool tracks each worker's start (settled on 'ready', error or exit); destroy() answers outstanding calls first, then terminates each worker once it has started, capped at 15 s - the parse pool's #2301 mechanism. - MCPEngine.stop() awaits the pool. - #2301's explanation is corrected: on the Windows VM, terminating workers mid grammar compile never crashed (0 in 2,161), but terminating them while they load CodeGraph's extraction modules did. The parse pool's wait already covers that window, so only its comment and changelog wording change. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
9d4e805bec |
test(windows): daemon-pid-reuse cleanup retries a still-held temp dir (#2304)
afterEach SIGKILLs the servers and the detached daemon, waits 50 ms and removes the temp dir. A killed process releases its handles asynchronously on Windows, and under full-suite load the removal hit EPERM on the VM (the test itself passed; it passes 3/3 alone). Retry the removal like the other process-spawning suites do. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
e0f9c95e2f |
fix(resolution): a busy machine doesn't boot the resolver pool for a small project (#2303)
The resolve loop boots the worker pool mid-run when the remaining refs' projected sequential settle (observed per-ref rate x refs left) clears 400 ms. Load inflates the observed rate, so on a loaded 4-core Windows VM a ten-file project (19 refs) projected 506 ms for its last 11 and booted three workers in 122 of 240 runs under 24 parallel inits (0 of 80 at 8). The pool only fans out batches of 1,000 refs, so it resolved nothing; synthesis then waited out its boot (dedupe-merge 6.3 s vs a 6.5 s boot) and its three extra database connections pushed the VM's free commit to 11 MB. Adaptive engagement now also needs 20,000 refs left. tokio-class Rust repos (~55k refs at ~36 us) still engage; a smaller remainder never repays a seconds-long boot. With the floor, the same load booted the pool in 0 of 240 runs. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
91b9bba133 |
fix(extraction): never terminate a parse worker while it is loading grammars (#2301)
On a loaded Windows machine `codegraph init` / `index` sometimes died right after the parse phase with 0xC0000005 (STATUS_ACCESS_VIOLATION), no WER report and no output past the last parsed file. The parse pool's destroy() terminated every worker at once, and a short index often ends while a late-spawned worker is still compiling its grammar WASM; terminating a worker with that compile in flight crashes the process. Reproduced on a Windows 11 ARM64 VM without CodeGraph's pipeline: starting three parse workers with preloaded grammar bytes and terminating them mid-load crashed 5 of 720 child processes, terminating them only after grammars-loaded crashed 0 of 360. Under the real load (24 parallel `codegraph init` runs on 4 cores) main crashed after parsing in 8 of 1,200 runs; with this change 0 of 1,080 did. destroy() now waits for a worker that is still loading to report grammars-loaded or exit before terminating it, capped at 15 s so a wedged load can't hold teardown forever. Recycling only ever touches idle workers, and a hung parse is still killed by the hard timeout. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
8abebda02c |
test(windows): two #1910 tests no longer time out on Windows (#2298)
oversize-file-not-read's drift test wrote a 1.4 MB `.js` made of one statement repeated 110k times. Windows Defender's script scan of that freshly written file takes about 54 s on its first read (the viewer's read in the test), so the test always timed out on Windows and its cleanup then hit EPERM on the still open file. A varied 2.4 MB `.js` and a real 2 MB bundle scan in 35 ms and 144 ms, so this is the fixture, not something users hit; the fixture now has a different statement on every line. The mpeg-ts git-path cases build a git repository and index it, the last one twice. At ~100 ms per git process on Windows that is 5–6 s, past vitest's 5 s default; the describe now allows 30 s. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
273e5cdc4c |
fix(upgrade): a Windows upgrade no longer breaks the install while CodeGraph is running (#2185) (#2294)
* fix(upgrade): a Windows upgrade no longer breaks the install while CodeGraph is running (#2185) On Windows, `codegraph upgrade` renamed only node.exe aside and then Copy-Item'd the new bundle over current\. Any open agent session runs a CodeGraph MCP server from current\, which keeps the native kernel loaded, so the copy failed on lib\kernel\codegraph-kernel.node halfway through, leaving no node.exe and a mix of versions that no codegraph command could repair. Re-running install.ps1 failed the same way, because it Remove-Items current\. Both now share one PowerShell function (a test pins the two copies equal). The bundle is unpacked next to current\ (same volume), then swapped in file by file: every file being replaced or dropped is first renamed aside (Windows allows renaming a running exe or a loaded DLL, just not overwriting or deleting it), then the staged file is moved in. Any failure or interruption undoes every step. Renamed-aside files, including the node.exe.old-* left by earlier upgrades, are deleted on the next successful run once nothing holds them. The upgrade's exit code now tells whether the install was left unchanged or needs a reinstall, and the message says which. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(changelog): Windows upgrade with CodeGraph running (#2185) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
7bbc1fb7f6 |
fix(tests): the test suite no longer writes to the developer's real home dir (#2275) (#2295)
Running the suite on a machine with Claude Code configured could wire the CodeGraph prompt hook into the developer's own ~/.claude/settings.json: every upgrade test whose fake upgrade succeeded ran the real post-upgrade prompt-hook self-heal, which detected the real global Claude profile and wrote it. Daemon suites likewise left records in the real ~/.codegraph/daemons. On Windows a HOME-only redirect would not have helped, since os.homedir() reads USERPROFILE there. - The prompt-hook writer is now injected through UpgradeDeps (wirePromptHook); the CLI passes the real defaultWirePromptHook and the upgrade tests a recorder. New tests pin that a successful upgrade calls the recorder and that the real writer never touches a configured profile. - A vitest setupFiles entry gives every engine test file a throwaway home: HOME/USERPROFILE (plus HOMEDRIVE/HOMEPATH/APPDATA/LOCALAPPDATA on Windows), XDG_CONFIG_HOME, and GIT_CONFIG_GLOBAL (seeded with a dummy identity) point into it, and CLAUDE_CONFIG_DIR/CODEX_HOME/COPILOT_HOME/ HERMES_HOME are cleared. Spawned CLI/MCP children inherit it. A sandbox test asserts os.homedir(), the Claude profile, the daemon registry, a child process and git's global config all resolve inside it. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
e7389b22fb |
fix(mcp): a session serving in-process hands the project back to the shared daemon (#2277) (#2293)
* fix(mcp): a session serving in-process hands the project back to the shared daemon (#2277) When a proxy lost its daemon (or never reached one) it served the rest of the session through an in-process engine. That engine takes the project's writer lock in fallback mode and nothing ever released it or tried the daemon again, so every later daemon start for the project failed on the held lock and every other session ran read-only without auto-sync until that one session ended. Sessions that fell back read-only stayed read-only for life too. The degraded proxy now retries the shared daemon with backoff (5 s, doubling to a 5 min cap; CODEGRAPH_DAEMON_RETRY_MS / _MAX_MS, 0 disables) through the existing probe/spawn/poll path. A writing engine is stopped first, after the calls it is serving have answered, so its lock is free before a daemon is spawned and two writers never overlap; calls arriving meanwhile are buffered and go to the daemon, or are served in-process again if none comes up. A read-only engine keeps serving until a daemon answers and is closed once its calls finish. The client's initialize is replayed to the new daemon with its reply suppressed, as on first attach. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(changelog): a degraded session hands the project back (#2277) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
45a680df07 |
fix(mcp): one daemon per project however its root is cased on Windows (#2278) (#2285)
* fix(mcp): canonicalize the daemon rendezvous root on Windows
A project spelled d:\work\codegraph or D:\work\codegraph is one directory, but the daemon socket/pipe name and the registry record name hashed a raw path.resolve, which preserves whichever casing the caller passed. A proxy that computed the other casing probed a socket nobody bound, spawned a redundant daemon, and that daemon exited on the lock the running one held, so the session silently served the graph in-process: no shared watcher, no auto-sync, one extra copy of the index per open window.
Both names now come from one shared canonicalProjectRoot (native realpath, lowercased on win32), so every spelling meets the same daemon and codegraph list / stop --all see one record per project.
(cherry picked from commit
|
||
|
|
7c01910e54 |
fix(cfml): tag-based calls, wrapped functions and <cfinterface> are indexed (#2091) (#2296)
* fix(cfml): tag-based calls, wrapped functions and <cfinterface> are indexed (#2091) Tag-based CFML lost almost all of its call edges. The tag walk sent only <cfscript> and <cfquery> bodies through call extraction, although the cfml grammar parses the expressions in <cfset>, <cfif>/<cfelseif>, <cfreturn> and #hash# (output, strings, attributes) with the same rules as cfscript. The walk now records each such expression with the scope that owns it (the enclosing <cffunction>, else the component, else the file) and reads them all through the existing cfscript extraction in one parse per file: the expressions are copied out on their original lines, each ended with a `;`, and every reference is handed back to its expression's scope with its column restored. <cfloop condition="..."> is read the same way. A <cffunction> nested in a generic container tag at component or template scope (<cfprocessingdirective> in Application.cfc, <cfsilent>) was skipped because only direct children were examined; it is now extracted (never through an ERROR node, where a lost <cfcomponent> would misfile methods as top-level functions). <cfinterface>, a generic tag to the grammar, now yields an interface node with its functions as methods and an `extends` ref per comma-separated parent, so `implements` resolves to it. The tag-based parse tree is now freed after the walk. Resolution: the CFML/Ruby/C# bare-call scope filters evaluated the receiver-on-line check once per same-named candidate though only the ref decides it. With tag calls extracted, that made Mach-II's resolution ~8x slower; the check is now memoized per ref (edges byte-identical). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(changelog): tag-based CFML calls (#2091) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
2ff7a99c0e |
fix(cpp): bound the macro-visibility include walk (#2127) (#2292)
* fix(cpp): bound the macro-visibility include walk (#2127)
isVisibleCppMacro walks the call site's translation unit to learn whether a
function-like macro is visible there (#1838). A header whose guard the walk
cannot decide -- `#define X_H 1` (the OpenSceneGraph / osgEarth idiom, which
guardsItself does not read as a guard), or any header reached under an
unknown `#if`, where neither its guard nor `#pragma once` settles -- was re-scanned on
every inclusion path, and every re-scan pushed its define events onto the
cached timeline again. On a layered include graph that is 2^depth scans and
2^depth timeline entries: "Resolving refs" died with "Ineffective
mark-compacts near heap limit" at the first ref whose file reached such a
tree, at the same ref whatever the heap size.
The walk now keeps a version that every observable change bumps
(definitions, `#pragma once`, the macro-name set). A re-entry with the same
inherited truth, while the version still equals that of an earlier visit
which itself changed nothing, is skipped: it would start from the same state,
take the same branches and change nothing again, and the events it would push
repeat each macro's current state, already its last event. Include cycles cut
by the recursion stack are recorded per visit and must still be cut for a
skip. A hard budget of 1M directive events per walk bounds what is left;
past it the timeline is unknown and nothing at or after the cut line is
suppressed, so the budget can only fall back to ordinary resolution, never
invent a macro.
Answers are unchanged: 20,000 random include graphs (guards bare / valued /
pragma once / under flags, #if/#elif/#else, #undef, cycles) give identical
isVisibleCppMacro results to the previous walker on 1.25M queries, with
about one re-entry in eight skipped.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit
|
||
|
|
f838c5a02a |
fix(resolution): resolve method values on untyped Python module globals (#2074) (#2291)
* fix(resolution): resolve method values on untyped Python module globals (#1820) A method value whose receiver is a module global without a static type (`thread_pool_exec(settings.conn.fetch, ...)`, where `conn = None` is rebound by `global conn; conn = Backend()`) resolved to a `variable` node and produced no edge. The global's type is now the set of classes its module assigns to it, at module scope or in a function that declares it `global`. One class resolves to its own method; several bind to the nearest declaration every candidate inherits, as a base-typed receiver does. No edge (a wrong callback edge is worse than none) when: - any binding of the global in its module is not a whole constructor call: a factory, a conditional, a tuple target, `for`/`with`/import, a star import, or an annotation the constructor contradicts; - the caller or an enclosing function binds the name itself: parameter, assignment (any target position), loop, comprehension, `as`, `case` pattern, lambda parameter; - the receiver is a deeper chain (`settings.conn.pool.fetch`); - the importing file rebinds the name, or imports it from two sources. Statements are read with their continuation lines, and only statement lines (bracket depth 0) can assign. Not seen: rebinding the global from another module (`settings.conn = X`) or through `globals()`. `pkg.mod.Cls` now resolves through `import pkg.mod`, unless two namespace imports share the last segment. Python import mappings also read parenthesized from-imports (`from x import (\n A,\n B,\n)`), which previously yielded the single name `(` and hid every class imported that way from receiver and inheritance resolution. Comments and docstrings are stripped first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018CQubRS1bXvaoApgPyLf66 (cherry picked from commit |
||
|
|
a4bd890ec6 |
fix(python): a call on self is never resolved through a same-named import (#2290)
The Python extractor records `self.get_ip(request)` by its bare name, so the import strategy matched it to a function the file imports as `get_ip` and the call never reached the class's own (or inherited) method. A bare-named Python call written on `self` now skips import resolution; the method strategies then find it. A plain `get_ip(request)` still binds to the import. Across allauth, DRF, netbox and mealie this retargets 16 calls, all from an imported helper to the class's own method; no edge is added or lost. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
a5184ed5d4 |
fix(pascal): Delphi .inc include files are indexed as Pascal, not PHP (#2279) (#2289)
* fix(pascal): Delphi .inc include files are indexed as Pascal, not PHP (#2279) `.inc` maps to PHP (Drupal), so a Delphi/Free Pascal include — a `{$IFDEF}`/`{$DEFINE}` block or a declaration fragment pulled in with `{$I defs.inc}` — was parsed as PHP (untagged text = inline HTML) and contributed nothing. detectLanguage now decides `.inc` by content: a PHP open tag (`<?php`, `<?=`) anywhere keeps it PHP; otherwise a Pascal-only line shape (a compiler directive, a `;`-closed routine header, `unit`/`uses`, a const/var/type section, a begin/end; pair, Lazarus `{%MainUnit}`) makes it Pascal; anything else keeps the PHP routing so C / asm / POV-Ray / SourcePawn / Makefile includes are not handed to the Pascal grammar. A leading BOM is skipped. An explicit codegraph.json `.inc` mapping wins. The rule is per file, so a full index and a sync always agree. The grammar preload set adds Pascal whenever an `.inc` is present (an include-only tree otherwise had no Pascal parser), and the kernel-parity script re-detects `.inc` like the indexer does. Checked on 875 real `.inc` files: 0 of 436 non-Pascal ones (Drupal 7, php-src, LLVM, FreeBSD mk, POV-Ray, SourceMod, edk2) flip; 433 of 439 Pascal ones (FPC, Lazarus, Castle, mORMot2, JCL) are detected — the rest are statement-only or bare-literal fragments with nothing to extract. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(changelog): Pascal include files (#2279) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
e28ac71048 |
fix(sync): same-named symbols keep their own cross-file callers across a re-index (#2276) (#2288)
* fix(sync): same-named symbols keep their own cross-file callers across a re-index (#2276) Re-storing a file re-attaches the edges other files had into it by moving each edge to the re-indexed node that replaces its target. That pairing keyed new nodes by (kind, name) with the last one winning, so in a file defining a name twice — the same method on two classes, a method's overloads — any edit, even a body-only one, moved every incoming caller of those symbols onto one of them. A body-only edit leaves the sync's definition delta empty, so nothing re-resolved them afterwards. The re-store now reads the file's old node identities alongside the edge snapshot and pairs old nodes with new ones by (kind, name), then qualified name, then signature, then position within a same-sized group of otherwise identical nodes. A target no tier can pair one-to-one is resurrected as its original unresolved reference, so resolution rebinds it instead of the store guessing. The common in-memory re-store path also now deletes the old rows, stores the new ones and re-attaches incoming edges in one transaction. Committed separately, a failure between the steps left the file looking new or already current, and other files' edges into it were lost until a full re-index. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(changelog): same-named symbols keep their callers across a sync (#2276) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
455175da67 |
fix(mcp): a projectPath in a nested repo the ancestor index leaves out isn't answered from it (#2110) (#2287)
* fix(mcp): a projectPath in a nested repo the ancestor index leaves out isn't answered from it (#2110) An explicit projectPath resolves to the nearest .codegraph/ walking up, and that walk crosses git boundaries. Inside an independent repository nested under an indexed parent that excludes it (typically gitignored), every call silently answered from the parent's code. After resolving the index root, ToolHandler now asks whether the path sits in a different git repository strictly below that root (git toplevel + common dir, memoized per path and root) and, if so, whether the index holds any file under it (one primary-key range probe per call). None means the success-shaped "isn't indexed" guidance. Nested repos the parent does index (submodules, gitlinks, embedded clones), ordinary subdirectories, linked worktrees of the same repository (the when git can't run, behavior is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(changelog): nested repositories the index leaves out (#2110) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
5271c89f08 |
fix(php): a class written with a namespace in its name resolves to that class (#2256) (#2286)
* fix(php): a class written through a namespace alias resolves to its class (#2256) `use App\Fields as Field;` aliases a namespace, so `new Field\FirstName()` names `App\Fields\FirstName`. Expand the first segment through the file's use map and look the class up by its qualified name, before the pre-filter that drops names without a `.` or `::`. (cherry picked from commit 3917de431cbe8d8bbba32d9f4c739458c36e3fed) * fix(php): resolve every qualified class name the way PHP reads it (#2256) Extends the namespace-alias fix beyond `new`: extends, implements, static calls (`Alias\X::make()`), names relative to the current namespace and fully qualified `\Ns\X` now all resolve by their exact qualified name. A name with no single project class is outside the project, so it no longer falls through to a same-named guess; a static method the class only inherits is left to the strategies that walk supertypes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 12c742a917ebd455840c148db1e37c895b9587ad) * docs(changelog): one entry for the PHP qualified class names fix Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: drakeo338 <paranoyouz@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
fdc7754a72 |
fix(mcp): return status and stale refusals as text only (#2088) (#2284)
* fix(mcp): return status and stale refusals as text only (#2088) Claude Code shows the model a tool result's structuredContent in place of its content text, under every protocol version. codegraph_status and explore's stale-index refusal both set structuredContent.freshness, so the model saw a JSON object instead of the refusal, its retry guidance or the status report. The text already carries every field, and nothing in the codebase reads structuredContent, so both results drop it. The tests now assert that the field is absent and that the text keeps the refusal line and the stale file list. (cherry picked from commit |
||
|
|
637fb0fac8 |
fix(mcp): release an idle explicit project and its writer lock (#2087) (#2283)
* fix(mcp): release an idle explicit project and its writer lock (#2087)
A daemon that answered a `projectPath` query for another indexed project
opened it in-process and, with no owner there, took its writer lock. The
project cache only evicted over its LRU bound or on shutdown, so the lock
stayed held for the daemon's whole life: the project's own daemon could not
become its writer and `codegraph index` there was refused.
Release a cached explicit project once it has gone unused for 10 minutes
(CODEGRAPH_PROJECT_IDLE_TIMEOUT_MS; 0 never releases), through the same
trim path as LRU eviction, so an active call or a running catch-up still
defers it. The next call reopens the project and catches it up (#1835).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit
|
||
|
|
49248e25eb |
fix(mcp): answer server/discover locally in the stdio proxy (#2084) (#2282)
* fix(mcp): answer server/discover locally in the stdio proxy (#2084)
Clients that probe `server/discover` before the legacy `initialize`
(Antigravity 2.5) wait for its answer. The proxy forwarded it to the shared
daemon, so no reply was written until the daemon connected: a client that
closed stdin first got nothing, and with the daemon unavailable the reply
came ~6s later as -32603 "daemon unavailable". Answer it in the proxy with
-32601 Method not found, which is what sends such a client on to
`initialize`. The direct and daemon sessions already answer unknown methods
with -32601; the new test pins that too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit
|
||
|
|
96ba880556 |
fix(prompt-hook): skip Claude Code <agent-message> subagent hand-backs (#2184) (#2281)
* fix(prompt-hook): skip Claude Code <agent-message> subagent hand-backs
Claude Code delivers a subagent's report to the parent session as a
UserPromptSubmit prompt wrapped in <agent-message from="…">…</agent-message>.
The prompt hook treated it as user input and ran a full codegraph_explore
on the whole report, which is slow on a multi-KB prose query (past the
30 s hook timeout in the report) and injects context unrelated to the
user's question.
Add isAgentMessage next to isTaskNotification (#1832) with the same rule:
only a prompt that is entirely the envelope is skipped, so a user question
that mentions the tag still goes through the gate.
Fixes #2184
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit
|
||
|
|
404e7827bc |
fix(windows): hide the git calls that flash console windows (#2094) (#2280)
* fix(windows): hide the git calls that flash console windows (#2094)
The two git calls added for exclude-standard scope parity (#1728),
`git config --get core.excludesFile` and
`git ls-files -o -i --exclude-standard --directory`, ran without
windowsHide. Inside the detached, console-less MCP daemon each one got a
fresh visible console, so a window flashed several times on daemon start
and again on every auto-sync. With Windows Terminal as the default
terminal each flash is a full Windows Terminal window. `git config
user.name` in the viewer's trails API had the same gap.
Add windowsHide: true to all three, and replace the per-file regex guards
with an AST guard over every child_process spawn in src/: options passed
through a variable or a function parameter are traced back to the object
literal that defines them, through every call site.
Supersedes #2095 (same fix, rebased on current main). Closes #2094,
closes #2096.
(cherry picked from commit
|
||
|
|
ec738ec7a3 |
fix(viewer): a Steps picture with nothing past its anchor says why (#2274)
Steps from a helper that only computes — the most-depended-on symbol of most libraries — drew one box and no explanation (179 of 304 projects in the browser pass). The side panel now says what the walk looked for, that plain calls fold into the lines, and links to the symbol's callers and callees. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
911fcf6033 |
fix(vapor): a closure route's calls are the route's (#2273)
A Vapor route whose handler is a trailing closure had no handler symbol and linked to nothing (vapor's own repo: 76 of 80 routes bound to nothing in the viewer browser pass). The closure is the handler: its body's calls, each once with its receiver, are referenced from the route, as an Express inline handler's are. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
f416357b93 |
fix(cobol): a file ending inside the sequence area no longer hangs the parser (#2272)
* fix(cobol): a file ending inside the sequence area no longer hangs the parser The vendored COBOL grammar's external scanner skips the fixed-format sequence area (columns 1-6) with `while (get_column() <= 5) advance()`. At end of input advance() is a no-op, so a file whose last line stops in that area (` .`, `X\n .`, a sentence closed by a short ` .` line) never reached column 7 and the scanner spun forever inside wasm. Indexing hit the parse worker's hard timeout three times (~93 s) and stored the file with zero symbols; the viewer's highlighter froze on the same text. The loop now also stops at eof(). It is upstream code (present at e99dbdc3), so the change is a standalone upstreamable fix; inputs that parsed before are unaffected (412 NIST + ocesql test sources give byte-identical trees with the old and new wasm, and the upstream corpus keeps its single pre-existing failure). Regenerates docs/grammars/tree-sitter-cobol.patch, rebuilds the wasm per docs/grammars/tree-sitter-cobol.md, and adds an out-of-process regression test under a hard deadline (a regression is an uninterruptible wasm loop). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(viewer): force the highlight deadline instead of relying on a grammar bug The bounded-highlight test used the COBOL ` .` slice as its slice that never finishes; the grammar fix makes it parse, so the deadline is now forced through tokenizeBounded's deadline parameter. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
104b8de3e6 |
fix(laravel): a string controller resolves without a Controller suffix (#2271)
Only `XController@action` was claimed and resolved, and the written namespace was stripped, so akaunting's 'Common\\Uploads@inline' (and a Route::resource with a string controller plus options) bound 304 of 313 routes to nothing. Any `Class@action` is claimed; the namespace path under App\\Http\\Controllers is kept and picks between same-named controllers; a resource reads its first argument and resolves its controller class in the Laravel resolver (no `use` brings a string-named class into scope). Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
e12f5bbb92 |
fix(nuxt, astro): a file-routed page is bound to its component (#2267)
A Nuxt pages/x.vue or Astro src/pages/x.astro route linked to nothing, so the viewer's Steps drew a page alone and the routes list never named what serves it. Each page route now references its file's own component (never a same-named one — every index.vue is a component named index), an Astro endpoint its exported verb handlers, and the routing manifest accepts a component as what serves a route, which also lists Vue Router screens. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
41cfc8356f |
fix(viewer): highlighting runs in a worker the server can stop (#2266)
The viewer's server classifies a code slice with the engine's tree-sitter grammars on its one thread. The COBOL grammar's scanner loops forever on a line like ` .`, which a cobolcraft Flow card's window ends with, so the whole server froze — /api/stats included — and a parse inside WebAssembly cannot be interrupted from JavaScript. Slices are now classified in a worker that is terminated past a 2 s deadline; the slice is served plain and the next one gets a fresh worker. Request-time parses also carry a progress-callback budget. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c0d3285b55 |
fix(rails): only:/except: are read as %i[], %w(), a string or :only => (#2269)
Only a bracketed list (and the scoped reader's bare :sym) was read, so maybe's `resources :family_exports, only: %i[new create index]` drew all seven RESTful routes — four to actions the controller lacks, listed by the viewer as routes bound to nothing. Both route readers now share one option reader for every form Rails accepts. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c04cb5c629 |
fix(viewer): the Map opens on a picture, not a single box (#2270)
pickDefaultRoot ignored files loose in the repository root, so git's top-level .c files made builtin/ the program's root. And a source folder whose files all sit in one folder (Express lib/, R's R/, Erlang src/) drew one box at any depth; pickDefaultView opens the whole repository then, when it draws more. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
d6a2bdb0d6 |
fix(sfc): a Vue/Svelte/Astro component owns its script (#2268)
The single-file-component extractors kept the delegated script's file node and made the component contain it and every node, so the hierarchy ran file-inside-component, members had two parents, and a file with no script had no file node. And a per-instance script's top-level calls stayed on the file, so a walk from the component found nothing. Now: one file node (the whole file) holds the component; the component holds what the script holds at top level, nested symbols keep their parent; <script setup>, a Svelte instance script and Astro frontmatter give their top-level calls — a top-level constant's initializer's included — to the component, while imports and module-level scripts stay with the file. Svelte 5's `<script module>` is a module script too. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c4babfce6a |
fix(js): a CommonJS require and a module path import the file they name (#2263)
* fix(js): a CommonJS require and a module path import the file they name
A `require('./application')` was only a call, so a CommonJS file depended
on nothing at file level: Express's lib/express.js showed no imports and
impact missed the files it loads. Local require specifiers are now read
after extraction (both the wasm extractor and the kernel, including its
deferred-decode transport) as `imports` refs from the file, and a JS/TS
module path resolves to its file the way the runtime finds it —
extensions, index files, aliases — which also lands an extensionless
`import './polyfills'` on its file instead of its own import node.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(js): read require imports inside each extractor, so kernel parity holds
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
95f6f0f4a5 |
fix(viewer): a directed Flow looks up both ends by their exact names (#2262)
The Flow strip's from/to were run through explore's free-text token filter, which drops an Objective-C selector (`initWithFrame:`), a Ruby predicate (`valid?`) and any name under three letters (`ok`) — then reported the OTHER end as naming nothing in the index. Directed mode now takes the two names as written; explore's named mode is unchanged. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |