| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
fix(sync): a long-running session picks up a framework added while it runs (#2941) The MCP server's watcher holds one CodeGraph for the whole session, and its framework detection was cached from the first time it ran: the orchestrator reset it only in indexAll, and CodeGraph.sync never re-initialized the resolver. A framework that arrived mid-session, like Express in a new package.json, was missing from every file synced after it, so a new route file got no route nodes until `codegraph index`. The resolver's declared-dependencies cache was also keyed by the indexed file COUNT (PR #2519): swapping one app for another of the same size, or editing a manifest, kept the old dependency set, even through reinitializeResolver(). Detection now records what it read: indexed source files, which a sync's change lists cover, and every other path it read or probed, with a size and mtime signature, including the per-app manifests the extractor gating reads. Before a sync extracts files, it detects again only when files were added or removed since, a source file detection read changed, or one of those paths changed on disk. A scoped sync takes the file list from the index plus the files it adds, so it never walks the project (the first sync in a process used to run a full scan for its first detection). What detection read is kept between detections and dropped by the same change lists and signatures, so detecting again reads only what changed. When the detected set differs from the resolver's, the sync re-initializes the resolver. Its initialize() now clears its caches before detecting, and clearCaches() forgets the declared-dependencies cache with the other per-context memos. Files indexed before a framework change are not re-extracted. A framework like Express applies to every file of its language, and re-reading them all inside a watcher's sync would cost what a full index does. Instead a full index records the frameworks it used, a sync records how detection moved since, and `codegraph status` (text, and --json with reindexRecommended and frameworksChangedSinceIndex) recommends `codegraph index` while they differ. Status only reads that record. Validation: - sync-framework-detection.test.ts (new, 8 cases): Express arriving with a new route file, by full and by scoped sync; a .svelte file added with no manifest change; an edited source file that detection read; a manifest-only edit picked up by the next sync, with the status record, cleared on revert and by a full re-index; a fresh process's scoped syncs detecting once, from the index, with no scan; the equal-size app swap; and the dependency cache forgetting on request. 7 of 8 fail on main's source (the 8th needs the new export). Ten mutations, one per mechanism, each fail a test. - The triage probes: main detects [] after Express arrives and routes.js gets no route; the fix detects express and extracts GET /hello, by full and by scoped sync. For the swap, main's reinitializeResolver() gives [react] and the fix [express]. - Cost on excalidraw (737 files, scoped syncs in one long-lived instance): the detection step takes 8 ms on a sync where nothing it read changed and 8-13 ms when it detects again, against syncs of about 260 ms (add) to 2 s (modify). The first sync in a process detects once (about 300 ms) and no longer scans the project. Reported in PRs #2516 and #2519 by @nk33-dev. This reimplements both fixes independently. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> | 14 小时前 | |
fix(graph): the type hierarchy returns a type's subtypes, not only its ancestors getTypeHierarchy() walked a type's ancestors and then its descendants with one visited set. The ancestor walk marks the focal node first, so the descendant walk returned at its visited guard before reading a single incoming edge: no type ever had subtypes or implementors, in any language. Each direction now keeps its own visited set. The same walk feeds the hierarchy step of findRelevantContext, which codegraph_explore and codegraph context build on. That step promised subclasses within a budget of a quarter of maxNodes, but checked the budget only between entry points, which was harmless while the walk returned ancestors alone. With descendants coming back, one widely implemented interface brought its whole tree: vscode's IDisposable, in src/vs/base and src/vs/editor, has 613 types in its hierarchy, and codegraph context IDisposable then traded the interface's own dispose for an arbitrary implementor. Added on top of the contributor's fix: the budget now holds per node, and only edges between kept nodes are added. Validation: graph.test.ts gains a test that getTypeHierarchy() returns a base class's subclass and an interface's implementor; it fails on main. context.test.ts gains one that the hierarchy step adds an interface's implementors within its budget (12 nodes, no traversal: 1 to 3); main gives 0, and the traversal fix without the cap gives 10. On vscode's base and editor subtree, codegraph context and codegraph_explore keep main's size and speed (0.8 to 1.3 s), and the hierarchy they show is now the queried type's own: StandaloneCodeEditor and EmbeddedCodeEditorWidget for "ICodeEditor CodeEditorWidget". On prometheus, 3 of 4 explore answers are identical, and the fourth names more of storage.Appender's implementors. The contributor's codegraph implementations command and its test-code helper are not part of this change. This is the getTypeHierarchy() half of issue #2833. Original PR #2839 by @danusha2345. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> | 4 天前 | |
fix(cli): read commands and the prompt hook open the index read-only (#2936) Running query, files, node, callers, callees, impact, context, explore or affected while `codegraph index` rebuilt a project could make the rebuild fail with "database is locked", and so could the hidden prompt-hook, which Claude Code runs on its own for structural prompts. Each opened the index read-write, so its open ran the writer's repairs: during the rebuild's bulk load they recreated the secondary indexes the indexer had dropped, and the indexer's next write, whose transaction had already read, failed with SQLITE_BUSY_SNAPSHOT (517). PR #2930 fixed the same thing for status. These commands now open the index read-only, through one helper, and leave migrations, index recovery and WAL maintenance to the writer: - query, files, node, callers, callees, impact, context, explore, affected and prompt-hook are read-only. explore and node run the MCP tool handler in process without a lifecycle, so they never sync or open another project. - While another live process holds rebuild.pid they say so in one line on stderr (a --json answer stays parseable), as status does. - The prompt hook stays out of a running rebuild altogether: the index is half-built until it ends, the MCP tools answer "rebuild in progress" meanwhile, and not opening it keeps the hook from holding the file the rebuild replaces. Its gate records the new outcome noop-rebuild. - The hook's one write, filling an empty segment vocabulary (issue #1142), goes through a writer connection of its own, opened only once it holds the index lock, which every index and sync holds while it writes. - status (read-only since PR #2930), sync, index, init and uninit are unchanged; the last four write. A read-only open migrates nothing. QueryBuilder.hasSchema lets each read that needs a later table or column answer as that migration's defaults would: files.generated (v9), unresolved_refs.status and name_tail (v8), and name_segment_vocab (v7). The status pending count from PR #2930 uses it too. Validation (Windows 11, Node 22.22): - The issue's interleaving, each command in another process inside the writer's edge transaction: on main all 14 command forms make the insert fail with errcode 517 and bring the 5 dropped edge indexes back; with this change all 14 inserts succeed and the indexes stay dropped. - Real rebuilds of excalidraw 95307fa1 (736 files) with query and the prompt hook looping beside them, default settings and with the resolution window forced: main failed 6 of 14 ("store worker: database is locked" with an empty index, or "database is locked" partway through resolution), and 12 of its 109 query runs failed at open with SQLITE_BUSY. This change failed 0 of 14, with 0 failures in 299 query and 284 hook runs and the same graph every time (14,236 nodes, 61,210 edges). - Old indexes: 24 command forms on indexes at schemas 4 to 16 (11 schemas rebuilt from a current index, plus indexes built by 0.9.9, 1.4.0, 1.5.0, 1.6.0 and 1.6.2): output identical to main's, which migrates first, 384 of 384. The schema stays as it was, except where the hook fills the vocabulary of a pre-v7 index; that writer's open migrates it, as every read-write open did. - Timing on excalidraw at rest, 22 interleaved runs per command: medians within -5.8% to +3.3%, -0.9% in total. - read-commands-during-rebuild.test.ts: 38/38 pass. With main's sources 35 fail; with only the CLI opens reverted 33, only the schema fallbacks 19, only the read-only vocabulary fill 2. Follow-up to PR #2930. Fixes #2913 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> | 15 小时前 | |
fix(kotlin): a call through a property or a receiver chain resolves on its declared type Kotlin indexes a class property without a signature, and a primary-constructor property (class A(private val repo: Repo)) is no node at all, so a call through either (probe.close() on a lateinit var probe: HttpProbe) found no receiver type and fell to the name-only strategies: the interface's method, or any same-named method elsewhere. A call through a chain (engine.pump.drain()) was emitted as its bare method name, and a local bound to a qualified call (val lease = HardwareLock.tryBegin()) was untyped. tree-sitter-kotlin also predates fun interface: such a declaration parsed as a broken function and, with a doc comment after it, its error recovery swallowed the next declaration too. The declared type is now read from the declaration itself: a property's own lines, the class header for a constructor property, a callee's declared return type for a local bound to a call, an enum entry's enum, each segment of a receiver chain in turn (the chain is kept in the reference name, by the wasm extractor and the native kernel alike), a superclass's property, and an outer function's local inside an anonymous object. A receiver whose type the project does not declare (Regex, Properties, a JDK class) gets no edge instead of a name-only guess, unless the project extends that type. The extractor's preParse blanks the fun of a fun interface, offset-preserving, so the file parses cleanly in both paths. Validation: - kotlin-property-receiver.test.ts: 17 tests, 12 red on main, all green here; kernel-kotlin-parity.test.ts pins the fun interface fixture. - Kernel parity (kotlin), 0 diffs: okhttp 588/619 files byte-identical (main's kernel 578, the rest deferred to wasm on parse errors), mihon 891/940 (887), nowinandroid 340/350 (339): the fun interface files no longer defer. - A/B against main with #1942 (positions only: the extractor now keeps a chain in the reference name): okhttp +299/-257, 2,554 retargeted, 2,158 of them test calls moving from the deprecated okhttp3.mockwebserver.MockWebServer (a 0.65 name guess) to the mockwebserver3.MockWebServer each file imports; mihon +82/-118, 337 retargeted (Preference get/changes/set/register onto the declared Preference); nowinandroid -9, 23 retargeted (properties onto their concrete test doubles); SystemUI's Kotlin +1/-104, 7 retargeted; jextract, click and gin identical. The removed are name guesses on library types (okio sinks, collections' filter and clear, OkHttp bodies, MutableStateFlow.update, Kotlin's apply). 7 right edges are lost where a chain passes through a library wrapper (Lazy.value, StateFlow.value, Pair.first, a ViewBinding's view). - Cost (mihon, median of 3 interleaved indexes): CPU 25.8 s on main with #1942 vs 26.7 s, wall 8.3 vs 8.4 s. Original PR #1933 by @danusha2345. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> | 11 小时前 | |
fix(paths): a project at a Windows drive root indexes its files (#2931) A project at the root of a drive, such as a mapped network drive Z:\, a subst drive or a UNC share root, indexed nothing on Windows: every file was refused as "Path traversal blocked", and code reads under the root were refused too. The containment check appended a separator to the root before comparing, and a root such as C:\ already ends with one, so the prefix became C:\\ and no path under the root matched. The dead code report's default file reader had its own copy of the same check, so it treated every file of such a project as unreadable. The check now adds a separator only when the root doesn't already end with one. The dead code reader uses the shared lexical containment check instead of its own copy. Validation on Windows 11: - subst R: onto a two-file fixture, then codegraph init -y --force R:\: main indexes 0 files ("all 2 files had errors", 2 "Path traversal blocked"), and validatePathWithinRoot(R:\, a.ts) is null. The fix indexes both files (6 nodes, 6 edges) and getCode returns both functions' source. The mapping was removed after each run. - Dead code report against one R:\ index: main's reader drops the unused function as unreadable; the fix reports it. - Tests: 3 new tests fail on main's source and pass with the fix: a file under the filesystem root (this drive's root here, / on POSIX), path math under Q:\ and \\server\share\, and read-only realpaths under C:\. Reported in PR #415 by @Kamilslaoui93; this reimplements the fix independently. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> | 19 小时前 | |
fix(installer): Hermes keeps your MCP entry and gets no platform_toolsets line (#2774) Root cause, two parts: 1. Install and refresh appended `mcp-codegraph` to `platform_toolsets.cli` in `~/.hermes/config.yaml`. It was never needed. Hermes loads every enabled MCP server on a platform whose list names none (hermes_cli/tools_config.py `_merge_mcp_servers`, a rule present back in May 2026 when the target was written). The name itself is harmful: - Hermes up to 0.22.2 rejects it at startup ("Unknown toolsets"). - Hermes 0.22.4 maps `mcp-<server>` onto the server, so the list becomes an MCP allowlist and every other server disappears from CLI sessions. A user who deleted the line got it back on the next upgrade. 2. When the `mcp_servers.codegraph` block differed from the template, it was replaced wholesale. That reverted an absolute command path, the timeouts, `enabled: false` and `env`. The line-based YAML code also missed some shapes: - a codegraph key written on one line or quoted, which got a second codegraph key; - a 4-space-indented `mcp_servers`, which came out unparseable; - a one-line `mcp_servers: {…}`, which got a second top-level key, and Hermes's PyYAML reader then dropped the user's servers. CRLF files came back LF. Fix: - CodeGraph never writes `platform_toolsets`. - A `- mcp-codegraph` line an earlier install left in `platform_toolsets.cli` is removed, or becomes `- codegraph` when the list is an allowlist naming other MCP servers (the spelling every Hermes version reads as the server). An allowlist that leaves CodeGraph out gets a note, not an edit. - An existing `mcp_servers.codegraph` entry is the user's: nothing in it changes. A block entry missing `command` or `args` gets just those, at its own indent. - A new entry goes in at the indent its siblings use, and a codegraph key is found however it is written. `mcp_servers: {}` becomes a block. - A one-line mapping that holds other servers makes install refuse with a clear error and change nothing. One that holds codegraph is left as written, with a note, and uninstall says it can't remove it. - The file keeps its line breaks, and is written only when something was edited. - Uninstall also removes a `- codegraph` the refresh may have written. Hermes references (NousResearch/hermes-agent v0.22.4, 64dbae64): - hermes_cli/tools_config.py `_get_platform_tools` / `_merge_mcp_servers` ("mcp-<server> names the same server as its bare alias: either spelling selects only that server"). - hermes_cli/toolset_validation.py `saved_toolset_resolver`. - website/docs/reference/mcp-config-reference.md (command, args, env, enabled, timeout, connect_timeout). - hermes_cli/tools_config.py at 1b9c539c (2026-05-17) for the May 2026 rule. Validation: - installer-targets.test.ts: 13 new Hermes tests. 3 existing tests that asserted the `mcp-codegraph` write are updated; the #456 same-indent list case now proves the old line is removed cleanly. With main's hermes.ts put back, those 16 fail, all on behavior. The 7 other Hermes tests pass on both. - The real CLI on this Windows machine against a private HERMES_HOME: `install --yes`, a second `install --yes`, `install --refresh` and `uninstall --yes --keep-cli`, for 14 config.yaml shapes. Each result was loaded with PyYAML (the reader Hermes uses), checked for duplicate keys, and run through a re-implementation of the cited selection rule. Of 42 results: - main: 2 unparseable, 10 with duplicate keys, 26 carrying `mcp-codegraph`, 10 where Hermes 0.22.4 would load only codegraph in CLI sessions, 3 converted CRLF to LF, and the customized entry reverted. - The fix: 0, 0, 0, 0, 0, the customized entry kept byte-for-byte, and 1 install refused by design (a one-line `mcp_servers` holding other servers). Fixes #2485 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> | 6 天前 | |
fix(cli): codegraph status opens the index read-only, and says when a rebuild is running Status opened the database read-write, so its open ran the writer's repairs: migrations, the FTS-trigger and secondary-index recovery an interrupted bulk load leaves behind, and WAL maintenance. During a `codegraph index`, that recovery recreated the indexes the indexer had dropped for its bulk load, and the indexer's next write, whose transaction had already read, failed with SQLITE_BUSY_SNAPSHOT (517): "Failed to index: database is locked". Status now opens with `readOnly: true` and leaves every repair to the writer, as the MCP reader connections already do. When another live process holds `rebuild.pid`, it says so: a warning in the text output and `rebuildInProgress` in `--json`, the only new key. `isRebuildInProgress` is the check `assertNoRebuild` already made. Added when landing it: - A read-only open migrates nothing, so status on an index built by 1.4.0 or older (schema 7, no `unresolved_refs.status`) failed with "no such column: status". The pending-reference count counts every row when the column is missing, as the v8 migration marks them pending. - While a rebuild runs, status skips the advice meant for an index at rest: the interrupted-run references, "run sync", "re-index", and the oversized-WAL warning, which mid-rebuild all describe the live run. - Status no longer reclaims a killed session's oversized WAL, so the warning names the next `codegraph sync` instead of status itself. - Tests: the issue's exact interleaving (status inside the writer's edge transaction, as in PR #2916's test), a pre-v8 index, and the mid-rebuild advice. Validation (Windows 11, Node 22.22): - The issue's reproduction with our build of main 69af19ac: status inside the transaction exits 0, the writer fails with errcode 517 and the five dropped edge indexes come back. With this change the insert succeeds and the indexes stay dropped. - Real rebuilds of excalidraw 95307fa1 (716 files) with `codegraph status` looping beside them, 3 with default settings and 3 with the resolution window forced: main failed 6 of 6, each with "store worker: database is locked" and an empty index; this change failed 0 of 6 over 46 status runs, with identical complete graphs (14,236 nodes, 61,210 edges). - status-during-rebuild.test.ts: 7/7 pass. With main's sources 6 of 7 fail; without `readOnly` 3 fail, one with "database is locked"; with the PR as written the pre-v8 and mid-rebuild cases fail. - Indexes built by v1.6.2 (schema 11) and v1.4.0 (schema 7): the same numbers as main, and the schema is left as it is. Interrupted-index warnings (indexing, partial, failed) are unchanged. Original PR #2921 by @danusha2345. Fixes #2913 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> | 20 小时前 | |
fix(kotlin): a call through a property or a receiver chain resolves on its declared type Kotlin indexes a class property without a signature, and a primary-constructor property (class A(private val repo: Repo)) is no node at all, so a call through either (probe.close() on a lateinit var probe: HttpProbe) found no receiver type and fell to the name-only strategies: the interface's method, or any same-named method elsewhere. A call through a chain (engine.pump.drain()) was emitted as its bare method name, and a local bound to a qualified call (val lease = HardwareLock.tryBegin()) was untyped. tree-sitter-kotlin also predates fun interface: such a declaration parsed as a broken function and, with a doc comment after it, its error recovery swallowed the next declaration too. The declared type is now read from the declaration itself: a property's own lines, the class header for a constructor property, a callee's declared return type for a local bound to a call, an enum entry's enum, each segment of a receiver chain in turn (the chain is kept in the reference name, by the wasm extractor and the native kernel alike), a superclass's property, and an outer function's local inside an anonymous object. A receiver whose type the project does not declare (Regex, Properties, a JDK class) gets no edge instead of a name-only guess, unless the project extends that type. The extractor's preParse blanks the fun of a fun interface, offset-preserving, so the file parses cleanly in both paths. Validation: - kotlin-property-receiver.test.ts: 17 tests, 12 red on main, all green here; kernel-kotlin-parity.test.ts pins the fun interface fixture. - Kernel parity (kotlin), 0 diffs: okhttp 588/619 files byte-identical (main's kernel 578, the rest deferred to wasm on parse errors), mihon 891/940 (887), nowinandroid 340/350 (339): the fun interface files no longer defer. - A/B against main with #1942 (positions only: the extractor now keeps a chain in the reference name): okhttp +299/-257, 2,554 retargeted, 2,158 of them test calls moving from the deprecated okhttp3.mockwebserver.MockWebServer (a 0.65 name guess) to the mockwebserver3.MockWebServer each file imports; mihon +82/-118, 337 retargeted (Preference get/changes/set/register onto the declared Preference); nowinandroid -9, 23 retargeted (properties onto their concrete test doubles); SystemUI's Kotlin +1/-104, 7 retargeted; jextract, click and gin identical. The removed are name guesses on library types (okio sinks, collections' filter and clear, OkHttp bodies, MutableStateFlow.update, Kotlin's apply). 7 right edges are lost where a chain passes through a library wrapper (Lazy.value, StateFlow.value, Pair.first, a ViewBinding's view). - Cost (mihon, median of 3 interleaved indexes): CPU 25.8 s on main with #1942 vs 26.7 s, wall 8.3 vs 8.4 s. Original PR #1933 by @danusha2345. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> | 11 小时前 | |
fix(explore): a long query, like a pasted log, answers in seconds on a large index (#2804) The prompt hook hands codegraph_explore the whole prompt, so a pasted log, document or source file arrives as a query of hundreds of words. findRelevantContext searched every word several times, and the reads behind those searches scanned the index each time: - findNodesByNameSubstring, four times per symbol word: `name LIKE '%word%'` with a leading wildcard reads every node of those kinds, about 80 ms each on vscode (562,636 nodes); - the LIKE fallback after an FTS miss (`name LIKE` or `qualified_name LIKE '%word%'`), about 120 ms per miss, and most words of a log miss; - the fuzzy fallback after that re-read `SELECT DISTINCT name` each time (about 150 ms), and getAllNodeNames, documented as cached, was not; - FTS joined nodes on the TEXT id instead of the rowid; - scorePathRelevance re-split every query word for every scored path, and the deprioritize predicate stat'ed codegraph.json per candidate. On vscode a 1.7k-character log took 80 s in explore, a 10k-character skill document 258 s, and the hook gave up on both. A per-statement profile put 65-80% of that in those scans. The fix: - A name-substring index answers the substring lookups from memory with exactly the rows SQLite returns, in its order: ASCII-only case folding and `_` as one character, like LIKE, and ties in length in the order the plan reads rows, the kind index's or rowid's. The plan is asked of SQLite once per statement shape, since a small index is scanned whole; a `%`, a language filter or any other plan goes to SQLite as before. - A word of letters and digits that no name and no letters-and-digits run of a qualified name holds can match nothing, so the LIKE fallback returns empty without scanning for it. - The fuzzy fallback reads the name list once and skips names outside its edit-distance length window. - These reads are memoized per database change stamp, like the dominant file (PR #1864): rebuilt after any write from any connection, never kept inside a transaction, dropped on rebind. - FTS joins on rowid, with CROSS JOIN keeping nodes_fts the outer loop as before. - Path relevance prepares each query once and each path is scored once per search; the deprioritize patterns are read once per search. - findRelevantContext searches at most 64 symbol names and 128 search terms of a query. Both lists put code-shaped words first, so a long paste is searched by its identifiers and its opening words. A 16-symbol bag (75 terms) stays whole. This is the one intended change in output, for long queries only. Validation (vscode ff0a86dc, median of 3 interleaved, fresh process): - explore, ordinary queries: 6 of 6 outputs byte-identical, 5.5-7.5 s fresh (the dominant-file aggregation is about 4 s of that) and 0.9-2.1 s warm vs 2.7-3.9 s on main; - explore, long queries: log 1.7k 80.1 to 10.7 s, markdown 3k 73.3 to 9.0 s, Java 3k 108.8 to 12.2 s, TS 3k 63.4 to 11.9 s, markdown 10k 258.3 to 10.8 s, Java 10k 144.7 to 11.1 s, TS 10k 146.7 to 9.6 s; - the real prompt hook on the issue's prompt shapes: short 7.2 to 7.0 s, log 84.2 to 12.9 s, markdown 3k 78.1 to 10.2 s, Java 3k 113.1 to 13.3 s, markdown 10k 261.7 to 11.3 s, Java 10k 146.3 to 11.1 s; - without the word caps, all 7 long queries gave byte-identical output 4.3-9.0x faster; - django 79502d08 and prometheus ed26d727: 11 of 11 queries byte-identical, tracebacks included, 1.0-2.9x faster; - the substring index against the LIKE scan on vscode: 4,830 of 4,830 lookups identical (805 words, snake_case included); - explore-long-query.test.ts: 13 tests; 8 fail on main's source. Issue #2620. Closed contributor PR #2621 (retsamekohC) was read for guidance; open PR #2260 (danusha2345) overlaps on the name-list memo and path relevance. Fixes #2620 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> | 5 天前 | |
fix(watcher): a deleted link target no longer pins a CPU core on Windows (#2938) On Windows, libuv reports a watched folder that is being deleted by the folder's own absolute path (\\?\D:\...) and re-arms the read, which fails the same way at once: about 70,000 events in half a second until the stream is closed. The watcher gives each directory link in a project its own recursive stream, so deleting a link's target outside the project started that storm. handleDirEvent joined the absolute name onto the watched folder, got a path that doesn't exist, scheduled a full sync and nothing else. The stream was never closed, the server held a core until restart, and the storm kept re-arming the debounce, so no sync ever ran: the deleted files stayed in the index and later edits never auto-synced. The project root's stream had the same blind spot. Fix: handleDirEvent, which both stream callbacks use, now treats an absolute name as the watched folder itself reporting it is gone. The first such event on a stream arms one check 200 ms later; the rest of the burst finds it armed. The check closes a link's stream when the link no longer leads to the folder it watched (deleted, or pointed elsewhere) and re-reads the tree, whose full sync drops the link's files; a link that leads somewhere live is watched anew. A project root that is gone degrades the watcher with a "project folder was deleted or moved away" reason, which closes every stream. A normal event pays one path.isAbsolute check. Validation (Windows 11, Node 22.22, libuv 1.51): - Raw probe: fs.watch on a junction whose target is deleted gets 70,009 events named \\?\<the junction> in 500 ms (499 ms CPU), and none after close(). - Real MCP server over a project with a junction libs -> outside, outside deleted: main used 3484, 3406, 3391, 3281 ms of CPU per 3 s window and kept the linked symbol; an edit afterwards never synced. With the fix: 296, 0, 0, 79 ms; the linked symbol left the index; the edit synced. - New watcher tests: a fake fs.watch link storm (10,000 self-named events cost no fs call, the stream closes once, a full sync runs, the project stays watched), a fake root storm (degrades once, closes the stream), and a Windows-gated real junction whose storm must go quiet. All 3 fail on main's watcher.ts; watcher.test.ts passes 61/61. Reported in PR #2487 by @L0garithmic; this reimplements the fix independently. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> | 13 小时前 | |
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> | 25 天前 | |
fix(viewer): a file with % in its name opens as itself (#2933) codegraph ui couldn't show a file whose name contains a %: 100%.ts was refused as an unusable path, and a%20b.ts was served as changed on disk with no lines. resolveProjectFile, the chokepoint for every file the viewer reads, percent-decoded its path. But that path is either one the index stored or a query value the URL parser had already decoded once, so this was a second decode. It threw on 100%.ts and turned a%20b.ts into a b.ts, while resolveRequestedFile looked the file up in the index by the name as given: the bytes of a b.ts were hashed against a%20b.ts's record. The chokepoint no longer decodes. It refuses raw control bytes, and on Windows a backslash, then checks containment as before. Raw request paths (the viewer's own assets) are still decoded where they are read. The File view's /api/file/<path>, whose path the HTTP layer also decodes once, had the same double decode and is fixed by the same change. The POSIX half of PR #2009 (backslashes kept in names on POSIX) is left out, as it needs a wider change and a POSIX run. Validation on Windows 11, with a fixture holding src/a%20b.ts, src/a b.ts and src/100%.ts: - codegraph ui over HTTP, /api/source with the query a browser sends: main serves a%20b.ts as drift=true with no lines and answers 403 for 100%.ts; the fix serves all three files as themselves, drift=false. - Tests: 2 new tests (the chokepoint, and /api/source plus /api/file/<path> over HTTP) fail on main's source and pass with the fix. Two existing cases that fed an encoded ..%2f and %00 straight to the chokepoint now go through the URL parser's single decode, as requests do, and are still refused. Reported in PR #2009 by @shrankr; this reimplements the viewer half of the fix independently. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> | 19 小时前 | |
fix(ui): consistent frame glyphs on Windows — agree with clack, keep raw path ASCII (#1307) codegraph's glyphs were ASCII on every Windows console while @clack/prompts drew its Unicode frame around them, so one index block mixed `|` and `│` rails (#398). supportsUnicode() now mirrors the is-unicode-supported detection clack bundles (Windows Terminal, VS Code, ConEmu/Cmder, Alacritty, xterm-256color, JetBrains, CI), so both systems always pick the same glyph family. The shimmer worker's raw fs.writeSync(1) bytes still decode through the console codepage (OEM codepages mojibake UTF-8 even under Windows Terminal — the #168 regression to avoid), so: - the raw path gets its own supportsUnicodeRawWrites() that stays ASCII on win32 unless CODEGRAPH_UNICODE=1, and - the persistent "phase done" lines move from the worker to the parent, written via process.stdout (wide-char console API, codepage-immune) at phase transitions — the main thread is alive there, it's delivering the progress callback. Only transient, self-erasing animation frames remain on the raw path, so ASCII never lands in scrollback. Fixes #398 Co-authored-by: Claude Fable 5 <noreply@anthropic.com> | 3 个月前 | |
fix(upgrade): a global npm install is upgraded with -g from any folder, even home (#2777) codegraph upgrade told a global npm install from a local one by whether the running file sat under the current folder. npm's global folder is often inside the home folder (Windows %APPDATA%\npm, nvm, fnm, ~/.npm-global), so run from home every such global install was taken for a local one: npm ran without -g, the global copy stayed on the old version and a second copy landed in the home folder. The same test sent a project's own install to npm -g when upgrade ran from one of its subfolders or from outside it, and installed into the parent folder instead when it ran from one. The scope now comes from npm's layout around the outermost node_modules. A global launcher beside it (<prefix>\codegraph.cmd on Windows, <prefix>/bin/codegraph next to <prefix>/lib on POSIX) means global. A project around it (node_modules/.bin/codegraph or a package.json) means local, and npm then runs in that project, wherever upgrade was run from; the version npm left there is read back, with a warning when it did not change. A pnpm, yarn or bun install (a .pnpm layout, or that manager's lockfile beside node_modules) is named and given that manager's command, since npm run in its folder would only add a second copy. Validation: on Windows, the real CLI was run with a private USERPROFILE, HOME, APPDATA and LOCALAPPDATA (npm config get prefix = the private AppData\Roaming\npm) against installs laid out the way npm lays them out, with a fake npm first on PATH that logs its command and folder. main ran the wrong npm command in 6 of 9 cases (a global install under %APPDATA%\npm or ~\.npm-global run from home, and project installs run from a subfolder, from home, from the drive root or from another project), the fix ran the right one in 9 of 9; an npx cache run and the bundled runtime are detected the same by both. On main's code 14 of the 91 upgrade tests fail on assertions (12 new ones, and 2 existing ones that now also expect the project folder); all pass with the fix. Fixes #2477 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> | 6 天前 | |
fix(prompt-hook): desktop reminders and other sessions' messages no longer drive the hook (#2798) Claude Code's desktop app puts <system-reminder> blocks (which local sessions are linked, and similar status) in front of the prompt it submits, and UserPromptSubmit hooks receive them inside `prompt`. The hook gated, picked the project and explored on the whole string, so the reminder's words drove it: a linked session titled after its work, like "trace-order-flow", carries structural keywords, so every turn, a bare "yes" included, got a full explore of the reminder. A reminder in front of a task notification or a subagent hand-back also defeated the start-anchored envelope checks, and a message from another session (a <cross-session-message from=...> envelope) was never skipped at all. The hook now removes every complete <system-reminder> block before the envelope checks, the gate, project selection and explore, returns early when nothing is left, and skips a prompt that is entirely a <cross-session-message> envelope, as it already did for task notifications and subagent hand-backs. The scan stops at the last closing tag and the attribute run stops at `<`, so a pasted prompt full of unclosed tags stays linear (520 KB in under 1 ms; the `[^>]*` form took 83 ms on a tenth of that and grows quadratically). Validation: - Prompt shapes come from the Claude Code desktop transcripts on this machine (314 transcripts): reminders always lead, one to four of them, separated by one or two newlines, and cross-session messages follow one to four reminders. - frontload-hook.test.ts: 15 new tests (8 spawn the built hook with Claude Code-shaped payloads) fail on main and pass with the fix; the file's 48 other tests pass on both. - Built CLI, private HOME, 13 payloads: on main every prompt behind a reminder got a full explore of the reminder (2.1 to 5.0 KB); with the fix the 8 that are not user input inject nothing and exit in about 200 ms instead of 680 ms, and a question behind reminders injects byte-for-byte what the same question alone does. Issue #2635. Contributor PR #2642 (yang0228) was read for guidance. Fixes #2635 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> | 6 天前 | |
feat(ui): serve the viewer from `codegraph ui`, loopback-only and read-only (CG-41) Adds the `codegraph ui [path]` command (alias `web`) and `src/ui-server/`, a `node:http` server with no framework and no new dependency. The command reads an index that already exists — it never creates one, so a missing index prints the same friendly guidance the MCP tools give instead of a stack trace, and a sensitive system directory is refused up front. Security is the substance here, not the routing. The server binds 127.0.0.1 only, answers GET and HEAD only, and sends no CORS headers ever. The realistic attack on a process that serves your source code from a local port is DNS rebinding, so every request must carry a loopback `Host` (on our port) and, if it carries an `Origin` at all, a loopback one — anything else is 403 before the filesystem is touched. Every path resolves through the engine's existing `validatePathWithinRoot` chokepoint, which already handles `../` traversal and in-tree symlinks pointing out of the root (#527); `..` segments are refused outright so a traversal attempt gets a 404 rather than the SPA shell. `PathRefusalError` moves from `mcp/tools.ts` into the dependency-free `errors.ts` (re-exported from its old home, so class identity and every `instanceof` check are unchanged) — that is what lets a non-MCP read sink enforce the same refusal without importing the MCP tool graph. Assets come from `dist/viewer/` resolved relative to `__dirname`, the way `db/index.ts` finds `schema.sql`. Hashed assets are cached immutably, `index.html` never. Port 4747, or the next free one — an explicit `--port` stays explicit rather than silently moving. `--no-open` skips the browser, and `CODEGRAPH_BROWSER` picks one (or `none` to suppress it), which is also what makes "did it open a browser" testable end to end. `resolveProjectFile` and the `/api/` handler seam are the boundary CG-42's JSON API plugs into; `/api/*` 404s as JSON so a typo'd endpoint never returns the app shell. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> | 2 个月前 | |
fix(extraction): never read an oversize file; an MPEG-TS video named .ts is not TypeScript (#1910) (#2082) * fix(extraction): an MPEG-TS video named .ts is not TypeScript (#1910) `.ts` mapped to TypeScript by extension alone, so MPEG transport-stream fixtures (`testdata/*.ts`) were parsed by tree-sitter: ~28 s of CPU for a 900 KB clip, minutes for a directory of them, for zero symbols. Recognise the stream from the head of the file — the 0x47 sync byte at offsets 0, 188, 376 and 564 plus a NUL byte, which every stream carries in its first packets and UTF-8 source never does — and drop it at discovery: not indexed, not parsed, not counted, not tallied as an unsupported language. The check reads under 1 KB and only for `.ts` files; the batch reader sniffs the bytes it already read, and the single-file path (sync, watcher) does the same head read, so a clip handed in by name is skipped too. The skip happens before parse dispatch, so no kernel mirror is needed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(extraction): a video .ts is never pending on the git path or a scoped sync Review found two places that decide "is this a source file" without the MPEG-TS check the scan applies: the git fast path's candidate loop in getChangedFiles and the scoped-sync path filter (watcher events). An untracked clip in a git repo was reported as `added`, skipped by sync without a record, and reported as `added` again on every status; a tracked .ts that became a clip stayed `modified` with its stale nodes. Both now treat the clip as non-source: removed when tracked, ignored otherwise. Regression tests fail without this change and pass with it, on both arms. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(extraction): require a binary head before calling a .ts file video (#1910) Review on #1915: the sniff took four aligned 0x47 sync bytes plus any NUL as proof of a transport stream, so TypeScript with a `G` at four 188-byte strides and one raw NUL in a comment was dropped unindexed. The sniff now needs the sync byte on sixteen consecutive packets (3 KB of head) and at least 1/64 of the head to be control bytes. Compressed payload carries 8-20% of them (checked on ffmpeg-made H.264/AAC, MPEG-2 and MP2 streams); source text carries none. A shorter clip is cheap to parse anyway. Also drops the benchmark figures from the changelog entry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(extraction): a file over the size limit is never read (#1910) The size gate ran after `readFile`, so a committed video or blob fixture was decoded in full — 3.4 GB of RSS for a 400 MB file, `Invalid string length` past ~512 MB — only to be stored as skipped. It was then read again by every pass that scans files by name: the framework detectors' `readFile` and the resolver's cached reader, three full passes on the reporter's fixtures. Stat first, everywhere a source file is read by path: the batch reader and `extractFile` store an oversize file with a size stamp in place of its content, change detection hashes the same stamp (so a same-size rewrite of a file nothing is indexed from is not a change, while crossing the limit in either direction is), and both by-name readers return null above the limit. The limit moves to `src/file-limits.ts` so the three sites share one number. strace on a sparse 400 MB `.ts`: 153 603 reads before, 0 after; indexing it next to a real file costs no measurable RSS. Stacked on #1914, which shares the batch-reader hunk. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * refactor(extraction): one-purpose size-stamp reader, comments in operation order `readSourceOrStamp` took a `stats` argument no caller passed and returned stats no caller read; it now returns the text or stamp change detection hashes. The batch reader's two comments are back in the order its code runs: the size gate, then the MPEG-TS head check. No behaviour change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(extraction): bound the read itself, not only the stat before it (#1910) A file can grow between the stat that gates the size limit and the read that follows (a log, a download, a build output being written). Every source read now goes through readBoundedSource/readBoundedSourceSync: the open descriptor is re-checked, and the read stops one byte past the limit, so an oversize file is still stamped rather than decoded. The extraction batch reader, single-file extraction, framework detectors and the resolver's file cache all use it. Ported from the maintainer's hardening of this fix in #1919. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(extraction): bound the parse-retry reads too (#1910) The retry passes for files whose worker crashed or timed out re-read the source with an unbounded fsp.readFile. The file can have grown since the first, bounded read; an oversize file is now skipped by the retry instead of being decoded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(extraction): check an oversize file against its size stamp everywhere (#1910) Review on #1915: - The viewer (readFileShape, hasDriftedOnDisk, the source endpoint) and MCP's drift gate hashed a file's real bytes, but the index stores the size stamp for a file over the limit, so every unchanged 1-8 MB file read as "changed on disk". oversizeStamp moves to file-limits.ts with an indexedHashInput helper, and all four checks hash what the index stored. An oversize file is now compared without being read. - git-index-currency's "keeps a committed path pending when sync cannot read it" injected its failure only into readFileSync, which the bounded reader no longer calls, so it failed. The failure now reaches openSync too. - The changelog entry drops its benchmark figures. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(mcp): check an oversize answer file against its size stamp (#1910) #2043's answer-freshness check hashes each contributing file whole and compares it with the stored contentHash. For a file over the index's size limit the stored hash is the size stamp, so an unchanged oversize file would read as stale. Compare it by its stamp, without reading it, like the viewer and the MCP drift gate do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * perf(extraction): judge a .ts clip from the bytes it is read for, not at discovery (#1910) Discovery sniffed the head of every `.ts` file on every full scan — an open + 3 KB read + close per file, before anything asked for its content. On a 10k-file TypeScript tree that took a warm-cache scan from ~105 ms to ~243 ms; on a slow disk or a network share it is a random read per file per scan, the access pattern #1231 and the SMB reports (#1014, #448) are about. The bytes are now judged where they are read anyway, so the check costs no I/O and runs only for a file that is new or changed: the batch reader (as before), `indexFile`, the sync reconcile (an untracked clip is skipped, a tracked file that became one is removed through the same removal path as a deletion), and both `getChangedFiles` paths. The scan lists `.ts` files by name again; a test pins that discovery opens none of them on the walk or the git path. Scan time is back to main's (~98 ms), and a full index is unchanged within noise. Also collapses the four #1910 changelog bullets (#1914 and #1915 each added both) to two, without the benchmark figures. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: danusha2345 <danusha2345@users.noreply.github.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-authored-by: danusha2345 <ewidusoc498@gmail.com> | 29 天前 | |
fix(sync): a long-running session picks up a framework added while it runs (#2941) The MCP server's watcher holds one CodeGraph for the whole session, and its framework detection was cached from the first time it ran: the orchestrator reset it only in indexAll, and CodeGraph.sync never re-initialized the resolver. A framework that arrived mid-session, like Express in a new package.json, was missing from every file synced after it, so a new route file got no route nodes until `codegraph index`. The resolver's declared-dependencies cache was also keyed by the indexed file COUNT (PR #2519): swapping one app for another of the same size, or editing a manifest, kept the old dependency set, even through reinitializeResolver(). Detection now records what it read: indexed source files, which a sync's change lists cover, and every other path it read or probed, with a size and mtime signature, including the per-app manifests the extractor gating reads. Before a sync extracts files, it detects again only when files were added or removed since, a source file detection read changed, or one of those paths changed on disk. A scoped sync takes the file list from the index plus the files it adds, so it never walks the project (the first sync in a process used to run a full scan for its first detection). What detection read is kept between detections and dropped by the same change lists and signatures, so detecting again reads only what changed. When the detected set differs from the resolver's, the sync re-initializes the resolver. Its initialize() now clears its caches before detecting, and clearCaches() forgets the declared-dependencies cache with the other per-context memos. Files indexed before a framework change are not re-extracted. A framework like Express applies to every file of its language, and re-reading them all inside a watcher's sync would cost what a full index does. Instead a full index records the frameworks it used, a sync records how detection moved since, and `codegraph status` (text, and --json with reindexRecommended and frameworksChangedSinceIndex) recommends `codegraph index` while they differ. Status only reads that record. Validation: - sync-framework-detection.test.ts (new, 8 cases): Express arriving with a new route file, by full and by scoped sync; a .svelte file added with no manifest change; an edited source file that detection read; a manifest-only edit picked up by the next sync, with the status record, cleared on revert and by a full re-index; a fresh process's scoped syncs detecting once, from the index, with no scan; the equal-size app swap; and the dependency cache forgetting on request. 7 of 8 fail on main's source (the 8th needs the new export). Ten mutations, one per mechanism, each fail a test. - The triage probes: main detects [] after Express arrives and routes.js gets no route; the fix detects express and extracts GET /hello, by full and by scoped sync. For the swap, main's reinitializeResolver() gives [react] and the fix [express]. - Cost on excalidraw (737 files, scoped syncs in one long-lived instance): the detection step takes 8 ms on a sync where nothing it read changed and 8-13 ms when it detects again, against syncs of about 260 ms (add) to 2 s (modify). The first sync in a process detects once (about 300 ms) and no longer scans the project. Reported in PRs #2516 and #2519 by @nk33-dev. This reimplements both fixes independently. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> | 14 小时前 | |
feat(config): add codegraph.json "deprioritize" for ranking-only path down-weighting (#982) (#1463) * feat(config): add codegraph.json "deprioritize" for ranking-only path down-weighting matchesNonProductionDir hardcodes example/sample/fixture/benchmark/demo, so a peripheral tree only the project knows about — optional-skills/, scripts/ — gets no de-prioritization. When helpers there carry generic symbol names, an exact name match hands them a large bonus and they crowd out the product code that answers the query (#982). deprioritize is the RANKING counterpart to exclude: those paths stay indexed and findable, they just stop outranking first-party code. It is deliberately distinct from the corpus-frequency discount, which keys on a name being common and is near-inert on #982's own repro where only two symbols are named usage. The -15 path penalty alone is not enough, and measuring showed why: on that repro a usage() helper sits at 74.8 against 51.2 for the top product symbol, so -15 lands at 59.8 and still leads. The path penalty is additive and the name bonus it must counter is additive and larger. A de-prioritized path is saying its symbol NAMES are not the answer, so the exact-name bonus is damped to 0.25x there as well — damped, not zeroed, so the tree still ranks when it genuinely is what you asked for. Refs #982 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SKXAJMrVrdHS5Uco6ABtky * fix(config): read deprioritize lazily and apply it in explore too Review of the first cut found two real defects. The matcher was built once in wireLayers(), which runs only from the constructor and from reopenIfReplaced(). The MCP server keeps one CodeGraph per project root alive for its whole lifetime, so editing codegraph.json appeared to do nothing until the process restarted -- exclude and include do not behave that way. The predicate now reads loadDeprioritizePatterns() per call (mtime-cached, one stat) and memoizes the compiled matcher on the pattern array's identity. A regression test writes the config after opening the project and fails on the old code. Explore passed no matcher to scorePathRelevance at either of its two call sites, so the setting only half-applied -- and #982's reproduction rows B, C and D are all codegraph explore, which made this the surface the issue actually reports on. Both sites now pass it. Explore's hard early-continue filters and its non-production budget cap are deliberately NOT joined: those REMOVE content, and deprioritize is a ranking lever by definition. README narrowed accordingly -- it previously claimed this extends the built-in list, which overstated it. Also from review: scorePathRelevance takes a boolean rather than a predicate (the caller already evaluated it, and it was being invoked twice per result), the predicate body is exception-guarded so a bad path can never take a search down, the misplaced const moved out from between imports, two vacuous test assertions tightened, and tests added for the single-penalty invariant, the deliberate isTestQuery asymmetry, and a query that genuinely targets the de-prioritized tree. Refs #982 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SKXAJMrVrdHS5Uco6ABtky * fix(search): derive the deprioritize name-bonus damping instead of picking it (#982) The 0.25 scale was a guess. On a 62k-node django index it measurably breaks the "discount, don't erase" rule the lever is built on: exact-name queries for symbols that live only in the de-prioritized tree (child, parent, method) fall behind mere prefix matches (children, all_parents, method_decorator). The prefix arm of nameMatchBonus tops out below 40, and a de-prioritized node also takes the -15 path penalty, so 80 * SCALE - 15 > 40 is the bound that keeps a damped exact match ahead of a prefix match at any corpus shape. 0.75 clears it; crowd-out removal is nearly identical to 0.5 (39 vs 40 of 88 peripheral top-10 slots cleared on django), so the deeper discount bought almost nothing and cost the invariant. Two tests pin the bound, including one that fails at the old 0.25. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> | 2 个月前 | |
fix(java): a record is indexed as a class, with its components' fields and accessors Neither engine treated record_declaration as a class type, so a Java record left no node of its own. Its methods and explicit constructor became package-level functions (p::norm, p::Point), a nested record's methods were filed under the class around it, compact constructors, static fields and records nested in a record were dropped, and calls on a record-typed value resolved to nothing or to a same-named member of another type. The Java extractor and the kernel now treat record_declaration as a class and a compact constructor as a method. Each component becomes a private field ("<Type> <name>") and a public accessor name() with the component's type as its return type, unless the body declares it; they are synthesized before Lombok's members, the same way. An implicit accessor is reached only through what types its receiver: a typed receiver, a method reference, a qualified call, or a call from inside the record. The name-only strategies (the receiver-word guesses, exact-name and fuzzy matching) no longer settle on one, so result.content() on a library type stops linking to whichever record has a content component. Records usually sit in sealed hierarchies, and that exposed a resolver gap: the member-type reader took the head "sealed interface Blocks permits Block" for a member named Block of type "permits", typed the receiver of Block.fromKey(...) inside the record as a built-in and dropped the call. "permits" and "throws" are now among the words that are not a type there. Validation: - java-records.test.ts, 5 cases on both engines (10 tests), all red on main; the sealed-permits and name-only-guess cases are each red without their resolver line. - Kernel parity: TortureRecord.java in the torture and CRLF runs; real-repo sweeps at full parity with deferrals unchanged (jextract 218/218, spring-modulith 682/682, playframework 801/801, kestra 2997/2997, spring-ai 2491/2492, Minestom 1859/1894). - A/B against main d02a88d0, every bucket triaged by sampling (edge sites added/removed): jextract +207/-1, spring-modulith +375/-1, playframework +80/-2, Minestom +17,963/-287, spring-ai +8,806/-471, kestra +10,077/-497; most retargets are a constructor function becoming its record. The accessor rule drops only name-only results (spring-ai 1,063, kestra 765, Minestom 408 sites at 0.65-0.8 confidence); no typed call, method reference or qualified call moves. click and gin byte-identical. - Cost (process CPU, median of 3 interleaved indexes): Minestom 47.2s -> 50.3s (+6.6%, for 17% more nodes and 23% more edges), spring-ai 93.5s -> 94.8s (+1.4%). Original PR #1946 by @danusha2345. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> | 4 天前 | |
fix(index): a deleted file git still lists is skipped, not reported as unreadable (#2928) A full index reads every path the scan lists, and `git ls-files` keeps listing a tracked file deleted from the working tree until the deletion is staged. So `codegraph init` and `codegraph index` stat'ed the missing file, got ENOENT and reported "1 files could not be read" with an errors.log entry. The errored file also kept the run from recording the commit it indexed, so `codegraph status` fell back to a full scan afterwards. The batch reader in indexAll and the single-file indexFile now take ENOENT and ENOTDIR to mean nothing is there: the file is skipped and counted as skipped, so the run reads as complete rather than PARTIAL. A symlink that points at nothing is skipped the same way, as the directory walk for projects without git already did. Every other read failure (EACCES, EIO, a folder where a file was) is still reported. On POSIX a path under a file fails realpath with ENOTDIR, which validatePathWithinRoot rejected as if it escaped the root; it is now judged like any missing leaf, by its nearest existing ancestor, so it reaches the reader and is skipped too. Discovery is unchanged, with no extra stat per tracked file. Validation: - index-gone-tracked-files.test.ts (new): all 5 cases fail on main's source and pass with the fix: committed and staged deletions, a folder replaced by a file, a dangling symlink, and EACCES still reported by both the full index and indexFiles. Mutating the single-file path, the skipped tally or the EACCES boundary fails the test each time. - security.test.ts: a path under an existing file validates, and one under a symlink to a file outside the root is still refused. - The issue's CLI repro: main prints "1 files could not be read" and writes errors.log; the fix prints "Indexed 1 files", writes no errors.log, and status reports nothing pending. Reported in PR #2910 by @xianzhi-xzw, who also filed issue #2904. This reimplements the fix independently, at read time rather than with an lstat of every tracked file during discovery. Fixes #2904 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> | 19 小时前 | |
Add WASM fallbacks for tree-sitter and SQLite, fix installer Replace native tree-sitter with web-tree-sitter + tree-sitter-wasms for universal cross-platform support. Add node-sqlite3-wasm as a fallback when better-sqlite3 native bindings aren't available. Move better-sqlite3 and sqlite-vss to optionalDependencies so installs never fail. Fix installer to use npx fallback when global npm install fails, so MCP config, hooks, and quick-start instructions all work without the bare codegraph command in PATH. Fix tests: update schema version expectation, fix db test paths and method names, extract MAX_OUTPUT_LENGTH as module constant, normalize Windows path separators in import resolver. | 8 个月前 | |
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> | 25 天前 |
| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
| 14 小时前 | ||
| 4 天前 | ||
| 15 小时前 | ||
| 11 小时前 | ||
| 19 小时前 | ||
| 6 天前 | ||
| 20 小时前 | ||
| 11 小时前 | ||
| 5 天前 | ||
| 13 小时前 | ||
| 25 天前 | ||
| 19 小时前 | ||
| 3 个月前 | ||
| 6 天前 | ||
| 6 天前 | ||
| 2 个月前 | ||
| 29 天前 | ||
| 14 小时前 | ||
| 2 个月前 | ||
| 4 天前 | ||
| 19 小时前 | ||
| 8 个月前 | ||
| 25 天前 |