mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-29 14:49:36 +08:00
main
25
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2afee00cb0 |
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> |
||
|
|
5e0c42cb63 |
fix(security): the sensitive-folder guard refuses links, case variants and \?\ paths (#2929)
The guard that keeps ~/.ssh, ~/.gnupg, ~/.aws, ~/.config and system folders such as /etc and C:\Windows from being opened as a project compared the path as typed. A junction or symlink to one of them, a different capitalization on Windows or macOS (~/.SSH), or a \\?\ or \\.\ spelling on Windows passed, so an MCP projectPath, `codegraph ui` and the viewer's per-file reads all served what was inside. The MCP gate also skipped the guard for a path that doesn't exist and then walked up from it, so ~/.config/no-such-dir reached an index of ~/.config. validateProjectPath now checks the path as spelled (with a Win32 namespace prefix stripped) and its fs.realpathSync.native form. Both are compared with the system set and the system folders' own real paths (macOS /private/etc), and with the home folders as spelled, under the home's real path and by their own real paths (a linked ~/.ssh). Case is folded on win32 and darwin. A path that can't be resolved keeps the lexical check. It stays at one realpath per call: the system folders' real paths are read once and the home folders' are cached for two seconds. The MCP handler also checks the root its up-walk lands on. Validation on Windows 11, with a private home holding a fake token: - Probe: on main 11 of 16 sensitive spellings were allowed, and the token was served through 6 MCP projectPath shapes and 2 viewer roots. With the fix all are refused. Controls (an ordinary folder, its upper-case and \\?\ spellings, the home itself) are still allowed. - codegraph ui end to end: main served the token for 4 of 6 spellings and started the viewer for a fifth; the fix refuses all 6 at startup. - Tests: the 9 new tests that run on Windows fail on main's source and pass with the fix. The POSIX and macOS cases are gated and were not run here. - Cost: validateProjectPath 48 -> 130 us per call, the viewer's per-file resolveProjectFile 700 -> 780 us (medians of 5 rounds, 3 runs). Reported in PR #1930 by @mixxer; this reimplements the fix independently. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
a565f215f1 |
fix(viewer): saved trails stay inside the project when a folder or trail file is a link
validatePathWithinRoot returned the lexical path whenever the path did not exist yet (ENOENT). With .codegraph/ui a link to a folder outside the project and no trails folder yet, the trails folder passed the check, mkdir followed the link, and the trail was written outside the project. The save still answered 403, but only because the list it sends back re-checked the folder that now existed. The trails list also joined each file name onto the checked folder and read it with statSync and readFileSync, which follow links, so a trail file linking to a trail-shaped file outside the project was listed and served. validatePathWithinRoot now judges a missing path by its nearest existing ancestor, and returns null when the root itself is missing. The list reads every file through the same per-file check as a single read, opens it with O_NOFOLLOW | O_NONBLOCK where the platform has them, and checks the open file with fstat. Windows has neither flag; there the per-file check is what refuses a planted link. A save re-checks the folder after mkdir and writes its temp file under a random name with flag wx and mode 0600. The trail author is cached per project instead of per process, and a trail file that declares an unknown format version is skipped. Added on top of the contributor's change: the link tests also run on Windows (a junction and a directory symlink at .codegraph/ui, a file symlink in the trails folder), each test removes its own links before deleting its folder, the helper gets a junction case and three controls (an in-root link above a missing leaf, allowSymlinkEscape above a missing leaf, a missing root), its doc comment names the new missing-path rules, and the viewer changelog lines are reworded and moved next to the other saved-trails line. Validation: 8 tests fail on main and pass here (the helper below a symlink and below a junction, a missing root, a save below a junction and below a symlink, a linked trail file, the per-project author, the format version). Through the real CLI (codegraph ui), main wrote the trail outside the project through a junction and through a directory symlink, and listed a linked outside file with its note; this branch refused all three and wrote nothing outside. Graph dumps of express, zod, excalidraw, flask and gin are byte-identical to main's, and 55 CLI read commands and 45 MCP tool calls answer the same (codegraph_status differs only by its last-indexed time). Index time is unchanged (gin over 9 interleaved rounds: 2645 ms on main, 2687 ms here). Listing 200 saved trails, the list's cap, costs 108 ms on main and 236 ms here on Windows; 10 trails cost 6 ms and 14 ms. Original PR #1667 by @danusha2345. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
33dc2ae72b |
fix(viewer): the routes list's limit counts routes, not one route's calls
The routing manifest has a row per (route, symbol it reaches), and the routes list behind Entry points and the Steps picker folds those rows to one per route. To have rows to fold, it fetched limit * 4 + 1 of them, so an inline handler, which has a row per call in its body, could use up the window by itself, and the routes after it were never fetched. On an Express app with one 24-call inline handler before two routes, limit=3 listed only GET /dense. getRoutingManifest takes a perRoute flag that applies its limit to routes, in the same file and line order, and returns every row of those routes. Without the flag the query and its result are unchanged. Added on top of the contributor's change: the window is still over-fetched, four routes per route shown, because some routes are dropped only after their rows come back (a test or generated handler, an inline route written in a test). A window of exactly limit + 1 routes could hold only those. On hono, whose routes are nearly all registered in test files, Entry points then listed 0 routes instead of main's 28, and the routes list 21 instead of 32. With the over-fetch, every route main lists is still listed at each limit tried (3, 12, 60, 200 and 300), and hono's Entry points list 32. truncated also keeps main's check that the window came back full. Validation: the new limit=3 test fails on main (only GET /dense) and passes here. A project whose test-file routes sort first fails with the PR as first written (it reads as unrouted) and passes here and on main. Routes listed on Wiki.js (132 routes) and realworld-express are identical to main, and on hono they differ only by the additions above. The ui suites and the other route consumers: 34 files, 723 passed, 0 failed, 3 skipped (environment-gated). The changelog line is in docs/viewer-launch-changelog.md, since the viewer is not released yet. Original PR #2370 by @danusha2345. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c54b72692c |
fix(react-router): a JSX index route is the page at its parent's address, inside the layout around it (#2423)
Route objects already read `{ index: true, element }` as the page at the
parent's address and a route object around others as their layout. JSX
routes did neither: `<Route path="/" element={<Layout/>}><Route index
element={<Home/>}/>…</Route>` bound `/` to Layout, the index child was never
read, and Layout's links counted only from `/`.
The JSX branch of scanRoutes now:
- reads `<Route index>` / `index={true}` (and a `path=""` leaf with an
element) as the page at its parent's address, and lets it claim that
address from the layout or the path-only `<Route path>` around it;
- treats a `<Route element>` with `<Route>`s inside as their layout
(`layout:` references), a pathless or `path=""` one included, and threads
those layouts into a table mapped inside it when the table is written in
the same file;
- reads an index route only where its parent's address is written down: an
enclosing route with a readable path, or the router itself
(`createRoutesFromElements(…)`, `<BrowserRouter>`). At the top of a
component's own `<Routes>` the address is wherever another file mounts the
component, and under `path={paths.x}` it is not spelled out;
- reads `path={"agents"}` as a path.
Reading what an element renders: tags inside an attribute are no longer the
page unless the attribute hands one over (`component`, `element`, `page`),
so `<Suspense fallback={<Loader/>}>` no longer binds the spinner; the line
break Prettier writes after `element={` no longer hides the element; a guard
that wraps only `<Outlet/>` is the guard; and `element={<Outlet/>}` renders
nothing of its own.
Validated against the merged #2400 build: 16 control repos byte-identical,
578 of 581 new layout edges name a component of an enclosing <Route> (3
inherit a pre-existing default-import resolution bug), 27 of 27 new
navigations precise, no navigation lost.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
e0bbb6620f |
fix(dead-code): a type that declare module or declare global merges outward is not dead (#2418)
The viewer's Dead code screen listed TypeScript interfaces that augment a
type declared outside the project: chart.js's `PluginOptionsByType` in
`declare module 'chart.js' { … }` (ghostfolio), `interface Window` in
`declare global { … }` (angular-realworld). The library or the runtime
reads them through the type they merge into, so nothing in the repository
names them, and the index recorded them as unexported file-level
interfaces.
TypeScript exports what such a body declares without the keyword. The
binder makes an ambient module body an export context unless it holds an
export declaration (`export {}`, `export =`, `export default x`), and a
namespace nested in one is ambient too. Both extractors now set isExported
that way: isAmbientExport in languages/typescript.ts, shared by TSX and
ArkTS, and is_ambient_export in the kernel's tsjs walker. The exported rule
then drops them, counted under `exported`, and that count's label now
names them. `declare namespace X` is unchanged: in a module file X belongs
to that file.
The resolver sees them as exported too. A file holding `declare global`
was already treated as exporting, so nothing moves there. A `declare
module` member becomes reachable across files by name.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
dd7064698c |
fix(react-router): read route tables another file hands the router (#2400)
A route table kept in a file of its own produced no routes: the ASP.NET
Core React template's AppRoutes array, which App.js maps into
<Route {...rest}>, a routes array imported into useRoutes(routes) or
createBrowserRouter(routes), and codedthemes' one-object-per-file
[MainRoutes, LoginRoutes]. Route objects were read only from a file that
itself names a data router, so these apps had no routes and their
navigate()/<Link to> navigation reached nothing.
The table's own file cannot tell it from a menu's { path, element } list,
so the evidence is the file that hands it over: a useRoutes /
create*Router argument, or a .map whose callback renders a <Route> from
the item's own fields ({...route}, {...rest}, path={route.path}). A new
FrameworkResolver.crossFileNodes hook keeps those routes, reconciled in
runPostExtract on every index and sync (removal-only syncs included):
new ones are inserted with pending references, ones no longer wanted are
removed with their incoming resolution edges resurrected, unchanged ones
keep their edges. The React pass starts from the files that import React
Router, follows the import to the table and the tables it names
(...ApiAuthorizationRoutes, children: x), each under its mount, and owns
its routes by id (route:<file>:<line>:table:<path>).
Route objects (tables and data routers alike): an index: true route is
the page at its parent's address; a route object around others is their
layout (a references edge marked layout: true, as Angular's), and is not
a page of its own where a child claims its address. A route renders past
a *Wrapper or *Route guard, and through a same-file
lazy(() => import(...)) to the module it loads, never a same-named
component in another app. useRoutes joins the file-level gate.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
9a515c3e64 |
fix(dead-code): a method a framework calls by decorator or by name is not dead (#2405)
The viewer's Dead code screen listed two kinds of framework-called methods. A method whose decorator resolves outside the project (`@HostListener`, `@Cron`, `@OnEvent`, `@Process`, `@BeforeInsert`, `@InitBinder`) has no `decorates` edge and no `node.decorators`, only the resolver's unresolved `decorates` row. That row now counts as a decorator, the same evidence the opaque-ancestor rule reads for an `extends`/`implements` row, under the existing `decorated` count. A lifecycle hook on a class that skips the `implements` clause (`ngOnInit` on a `@Component` without `implements OnInit`) had no evidence at all. A new `hooks` rule drops a TypeScript method when its class carries a decorator and an interface outside the index carries the name. What the interface carries is read off the classes that write it out: every one has the method, declared or inherited, at least two and more than half declare it, and more than half of them carry a decorator. TypeScript only, because `implements` is erased there and a framework can only be finding the hook by name. The ancestor walk moves out of overrideCandidates into ancestorsOf, shared by both rules. Declarations are read from the candidate list rather than searched by name: a hook is never called from inside the index, and a name some declaration of which is called is the ambiguous-name rule's anyway. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
9a274a4b47 |
fix(angular): a binding, interpolation or control-flow block links the member it calls (#2398)
The Angular template pass read own-member calls only in event bindings, so
a member a template calls from `[name]="icon()"`, `{{ label() }}`,
`*ngIf="isOpen()"` or `@if (loading())` had no incoming edge. With signals
that is the usual way a template reads state, and the dead-code derivation
listed such members as unused (CleanArchitecture's ThemeToggleComponent
icon and label).
Every render-time expression is now read: property bindings,
interpolations, structural-directive microsyntax, control-flow blocks and
@let. A call of one of the component's own members becomes an
`angular-binding` edge with the binding as `via` and no trigger. It runs at
render, and a trigger drew computed signals as user actions in Steps. A
read without a call counts only for a method-kind member: a getter is
called, a function handed to a child is a fnRef reference, and a field a
call filled is a plain reference. Property reads link nothing, as in
TypeScript. Pipes, object and microsyntax keys, members of other objects,
template locals, strings and comments link nothing either.
Validated on CleanArchitecture, angular-realworld, ngx-admin and Ghostfolio:
272 edges added, none removed, and every registeredAt line checked. The
proshop_mern and next-saas-starter dumps are byte-identical.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
3f5a9e2e1d |
fix(sync): an import links the file it names when that file appears later (#2392)
A sync retried parked failed refs by name_tail (#1240), but an import names a file, a folder or a namespace, not a symbol, so the lookup never found one: the tail of `package:app/b.dart` was `dart`, of `inc/db.php` `php`, of `./req` `/req`. Most languages never parked theirs at all: an import that named no file reached the importing file's own `import` node through matchByQualifiedName (its qualified name is the module path) and resolved there, so `./x` in TS/JS, `import pkg.mod`, `#include "a/b.h"`, `<stdio.h>`, a C# `using` of an outside namespace, Go module paths and R, Solidity, Svelte and Vue imports were consumed by an edge to themselves. A Lua `require` bound the local it is assigned to. The import linked only once the importer changed, or on a full index. - resolveOne's target gate drops an `imports` ref's own import statement, so the ref parks as failed (#2388 did this for Dart alone). - A Lua / Luau `require` resolves to a module file or nothing. - An import written as a path is parked under its last segment's stem (`package:app/b.dart` -> `b`, `./z.js` -> `z`): src/db/reference-tail.ts. - Sync retries the failed imports an ADDED file can satisfy: by its name, stem and folder (importPathKeys), as a failed import's tail or whole name, and by the changed files' namespace / module names as a whole name (C# `using Foo.Bar`, Java `import com.z.*`). Same per-key ceiling. - Schema v12: two partial indexes over failed imports, which the planner needs to avoid reading every failed call that shares a tail; the migration rewrites the tails of path imports parked by an older version. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
69ee3b9a85 |
fix(ui): fall back past a port Windows refuses with EACCES, and say why (#2299) (#2353)
On Windows, a port another program holds exclusively (SO_EXCLUSIVEADDRUSE)
or one inside an excluded port range fails listen() with EACCES rather than
EADDRINUSE. The `codegraph ui` port fallback only moved on after EADDRINUSE,
so it stopped at the first such port. The error then blamed the POSIX
privileged-port rule ("Ports below 1024 usually need elevated privileges")
for ports like 4747 or 49912. Confirmed on Windows 11: a .NET listener on
0.0.0.0:P with ExclusiveAddressUse makes Node's listen(P, '127.0.0.1') fail
with EACCES.
Fix:
- shouldTryNextPort moves past EADDRINUSE on every platform. It moves past
EACCES at any port on Windows, which has no privileged ports, and from
1024 up elsewhere.
- Below 1024 off Windows, EACCES is the privileged-port rule, and the next
port falls under it too. The walk stops there with the message it always
gave, rather than trying 20 privileged ports.
- describeBindFailure takes the platform:
- Windows names the exclusive hold or reserved range and how to list the
ranges (netsh int ipv4 show excludedportrange protocol=tcp).
- POSIX keeps the privileged-port text below 1024. Above it, it says the
system refused permission.
- A walk that ran out says "in use or reserved" (Windows) or "in use or
not allowed" when any port was refused rather than in use.
- Both helpers take the platform as a parameter, like browserOpenCommand,
so the tests check every platform's answer on any host.
This builds on PR #2319 by @sx4im, which advanced on EACCES, used the
Windows wording and added the issue's listen-spy test. That PR advanced on
EACCES on every platform and printed "Windows refused port N ... netsh" on
Linux and macOS too. On POSIX it would also walk 20 privileged ports and
end with "Ports 80-99 are all in use or reserved". This change restricts
both to where they are true. The changelog entry goes in the viewer-launch
file, because the viewer is not released yet.
Verification:
- __tests__/ui-server.test.ts gains three kinds of test:
- The issue's end-to-end test: port N is taken and N+1 is refused through
a listen spy, so the server must land past N+1 and serve 200.
- A pinned-port message test.
- Unit tests of the decision and the messages for win32, linux and darwin.
- Results on __tests__/ui-server.test.ts:
- Main source: 9 failed / 40 passed. The walk fails with "Not allowed to
listen on port 57423. Ports below 1024 usually need elevated privileges".
- With the fix: 49 passed / 2 skipped (existing POSIX-gated tests).
- Built CLI on Windows, with 0.0.0.0:<port> held exclusively:
- `codegraph ui` while 4747 is held: main exits 1 with the privileged-port
message. With the fix it serves on 4748 (GET / 200).
- `codegraph ui --port <held>`: prints the Windows message.
- A 3-port walk over held ports reports "in use or reserved".
- Re-listening on the same server after a real EACCES works on Node 22.22
and 24.16.
- tsc --noEmit is clean and npm run build succeeds. The 20 viewer test
files pass (439 passed, 5 skipped).
- Full suite: 5732 passed, 10 failed, 319 skipped. The 10 failures are
timeouts and EBUSY teardown under load in function-ref,
git-index-currency, index-daemon-rebuild and rust-self-owner. All four
files pass when run alone.
Issue #2299 reported by @ijbranch.
Co-authored-by: Saim Shafique <msaimshafique4664@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
ec738ec7a3 |
fix(viewer): a Steps picture with nothing past its anchor says why (#2274)
Steps from a helper that only computes — the most-depended-on symbol of most libraries — drew one box and no explanation (179 of 304 projects in the browser pass). The side panel now says what the walk looked for, that plain calls fold into the lines, and links to the symbol's callers and callees. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
e12f5bbb92 |
fix(nuxt, astro): a file-routed page is bound to its component (#2267)
A Nuxt pages/x.vue or Astro src/pages/x.astro route linked to nothing, so the viewer's Steps drew a page alone and the routes list never named what serves it. Each page route now references its file's own component (never a same-named one — every index.vue is a component named index), an Astro endpoint its exported verb handlers, and the routing manifest accepts a component as what serves a route, which also lists Vue Router screens. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
41cfc8356f |
fix(viewer): highlighting runs in a worker the server can stop (#2266)
The viewer's server classifies a code slice with the engine's tree-sitter grammars on its one thread. The COBOL grammar's scanner loops forever on a line like ` .`, which a cobolcraft Flow card's window ends with, so the whole server froze — /api/stats included — and a parse inside WebAssembly cannot be interrupted from JavaScript. Slices are now classified in a worker that is terminated past a 2 s deadline; the slice is served plain and the next one gets a fresh worker. Request-time parses also carry a progress-callback budget. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c04cb5c629 |
fix(viewer): the Map opens on a picture, not a single box (#2270)
pickDefaultRoot ignored files loose in the repository root, so git's top-level .c files made builtin/ the program's root. And a source folder whose files all sit in one folder (Express lib/, R's R/, Erlang src/) drew one box at any depth; pickDefaultView opens the whole repository then, when it draws more. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
95f6f0f4a5 |
fix(viewer): a directed Flow looks up both ends by their exact names (#2262)
The Flow strip's from/to were run through explore's free-text token filter, which drops an Objective-C selector (`initWithFrame:`), a Ruby predicate (`valid?`) and any name under three letters (`ok`) — then reported the OTHER end as naming nothing in the index. Directed mode now takes the two names as written; explore's named mode is unchanged. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
81e08b76ed |
fix(viewer): pages that went blank — repeated ids, and Steps on a function full of checks (#2264)
* fix(viewer): a repeated id no longer blanks Entry points or a component's Symbol view The viewer keys its lists by id and a repeat stops the page drawing. The routes list was a row per (route, edge), so an inline handler came back once per call in its body; it is now one row per route — its bound handler, or the route itself as an inline handler. A Vue/Svelte/Astro component's members listed its script's symbols twice, plus the component and its file; each member is now listed once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(viewer): Steps reads a function full of checks in one pass The in-order reading kept every way forward through an `if` with no `else` twice — under its condition and without — so eighty checks that draw nothing (jsoup's `parse`) made 2^80 ways and the page died on a stack overflow. Ways waiting at the same place are merged, keeping the conditions they all share. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
068c422e1d |
fix: a minified or bundled script is generated, and generated files are not hubs (#2122)
Found by the README-wide sweep: vendored bundles topped "most depended on"
across many repos — delphimvcframework's bundled `Buffer` (6,162) and four
`n`s (2,386 each), retrofit's docs `main.js` `t`/`e`/`i`, TestBox's
`__webpack_require__` — because only `*.min.js` was recognized as generated.
- detectGeneratedFile (index time, persisted as files.generated) now also
flags a .js/.mjs/.cjs file whose text is mostly lines of 1,000+ chars AND
whose long lines are code (≥3% `;{}(),`) — not an ordinary file carrying
one long base64 string — and any file that defines webpack's
`__webpack_require__` loader, however readable its lines. The path rule
also matches `-min.js` (underscore-min.js).
- Entry points' hubs skip nodes in generated files, as they skip tests.
Scanned every .js file of express, axios, lodash, excalidraw, typeorm, zod,
trpc, hono, bulletproof-react: zero new flags on hand-written code; newly
caught: lodash vendor/underscore/underscore-min.js, retrofit website
main.js (754 KB) + prism.js, TestBox syntaxhighlighter.js (webpack).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
7cc2edadce |
fix(ui): the viewer server exits when the command it runs under is killed (#2119)
`codegraph ui` re-execs itself with `--liftoff-only`; the shim the user started blocks in spawnSync and cannot forward a signal. Killing it by pid — a process manager, an IDE task, `kill` — left the re-exec'd server serving its port forever (found during a sweep: servers from hours earlier were still answering on ports 4801–4808 against deleted indexes, holding their stdout pipes so the harness that started them never exited). Ctrl+C was unaffected (the signal reaches the whole process group). `index`/`init` already guard this with the #277 PPID watchdog. Factor it out of installCommandSupervision as `watchParent(onLost)` and use it in `ui` with the same shutdown as SIGINT/SIGTERM (close the index, then the socket). The liveness watchdog is deliberately not installed for `ui`: a long Steps computation can legitimately hold the event loop. Test: POSIX end-to-end through the real relaunch (CODEGRAPH_WASM_RELAUNCHED unset, 200ms poll) — SIGKILL the shim, the port stops answering; confirmed failing without the fix. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c3efbbf4e7 |
feat(angular): a template's event bindings call the component's methods (#2114)
An Angular handler's only caller is its template: `(click)="toggleFavorite()"`,
`(ngSubmit)="submitForm()"`. With no edge, the method showed no callers,
impact stopped at it, and the Steps picture of a screen reached its
handlers only through class containment, with nothing to say what fires
them.
The template pass now reads each `(event)="…"` binding — not
`[(ngModel)]`'s two-way half — and links the component to every method of
its own the statement calls (`save()`, `this.toggle(x)`; not
`closed.emit()` or `form.reset()`), as a `calls` edge
(`synthesizedBy: 'angular-event'`) carrying the binding as
`metadata.trigger`: `{ kind: 'prop', name: '(click)', of: 'button' }`.
The binding is in the template, so it cannot be read back from the source
at the edge's line; Steps now takes a trigger an edge carries before
reading one at the site.
angular-realworld: 21 binding sites → 17 edges (3 repeats of a method and
event, 1 `delete.emit(true)`); Steps for /article/:slug draws
deleteArticle on (click) · <button> → DELETE and back to /, addComment on
(ngSubmit) · <form>, the favorite button's toggle → POST/DELETE or
/register. Ghostfolio 225 edges of 302 sites, ngx-admin 75 of 100 — the
rest are `$event.stopPropagation()`, output emits, assignments and
repeats. Controls (koel, proshop) byte-identical.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
761611c18e |
feat(angular): routes, navigation and the component tree for Angular apps (#2113)
An Angular app had no route nodes, no navigates edges and no link from a template to what it renders — the viewer's Steps tab said "No screens or endpoints" and there was no Screens tab. Routes (frameworks/angular-router.ts): every `Routes` array — typed `Routes` / `Route[]`, `RouterModule.forRoot/forChild`, `provideRouter`, a routes file's `export default [...]` / `as|satisfies Routes` — walked as objects. `component` binds by `references` (route-roots takes a class as a named handler); a lazy `loadComponent` binds to the file's `@Component` class. `children` join their parent's path; a lazily loaded file's routes are put under the path that loads them in postExtract (following an NgModule `loadChildren` to the routing module it imports), idempotently from the in-file path kept in the qualified name. A route with children is a layout: a screen only where no child claims its address, and linked from each screen inside it (`references`, `layout: true`). Paths written as route constants (`internalRoutes.account.path`, through destructured locals and `const x + '/:id'`) and `$localize` strings are read from the constant object. `redirectTo` aliases an address to the screen it sends the user to; a root `**` redirect answers `/` only. A `matcher` route and a `**` catch-all are not screens. Navigation: `router.navigate([...])`, `navigateByUrl`, a guard's `createUrlTree` / `parseUrl` — a command array (non-literal elements are holes), a static string, a route constant, a component property holding one, a function constant called with arguments, `.concat(id)`. A `relativeTo` navigation, a query-only `navigate([])` and a destination made only of holes (it would match any route of its length) are left unresolved. Templates (angular-template-synthesizer.ts): a component's `templateUrl` file or inline `template:` is read at synthesis time. `<app-foo>` by element selector (same app first; two candidates = none) is a `calls` edge from parent to child (`angular-template`), the edge a navigation in a child component rides to its screen. `routerLink` / `[routerLink]` and a `routerLink:` field written in the class (a tab bar's config) are navigates edges. Gated on an `@angular/core` dependency. Measured: - angular-realworld: 10 routes all bound; 11/11 navigation sites accounted for (10 resolved, 1 query-only); 31/31 routerLink sites resolved; 18 renders. - Ghostfolio: 84 routes all bound, 0 unresolved constant paths; 27 navigate resolved, 23 correctly not (8 relative, 9 query-only, 2 computed, 4 aimed at a matcher route); 162 routerLink edges, every one checked against its site; 170 renders. - ngx-admin (NgModule): 55 routes, 47 bound (8 are @nebular/auth's); no navigation calls in the source. - Controls byte-identical vs main (nodes and edges incl. metadata): koel, IceCubesApp, spring-petclinic, proshop. Ghostfolio index time at parity. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
7639c787e4 |
fix(swift): what a SwiftUI app's viewer showed that was not so (#2109)
Three things IceCubesApp showed in `codegraph ui`:
Steps labelled calls that leave the index by other stacks' names:
`rawValue.data(using: .utf8)` network (25 boxes), Foundation's `Timer`
telemetry, `Calendar` and a SiriKit `intent` device, the app's own
`Notifications` endpoint enum device (expo-notifications' row), and
`viewModel.votes.firstIndex(of:)` a database read (the `\w*Model` row).
Now: a Swift `.data/.upload/.download/.bytes` is network only on a
`*session` receiver; Timer/Calendar/intent are no effect in Swift; a
`viewModel` is not a table (any language); a keychain receiver is
storage ahead of Swift's generic `x.delete` database row. A call whose
receiver is a type the Swift project declares — not an `extension X {}`,
which declares nothing (read through the site reader) — no longer
matches the library-name rows; the model and reply rows, written for
project types, still do.
"Files that run something" listed every view with a `#Preview { … }`:
the macro sits at a file's top level, so its constructions were
module-level edges. Entry points now subtract calls inside a Swift
file's top-level previews (trailing closures included — WidgetKit's
`} timeline: {`), drop a file left with none, and re-rank.
Every `struct X: View` had a one-line `component` twin, and `@main`
apps, view controllers and UIView subclasses a `class` twin, from the
SwiftUI/UIKit resolvers' regex extract(): 188 nodes on IceCubesApp, 184
on mastodon-ios, zero edges into any of them — dead ends in search and
the symbol view. The two extract()s are removed.
IceCubesApp's effect sweep is now URLSession-only network, no
telemetry, no database, keychain storage; its entry files drop from
eleven preview files to its one real script. SwiftPackageIndex's Fluent
database and reply steps are unchanged.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
179ac85bd9 |
fix(steps): draw how a server-rendered endpoint answers; bare PHP calls are functions (#2103)
Found testing `codegraph ui` on Spring and Laravel: a server-rendered
endpoint's Steps picture never said how it answers. petclinic's POST
/owners/new showed its database write and no reply; BookStack's book page
showed a 404 and not the page it renders.
Spring MVC answers by what a handler RETURNS, which is no call:
- A new tree-sitter reader, returnsInTree / returnsForFile (graph/
branch-guards.ts, exposed as SiteReader.returns), lists the returns a
definition makes itself — never one in a lambda, a local function or a
nested / anonymous class — and the string a same-class constant holds.
- steps.ts: for a mapped method of a class that renders views (not
@RestController, not @ResponseBody), a returned view name / constant is a
200 render, `"redirect:…"` a 302, `"forward:…"` a forward, each under the
condition it is returned in (effectLink takes a preset effect for a site
that is no call). Non-literal returns are not guessed at.
- effects.ts: `new ModelAndView(…)` (200, or 302 for "redirect:…") and
`new RedirectView(…)` (302) are replies.
Laravel's `view(…)` and `redirect(…)` were already reply rules, but never
reached them:
- name-matcher.ts: a PHP call written without a receiver is a function
call, so only a `function` is a candidate — BookStack's every
`return redirect(…)` bound to ApiDocsController::redirect, every
`view(…)` to a `$view` field (and, through the fuzzy fallback, to the
class `View`). PHP refs record the call expression's column, so the call
text at that column decides. koel alone loses 60 such wrong edges
(`basename()`, `auth()`, `value()`, …).
- effects.ts: the PHP reply rule never matched a chain — rules see the text
with argument lists stripped (`response->json`), the rule wanted `()->` —
so `response()->json(…)`, `redirect()->route(…)`, `redirect()->back()->
withErrors(…)` were never replies; `to_route` added; redirect chains are
302s. PHP's SPL programming-error exceptions (InvalidArgumentException,
LogicException, …) are no longer replies, and a namespaced exception is
read by its class name.
petclinic POST /owners/new: 200 `view owners/createOrUpdateOwnerForm` WHEN
result.hasErrors(), 302 `redirect:/owners/${…}` WHEN !result.hasErrors().
BookStack GET /books/{slug}: 404, 200 `view`, 302 `redirect` (the
InvalidArgumentException from a helper is gone). realworld's REST login is
unchanged.
Not in scope: PHP has no site reader (no branch-guard rules), so Laravel
replies carry no WHEN and no arguments — `abort(Response::HTTP_…)` and
`response()->json()` show without a status.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
dcd79ad3c5 |
fix(ui-map): count depth in folders that fork, so a Maven project opens on its packages (#2102)
A Maven / Gradle project keeps every line of Java under src/main/java/org/<company>/<app>/, and those folders hold nothing but the next one. The Map cut one folder per level, so petclinic drew the whole program as one `src/main/java/org` box, and the deepest grouping option (4) stopped at `src/main/java/org/springframework` — no setting reached the packages seven levels down. A folder with exactly one subfolder and no file of its own (read from every indexed file, tests included, so ids don't move when tests are toggled) now joins the level below it. moduleIdFor takes the set as an optional fourth argument — without it nothing changes — and pickDefaultDepth counts the same levels. Collapsing never splits a group at the same depth (a pass-through folder has one child), so repos without such chains keep their grouping; a lone chain like koel's `app/Console` → `app/Console/Commands` is renamed to the folder that holds the files. The box label (the wire `label`, which nothing read) now elides a chain of three or more folders — `src/main/java/…/petclinic/owner` — and ModuleNode and the width calculation use it; the tooltip and side panel keep the full path. Everywhere else the label equals the id, so other maps look the same. petclinic's default map: 8 boxes (owner, vet, model, system, the package's root files, resources) instead of one; realworld opens on io/spring's api, application, core, graphql, infrastructure. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
2c4d4eb9e4 |
release: withhold codegraph ui until the viewer launches; hold its changelog entries (#2089)
* feat(cli): withhold `codegraph ui` until the viewer launches (CODEGRAPH_UI=1 opts in) The viewer has been exercised on Expo apps only, not on API, Laravel, Spring, Angular or Swift projects, so it stays out of this release. `ui` and its alias `web` — also as `help ui` or `ui --help` — are refused before any startup work, and the command is hidden from `--help`, unless CODEGRAPH_UI=1 is set, which keeps it usable for the people testing it. The viewer assets still ship, so the opt-in works on a released build. Reverting this commit is the launch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(site): say the viewer is not in a release yet The viewer pages have been live on the docs site since they landed, and 1.6.0 answers `codegraph ui` with "unknown command" (#1816, #1900). Until the viewer launches, the guide, the CLI reference and the next-steps link say so. Reverting this commit is part of the launch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(changelog): hold the viewer's entries for its launch; new Highlights for the fixes release 53 entries that describe `codegraph ui` and its screens move, verbatim, to docs/viewer-launch-changelog.md along with the launch Highlights. 12 entries whose graph and `codegraph_explore` half ships now (the router navigation edges, the cross-tier hops, explore's `when` conditions, SvelteKit layouts, wrapped Express handlers, …) are reworded around the graph; their original wording is kept in the holding file for the launch. A few kept entries lose a viewer clause. The Highlights are rewritten for this release. prepare-release.mjs promotes the section and extract-release-notes.mjs reads it back with no mention of the viewer; the released sections are untouched. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |