Imported from friedbotstudio/baseline (
.claude/skills/code-browser/SKILL.md). Install upstream withnpx skills add friedbotstudio/baseline --skill code-browser. Copyright stays with the author.
Code Browser
A natural code-traversal skill. Answers "where does this come from?" by walking from the entry point downward, not by string-searching for the name.
It is the primary path for code-navigation questions in any language (CLAUDE.md Article X.5): the universal walk is the first attempt; reach for the Explore agent or grep only when the repo has no resolvable structure or the walk dead-ends. Pure full-text search and direct type/util definition lookups stay with grep — they are not navigation.
When this skill exists
The user-visible label of a thing (a third-party integration's icon, a "Refresh" button, a panel header) is rarely the right keyword to grep for. The same word often appears in unrelated flows — e.g., grepping a vendor's product name can land on an unrelated documents API while the icon itself is rendered by a generic integrations registry that fetches a different endpoint. The reliable path is always top-down: start at the page, follow imports, stop at the network boundary. This skill encodes that walk and supplies a fast helper so it costs essentially nothing.
The universal walk
This works on any codebase, regardless of framework:
- Locate the entry point for the user-visible thing. For web: the page/route file. For a single-page app, the screen component. The user's question almost always names the page directly or implicitly (e.g. "the loan-application page").
- Read the entry point and follow imports downward. Note components rendered (JSX, route children), data hooks called (
useFoo()), and service/API helpers invoked. - Stop at a network/IO boundary: a
fetch/axios/SDK call, a SQL query, a file read. The URL or query you find there is the answer. - Verify: open the leaf file once with
Readand confirm the URL/symbol is still what the cache claims.
If at any point you find yourself globally grepping for the user-visible label, stop — you've left the walk.
Optional accelerator (JS/TS only): walk.mjs
The universal walk above is the primary path and works on any language. For JS/TS codebases this skill has been initialized in (i.e. conventions.json exists alongside this file), an optional accelerator does the walk deterministically in milliseconds and returns the entire reachable graph as JSON with flat indexes — no per-step LLM round trips. On other languages (Python, Go, Rust, …) walk.mjs/discover.mjs do not apply — do the universal walk by hand with Read; that is the primary mechanism, not a fallback.
node .claude/skills/code-browser/walk.mjs --page <entry-file-path>
Output shape (truncated; values are illustrative placeholders — the actual symbols and paths come from the repo being walked):
{
"entry": "src/app/<route>/page.tsx",
"summary": { "filesVisited": 120, "hooks": 18, "services": 14, "apiCalls": 22, "components": 95 },
"indexes": {
"byHook": { "useGetWidgets": { "file": "src/context/widgets/hook.ts" } },
"byService": {
"getWidgets": { "file": "src/services/widgets/get-widgets.ts", "isDefault": true, "apiCalls": [{"method":"GET","url":"/api/v1/widgets"}] },
"getWidgetById": { "file": "src/services/widgets/get-widgets.ts", "isDefault": false, "apiCalls": [{"method":"GET","url":"/api/v1/widgets/:param"}] },
"listGadgets": { "file": "src/services/gadgets/list-gadgets.ts", "isDefault": true, "apiCalls": [{"method":"GET","url":"/api/v1/gadgets?..."}] }
},
"byApiCall": { "GET /api/v1/widgets": "src/services/widgets/get-widgets.ts" },
"byComponent": { "WidgetPanel": "src/components/dashboard/widget-panel.tsx" }
},
"tree": { /* nested file -> children, with rendersComponents, usesHooks, apiCalls per node */ }
}
Reading the output efficiently — for big trees (the output can run to hundreds of KB), don't read the whole JSON into context. The shape is intentional: entry, summary, and indexes come first and are small. Use Read with a small limit (~200 lines) to get just the head; that's almost always enough. Only fall back to reading the tree section if a question requires "which component renders this hook" or similar parent/child information that the flat indexes don't answer.
How to use the output:
- Scan
indexes.byHook/byService/byComponentfor symbols that match the user's question (substring or fuzzy). The flat indexes exist so you don't have to recurse the tree manually. byServiceis keyed by exported function, not by file. A single service file can export many functions, each with its own URL — for examplegetWidgetsandgetWidgetByIdmay both live inget-widgets.tsbut hit different endpoints. Match the question's topic to the function name inbyServiceand use that function's URL. Do not assume the file's default-export URL applies to all functions in the file.- The page's
treenode listsimportedAs: [...]showing which specific functions it actually imports — consult that to confirm whichbyServiceentry applies. - Walk the
treewhen you need parent/child relationships — e.g., "which component wraps X" or "which component renders X".byComponenttells you where a component is defined; the tree tells you who renders it. - For reverse queries ("what page uses /api/foo?"), walk the relevant page(s) and check their
byApiCallindex — or walk from each candidate page and inspect.
Walker vs LLM: who does what
The walker is deterministic — fast, free, gives you complete facts about the import graph and reachable URLs/components. You're the navigator — read what the user is actually asking and decide which facts matter. Best results come from mixing them.
The walker handles (facts):
- Listing every URL, component, and hook reachable from a page
- Mapping a function/hook/component name to its defining file (
byHook,byService,byComponent) - Confirming a literal still exists at a path
- Route-path → page-file resolution (Next.js: a route like
home/c/[channelId]/p/[pid]/dashboardmaps deterministically tosrc/app/.../dashboard/page.tsx)
You handle (judgment):
- Picking among multiple candidates the walker surfaces — match the question's wording to function/component names
- UI-element references ("the icon", "the panel on the right", "the documents list") — read the JSX of relevant components to see what's actually rendered where
- Vague questions — ask back, or pick the most central candidate with reasoning
- Cross-cutting concerns (analytics, error tracking, side effects) — not modeled by the walker
- Genuine ambiguity — when two URLs or two components both reach the page and both fit the question, name both and give your best guess with reasoning
- Choosing whether to use the walker at all — direct concept-to-file lookups (see Example 4 below) don't need it
Strategic mix: walker narrows the search space (e.g. 86 URLs → 5 task-related ones), you pick the right one by reading question intent against function/component names, walker confirms with one Read.
Caching and freshness
Results are cached at cache/trees/<hash>.json per entry file. On reuse the walker compares every visited file's mtime against the cache build time; if any descendant is newer, the cache is rebuilt automatically.
Always verify the leaf — but only confirm, don't substitute. A cached path is a hypothesis. Before quoting a URL or service to the user, do one Read on the leaf service file that byApiCall names — confirm the URL literal is present. If the service file contains multiple functions with multiple different URLs, do NOT switch to a different URL just because its function name sounds more relevant to the question. The byApiCall entry already resolved which specific call the page reaches; your only job is to confirm that URL literal still exists in the file. For example: if byApiCall says GET /api/v1/<resource-a>/:param and the leaf file also contains a sibling function calling /api/v1/<resource-b>, do not substitute — the page reaches <resource-a> and that is the answer, even if <resource-b> sounds closer to the question topic.
If the walker fails or returns a partial tree (e.g., dynamic imports, conditional renders the walker can't see, unusual export patterns), fall back to the procedural walk: open the entry file by hand, read what it renders, recurse. The walker is an accelerator, not a contract.
Adapting to a new codebase
conventions.json records where each layer lives in this specific repo. If it's missing, or if the repo has been refactored, regenerate it:
node .claude/skills/code-browser/discover.mjs
discover.mjs reads tsconfig.json for path aliases, samples package.json for the framework, finds layer directories under src/, and detects the API URL prefix by sampling service files. It writes both conventions.json (consumed by walk.mjs) and conventions.md (a human-readable summary you can read for context).
The universal walk works without conventions.json — it's only required for the fast path. If conventions don't exist and the repo doesn't follow common conventions you can infer, just do the walk manually with Read.
What this skill is not for
- Writing/editing code: this is a read skill. Don't use it to refactor.
- Finding type definitions / utility implementations: those live in skipped dirs (
src/types/,src/utils/). Grep is fine there — the keyword-as-name correspondence is usually direct. - Pure full-text search: for "find every file containing string X", use
grepdirectly. Use this skill when the question is about structure or wiring — what data drives a UI element, what wraps a component, where a feature's code lives — rather than substring matching.
The single most common mistake: name-similarity over walker truth
When byApiCall shows several URLs and one of them sounds like the question's topic, that is not how you pick. The right URL is the one that the specific page's subtree reaches — not the one whose name best matches the keyword. The walker already filtered for "reachable from this page". Trust that.
Concretely:
- If the question is about a page whose subject is "X" and
byApiCallhas both an/api/.../Xendpoint and a sibling/api/.../Yendpoint, do not pick/Xbecause the page is about X. The walker already filtered for reachable calls from this page — if/Yis the one in the tree, that is the answer. Pages routinely call APIs whose name is unrelated, opposite, or more general than the page's purpose (a "manage Xs" screen may fetch a related entity to compare or assign; two screens often reuse the same generic endpoint). - If the question asks about a domain term and
byApiCallhas both a general-sounding URL (.../get_all_things) and a more specific-sounding URL (.../get_user_things), do not substitute the more "intuitive" one. Report exactly the URL the walker found for this page's subtree, even if it sounds generic. - If the question's topic word matches two URLs in the index, prefer the one whose
:parampath-shape matches what a real call from this page would look like. When in doubt, walk the tree from the page node to confirm which leaf the call actually reaches.
A distinct but related failure mode: substituting an endpoint that is entirely absent from the page's byApiCall. byApiCall is exhaustive — every URL statically reachable from the page's import chain is listed there. If an endpoint does not appear in byApiCall, the page does not call it. Do not add endpoints sourced from Grep results, prior knowledge, or intuition about what the page "should" call. For example: if byApiCall for a page lists only the "all items" endpoint, do not report a hypothetical "user-scoped items" endpoint even if it sounds more user-specific — it is not in the tree, so this page does not reach it. The walker's list is the ground truth; anything outside it is speculation.
If you find yourself reasoning "the URL contains the same word as the question, so it must be the answer", stop. Re-read byApiCall for this page only.
Worked examples
These show how the walker + LLM judgment combine across different question shapes. The skill exists for all of them, not just data-flow traces.
The examples below use generic concepts (notifications, search, cart, etc.). Substitute the equivalent file paths and component names from
conventions.mdfor the actual codebase you are working in. The walk shape is the same regardless.
Example 1 — Data trace ("where does the data come from?")
Question: "Where does the notifications list come from on the inbox page?"
- Identify page → resolve "inbox" against the framework's routing convention (e.g.
src/app/inbox/page.tsxfor Next.js app-dir,pages/inbox.tsxfor pages-dir). node walk.mjs --page <file>→ tree + indexes.- Scan
byServicefornotification→ a handful of candidates with URLs attached. Match "notifications list" to e.g.getUserNotifications(notmarkNotificationRead,subscribeNotifications, etc. — function names disambiguate). Readthe leaf service file → confirm the URL literal.- Answer:
GET /api/v1/notificationsat the leaf service path.
Example 2 — Component to modify for a UI feature
Question: "We need to add a download-CSV button to the report panel on the analytics dashboard."
This is a component-tree question — the user wants the file to edit, not the API to call. API tracing alone won't help; the answer is a React component.
- Identify page → the analytics dashboard's page file.
- Walk it.
- Scan
byComponentfor terms from the question (ReportPanel,Report,Export). The page's subtree typically renders a layout component, which in turn renders the named panel. - Follow the
treedownward from the page to find where the report panel + its UI actually lives. - Answer: the component file containing the panel's JSX, e.g.
src/components/analytics/report-panel/index.tsx. That's the file to edit.
Example 3 — Find the wrapper / parent component
Question: "Show me the wrapper for the search results list."
- Identify page → the search page.
- Walk it.
- Search
byComponentforSearchResultsList. The wrapper file is named with awrappers/orcontainers/convention if the project uses one. - Confirm via the
treethat the page's content component loads the wrapper (chain: page →SearchContent→SearchResultsListWrapper→ inner list). - Answer: the wrapper file path, e.g.
src/components/search/wrappers/search-results-list.tsx.
Example 4 — Direct concept-to-file lookup (no walk needed)
Question: "Show me the reducer for the cart state."
This is a structural lookup — the answer is derivable from convention alone. Walking is overkill and slow.
- Read
conventions.md: contexts/state live under a convention path likesrc/context/<feature>/orsrc/store/<feature>/, and each feature directory typically contains files likereducer.ts,actions.ts,hook.ts,types.ts. - "Cart" → the cart feature directory.
- Reducer →
reducer.ts. - Skip the walker. Use
Globor just confirm the path with oneRead. - Answer: e.g.
src/context/cart/reducer.ts.
Use this pattern whenever the question names a feature and a kind of file (reducer, types, mutations, hook, context, service). Those map deterministically to convention paths — no traversal needed.
How to decide which example pattern applies
| The question is about… | Use… |
|---|---|
| Data shown on a page (lists, fields, status) | Example 1 — page → walker → byService/byApiCall |
| Adding/modifying a UI feature on a page | Example 2 — page → walker → byComponent + tree |
| What wraps / contains a component | Example 3 — byComponent + tree parent navigation |
| A specific kind of file (reducer, types, hook) for a named feature | Example 4 — conventions + Glob, skip the walker |
When in doubt, lean on the walker — even if it ends up being unnecessary, it costs ~300ms and gives you grounded facts. Reaching for grep should be a last resort, not a first instinct.
Failure modes to watch for
- Dynamic imports / lazy loading: the walker follows static
importstatements. Components loaded viaReact.lazy(() => import(...))may need manual follow-up. - Renamed re-exports (
export { Foo as Bar } from "..."): the regex parser sees the new name but may not perfectly track the original. Verify with one Read at the leaf. - Server vs. client boundary (Next.js): a server component may pass data to a client component which then uses a hook. Walk follows the import chain across this boundary.
- Stale conventions: if
discover.mjswas run before a refactor, the layers may have moved. Re-run it.
In all of these, the universal walk by hand is the safety net.