-
Notifications
You must be signed in to change notification settings - Fork 0
Comparing changes
Open a pull request
base repository: dpsanders/codegraph
base: main
head repository: colbymchenry/codegraph
compare: main
- 15 commits
- 92 files changed
- 8 contributors
Commits on May 22, 2026
-
docs: validate Windows PRs via Parallels+SSH; gitignore .parallels
Document the Mac-host -> Parallels Windows 11 SSH workflow for validating Windows-specific behavior, the win32-gated test convention (it.runIf), and guest toolchain quirks (PATH refresh, Windows-local clone, VC++ ARM64 redist). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Configuration menu - View commit details
-
Copy full SHA for c9d2a25 - Browse repository at this point
Copy the full SHA c9d2a25View commit details -
fix: remove dead try/catch in insertNode; fix SENSITIVE_PATHS case-se…
…nsitivity (colbymchenry#327) Drop the no-op try/catch around insertNode.run, and lowercase the Windows SENSITIVE_PATHS entries so validateProjectPath's case-insensitive check actually blocks c:\windows. Adds a validateProjectPath test (POSIX + Windows-gated); the Windows-gated case was validated on a real Windows 11 VM. Closes colbymchenry#327
Configuration menu - View commit details
-
Copy full SHA for 7d5dd4c - Browse repository at this point
Copy the full SHA 7d5dd4cView commit details -
fix: validate projectPath in MCP handler to block sensitive directori…
…es (colbymchenry#230) Validate projectPath in getCodeGraph so MCP clients can't open a codegraph in a sensitive system directory. Guarded with existsSync so nested/not-yet-created sub-paths still resolve up to the default project (preserves issue colbymchenry#238). Adds MCP-handler rejection tests (POSIX + Windows-gated); validated on a real Windows 11 VM. Closes colbymchenry#230
Configuration menu - View commit details
-
Copy full SHA for 02ea482 - Browse repository at this point
Copy the full SHA 02ea482View commit details -
fix(mcp): make session-marker symlink resistance work on Windows (col…
…bymchenry#337) O_NOFOLLOW is undefined on Windows (libuv ignores it), so the bitwise-OR silently dropped it and markSessionConsulted would follow a pre-planted symlink at the tmp marker path — the CWE-59 gap colbymchenry#280 closed on POSIX but not Windows. Add a cross-platform lstatSync isSymbolicLink() refuse-check before openSync (O_NOFOLLOW stays as the atomic, TOCTOU-free guard on POSIX). The existing Session-marker-symlink-resistance test now passes on Windows. Refs colbymchenry#280 Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Configuration menu - View commit details
-
Copy full SHA for 6f4b521 - Browse repository at this point
Copy the full SHA 6f4b521View commit details -
docs(readme): link support badges to sections (colbymchenry#326)
Point the previously-dead (#) support badges at new Supported Platforms / Supported Agents sections, grouped with Supported Languages near the bottom of the README. Co-authored-by: Andrew Barnes <bortstheboat@gmail.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Configuration menu - View commit details
-
Copy full SHA for fd6a649 - Browse repository at this point
Copy the full SHA fd6a649View commit details -
fix(mcp): reap serve --mcp child when parent is SIGKILL'd (colbymchen…
…ry#286) Add a PPID watchdog to the MCP server so a `codegraph serve --mcp` child terminates when its host (Claude Code, opencode, …) is force-killed — OOM killer, `kill -9`, container teardown — and the stdin close handlers don't fire. The child would otherwise linger indefinitely, holding inotify watches, file descriptors, and the SQLite WAL. Also propagates the host PID across the `--liftoff-only` re-exec (CODEGRAPH_HOST_PPID) so the watchdog reaps the orphan on the from-source path too, not just the bundled launcher. Poll interval is CODEGRAPH_PPID_POLL_MS (default 5000ms, 0 disables). Resolves colbymchenry#277. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Configuration menu - View commit details
-
Copy full SHA for fb45959 - Browse repository at this point
Copy the full SHA fb45959View commit details -
feat(cli): add callers, callees, impact commands for CLI/MCP parity (c…
…olbymchenry#204) Add `codegraph callers`, `codegraph callees`, and `codegraph impact` CLI commands, bringing the CLI to parity with the codegraph_callers/callees/impact MCP tools — so the graph-traversal queries work in scripts, CI, and git hooks without a running MCP server. All three support `--path` and `--json`; `impact` groups output by file to match the MCP layout. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Configuration menu - View commit details
-
Copy full SHA for 1f11de7 - Browse repository at this point
Copy the full SHA 1f11de7View commit details -
docs(readme): add codegraph_explore to the MCP Tools table (colbymche…
…nry#226) Add the missing `codegraph_explore` row to the 'MCP server exposes these tools' table — tools.ts exports 9 tools but the table listed 8. (The PR's Node-badge bump was dropped: that badge was replaced by 'Node.js bundled · none required' when the runtime became self-contained.) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Configuration menu - View commit details
-
Copy full SHA for f366222 - Browse repository at this point
Copy the full SHA f366222View commit details
Commits on May 24, 2026
-
Release 0.9.4: framework-aware routing + dynamic-dispatch coverage + …
…retrieval improvements (colbymchenry#365) * feat(resolution): close dynamic-dispatch coverage holes (callback synthesis + django ORM) Static tree-sitter extraction misses calls whose target is computed or indirect, so flows through callbacks, observers, and descriptors were absent from the graph. - callback-synthesizer.ts: whole-graph pass after base resolution. Detects registrar/dispatcher channels (field-backed observers + string-keyed EventEmitters), correlates registration sites, and synthesizes dispatcher->callback `calls` edges (provenance:'heuristic'). Records the registration site (registeredAt) in edge metadata. Precision guards: named handlers only, registrar-name match, event fan-out cap. - frameworks/python.ts + resolution/{index,types}.ts: claimsReference hook + django ORM resolver (_iterable_class -> ModelIterable.__iter__). - extraction/tree-sitter.ts: extract named nested functions so inline named handlers become linkable nodes. trace(mutateElement, triggerRender) and trace(_fetch_all, execute_sql) now connect; node count stable (no explosion). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(mcp): self-sufficient flow output + fix explore budget regression - Surface synthesized-edge evidence in trace, the node trail, and context call paths: a dynamic-dispatch hop now shows "callback via onUpdate @App.tsx:3148" with the registration site inline (and trace inlines each hop's call-site source line) -- the exact glue agents previously Read/Grep'd to reconstruct. - Fix non-monotonic explore output budget: the 500-5000 file tier capped maxCharsPerFile at 2500, BELOW the <500 tier's 3800, so on god-file projects (excalidraw's 415 KB App.tsx) one explore returned <1% of the file and forced a Read. Raised to 6500/file, 28000 total. - Stop explore from inviting Read: truncation/trim notes said "use Read for more"; they now steer to another codegraph_explore and treat returned source as already Read. Measured on excalidraw: best-case flow answer went from 5 reads / 131s to 0 reads / 73s with ~3-4 codegraph calls. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(agent-eval): coverage probes, block-read hook, and design docs Dev-only validation harness for the dynamic-dispatch coverage work: - probe-{trace,node,context,explore}.mjs: drive MCP tools against a built index without a full agent run. - block-read-hook.sh + hook-settings.json: PreToolUse experiment that denies source Reads to measure codegraph sufficiency (forced Read-0). - docs/design/: callback-edge-synthesis + dynamic-dispatch-coverage playbook. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(resolution): bridge React boundaries — re-render + JSX child synthesis Closes the two dynamic-dispatch hops that broke "state mutation -> on-screen render" flows in React apps. Both are call-invisible (React-internal) but the code between them is fully call-connected, so one synthesized edge each makes the whole flow trace end-to-end. - reactRenderEdges: setState(...) re-runs the component's render(). For each class with a render method, link sibling methods calling this.setState -> render. The setState gate keeps it to React class components. - reactJsxChildEdges: a component that returns <Child .../> mounts Child. Link parent -> each capitalized JSX child, resolved to a component/function/class node (the resolution gate drops TS generics like Array<Foo>). File-oriented, capped per parent. - Surface both in synthEdgeNote (trace + node trail) and context call-paths. Validated on excalidraw: trace(mutateElement, renderStaticScene) now connects in 6 hops across callback -> react-render -> jsx-child; 1 + 46 + 280 synthesized edges, node count stable (no explosion). Partial coverage is worse than none: react-render alone raised agent reads (revealed a hop it then drilled); adding the jsx hop closed the flow and dropped reads to 0-1. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(claude): retrieval performance contract + coverage validation methodology Add a "Retrieval performance & dynamic-dispatch coverage" section so future changes/PRs don't silently regress agent retrieval: - the explore call+output budget table by repo size, with the monotonic-per-file invariant (the bug that started this: <5000 tier's 2500 < <500 tier's 3800). - the "partial coverage is worse than none" principle. - the required validation methodology (small/medium/large x >=3 prompts per language x framework; deterministic probes + agent A/B; pass bar). - the Excalidraw worked example (before/after numbers) as the template to replicate for every language/framework. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(claude): use full n=4 measured range in Excalidraw worked example Best run 0 Read/3 cg/76s; typical ~1 Read/~4 cg; occasional over-drill outlier. Report the range, not a single run — run-to-run variance is large. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(mcp): steer flow questions to codegraph_trace first (tightens variance) codegraph_trace was absent from every steering intent map — all three guidance files routed "how does X reach Y" to context+explore, never to the trace tool. So agents used trace only by chance; when one didn't, it floundered reconstructing the path with search+callers (an 18-call run vs ~6 for trace-users). Add codegraph_trace to the intent map + a "flow" common chain (trace from->to FIRST = the whole path in one call, then ONE explore for bodies) across all three synced files (server-instructions, instructions-template, .cursor rule). Validated on excalidraw (hard "to the screen" Q, n=4 before/after): - call count 3-10 -> 3-4 (over-drill outlier gone) - duration 64-112s -> 51-74s - trace adoption 3/4 -> 4/4; search+callers path-reconstruction -> 0 - fully-clean runs (0 Read, 0 Grep) 0/4 -> 2/4; best 3 cg / 0 / 0 / 51s Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(resolution): Vue SFC template coverage (events + kebab components) The .vue extractor only parses <script>, so template usage is invisible — handlers and kebab child components used only in <template> have no edge. Add a vueTemplateEdges channel (scoped to the <template> block of .vue files): - event bindings: @click="onClick" / v-on:submit="save" -> handler method/function (skips inline arrows and $emit; resolves same-file first to avoid cross-app mis-match in monorepos). - kebab child components: <el-button> -> ElButton (PascalCase children like <VPNav/> are already caught by the JSX channel via the SFC component node). Surface vue-handler in synthEdgeNote (trace/node trail) + context call-paths. Validated on vue repos (reindex, no node explosion): - vue-handler edges: vitepress 15, vben 404, element-plus 603 — all precise (code-login @submit -> handleLogin, register @submit -> handleSubmit, ...). - callers(handleLogin) now includes the login component (was 0); each monorepo app's login resolves to its own same-file handler. - composition: PascalCase + kebab work; element-plus's el-/filename naming (el-button -> button.vue) is a known library-prefix limitation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(playbook): record Vue validation in coverage matrix + limits Vue / Nuxt row → ✅ template events + composition (vitepress S / vben M / element-plus L); 🔬 reactive→render (vue-core Proxy runtime, deferred). §7: Vue results + the two real limits — composable-destructure handlers (@click="closeSidebar" from useSidebarControl, a data-flow frontier) and prefix-convention kebab (el-button→button.vue). Agent reads dropped in every size; strongest where handlers are local functions. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(resolution): resolve Vue composable-destructure template handlers @click="closeSidebar" where `const { close: closeSidebar } = useSidebarControl()` previously didn't resolve — the handler is a destructured composable return, not a local fn node. Now: parse the SFC's `use*()` destructures into alias→{composable, key}, and for an unresolved template handler follow alias → composable → the returned member (`close`) defined in the composable's file. Precise-only: no fallback to the composable itself (the component already has a static useX() call edge), so we add an edge only when the specific returned fn is found. Validated: vitepress Layout @click→close / @open-menu→open (in composables/ sidebar.ts); sidebar-flow agent run dropped 6→0 reads (best case). element-plus's fallback-only matches correctly drop to 0; node counts stable; direct handlers (vben handleLogin) unaffected. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(playbook): composable-destructure handlers now resolved (Vue) @click="closeSidebar" → composable returned fn; vitepress sidebar 6→0 reads. Remaining Vue limits: prefix-convention kebab + reactive→render frontier. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(extraction): extract function-valued properties of exported-const objects `export const actions = { default: async () => {...} }` (SvelteKit form actions, and general JS handler/route/reducer maps) left the arrow functions unextracted — the walker skips object-literal functions (deliberately, to avoid inline-object noise like `ctx.set({...})`). So an action's body (and its calls) was invisible. Now: for an EXPORTED const whose initializer is an object literal, extract each function-valued property (arrow / function expression) as a function named by its key and walk its body. extractFunction gains a nameOverride so ONLY this explicit path names pair-arrows — inline-object arrows reached by the general walker still fall through to the <anonymous> skip, so no noise returns. JS/TS-gated. Validated: fixtures extract the actions + walk bodies (default→helper, default→ api.post resolve); SvelteKit detection doesn't break it. Blast radius tiny: excalidraw +1 node, Python (django) +0, Vue repos +0, realworld +11 (the actions). Known residual: a `$lib`-alias namespace-member call (`api.post`) from an extracted action node doesn't resolve even though the same alias resolves for `load` — a deeper resolver interaction, separate from this extraction change. Local/relative calls from actions connect fine. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(playbook): record Svelte validation (already well-covered) + actions fix Svelte/SvelteKit row → already strong (template calls/composition/namespace/load); + exported-const object-of-functions extraction. Lesson: measure before assuming a hole — modern Svelte barely uses on:click={fn}; Svelte needed far less than Vue. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(resolution): connect Express inline arrow route handlers to their services The Express resolver created route nodes but linked handlers via a single regex whose `[^)]+` broke on inline arrows — so `router.post('/x', async (req,res) => {...})` (the dominant modern pattern) connected to NOTHING, and the anonymous handler's body (the actual request→service flow) was lost. The whole inline-handler API was unreachable: e.g. realworld's `POST /users/login` route → 0 edges. Now: match the route head, span the full call with a string-aware balanced-paren scan, and for an inline arrow handler extract its body's calls (string-aware brace scan) and attribute them to the route node as `calls` edges. A RESERVED denylist drops res/req/builtin methods (json, next, status, ...) to keep only business calls. Named-handler routes keep the existing reference behavior. Validated: realworld POST /users/login → login (auth.service); 19 precise route→service edges (was 0) — POST /articles→createArticle, .../favorite→ favoriteArticle, etc., no json/next noise. ghost +65 inline-handler edges. No node explosion (ghost 40767, parse 3394 unchanged). Framework-scoped: zero blast radius off Express. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(playbook): record Express validation (inline-handler fix) Express/Koa row → resolver already handled named handlers; the real hole was inline arrow route handlers (router.post('/x', async (req,res)=>{...})) — fixed: route→service body calls (realworld 19 / ghost 65 edges, no explosion). Agent A/B muddied by repo size (realworld tiny) / complexity (ghost layered API). Lesson inverse of Svelte: Express's dominant pattern WAS the uncovered one. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(playbook): record NestJS validation (already well-covered) NestJS row → resolver handles @decorator routes; DI controller→service (this.svc.method) resolves correctly at scale (immich: addUsersToAlbum→addUsers, etc.). Agent A/B: codegraph eliminated Grep (0 vs 3). No dynamic-dispatch hole. Surfaced a general hygiene gap (not NestJS): committed dist/ build output gets indexed (no default build-dir ignore) — narrow (real apps gitignore dist/), deferred as a core-indexer follow-up. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(resolution): Rails RESTful resources routing → controller#action The rails resolver only saw explicit `get '/x' => 'c#a'` routes, so apps using the dominant `resources :articles` / `resource :user` RESTful routing had ZERO route nodes (realworld + spree: 0 routes despite full routes.rb files). The whole request→controller flow was disconnected. Fix (frameworks/ruby.ts): - extract: expand `resources`/`resource` into their REST actions (only/except filters; pluralize the singular `resource :user` → users_controller), emit a precise `controller#action` ref per action. Explicit routes now also reference `controller#action` instead of a bare ambiguous `action`. - resolve: new `controller#action` pattern → the action method in <ctrl>_controller.rb (file convention + controller-class fallback). - claimsReference: claim `controller#action` refs so resolveOne's pre-filter doesn't drop them before resolve() runs (same hook the django ORM work needed — these refs name no declared symbol). Validated: realworld 0→16, forem 0→635 precise route→action edges (GET /articles→ index, resource :user→users#show, etc.), pluralization correct, no node explosion (route nodes proportional to resources). Agent A/B (forem, large): with codegraph 1-4 reads / 0 grep / 47-53s vs without 4-5 reads / 2-3 grep / 66-85s. Framework- scoped (zero blast radius off Rails). Residuals: Rails Engine routing (spree mounts an engine), ActiveRecord dynamic finders (metaprogramming frontier). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(resolution): Spring bare + class-prefixed route mappings → controller method The Spring resolver required a string path in the mapping regex, so BARE method mappings (`@PostMapping` with the path on the class-level `@RequestMapping`) were missed — the dominant multi-method-controller pattern. realworld's two-action ArticleFavoriteApi only linked one method; halo had 28 routes for 2444 files. Fix (frameworks/java.ts): - Treat class-level `@RequestMapping` as a PREFIX (not a bogus route) and join it onto each method's path. - Match verb-specific mappings (@GetMapping/@PostMapping/...) BARE or with a path. - Also handle method-level `@RequestMapping(value=..., method=RequestMethod.X)` (older style) — restored after an initial cut dropped it (mall regressed 292→1; caught by the regression check). Validated: realworld 13→19, mall 246 (all precise, class prefix joined: GET /subject/listAll→listAll, POST /articles/{slug}/favorite→favoriteArticle + DELETE→unfavoriteArticle), no node explosion. DI controller→service resolves (article→findBySlug, updateArticle→canWriteArticle). Agent A/B (mall cart flow): with codegraph 0 reads/0 grep vs without 2/2. Residuals: halo's complex custom patterns (9/29 resolve); Spring Data JPA derived queries (metaprogramming frontier). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(playbook): record Spring validation (bare-mapping routing fix) Spring row → bare @GetMapping/@PostMapping + class @RequestMapping prefix join → route→method (realworld 13→19, mall →246); DI controller→service resolves. A first cut regressed mall 292→1 (dropped @RequestMapping-on-method), caught by the route-count regression check. Residuals: halo custom patterns, JPA derived queries. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(resolution): Django DRF router.register → ViewSet Django's ORM (_iterable_class, prior work) and URL routing (path/url/as_view→view) were already covered. The remaining hole: DRF `router.register(r'articles', ArticleViewSet)` — the core CRUD endpoints — wasn't extracted (only path()/url()), so a DRF API's main resources connected to nothing (realworld's ArticleViewSet: 0 callers). Fix (frameworks/python.ts): match `.register(r'prefix', XViewSet)` → route→ViewSet class. The STRING first arg distinguishes DRF router.register from `admin.site.register(Model, Admin)` (model class first arg); View/ViewSet suffix keeps it to viewsets. The ViewSet class resolves via the existing View/ViewSet pattern. Validated: realworld VIEWSET /articles → ArticleViewSet (was 0). Narrow in corpus (realworld 1 router; wagtail=path, saleor=GraphQL) but real for DRF-router APIs. Agent A/B (wagtail Page flow, medium): with codegraph 4-7 reads / 1-4 grep / 58-81s vs without 7-9 reads / 6 grep / 82-86s. No regression (wagtail/saleor route counts unchanged — purely additive). Residuals: signals, DRF inherited viewset actions, GraphQL resolvers. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(resolution): Laravel route → precise Controller@method (not bare action) extractLaravelHandler discarded the controller: `Route::get([UserController::class, 'index'])` and `'UserController@index'` both emitted a BARE `index` ref. With the route in routes/api.php (not the controller file), name-matching mis-resolved every common action to the WRONG controller — realworld's GET user → ArticleController.index (should be UserController), GET articles/feed → ArticleController (should be FeedController), etc. The routes existed but pointed at the wrong handler. Fix (frameworks/laravel.ts): emit precise `Controller@method` (array + string syntax, namespace-stripped) and `claimsReference` it so resolveOne's pre-filter doesn't drop it before Pattern-4 resolveControllerMethod runs (the recurring hook, also needed by django ORM + Rails routing). Validated: realworld all routes now resolve to the correct controller; bookstack 267/332 precise (GET pages → PageApiController.list, array syntax). No node explosion. Agent A/B (bookstack page-view, large): with codegraph 2-3 reads / 1-2 grep / 51-60s vs without 4-6 / 3-5 / 60-74s. Residuals: firefly's fluent ->uses()/['uses'=>...] handler format (3/568 resolve), Eloquent dynamic finders. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(resolution): Gin/chi routes on group vars (any receiver, not just r/router) The route regex matched only `(router|r|mux|app|e).METHOD(...)`, but real Gin/chi apps route on GROUP variables — `v1.GET`, `PublicGroup.GET`, `userRouter.POST` — so group-routed apps connected almost nothing: gin-vue-admin had 4 routes for 625 files. Broaden the receiver to ANY identifier; the verb + string-path + handler-arg gates keep it route-specific (e.g. `http.Get(url)` has no handler arg, so it's excluded). Validated: gin-vue-admin 4→259 routes, 257 resolve precisely (POST createInfo→ CreateInfo, GET getInfoList→GetInfoList); realworld stable 24→25 (no regression); no garbage (257/259 resolve, not false positives), node count proportional. gitness (chi, custom handlers) is a residual (26/321). Inline `func(c *gin.Context){...}` handlers still lose their body (anonymous, like Express was) — separate residual. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(playbook): record Gin validation (group-var routing fix) Gin/chi row → routes on ANY group var (v1.GET/PublicGroup.GET), not just r/router (gin-vue-admin 4→259 routes). Agent A/B: 0 reads/0 grep/26-30s vs 3/3/52-53s — cleanest backend win yet. Residuals: inline func handlers, gitness chi custom. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(resolution): ASP.NET feature-folder detection + bare attribute routes Two holes left ASP.NET apps disconnected: 1. detect() only fired on a /Controllers/ dir, root Program.cs/Startup.cs, or a .csproj (which often isn't in the indexed source set). Feature-folder apps (realworld: Features/*/FooController.cs, subdir Program.cs) were never detected → 0 routes despite a full set of controllers. Broaden: scan Controller/Program/ Startup .cs source for ASP.NET signatures ([ApiController]/[Route]/[Http*], ControllerBase, MapControllers, WebApplication, Microsoft.AspNetCore). 2. The attribute regex required a string path, so BARE [HttpGet] (route on the class [Route("[controller]")]) was missed — eShopOnWeb was 24 bare / 2 string. Match bare-or-with-path + join the class [Route] prefix (like the Spring fix). No claimsReference needed: ASP.NET attribute routes are co-located IN the controller with the action, so the bare method-name ref resolves same-file. Validated: realworld 0→19 routes (all precise: GET /articles→Get, POST /articles→ Create, class prefix joined), eShopOnWeb 9→33. Route→action correct + co-located. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(playbook): record ASP.NET validation (detection + bare-attribute fix) ASP.NET Core row → feature-folder detection (realworld 0→19, was undetected) + bare [HttpGet] / class [Route] prefix (eShopOnWeb 9→33, jellyfin 362→399). No claimsReference needed (routes co-located in controller). Agent A/B (eShop): 1-2 reads/0 grep vs 6-7/1-6. Residual: EF Core LINQ. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(resolution): Flask/FastAPI route holes + Python builtin-name handler guard Three fixes that connect the request→route→handler flow for Flask and FastAPI. Validated S/L: fastapi-realworld 12→20, flask-microblog 6→27, Netflix dispatch 290/290 (100%), redash decorator routes 6/6; canonical flows trace end-to-end (login→get_user_by_email, create_user→from_dict). - Flask: the route regex required `def` immediately after `@x.route(...)`, so an intervening decorator (@login_required, @cache.cached) or stacked @x.route lines (one view bound to several URLs) dropped the route. Switch to the findHandler scan (match the decorator, then find the next def) like FastAPI — skips intervening decorators. - FastAPI: the path regex `[^'"]+` rejected the empty path `@router.get("")` (router/prefix-root routes, frequently multi-line). Allow empty path + guard the route name against a trailing space. - Python builtin-name guard (src/resolution/index.ts): a handler named after a Python builtin method (index/get/update/count…) was filtered by isBuiltInOrExternal and lost its route→handler edge. Mirror the dotted-method branch's knownNames guard onto the bare branch — a bare name a declared symbol owns is a real target, not a builtin call. +2 legit edges on realworld, 0 change on the django control (precision held). Tests: new Flask (intervening/stacked decorator) and FastAPI (empty-path, multi-line) extractor cases + a Flask end-to-end integration test (a view named `index` behind @login_required). Also corrects 6 pre-existing stale Laravel/Rails route-ref assertions surfaced by the suite — they expected the old bare action name, but the resolvers now emit precise controller@action / controller#action (from earlier precision commits). Full suite green (781 passed). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(playbook): record Flask/FastAPI validation (decorator + builtin-name fixes) Matrix row Python/Flask+FastAPI 🔬→✅ and a §7 note: Flask intervening/ stacked decorators, FastAPI empty-path routes, the Python builtin-name handler guard, S/L numbers, the login-auth A/B (0–1 read/0 grep with vs 3 read/2 grep without), and residuals (Flask-RESTful class-based add_resource; redash JS file-route false-positives). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(resolution): Drupal route-handler resolution (claimsReference, single-colon controllers, contrib detection) The *.routing.yml extractor and _controller/_form resolver existed but two gaps left most routes unlinked. Validated S/M/L: admin_toolbar 0→14 (14/14), webform 144/208, drupal-core 536→731/836 (87%); canonical flow traverses (getAnnouncements ← /admin/announcements_feed); node count unchanged. - claimsReference: Drupal handler refs are FQCNs (\Drupal\…\Class::method), bare form classes (\…\SettingsForm), or single-colon controller-services (\…\Controller:method). Only the ::method shape survived resolveOne's pre-filter (its member is a known method name); the bare-FQCN forms and single-colon controllers were dropped before resolve() ran. Claim FQCN / Class:method / hook_* refs (same pattern as Rails controller#action). - Single-colon controller match: broaden the controller regex from :: to :{1,2} and tighten the _form branch to !name.includes(':'). - Detection: detect() only checked composer `require` for a drupal/* dep, but a contrib module often has an empty require and is identified only by "name":"drupal/<m>" + "type":"drupal-module" (admin_toolbar → 0 routes). Broaden to composer name/type + a *.info.yml fallback. Remaining unresolved is the entity-annotation handler frontier (_entity_form: type.op) and OOP #[Hook] attributes (Drupal 11 moved ~all procedural hooks to attribute methods — out of scope here). Tests: contrib detection, *.info.yml fallback, claimsReference, single-colon controller. Full suite green (787 passed). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(playbook): record Drupal validation (claimsReference + contrib detection) Add the PHP/Drupal matrix row (✅) and a §7 note: the claimsReference pre-filter fix for FQCN/single-colon handlers, broadened contrib detection, S/M/L numbers (admin_toolbar 0→14, webform 144/208, core 536→731), the route→controller A/B (0 read/1 grep with vs 1 read/2 grep+glob without), and the frontier residuals (entity-annotation handlers, OOP #[Hook] attributes). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(resolution): Axum chained methods + namespaced handlers The Axum route extractor used a flat regex that captured only the first method(handler) of a .route() call and only a bare \w+ handler, so two dominant Axum idioms broke: - method chains: .route("/user", get(get_current_user).put(update_user)) emitted no node for the .put arm — half the API was missing. - namespaced handlers: get(listing::feed_articles) captured `listing` (the module), so the route resolved to nothing. Rewrite with a balanced-paren scan of each .route(...) call, a route node per chained method, and last-::-segment handler names. realworld-axum 12→19 routes, 19/19 resolved (every chained PUT/DELETE/POST now present, feed_articles resolves). Rocket needed nothing (550/556, 99%, attribute macros); crates.io confirms namespaced axum handlers resolve. Residual frontier: actix runtime routing web::get().to(handler) (the dominant actix style, unextracted; attribute macros 35/51). Fix is Axum-scoped — the attribute/actix/Rocket path is untouched. Tests: chained methods + multi-line namespaced handler. Full suite green (789 passed). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(playbook): record Rust/Axum validation (chained methods + namespaced handlers) Update the Rust matrix row 🔬→✅ and add a §7 note: the Axum chained-method + namespaced-handler fix (realworld-axum 12→19, 19/19), Rocket already 99%, crates.io (utoipa routes! macro frontier + SvelteKit frontend routes), the update-user A/B, and the actix runtime-routing frontier. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(resolution): Vapor grouped/RouteCollection routing (was 0 routes on real apps) The Vapor extractor only matched (app|router|routes).METHOD("path", use: handler), but real Vapor apps route on a grouped builder inside RouteCollection.boot(routes:): `let todos = routes.grouped("todos"); todos.get(use: index)` — any var receiver, no path arg (the path is the group prefix). Every real app tested extracted 0 routes (template, SteamPress, SwiftPackageIndex-Server, penny-bot, Feather). Rewrite the extractor: - any receiver (\w+), not just app/router/routes; - optional path segments that may be non-string (User.parameter, :id, a path constant) — the `use:` keyword discriminates a route from Environment.get("X") / req.parameters.get("X"); - a group-prefix map from `let X = Y.grouped("a")` and `Y.group("a") { X in }` so a grouped/nested route gets its full path (todo.delete(use: delete) -> DELETE /todos/:todoID). Result: vapor-template 0→3 (3/3, nested path exact), SteamPress 0→27 (27/27), SwiftPackageIndex-Server 0→14 (14/14 handler resolution). Canonical flow traverses (createPostHandler <- GET /createPost -> createPostView). Route names now carry a leading slash (GET /users), consistent with the other frameworks. Frontier: typed-route enums (SPI's SiteURL.x.pathComponents — handler resolves, path label only) and closure handlers (app.get("x"){ } — anonymous). Tests: grouped RouteCollection, self.handler + non-string segments, use:-discriminator. Full suite green (792 passed). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(playbook): record Vapor validation (grouped RouteCollection routing) Update the Swift/Vapor matrix row ⬜→✅ and add a §7 note: the extractor was dead on real apps (0 routes everywhere); rewrote for any receiver, optional non-string paths, .grouped/.group{} prefix tracking, and the use: discriminator. S/M/L all 100% handler resolution (template 0→3, SteamPress 0→27, SPI 0→14), the create-post A/B (0 read/0 grep with vs 1–4 read without), and frontiers (typed-route enums, closure handlers). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(resolution): React Router <Route> JSX route extraction react.ts extracted components/hooks and Next.js file routes but returned references: [], so React Router <Route> declarations produced no route nodes or route→component edges. Add <Route> JSX extraction: scan a window after each <Route (so the nested > in element={<Comp/>} doesn't truncate the match), pull path="…" + component={C} (v5) or element={<C/>} (v6) in any attribute order, emit a route node + component reference (resolved by the existing PascalCase resolveComponent). The <Routes> container is excluded via the \b boundary. react-realworld 0→10 routes, 10/10 resolved (/login→Login, /editor/:slug→Editor, /@:username→Profile). No regression on excalidraw (9,290 nodes, 46 react-render synth edges intact, 0 false routes). Tests: v5 component=, v6 element=, <Routes>-container guard. Suite green (794). Frontier: object data-router createBrowserRouter([{path,element}]) (modern v6) is object-based not JSX — not covered. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(playbook): record React Router routing (the React row's routing half) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(resolution): actix-web builder-API routing (web::resource / .to(handler)) Actix's attribute macros were covered, but the dominant actix style is the builder API — web::resource("/path").route(web::get().to(handler)), web::resource("/").to(handler) (all methods), and App .route("/path", web::get().to(handler)). The handler is in .to(handler), not get(handler), so the Axum .route scan extracted nothing — actix-examples had 80 web::resource calls all unlinked. Add an actix block: scan each web::resource("/path") (bounding its method chain at the next resource) for web::METHOD().to(h) pairs, fall back to a direct .to(h) (method ANY), plus the App-level .route("/x", web::METHOD().to(h)) form. actix-examples 51→128 routes, 35→112 resolved (GET /user/{name}→with_param, POST /user→add_user). No regression on Axum (realworld-axum still 19/19). Tests: resource+route, resource direct .to, App-level route. Suite green (797). Frontier: web::scope("/api") prefixes not prepended; anonymous .to(|req|…) closures have no named target. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(playbook): record actix builder-API routing validation Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(extraction): Flutter setState→build synthesis + Dart method body ranges Two changes that connect Flutter's reactive dispatch: - Dart method ranges (foundational): Dart models a method body as a SIBLING of the method_signature node, so every Dart method node had endLine == startLine (signature only) — body-level analysis (callees, context slices, the synthesizer's body scan) saw only `void f() {`. Extend endLine to the resolved body in the shared createNode, guarded to only ever extend (child-body grammars are a no-op; controls excalidraw 9,290 / django 302 unchanged). - Flutter setState→build synthesizer channel (the Dart analog of react-render): for each Dart class with a `build` method, link sibling methods whose body calls setState( → build. setState re-runs build (Flutter-internal, no static edge), so "tap → handler → setState → rebuilt UI" dead-ended at setState. counter initState→build, books build→BookDetail/BookForm. Widget composition needs no synthesis — Dart widgets are explicit constructor calls, already static (compass_app build→ErrorIndicator/HomeButton). Tests: Dart method spans its body; Flutter handler→build synthesis end-to-end. Suite green (798). Frontier: MVVM Command/ChangeNotifier dispatch (no setState) + Navigator.push route-as-widget navigation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(playbook): record Dart/Flutter validation (setState→build + method ranges) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(resolution): Spring Boot Kotlin routing (.kt + fun handlers) Kotlin had zero framework coverage — no resolver listed kotlin, and the Spring resolver was languages:['java'] with a .java-only extract gate and a Java-syntax handler regex (public X name()). Spring Boot Kotlin apps (same @GetMapping/@RestController annotations, .kt files) extracted 0 routes. Extend the Spring resolver: languages ['java','kotlin'], accept .kt, and add a Kotlin `fun name(` alternative to the handler-method regex (Kotlin has no access modifier; the return type follows the name). Also allow Kotlin class modifiers (open/data/sealed) in the class @RequestMapping-prefix detection, and tag route/ref language per file. spring-petclinic-kotlin 0→18 routes, 18/18 resolved; class @RequestMapping prefixes join, stacked annotations skipped, DI controller→repo resolves (showOwner ← GET /owners/{ownerId} → OwnerRepository.findById). Java Spring unchanged (realworld 19/19 — the Kotlin fun and Java public-X alternatives are disjoint per language). Jetpack Compose composition already works (@composable→child are plain function calls). Tests: Kotlin @GetMapping+fun, class-prefix + stacked annotation. Suite green (800). Frontier: Ktor inline-lambda routing, Compose recomposition, coroutines/Flow. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(playbook): record Kotlin validation (Spring Boot Kotlin + Compose) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(playbook): record Lua/Luau validation (module dispatch already covered) Measure-first: Neovim/Roblox dispatch is module-heavy (require + cross-file mod.fn calls), already resolved by general import+name resolution (telescope.nvim 220 imports + 335 cross-file calls; traces end-to-end). The matrix's assumed "callback synthesizer" hole isn't real — event-callback registration (keymap/autocmd/:Connect) is predominantly inline anonymous closures (corpus ~12 inline vs ~2 named), too rare to synthesize. A/B: 0 read/0 grep with codegraph vs 1 read without. No code change; validated. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(resolution): Play Framework conf/routes → controller routing (Scala/Java) Play declares routes in an extensionless conf/routes file (GET /computers controllers.Application.list(p: Int ?= 0)) the file walk never indexed (isSourceFile requires an extension), so Play apps had 0 route nodes. - grammars.ts: add isPlayRoutesFile (conf/routes + *.routes), opt it into isSourceFile, and map it to the no-grammar (yaml-style) path in detectLanguage so the framework resolver extracts it. Narrow match — only ADDS Play routes files, never affects other indexing. - play.ts: a Play resolver — detect (build.sbt/conf), extract (parse each METHOD /path Controller.action(args) line, drop package + args), resolve (Controller.action → the action method in that controller class), claimsReference for the dotted Controller.action handler. computer-database 0→8 routes, 7/8 resolved (the 1 unresolved is controllers.Assets.versioned — Play's framework controller, external); starter 0→4 (3/4). Flow connects request→route→controller→DAO. No-regression (excalidraw 9,290 / suite unchanged). Tests: routes parse + `->` include skipped, conf/routes file detection. Frontier: SIRD programmatic routers (-> include + case GET(p"/x")) + Akka actor message→handler. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(playbook): record Scala/Play validation (conf/routes → controller) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(extraction): C++ inheritance (base_class_clause) + virtual-override synthesis C/C++ direct dispatch already resolves well (redis 29k / leveldb 1.4k cross-file calls). Two changes close the C++ virtual-dispatch gap: - extractInheritance handled base_clause (PHP) but not C++'s base_class_clause, so C++ `extends` edges were missing/partial. Add the C++ branch (emit an extends ref per base type, skipping access specifiers) — leveldb extends 219→298. - cpp-override synthesizer channel (the C++ analog of react-render): for each extends edge, link each base method → the subclass override of the same name, so trace/callees from a virtual/interface method reach the implementation. Gated to C++, capped per class. leveldb 12 precise edges (Iterator::Next/Seek/Prev → MergingIterator), 0 on C (redis) and TS (excalidraw). Test: base virtual → subclass override bridge. Frontier: C callback structs (cmd->proc() → 422-way fan-out, too noisy) and C++ pure-virtual base methods (declarations aren't nodes, so those overrides can't bridge). Suite green (804). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(playbook): record C/C++ validation (inheritance fix + override synthesis) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(resolution): React Router object data-router + Next.js route precision - Object data-router (v6.4+): createBrowserRouter([{ path, element: <Comp/> }]) / { path, Component: Comp } — extract route + component (gated to files using the data-router API; requires a component so a stray `path:` field isn't a route). - Next.js precision: filePathToRoute treated config files (next.config.mjs, vite.config.ts) and a `nextjs-pages/` dir (substring of "pages/") as routes. Require a real page extension (.tsx/.ts/.jsx/.js), exclude *.config.* and _app/_document, and match pages/ + app/ as path SEGMENTS. bulletproof-react 4 bogus config "routes" → 0. Frontier: lazy data-router routes (path: paths.x.path + lazy: () => import()) use variable paths + lazily-imported modules — no literal path/named component. Tests: object-router literal form, config/nextjs-pages exclusion. Suite 806. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(resolution): Flask-RESTful add_resource + tuple methods + broader detection Three Flask gaps closed (redash Flask-RESTful 6→77 py routes; flask-realworld 0→19): - Flask-RESTful: api.add_resource(ResourceClass, '/path') (+ redash's add_org_resource) now extracts a route per path referencing the Resource class, whose get/post verb methods resolve as the handlers. - Tuple methods: @x.route('/p', methods=('POST',)) — the method regex only accepted a list [...]; now accepts a tuple (...) too, so POST/DELETE routes aren't mislabeled GET. - Detection: detect() only checked root app.py for the literal Flask(__name__); broadened to requirements/pyproject/Pipfile/setup.py + any entrypoint file (root or subdir, e.g. conduit/app.py) that imports flask and instantiates Flask(...). flask-realworld (subdir app-factory) 0→19; django not falsely detected. Tests: tuple methods, add_resource. Suite green (808). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(playbook): record frontier pass; test(go): gorilla/mux subrouter coverage Frontier triage after the main sweep — tractable partials closed (React object data-router, Next.js false-positive fix, Flask-RESTful add_resource, Flask tuple methods + detection, gorilla/mux confirmed), and the genuinely hard/low-precision ones (C callback fan-out, metaprogramming finders, reactive runtimes, Akka, anonymous closures, lazy data-router, C++ pure-virtual) left documented with rationale. Adds a gorilla/mux subrouter-var HandleFunc test (confirms the any-receiver handling already covers it). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(benchmarks): A/B with/without codegraph across every language (S/M/L) 37-cell matrix (every flow-relevant language × small/medium/large indexed repos): a headless agent answers one canonical flow question per repo, with the codegraph MCP vs without any MCP. Fresh re-index per cell so the with-arm reflects current resolvers. Result: 75% fewer file reads with codegraph (40 vs 158 across cells), ~70% fewer greps, never more reads in any cell. Biggest wins on medium/large backends (excalidraw 0R vs 9R, spring-halo 0R vs 9R+8 Bash, jellyfin 4R vs 13R+ 21 Bash + a spawned sub-agent); tie zone on tiny repos where the flow fits in 1-2 files. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(mcp): self-sufficient codegraph_trace + CODEGRAPH_MCP_TOOLS allowlist codegraph_trace now returns a complete flow dossier in one call: each hop with its full body inlined (not just the call-site line), plus the destination's own outgoing calls — the last mile agents otherwise explore/Read to get. Validated by A/B (arm I, 6 repos x 2): >= baseline on reads/turns/cost with no wall-clock regression, because one richer trace call displaces the explore+node+Read follow-ups. Sufficiency, not steering: complete context is what stops further investigation. Also adds CODEGRAPH_MCP_TOOLS, an optional comma-separated allowlist that trims the exposed MCP tool surface (inert when unset); used to run the tool-ablation experiment cleanly, and useful for constraining an agent to a minimal surface. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(benchmarks): call-sequence + tool-ablation analysis; agent-eval arms harness Records why codegraph read savings (-75%) under-convert to wall-clock (-16%): the bottleneck is round-trips + the synthesis turn, not reads. Ablation (arms A-I) shows explore is 68% of payload but load-bearing, trace is path-scoped but under-adopted, instruction/description steering cannot match an append-prompt's salience (and regresses), and the shippable win is making the trace output sufficient (arm I). Adds harness: seq-matrix, run-arms/arms-*, parse-arms. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(mcp): line-number codegraph_node + codegraph_trace source output node's code block and trace's inlined hop/destination bodies now carry cat -n line numbers (reusing numberSourceLines, matching codegraph_explore and Read), so the agent can cite or edit exact lines without re-Reading the file just to get them. Consistency across the code-returning tools + edit-workflow sufficiency. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(resolution): Java/Kotlin interface & abstract dispatch synthesis A call through an injected interface (Spring @Autowired svc.list()) or an abstract base dead-ended at the interface method — no static edge to the implementation — so request->service->impl flows broke at the DI boundary. Adds interfaceOverrideEdges: for each class implementing an interface (or extending an abstract base), synthesize interface/base-method -> same-name override 'calls' edges (JVM-gated, capped per class, overload-aware), with an 'interface-impl' trace label. trace + callees now follow the flow into the implementation. Validated on spring-mall: 310 synth edges, node count unchanged (edges only); trace(PmsProductController.getList, PmsProductServiceImpl.list) connects in 3 hops (controller -> service interface -> impl) where it previously dead-ended at the interface. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(playbook): record Java/Kotlin interface-DI synthesizer (probe-validated; agent A/B adoption-gated) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(mcp): codegraph_explore surfaces the execution flow from its named symbols Agents call explore far more than trace and pass a bag of symbol names that spans the flow they're after. explore now resolves those names and surfaces the longest call path AMONG them — riding synthesized dynamic-dispatch edges (callback/react-render/jsx/interface-impl) — leading the output with it, so a flow question answered via explore gets the trace-quality path without switching tools. Precision: ambiguous tokens disambiguated by CO-NAMING (keep candidates whose qualifiedName SEGMENT matches another named token, so 'list' resolves to PmsProductServiceImpl::list not OmsOrderService::list); BFS anchored at named symbols on both ends with <=1 consecutive unnamed bridge (crosses a missing intermediate, never wanders a god-function's fan-out). Validated by probe: spring-mall getList->service-interface->impl (3 hops); excalidraw mutateElement->triggerUpdate->[callback]->triggerRender->[react-render]->render->[jsx]->StaticCanvas (full re-render chain). No flow section on fuzzy queries (safe). Suite green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(mcp): explore-flow resolves qualified Class.method query tokens The agent often passes fully-qualified names to explore (PostEndpoint.publishPost, PmsProductServiceImpl.list) — its most precise input. The tokenizer's file-extension strip mangled Class.method into Class (treating .method as an extension), then the identifier filter dropped anything with a dot, throwing the method away. Now strips only REAL file extensions and keeps qualified tokens, which findAllSymbols resolves exactly; disambiguates ambiguous SIMPLE names by whether their container class is also named (segment match). Validated: 'PmsProductController.getList PmsProductServiceImpl.list' now surfaces getList->interface->impl. (spring-halo's publish flow stays absent — it's reactive/reconciler dispatch with no static edges, a coverage frontier, not an explore-flow gap.) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(claude): record the 'adapt the tool to the agent' retrieval principle The lever that decides whether a retrieval change lands: make a tool the agent already calls do more with the input it already gives; changes that need the agent to behave differently (different tool, query, examples) hit codegraph's low-salience channels and don't land. Captures the validated evidence (sufficiency + explore-flow pass; steering + new-tools + context-fuzzy-flow fail) and points coverage as the remaining lever. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: correct 'cost stays flat' → neutral-to-lower (excalidraw with/without A/B) Fresh with-vs-without A/B on excalidraw (current build, n=3): 3x faster (49s vs 145s), 15x fewer tool calls, ~0 vs 23 reads, and -40% cost ($0.41 vs $0.68). Cost is neutral-to-lower, not flat — compact codegraph answers cache across turns while the without-arm's read/grep thrash is fresh, poorly-cacheable input. Recorded in call-sequence-analysis.md; corrected the CLAUDE.md optimization-target note (still: don't optimize for cost; target wall-clock + tool-call count). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(benchmarks): current-build A/B on all 7 README repos + fix token-measurement bug Re-ran the README benchmark on the current build (7 repos reindexed, median of 4): avg 35% cost / 57% tokens / 46% time / 71% tool calls saved — reproduces the published README (35/59/49/70), no regression. Adds bench-readme.sh + parse-bench-readme.mjs harness. Fixes a token-measurement bug: result.usage is last-turn-only in current Claude Code; must sum per-turn assistant usage for cumulative tokens. Corrects the earlier excalidraw note (its '-34% tokens' was off this bug; real ~90%) and the cost MECHANISM (volume/fewer-turns, not cache-ability — the without-arm's huge token volume is mostly cheap cache-reads, so token savings 57% > cost savings 35%). Cost/time were always correct. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: finalize 0.9.4 — consolidate CHANGELOG + re-validate README benchmark Folds the framework sweep + retrieval work into [0.9.4] (2026-05-24). README benchmark table refreshed with current-build medians (avg 35% cost / 57% tokens / 46% time / 71% tool calls) + a v0.9.4 re-validation note. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(readme): add codegraph_trace to the MCP Tools table Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Configuration menu - View commit details
-
Copy full SHA for 025ebc8 - Browse repository at this point
Copy the full SHA 025ebc8View commit details -
docs(readme): answer directly with codegraph, not via an Explore agent (
colbymchenry#367) Replace the stale "## CodeGraph" example block (NEVER call explore directly / ALWAYS spawn an Explore agent) and the How-It-Works diagram with the validated "answer directly" guidance, and add codegraph_context/trace/explore to the tool table. Interactive A/B (Excalidraw + VS Code, n=3/arm) shows direct codegraph answering beats Explore-agent delegation at every scale: main-session context is ~scale-invariant (~50k), with 0 reads vs 17-26 and ~28% fewer tokens. Record the writeup under docs/benchmarks/answer-directly-vs-explore-agent.md. Docs-only; stays on 0.9.4 (no version bump). Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Configuration menu - View commit details
-
Copy full SHA for 1f3625a - Browse repository at this point
Copy the full SHA 1f3625aView commit details -
Add landing page + Starlight docs site (colbymchenry#375)
* udpated matrix * feat(site): add landing page + Starlight docs site Astro + Starlight site in site/ — a flat/paper editorial landing page plus 18 docs pages seeded from the README. Monochrome theme, hairline rules, square corners, live GitHub star count, light default + dark toggle. Deploys to GitHub Pages via .github/workflows/deploy-site.yml. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Configuration menu - View commit details
-
Copy full SHA for 4509b45 - Browse repository at this point
Copy the full SHA 4509b45View commit details -
docs(readme): link to the website & docs site (colbymchenry#376)
Add a prominent link to https://colbymchenry.github.io/codegraph/ at the top of the README so visitors landing on the repo can reach the site. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Configuration menu - View commit details
-
Copy full SHA for 7f30c4f - Browse repository at this point
Copy the full SHA 7f30c4fView commit details -
feat(site): add Docs & Languages links to the docs header (colbymchen…
…ry#377) Bring the Starlight docs header to parity with the landing nav — now Docs · Languages · GitHub · star pill, in that order. Added via the SocialIcons slot (no full header rebuild, so search/theme/mobile keep working); the text links and star pill are hidden on mobile, where the sidebar already covers navigation. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Configuration menu - View commit details
-
Copy full SHA for 6a2098b - Browse repository at this point
Copy the full SHA 6a2098bView commit details -
fix(site): center docs content on wide screens (colbymchenry#378)
Starlight right-aligns the content against the TOC (margin-inline: auto 0) on pages with both a sidebar and a TOC, dumping all the empty space on the left edge — very lopsided on wide monitors. Center the content in its pane. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Configuration menu - View commit details
-
Copy full SHA for 18a99f5 - Browse repository at this point
Copy the full SHA 18a99f5View commit details -
fix(site): large left-aligned feature icons to match the mockup (colb…
…ymchenry#379) Lay out each landing feature as icon | divider | text — a 64px icon on the left, a full-height vertical rule, then heading + description — instead of a small icon stacked on top. Pure CSS grid (divider via ::before), with smaller icon/gaps on mobile so the text stays readable. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Configuration menu - View commit details
-
Copy full SHA for 1be8e78 - Browse repository at this point
Copy the full SHA 1be8e78View commit details
This comparison is taking too long to generate.
Unfortunately it looks like we can’t render this comparison for you right now. It might be too big, or there might be something weird with your repository.
You can try running this command locally to see the comparison on your machine:
git diff main...main