clast-ai 0.2.1 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +105 -0
- package/README.md +337 -408
- package/dist/cli.js +11 -1
- package/dist/cli.js.map +1 -1
- package/dist/migrate.d.ts +0 -1
- package/dist/migrate.js +14 -3
- package/dist/migrate.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -2
- package/plugin/server.mjs +7 -1
- package/src/cli.ts +12 -1
- package/src/migrate.ts +15 -3
- package/src/version.ts +1 -1
package/README.md
CHANGED
|
@@ -1,193 +1,337 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
[tree-sitter](https://tree-sitter.github.io/), keeps a live index of symbols, calls, references, imports and
|
|
5
|
-
type relationships in SQLite, and exposes tools that answer navigation questions in one call instead of a
|
|
6
|
-
chain of greps and file reads.
|
|
3
|
+
# Clast
|
|
7
4
|
|
|
8
|
-
|
|
5
|
+
### Ask the code. Skip the grep marathon.
|
|
9
6
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
and what function bodies mention.
|
|
13
|
-
- **Exact symbol source** - the body of one function or class, read fresh from disk with line numbers,
|
|
14
|
-
together with its callers, callees, overrides and subtypes. No need to read the whole file.
|
|
15
|
-
- **Type-aware call graph** - calls are resolved with declared receiver types, imports, inheritance,
|
|
16
|
-
`this`/`self`/`super` and argument counts, so `record.topic()` goes to the right class, an overload
|
|
17
|
-
gets its own callers and `list.push()` is not mistaken for your `Stack.push`. Edges that are name-only
|
|
18
|
-
guesses are marked `?`.
|
|
19
|
-
- **References, type hierarchy, repo map, text search, git change impact** - see the tool list below.
|
|
20
|
-
- **Always fresh** - a file watcher re-indexes on save, and every query re-checks the files it is about to
|
|
21
|
-
quote, so line numbers and source are never stale, even right after the agent edits a file.
|
|
22
|
-
- **Fast** - parallel indexing in worker threads; warm start only stats files; queries take milliseconds.
|
|
23
|
-
- **No native compilation** - grammars are WASM, storage is Node's built-in `node:sqlite`.
|
|
7
|
+
A code index for coding agents. One call tells Claude who calls a function, what implements an<br>
|
|
8
|
+
interface, what breaks if you change it, and what nobody uses any more.
|
|
24
9
|
|
|
25
|
-
|
|
10
|
+
**Built for Claude Code** | **Works with Codex, Gemini CLI and any MCP client** | **14 languages** | **MIT licensed**
|
|
26
11
|
|
|
27
|
-
|
|
12
|
+
[Install](#install) / [What it does](#made-for-the-questions-grep-cannot-answer) / [Measured results](#measured-not-promised) / [First steps](#your-first-minute) / [Changelog](CHANGELOG.md)
|
|
28
13
|
|
|
29
|
-
|
|
14
|
+
</div>
|
|
30
15
|
|
|
31
|
-
|
|
16
|
+
An agent with only grep finds a name. To follow a call chain, list every subclass or count the
|
|
17
|
+
endpoints it has to search, read, and search again. Clast parses your repository with tree-sitter,
|
|
18
|
+
keeps a live index of symbols, calls, imports and type relationships, and answers those questions
|
|
19
|
+
directly, with exact `file:line` locations. The index follows every edit, so answers are never stale.
|
|
32
20
|
|
|
33
|
-
|
|
21
|
+
## Install
|
|
34
22
|
|
|
35
|
-
|
|
23
|
+
**Claude Code (recommended).** Two commands inside Claude Code, then restart:
|
|
36
24
|
|
|
37
25
|
```
|
|
38
26
|
/plugin marketplace add xls/clast
|
|
39
27
|
/plugin install clast@clast
|
|
40
28
|
```
|
|
41
29
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
The plugin brings the MCP server, a short skill that teaches Claude which tool answers which question,
|
|
45
|
-
and four slash commands:
|
|
30
|
+
The plugin brings the index server, a short skill that teaches Claude which tool answers which
|
|
31
|
+
question, and four slash commands:
|
|
46
32
|
|
|
47
33
|
| Command | What it does |
|
|
48
34
|
| --- | --- |
|
|
49
|
-
| `/clast:deadcode [path]` | Finds unused functions, methods and types, verifies
|
|
35
|
+
| `/clast:deadcode [path]` | Finds unused functions, methods and types, verifies them, and removes the ones you confirm |
|
|
50
36
|
| `/clast:impact <symbol>` | What must change with a symbol, every caller, and the tests to run |
|
|
51
37
|
| `/clast:audit [path]` | Inventory for due diligence or onboarding: size, structure, endpoints, database access, dependencies, dead code |
|
|
52
38
|
| `/clast:status` | State of the index |
|
|
53
39
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
40
|
+
Requires **Node.js 22.22 or newer**. The first start indexes the project in the background (seconds;
|
|
41
|
+
about half a minute for 15,000 files). Later starts are ready immediately.
|
|
42
|
+
|
|
43
|
+
**Codex, Gemini CLI and other agents.** Clast is a standard MCP server, so any agent that speaks MCP
|
|
44
|
+
can use it:
|
|
45
|
+
|
|
46
|
+
```sh
|
|
47
|
+
npm install -g clast-ai
|
|
48
|
+
|
|
49
|
+
codex mcp add clast -- clast-ai # Codex
|
|
50
|
+
gemini mcp add -s user clast clast-ai # Gemini CLI
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Then give the agent the usage guide the Claude plugin ships as a skill:
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
clast-ai guide >> AGENTS.md # or GEMINI.md, or your rules file
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
<details>
|
|
60
|
+
<summary><strong>Coming from an older install?</strong></summary>
|
|
61
|
+
|
|
62
|
+
If clast is already set up the old way (`npm install -g clast-ai` plus `claude mcp add`, or through
|
|
63
|
+
the old VS Code extension), install the plugin as above and restart. That is all, provided Node.js is
|
|
64
|
+
22.22 or newer (0.1 ran on Node 18; check with `node --version`).
|
|
65
|
+
|
|
66
|
+
The first session after that cleans up in the background, once per plugin version:
|
|
57
67
|
|
|
58
|
-
|
|
59
|
-
plugin
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
background, and writes what it did to `migrate.log` in the plugin's data folder. A `.mcp.json` that
|
|
63
|
-
is shared with a team is never changed. To see or do the same by hand:
|
|
68
|
+
- clast servers registered with `claude mcp add`, for all projects or for single ones, are removed
|
|
69
|
+
(the plugin provides the server; with both, every tool would be listed twice),
|
|
70
|
+
- entries the old VS Code extension wrote into `.claude/settings.local.json` are removed,
|
|
71
|
+
- the v0.1 index file `.clast/clast.db` is deleted (the new index is rebuilt by itself).
|
|
64
72
|
|
|
65
|
-
|
|
73
|
+
Restart Claude Code once more and `/mcp` shows a single `clast` server.
|
|
74
|
+
|
|
75
|
+
One case is left to you: a project whose `.mcp.json` lists clast. That file is shared with a team, so
|
|
76
|
+
it is never changed, and in that project the server stays listed twice until the entry is removed
|
|
77
|
+
from the file (`clast-ai migrate` names the file).
|
|
78
|
+
|
|
79
|
+
Optional: `npm uninstall -g clast-ai` removes the old global package. The plugin does not need it; it
|
|
80
|
+
only uses a global install that is at least as new as the plugin itself.
|
|
81
|
+
|
|
82
|
+
To see what would be removed, or to do it by hand:
|
|
83
|
+
|
|
84
|
+
```sh
|
|
66
85
|
clast-ai migrate # report only
|
|
67
86
|
clast-ai migrate --apply # remove the leftovers
|
|
68
87
|
```
|
|
69
88
|
|
|
70
|
-
|
|
89
|
+
What was removed is logged to `migrate.log` in the plugin's data folder. `CLAST_NO_MIGRATE=1` switches
|
|
90
|
+
the automatic clean-up off.
|
|
71
91
|
|
|
72
|
-
|
|
92
|
+
</details>
|
|
73
93
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
94
|
+
<details>
|
|
95
|
+
<summary><strong>Updating, and how the plugin starts the server</strong></summary>
|
|
96
|
+
|
|
97
|
+
Update with `/plugin marketplace update clast`, then `/plugin update clast@clast`.
|
|
98
|
+
|
|
99
|
+
The plugin starts the server from a global `clast-ai` that is at least as new as the plugin, otherwise
|
|
100
|
+
through `npx clast-ai@<plugin version>` (downloaded on first use, cached afterwards).
|
|
101
|
+
|
|
102
|
+
</details>
|
|
103
|
+
|
|
104
|
+
<details>
|
|
105
|
+
<summary><strong>Cursor, VS Code and other editors</strong></summary>
|
|
106
|
+
|
|
107
|
+
Add the server to the editor's MCP configuration. These follow each editor's documented format; they
|
|
108
|
+
have not been tested the way Claude Code, Codex and Gemini CLI have.
|
|
109
|
+
|
|
110
|
+
Cursor, `.cursor/mcp.json` in the project (or `~/.cursor/mcp.json`):
|
|
111
|
+
|
|
112
|
+
```json
|
|
113
|
+
{ "mcpServers": { "clast": { "command": "clast-ai", "env": { "CLAST_PROJECT_DIR": "${workspaceFolder}" } } } }
|
|
77
114
|
```
|
|
78
115
|
|
|
79
|
-
|
|
80
|
-
commands. Use one of the two ways, not both: with both, every tool is listed twice (the plugin removes
|
|
81
|
-
a registration made this way when it is installed later).
|
|
116
|
+
VS Code, `.vscode/mcp.json`:
|
|
82
117
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
and ask to be called again. Later starts are ready immediately.
|
|
118
|
+
```json
|
|
119
|
+
{ "servers": { "clast": { "command": "clast-ai", "env": { "CLAST_PROJECT_DIR": "${workspaceFolder}" } } } }
|
|
120
|
+
```
|
|
87
121
|
|
|
88
|
-
|
|
122
|
+
Clast indexes the directory in `CLAST_PROJECT_DIR`, else `CLAUDE_PROJECT_DIR`, else the directory the
|
|
123
|
+
agent starts it in. Editors do not always start servers in the project, hence the variable above.
|
|
89
124
|
|
|
90
|
-
|
|
91
|
-
# from GitHub, without npm
|
|
92
|
-
npm install -g github:xls/clast
|
|
125
|
+
</details>
|
|
93
126
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
127
|
+
<details>
|
|
128
|
+
<summary><strong>Claude Code without the plugin, a team-wide setup, building from source</strong></summary>
|
|
129
|
+
|
|
130
|
+
**As a plain MCP server** (the tools, without the skill and the slash commands):
|
|
131
|
+
|
|
132
|
+
```sh
|
|
133
|
+
npm install -g clast-ai
|
|
134
|
+
claude mcp add -s user clast clast-ai
|
|
99
135
|
```
|
|
100
136
|
|
|
101
|
-
|
|
137
|
+
Use the plugin or this, not both: with both, every tool is listed twice.
|
|
138
|
+
|
|
139
|
+
**For a whole team**, put this in `.mcp.json` in the project root:
|
|
102
140
|
|
|
103
141
|
```json
|
|
104
142
|
{ "mcpServers": { "clast": { "command": "clast-ai" } } }
|
|
105
143
|
```
|
|
106
144
|
|
|
107
|
-
|
|
145
|
+
**From source** (also how to work on clast itself):
|
|
108
146
|
|
|
109
|
-
|
|
147
|
+
```sh
|
|
148
|
+
git clone https://github.com/xls/clast.git && cd clast
|
|
149
|
+
npm install && npm run build
|
|
150
|
+
claude --plugin-dir /path/to/clast # try the plugin for one session
|
|
151
|
+
claude mcp add -s user clast node /path/to/clast/dist/cli.js # or register the build
|
|
152
|
+
```
|
|
110
153
|
|
|
111
|
-
|
|
154
|
+
</details>
|
|
112
155
|
|
|
113
|
-
|
|
114
|
-
|------|---------|-------------|
|
|
115
|
-
| `clast_overview` | What is in this repo / directory? | First call in an unfamiliar codebase: languages, layout with sizes, most referenced symbols, hub files, entry points |
|
|
116
|
-
| `clast_file_summary` | What is in this file / directory? | Outline of a file (signatures, line ranges, doc summaries, imports, importers) or a directory listing with main symbols |
|
|
117
|
-
| `clast_search` | Where is X? What is it called? | Ranked symbol search by name, partial name, abbreviation, typo, qualified name or descriptive words (matched against names, signatures, docs and what function bodies mention) |
|
|
118
|
-
| `clast_get_context` | Show me X. | Source of one symbol plus callers, callees, overloads, overrides, subtypes. Batch several names in one call |
|
|
119
|
-
| `clast_call_graph` | Who calls X? What does X call? | Caller / callee tree to depth N, test callers separated, `within` path filter. Works for external names too |
|
|
120
|
-
| `clast_references` | Where is X used? | Every usage of an identifier (calls, types, imports, assignments) with the enclosing symbol of each line |
|
|
121
|
-
| `clast_hierarchy` | What implements / extends / overrides X? | Supertypes and transitive subtypes; Ruby mixins; Go interface satisfaction by method set (names plus parameter / result counts); the same method across related types; `within` path filter |
|
|
122
|
-
| `clast_grep` | Where does this text appear? | Literal or regex search over all text files; each hit is labelled with its enclosing function |
|
|
123
|
-
| `clast_changes` | What did this diff touch? | Symbols changed vs HEAD or a base ref, their callers outside the change, and the tests that exercise them |
|
|
124
|
-
| `clast_impact` | What breaks if I change X? Which tests do I run? | Declarations, overrides and implementations that must change together; every direct caller by file and line; how far it reaches through indirect callers; the test files that call it directly or through its callers |
|
|
125
|
-
| `clast_unused` | What is dead code? | Functions, methods and types nothing references (or only other unused code does), by file with line ranges; code used only by tests; names that only appear in strings or non-code files |
|
|
126
|
-
| `clast_stats` | How many X are there? What does this code base consist of? | Exact totals grouped by directory, file, kind or name; inventory, endpoints, database access, dependencies |
|
|
127
|
-
| `clast_status` | Is the index healthy? | Counts, languages, indexing progress, watcher state |
|
|
128
|
-
| `clast_reindex` | Rebuild | Rarely needed; incremental rescan or forced rebuild |
|
|
129
|
-
| `clast_comment` | Docs for X | Existing doc comment, or one generated by a local LLM (optional) |
|
|
156
|
+
## Made for the questions grep cannot answer
|
|
130
157
|
|
|
131
|
-
|
|
132
|
-
passed straight to an editor, and a hint about the next useful call when something is not found.
|
|
158
|
+
### Know what breaks before you change it
|
|
133
159
|
|
|
134
|
-
|
|
160
|
+
Ask about one function and get everything a change touches: the overrides and declarations that must
|
|
161
|
+
change with it, every caller with its line, and the tests to run. That includes tests that never name
|
|
162
|
+
the function but reach it through its callers.
|
|
135
163
|
|
|
136
|
-
|
|
164
|
+
```
|
|
165
|
+
> clast_impact Context.Render (gin)
|
|
166
|
+
|
|
167
|
+
method Context.Render context.go:1201-1215
|
|
168
|
+
direct callers: 18 resolved in 1 file (each must be checked when the signature changes):
|
|
169
|
+
context.go: Context.HTML :1222 (x2), Context.IndentedJSON :1235, Context.SecureJSON :1242, ...
|
|
170
|
+
indirect callers (through the direct ones, up to depth 3): 5 functions in 2 files
|
|
171
|
+
tests to run (11 files: 2 call it directly, 9 reach it through callers):
|
|
172
|
+
context_test.go direct (TestContextRenderIfErr, TestContextRenderSSE, +26)
|
|
173
|
+
auth_test.go via Context.String (TestBasicAuthSucceed, TestBasicAuth401, +3)
|
|
174
|
+
routes_test.go via Context.String (TestRouteQuery, TestRouterMethod, +4)
|
|
175
|
+
...
|
|
176
|
+
```
|
|
137
177
|
|
|
138
|
-
|
|
139
|
-
|------|---------|
|
|
140
|
-
| bare name | `parseConfig` |
|
|
141
|
-
| member | `Parser.parse`, `Parser::parse`, `Parser#parse` |
|
|
142
|
-
| inherited member | `Engine.GET` (defined on an embedded / base type) |
|
|
143
|
-
| package or module path | `django.shortcuts.render`, `page.Clear`, `Illuminate\Pipeline\Pipeline` |
|
|
144
|
-
| file and name | `src/db/index.ts:open` |
|
|
145
|
-
| file and line | `src/db/index.ts:120` (the symbol containing that line) |
|
|
178
|
+
### Find the code nobody uses
|
|
146
179
|
|
|
147
|
-
|
|
180
|
+
Agents write a lot of code and refactors leave helpers behind. `clast_unused` lists functions, methods
|
|
181
|
+
and types that nothing references, and what only that dead code uses, so a removed feature's helpers
|
|
182
|
+
are found with it. Code used only by tests and names that still appear in a string, a template or a
|
|
183
|
+
config file are listed separately, because those need a look before they go.
|
|
184
|
+
|
|
185
|
+
```
|
|
186
|
+
> clast_unused path: app (a C++ application)
|
|
187
|
+
|
|
188
|
+
unused code: 80 symbols, 415 lines in 51 files - the name is never referenced outside
|
|
189
|
+
its own definition, or only from other unused code (10594 symbols judged under app)
|
|
190
|
+
app/panels/agc_ui.cpp (6 symbols, 109 lines)
|
|
191
|
+
function reasonText :12-23 (used only by unused agcGear)
|
|
192
|
+
function ampNow :42-49 (used only by unused agcStep)
|
|
193
|
+
function agcStep :79-111 exported
|
|
194
|
+
function agcGear :126-169 exported
|
|
195
|
+
...
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Entry points, routes and tests marked by a decorator or attribute, subclasses of library types and
|
|
199
|
+
other things a framework calls for you are left out, and counted so you can see what was skipped.
|
|
200
|
+
|
|
201
|
+
### Follow the call chain in one step
|
|
148
202
|
|
|
149
|
-
|
|
203
|
+
Calls are resolved with declared types, imports, inheritance and argument counts, so `record.topic()`
|
|
204
|
+
goes to the right class and `list.push()` is not mistaken for your `Stack.push`. Ask for depth 2 or 3
|
|
205
|
+
and get the chain, not just the next hop. An edge that is only a name match is marked `?`, so the
|
|
206
|
+
agent knows what to verify.
|
|
150
207
|
|
|
151
208
|
```
|
|
152
|
-
>
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
callers (1 resolved):
|
|
160
|
-
main src/app.ts:10
|
|
161
|
-
calls (1):
|
|
162
|
-
circleArea src/math/area.ts:2-4
|
|
209
|
+
> clast_call_graph Engine.handleHTTPRequest depth: 2 (gin)
|
|
210
|
+
|
|
211
|
+
callers (2 resolved; depth 2):
|
|
212
|
+
Engine.ServeHTTP gin.go:701
|
|
213
|
+
Context.FileFromFS context.go:1352 ?
|
|
214
|
+
RouterGroup.createStaticHandler routergroup.go:273 ?
|
|
215
|
+
Engine.HandleContext gin.go:713
|
|
163
216
|
```
|
|
164
217
|
|
|
218
|
+
### Describe it. Clast finds it.
|
|
219
|
+
|
|
220
|
+
No name to search for? Describe the behaviour. Search covers names, signatures, doc comments and
|
|
221
|
+
what function bodies mention, and copes with partial names, abbreviations and typos.
|
|
222
|
+
|
|
165
223
|
```
|
|
166
|
-
>
|
|
167
|
-
|
|
168
|
-
method
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
leveldb.MemTableInserter.Delete db/write_batch.cc:126
|
|
173
|
-
leveldb.MemTableConstructor.FinishImpl table/table_test.cc:315
|
|
224
|
+
> clast_search "client ip from forwarded headers" (gin)
|
|
225
|
+
|
|
226
|
+
method Context.ClientIP context.go:997-1046
|
|
227
|
+
// ClientIP implements one best effort algorithm to return the real client IP.
|
|
228
|
+
method Engine.validateHeader gin.go:505-524
|
|
229
|
+
// validateHeader will parse X-Forwarded-For header and return the trusted client IP address
|
|
174
230
|
```
|
|
175
231
|
|
|
176
|
-
|
|
232
|
+
### Count it exactly
|
|
233
|
+
|
|
234
|
+
How many endpoints, how many receivers, which models does the database layer touch most? `clast_stats`
|
|
235
|
+
returns exact totals grouped by directory, file, kind or name, with production, test and vendored
|
|
236
|
+
code kept apart. `/clast:audit` turns that into a due-diligence inventory: size, structure, HTTP
|
|
237
|
+
endpoints, database access, dependencies and dead code.
|
|
238
|
+
|
|
239
|
+
### Read the function, not the file
|
|
240
|
+
|
|
241
|
+
`clast_get_context` returns exactly the lines of one function or class, read fresh from disk, with
|
|
242
|
+
its callers, callees and overrides. The agent spends its context on the code it needs.
|
|
243
|
+
|
|
244
|
+
### Always current, nothing to maintain
|
|
245
|
+
|
|
246
|
+
A watcher re-indexes files as they are saved, and every answer re-checks the files it quotes, so line
|
|
247
|
+
numbers are right even a second after the agent edited a file. The index is a cache in `.clast/`:
|
|
248
|
+
delete it whenever you like. Grammars are WebAssembly and storage is Node's built-in SQLite, so
|
|
249
|
+
there is nothing to compile.
|
|
250
|
+
|
|
251
|
+
**Languages:** TypeScript, TSX, JavaScript, Python, Java, C#, Go, Rust, C, C++, Ruby, PHP, Swift, Dart.
|
|
252
|
+
|
|
253
|
+
## Measured, not promised
|
|
254
|
+
|
|
255
|
+
Every figure comes from real headless Claude Code sessions: the same question asked once with clast
|
|
256
|
+
and once with only grep and file reads. The rows below are from OnAir (C++, 7.5k files, 1.6M lines);
|
|
257
|
+
the vscode results (TypeScript, 15.7k files) are in the full tables.
|
|
258
|
+
|
|
259
|
+
| Kind of work | Tool calls (clast / grep) | Time | Cost |
|
|
260
|
+
| --- | ---: | ---: | ---: |
|
|
261
|
+
| Find dead code in a directory | 2 / 16 | 20 s / 124 s | $0.15 / $1.89 |
|
|
262
|
+
| Multi-hop questions (call chains, subclasses, impact) | 1.2 / 5.9 | 12.5 s / 25.8 s | $0.056 / $0.212 |
|
|
263
|
+
| Code described by behaviour, in a repo the model has not seen | 1.8 / 4.2 | 12.0 s / 20.5 s | $0.068 / $0.197 |
|
|
264
|
+
| Counting ("how many receiver classes") | 1.0 / 3.3 | 11 s / 34 s | about $0.02 / $0.29 |
|
|
265
|
+
| Simple lookups (where is X, who calls X) | 1.7 / 1.7 | 12.7 s / 14.3 s | $0.063 / $0.096 |
|
|
266
|
+
| Cross-cutting refactors (rename a method, add a parameter everywhere) | 9.8 / 7.8 | 26 s / 25 s | $0.25 / $0.24 |
|
|
267
|
+
|
|
268
|
+
Both sides answered correctly almost everywhere; the difference is how much searching it took.
|
|
269
|
+
|
|
270
|
+
**Where it does not help.** Looking up one name is a tie: a current model with ripgrep does that in
|
|
271
|
+
one call. Writing code is a tie too: the agent reads the surrounding code before it edits, index or
|
|
272
|
+
not. Clast earns its place on questions that take several hops, on counting, and on dead code. The
|
|
273
|
+
samples are small (two repositories, three runs per question), so read them as direction.
|
|
274
|
+
|
|
275
|
+
Full tables, method, answer-quality audits and index speed: [docs/benchmarks.md](docs/benchmarks.md).
|
|
276
|
+
|
|
277
|
+
## Your first minute
|
|
278
|
+
|
|
279
|
+
Open a project in Claude Code with the plugin installed, then try these:
|
|
280
|
+
|
|
281
|
+
1. **Get the lie of the land:** "Give me an overview of this repository."
|
|
282
|
+
2. **Trace something:** "Who ends up calling `saveOrder`, two levels up?"
|
|
283
|
+
3. **Plan a change:** `/clast:impact OrderService.save`
|
|
284
|
+
4. **Clean up:** `/clast:deadcode src/billing`
|
|
285
|
+
5. **Take stock:** `/clast:audit`
|
|
286
|
+
|
|
287
|
+
**Not sure it is running?** `/clast:status` shows the index, and `/mcp` lists the server with 15 tools.
|
|
288
|
+
|
|
289
|
+
## Reference
|
|
177
290
|
|
|
178
|
-
|
|
291
|
+
<details>
|
|
292
|
+
<summary><strong>All 15 tools</strong></summary>
|
|
179
293
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
294
|
+
| Tool | Answers |
|
|
295
|
+
|------|---------|
|
|
296
|
+
| `clast_overview` | What is in this repo or directory? Languages, layout, most referenced symbols, entry points |
|
|
297
|
+
| `clast_file_summary` | What is in this file or directory? Outline with signatures, line ranges, imports |
|
|
298
|
+
| `clast_search` | Where is X, what is it called? By name, partial name, abbreviation, typo, or a description |
|
|
299
|
+
| `clast_get_context` | Show me X: its source plus callers, callees, overloads, overrides, subtypes |
|
|
300
|
+
| `clast_call_graph` | Who calls X, what does X call, to depth N |
|
|
301
|
+
| `clast_references` | Where is X used at all: calls, types, imports, assignments |
|
|
302
|
+
| `clast_hierarchy` | What implements, extends or overrides X? Including Go interfaces by method set |
|
|
303
|
+
| `clast_impact` | What must change with X, who reaches it, which tests to run |
|
|
304
|
+
| `clast_unused` | What is dead code? |
|
|
305
|
+
| `clast_stats` | How many X? Inventory, endpoints, database access, dependencies |
|
|
306
|
+
| `clast_grep` | Where does this text appear? Each hit labelled with its function |
|
|
307
|
+
| `clast_changes` | What did this git diff touch, and who calls it? |
|
|
308
|
+
| `clast_status` | Is the index healthy? |
|
|
309
|
+
| `clast_reindex` | Rebuild (rarely needed) |
|
|
310
|
+
| `clast_comment` | Doc comment for X, optionally generated by a local model |
|
|
311
|
+
|
|
312
|
+
All tools return compact plain text: one line per fact, `path:line` locations, and a hint about the
|
|
313
|
+
next useful call when something is not found.
|
|
314
|
+
|
|
315
|
+
**Addressing symbols.** Anywhere a tool takes a name you can write:
|
|
183
316
|
|
|
184
|
-
|
|
317
|
+
| Form | Example |
|
|
318
|
+
|------|---------|
|
|
319
|
+
| bare name | `parseConfig` |
|
|
320
|
+
| member | `Parser.parse`, `Parser::parse`, `Parser#parse` |
|
|
321
|
+
| package or module path | `django.shortcuts.render`, `Illuminate\Pipeline\Pipeline` |
|
|
322
|
+
| file and name | `src/db/index.ts:open` |
|
|
323
|
+
| file and line | `src/db/index.ts:120` (the symbol containing that line) |
|
|
185
324
|
|
|
186
|
-
|
|
325
|
+
When a bare name has many unrelated definitions the tools list them instead of guessing.
|
|
326
|
+
|
|
327
|
+
</details>
|
|
328
|
+
|
|
329
|
+
<details>
|
|
330
|
+
<summary><strong>Command line</strong></summary>
|
|
187
331
|
|
|
188
|
-
The same queries
|
|
332
|
+
The same queries work from a shell, for scripts, hooks and debugging:
|
|
189
333
|
|
|
190
|
-
```
|
|
334
|
+
```sh
|
|
191
335
|
clast-ai overview # repository map
|
|
192
336
|
clast-ai outline src/db # directory or file outline
|
|
193
337
|
clast-ai search "resolve import path" # ranked symbol search
|
|
@@ -195,19 +339,21 @@ clast-ai symbol Indexer.indexAll # source + callers + callees
|
|
|
195
339
|
clast-ai callers resolveImports --depth 2 --no-tests
|
|
196
340
|
clast-ai hierarchy Shape
|
|
197
341
|
clast-ai refs DEFAULT_CONFIG
|
|
198
|
-
clast-ai grep "TODO" --path src
|
|
199
|
-
clast-ai changes main # symbols changed vs main and their impact
|
|
200
342
|
clast-ai impact Shape.area # what must change with it, callers, tests to run
|
|
201
343
|
clast-ai unused src/billing # dead code under a directory (or the whole project)
|
|
344
|
+
clast-ai stats endpoints # exact counts: inventory, symbols, endpoints, database, ...
|
|
345
|
+
clast-ai changes main # symbols changed vs main and their impact
|
|
346
|
+
clast-ai guide # the usage guide for agents, as Markdown
|
|
347
|
+
clast-ai migrate # leftovers of older installs
|
|
202
348
|
clast-ai index --force # rebuild the index
|
|
203
|
-
clast-ai parse file.go # debug: what the extractor sees
|
|
204
349
|
```
|
|
205
350
|
|
|
206
351
|
Add `--root <dir>` to point at another project. `clast-ai` with no arguments runs the MCP server.
|
|
207
352
|
|
|
208
|
-
|
|
353
|
+
</details>
|
|
209
354
|
|
|
210
|
-
|
|
355
|
+
<details>
|
|
356
|
+
<summary><strong>Configuration and languages</strong></summary>
|
|
211
357
|
|
|
212
358
|
Optional. Create `clast.config.json` (or `.claude/clast.config.json`) in the project root:
|
|
213
359
|
|
|
@@ -233,10 +379,10 @@ Optional. Create `clast.config.json` (or `.claude/clast.config.json`) in the pro
|
|
|
233
379
|
| `workers` | `0` (auto) | Parser threads for bulk indexing; `1` disables workers |
|
|
234
380
|
| `watch.enabled` | `true` | Re-index files as they change |
|
|
235
381
|
| `watch.debounceMs` | `200` | Debounce for file change events |
|
|
236
|
-
| `llm.*` | disabled | OpenAI-compatible endpoint for `clast_comment`
|
|
382
|
+
| `llm.*` | disabled | OpenAI-compatible endpoint for `clast_comment` (Ollama, LM Studio, vLLM, OpenAI, OpenRouter) |
|
|
237
383
|
|
|
238
|
-
Environment: `CLAST_PROJECT_DIR` (project root), `CLAST_DB_PATH` (index location
|
|
239
|
-
|
|
384
|
+
Environment: `CLAST_PROJECT_DIR` (project root), `CLAST_DB_PATH` (index location), `CLAST_GRAMMARS_DIR`
|
|
385
|
+
(alternative grammar directory), `CLAST_NO_MIGRATE=1` (no automatic clean-up), `CLAST_DEBUG=1` (timings).
|
|
240
386
|
|
|
241
387
|
| Language | id | Extensions |
|
|
242
388
|
|----------|----|------------|
|
|
@@ -254,305 +400,88 @@ Environment: `CLAST_PROJECT_DIR` (project root), `CLAST_DB_PATH` (index location
|
|
|
254
400
|
| Swift | `swift` | `.swift` |
|
|
255
401
|
| Dart | `dart` | `.dart` |
|
|
256
402
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
against the file's imports), imports and packages in TS / Python / Java / PHP, C# generic arity and
|
|
285
|
-
partial classes, Ruby lexical constant lookup, Rust module paths.
|
|
286
|
-
5. **Stay fresh** - a watcher re-indexes saved files; in addition every tool re-checks the files it is
|
|
287
|
-
about to quote and re-parses them on the spot if they changed.
|
|
288
|
-
|
|
289
|
-
The index lives in `.clast/index.db` and is a pure cache: delete it at any time.
|
|
290
|
-
|
|
291
|
-
### Performance
|
|
292
|
-
|
|
293
|
-
Measured with `npm run bench` (Node 24, Windows 11, Ryzen 9950X3D, up to 12 parser workers). Cold index is a first run on an
|
|
294
|
-
empty index; warm rescan is a restart with nothing changed.
|
|
295
|
-
|
|
296
|
-
<!-- BENCH:START -->
|
|
297
|
-
| repo | files | symbols | cold index | warm rescan | db size | search (name) | search (words) | symbol + callers/callees | callers depth 2 | references |
|
|
298
|
-
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
|
|
299
|
-
| vscode | 15726 | 461365 | 26.1 s | 1895 ms | 357 MB | 168 ms | 42 ms | 28 ms | 38 ms | 11 ms |
|
|
300
|
-
| aspnetcore | 11470 | 145271 | 10.2 s | 1382 ms | 135 MB | 9.3 ms | 86 ms | 33 ms | 38 ms | 6.6 ms |
|
|
301
|
-
| kafka | 6481 | 126393 | 9.5 s | 863 ms | 144 MB | 26 ms | 33 ms | 25 ms | 962 ms | 5.6 ms |
|
|
302
|
-
| rails | 3580 | 61549 | 3.8 s | 352 ms | 52 MB | 8.7 ms | 15 ms | 22 ms | 35 ms | 3.0 ms |
|
|
303
|
-
| framework | 3180 | 44124 | 3.0 s | 337 ms | 38 MB | 1.4 ms | 46 ms | 23 ms | 23 ms | 2.2 ms |
|
|
304
|
-
| django | 2987 | 60131 | 3.1 s | 522 ms | 42 MB | 29 ms | 41 ms | 21 ms | 48 ms | 2.5 ms |
|
|
305
|
-
| fastapi | 1224 | 10049 | 0.8 s | 161 ms | 7 MB | 5.0 ms | 17 ms | 5.7 ms | 48 ms | 2.4 ms |
|
|
306
|
-
| hugo | 939 | 16896 | 1.1 s | 127 ms | 11 MB | 7.2 ms | 38 ms | 18 ms | 18 ms | 2.4 ms |
|
|
307
|
-
| redis | 919 | 35752 | 1.9 s | 103 ms | 23 MB | 5.7 ms | 16 ms | 7.0 ms | 26 ms | 2.2 ms |
|
|
308
|
-
| tokio | 816 | 12341 | 1.1 s | 85 ms | 11 MB | 3.9 ms | 17 ms | 2.0 ms | 4.2 ms | 1.4 ms |
|
|
309
|
-
| http | 362 | 3947 | 0.7 s | 75 ms | 3 MB | 1.3 ms | 9.6 ms | 3.5 ms | 1.6 ms | 5.3 ms |
|
|
310
|
-
| express | 142 | 439 | 0.4 s | 20 ms | 1 MB | 0.4 ms | 1.0 ms | 0.8 ms | 0.5 ms | 1.6 ms |
|
|
311
|
-
| leveldb | 133 | 3456 | 0.6 s | 14 ms | 2 MB | 2.2 ms | 15 ms | 3.3 ms | 1.8 ms | 2.5 ms |
|
|
312
|
-
| Alamofire | 107 | 4085 | 1.5 s | 23 ms | 4 MB | 2.4 ms | 7.1 ms | 1.5 ms | 530 ms | 1.3 ms |
|
|
313
|
-
| ripgrep | 103 | 4496 | 0.6 s | 13 ms | 3 MB | 1.3 ms | 13 ms | 1.3 ms | 1.4 ms | 0.5 ms |
|
|
314
|
-
| gin | 99 | 2157 | 0.4 s | 12 ms | 2 MB | 1.5 ms | 7.7 ms | 2.2 ms | 4.3 ms | 0.7 ms |
|
|
315
|
-
| spring-petclinic | 50 | 323 | 0.2 s | 11 ms | 1 MB | 0.8 ms | 0.7 ms | 0.6 ms | 0.6 ms | 1.3 ms |
|
|
316
|
-
|
|
317
|
-
Node v24.7.0, win32 x64. Query times are medians of 5 in-process runs on the most-called function of each repo.
|
|
318
|
-
<!-- BENCH:END -->
|
|
319
|
-
|
|
320
|
-
Worst case measured: `callers IDisposable.dispose` on vscode (10.7k call sites, 4.7k definitions of
|
|
321
|
-
`dispose`) takes 5-6 s. Scoped with `within` it is fast.
|
|
322
|
-
|
|
323
|
-
---
|
|
324
|
-
|
|
325
|
-
## Clast vs grep + read
|
|
326
|
-
|
|
327
|
-
Measured with real headless Claude Code sessions (`bench/ab.mjs`): every question runs in its own
|
|
328
|
-
fresh session, once with clast connected and once with only Read / Grep / Glob. Two large repositories:
|
|
329
|
-
vscode (TypeScript, 15.7k files) and OnAir (C++, 7.5k files, 1.6M lines). The index was fully built
|
|
330
|
-
before the first session. Answers are scored against facts verified in the source beforehand; an
|
|
331
|
-
implementation task is scored on its diff.
|
|
332
|
-
|
|
333
|
-
"Cost" is the API-price equivalent that Claude Code reports for the session. It weights fresh input and
|
|
334
|
-
output above cached prompt tokens, so it is the closest single number to "tokens that matter".
|
|
335
|
-
|
|
336
|
-
| Kind of work | Repo | Sessions per arm | Tool calls (clast / grep) | Wall time | Cost per question | Correct |
|
|
337
|
-
| --- | --- | ---: | ---: | ---: | ---: | ---: |
|
|
338
|
-
| Multi-hop questions (call chains, transitive subclasses, impact) | vscode | 18 | 1.3 / 3.1 | 16.0 s / 19.8 s | $0.063 / $0.126 | 18 / 18 |
|
|
339
|
-
| Multi-hop questions | OnAir | 18 | 1.2 / 5.9 | 12.5 s / 25.8 s | $0.056 / $0.212 | 18 / 18 |
|
|
340
|
-
| Simple lookups (where is X, who calls X, what implements X) | vscode | 30 | 1.4 / 1.4 | 13.9 s / 13.9 s | $0.053 / $0.059 | 30 / 29 |
|
|
341
|
-
| Simple lookups | OnAir | 30 | 1.7 / 1.7 | 12.7 s / 14.3 s | $0.063 / $0.096 | 30 / 30 |
|
|
342
|
-
| Counting ("how many receiver classes under core/") | OnAir | 3 | 1.0 / 3.3 | 11 s / 34 s | about $0.02 / $0.29 | 3 / 2 |
|
|
343
|
-
| Small implementation tasks (new provider, new enum value, new subclass) | vscode | 6 | 7.5 / 8.5 | 30 s / 31 s | $0.29 / $0.24 | 6 / 6 |
|
|
344
|
-
| Small implementation tasks | OnAir | 6 | 11.5 / 12.8 | 39 s / 40 s | $0.58 / $0.56 | 6 / 6 |
|
|
345
|
-
| Cross-cutting refactors (rename a method in 28 files, add a parameter and update all callers) | vscode | 4 | 21.8 / 22.0 | 47 s / 48 s | $0.45 / $0.37 | 4 / 4 |
|
|
346
|
-
| Cross-cutting refactors | OnAir | 4 | 9.8 / 7.8 | 26 s / 25 s | $0.25 / $0.24 | 4 / 4 |
|
|
347
|
-
| Before a change (tests that reach a function, files to edit for a signature change, dead functions in a file) | vscode | 9 | 2.3 / 2.2 | 19.3 s / 19.8 s | $0.098 / $0.154 | 9 / 9 |
|
|
348
|
-
| Before a change | OnAir | 9 | 1.7 / 3.6 | 14.3 s / 17.4 s | $0.126 / $0.173 | 9 / 9 |
|
|
349
|
-
| Find dead code in a directory ("five unused functions under app/", answers checked by hand) | OnAir | 2 | 2.0 / 16.0 | 20 s / 124 s | $0.15 / $1.89 | 2 / 2 |
|
|
350
|
-
| Questions that describe behaviour without naming anything ("what takes out the spike in the middle of the spectrum") | OnAir | 12 | 1.8 / 4.2 | 12.0 s / 20.5 s | $0.068 / $0.197 | 12 / 12 |
|
|
351
|
-
| Questions that describe behaviour | vscode | 12 | 1.0 / 1.0 | 11.7 s / 11.8 s | $0.048 / $0.044 | 12 / 12 |
|
|
352
|
-
|
|
353
|
-
What this says:
|
|
354
|
-
|
|
355
|
-
- **Multi-hop questions are where clast pays off**: half the cost and 20% less time on vscode, a
|
|
356
|
-
quarter of the cost and half the time on OnAir. One call returns a call chain or a full inheritance
|
|
357
|
-
tree that grep needs three to twelve rounds for, and the agent reads 70-85% less tool output.
|
|
358
|
-
- **Simple lookups are a tie.** A current model with ripgrep answers them in one or two calls; clast
|
|
359
|
-
does not make that faster, it only returns a little less text.
|
|
360
|
-
- **Counting is faster and exact** with `clast_stats`. With grep the agent tallied matches by hand and
|
|
361
|
-
was off by one in one of three runs.
|
|
362
|
-
- **Finding dead code is the largest gap measured**: one `clast_unused` call and a confirming grep
|
|
363
|
-
against 11-21 greps, a sixth of the time and a thirteenth of the cost. Every function either arm
|
|
364
|
-
named was verified unused, and all ten the grep sessions found are in clast's list.
|
|
365
|
-
- **Describing behaviour instead of naming it** works on a repository the model has never seen
|
|
366
|
-
(OnAir): search over names, comments and what function bodies mention finds the place in one or two
|
|
367
|
-
calls where grep needs four rounds of guessing words. On vscode it is a tie at one call, because the
|
|
368
|
-
model already knows where things are in vscode; that row measures the model's memory, not the tools.
|
|
369
|
-
- **Questions before a change are cheaper, not always faster.** With `clast_impact` and `clast_unused`
|
|
370
|
-
the sessions cost 27-36% less because the agent reads less tool output. The clear win is "which
|
|
371
|
-
tests reach this function when none names it": 1 call and 11 s against 5.7 calls and 22 s on OnAir.
|
|
372
|
-
"Which files do I edit for a signature change" is one grep and stays a tie.
|
|
373
|
-
- **Implementation work showed no benefit**, neither small features nor cross-cutting refactors. Both
|
|
374
|
-
arms made the same edits in the same files in the same time (refactors were also checked for
|
|
375
|
-
completeness: nothing of the old name or call shape left). Finding every occurrence of one name is
|
|
376
|
-
a single grep, and before editing the agent reads the neighbouring code either way. Clast costs a
|
|
377
|
-
little more here because its tool definitions are in the prompt.
|
|
378
|
-
- **Small samples.** Two repositories, three runs per question, questions written by the tool's
|
|
379
|
-
author. The figures show the direction, not a precise ratio.
|
|
380
|
-
|
|
381
|
-
One result changed the server itself. Claude Code defers MCP tools behind its tool search by default,
|
|
382
|
-
and in that state the agent never called clast: 0 of 60 sessions, every question answered with Grep.
|
|
383
|
-
The navigation tools now ask the client to keep them loaded, after which the agent used clast in every
|
|
384
|
-
question session where it helped. The cost is about 5k tokens of tool definitions and instructions in
|
|
385
|
-
the cached prompt (measured with 13 tools: 23.7k against 19.3k prompt tokens for a session that calls
|
|
386
|
-
no tool; `clast_impact` and `clast_unused` add about 0.6k).
|
|
387
|
-
|
|
388
|
-
Reproduce with `node bench/ab.mjs <repo> bench/ab/<set>.json --runs 3`. The question sets are in
|
|
389
|
-
`bench/ab/`.
|
|
390
|
-
|
|
391
|
-
### Answer quality
|
|
392
|
-
|
|
393
|
-
Reviewer agents asked deliberately hard questions on 17 real repositories and checked every answer
|
|
394
|
-
against the source. Over the last five rounds the share of first answers judged correct rose from
|
|
395
|
-
roughly 45-60% to 65-78%, with new questions each round.
|
|
396
|
-
|
|
397
|
-
Two further audits read the cited source lines for call edges that clast shows as certain (no `?`):
|
|
398
|
-
|
|
399
|
-
| Repo | Language | Wrong | Checked |
|
|
400
|
-
| --- | --- | ---: | ---: |
|
|
401
|
-
| kafka | Java | 0 | 57 |
|
|
402
|
-
| tokio | Rust | 0 | 17 |
|
|
403
|
-
| laravel | PHP | 0 | 19 |
|
|
404
|
-
| vscode | TypeScript | 1 | 45 |
|
|
405
|
-
| django | Python | 1 | 39 |
|
|
406
|
-
| aspnetcore | C# | 1 | 58 |
|
|
407
|
-
| hugo | Go | 1 | 19 |
|
|
408
|
-
| rails | Ruby | 4 | 18 |
|
|
409
|
-
|
|
410
|
-
The vscode and aspnetcore errors were fixed afterwards; the django, hugo and rails ones are open (see
|
|
411
|
-
Known limitations).
|
|
412
|
-
|
|
413
|
-
---
|
|
414
|
-
|
|
415
|
-
## Changelog
|
|
416
|
-
|
|
417
|
-
### 0.2.1
|
|
418
|
-
|
|
419
|
-
- `clast-ai migrate`: finds and removes what older installs left behind (duplicate MCP registrations
|
|
420
|
-
next to the plugin, entries written by the old VS Code extension, the v0.1 index file). The plugin
|
|
421
|
-
runs it automatically, once per version, at the first session after an install or update.
|
|
422
|
-
- The plugin uses a global `clast-ai` only when it is at least as new as the plugin itself, so a
|
|
423
|
-
plugin update is not held back by an older global install.
|
|
424
|
-
|
|
425
|
-
### 0.2.0
|
|
426
|
-
|
|
427
|
-
New tools (7 to 13):
|
|
428
|
-
|
|
429
|
-
- `clast_references`: every use of a symbol, not just calls.
|
|
430
|
-
- `clast_hierarchy`: supertypes, subtypes, implementers and overrides, including Go structural
|
|
431
|
-
interfaces and Ruby mixins.
|
|
432
|
-
- `clast_overview`: map of a repository or directory.
|
|
433
|
-
- `clast_grep`: text search over all text files with the enclosing symbol on every hit.
|
|
434
|
-
- `clast_changes`: symbols touched by a git diff, plus their callers.
|
|
435
|
-
- `clast_impact`: for one symbol, what has to change with it (declarations, overrides, implementations,
|
|
436
|
-
overloads), every direct caller by file and line, the reach through indirect callers, and the test
|
|
437
|
-
files to run, including tests that only reach it through its callers.
|
|
438
|
-
- `clast_unused`: dead code. A name counts as unused when it is never referenced outside its own
|
|
439
|
-
definitions, or only from other unused code (so a deleted feature's helpers are found with it).
|
|
440
|
-
Reported separately: code used only by tests, names that still appear in a string, config or
|
|
441
|
-
template, and (Java, C#, C, C++, Rust) methods whose every call binds to another definition. Left
|
|
442
|
-
out, with counts: entry points, decorated or attribute-marked symbols, subclasses of library types
|
|
443
|
-
and their methods, object-literal members, exported Go methods, C# extension classes.
|
|
444
|
-
- `clast_stats`: exact counts grouped by directory, file, kind or name: inventory, symbols by name
|
|
445
|
-
pattern, direct and indirect subtypes, call sites by name and receiver, text matches, external
|
|
446
|
-
dependencies, and pattern-based inventories of HTTP / RPC endpoints and database access.
|
|
447
|
-
|
|
448
|
-
Call graph:
|
|
449
|
-
|
|
450
|
-
- Calls are resolved at query time using declared receiver types, imports and aliases, field types
|
|
451
|
-
across files, return types of call chains, inheritance and `super`.
|
|
452
|
-
- Edges are shown as certain, uncertain, or `?` (matched by name only).
|
|
453
|
-
- Argument counts separate overloads. A bare name means all overloads; `path:line` selects one.
|
|
454
|
-
- A call bound to a subclass override no longer counts as a caller of the base method.
|
|
455
|
-
- Language rules: Go packages and structural interfaces, Rust `impl Trait for Arc<T>` and std types,
|
|
456
|
-
C# generic arity / partial / static classes, Ruby mixins and bare calls, Java static imports, Python
|
|
457
|
-
submodule calls and imported instances, TypeScript renamed re-exports, C macro bodies, C++ namespace
|
|
458
|
-
functions.
|
|
459
|
-
|
|
460
|
-
Search:
|
|
461
|
-
|
|
462
|
-
- Ranked by exact name, prefix, abbreviation, typo and plain words.
|
|
463
|
-
- Descriptive queries also match what function bodies mention (marked `[body]`).
|
|
464
|
-
- Overloads fold into one hit.
|
|
465
|
-
|
|
466
|
-
Performance and storage:
|
|
467
|
-
|
|
468
|
-
- `node:sqlite` on disk (WAL, FTS5) replaces in-memory sql.js.
|
|
469
|
-
- Parsing runs in worker threads, with an inline fallback if a worker fails.
|
|
470
|
-
- The server connects first and indexes afterwards; on a first build, tools wait for the index.
|
|
471
|
-
- A watcher re-indexes changed files, and every tool re-checks the files it quotes.
|
|
472
|
-
|
|
473
|
-
Other:
|
|
474
|
-
|
|
475
|
-
- Swift and Dart added (14 languages).
|
|
476
|
-
- All dependencies updated (MCP SDK 1.32, web-tree-sitter 0.27).
|
|
477
|
-
- 290 tests, plus `bench/bench.mjs`, `bench/resolution.mjs` and the A/B harness `bench/ab.mjs`.
|
|
478
|
-
- Installable as a Claude Code plugin from this repository (`/plugin marketplace add xls/clast`,
|
|
479
|
-
`/plugin install clast@clast`): the MCP server, a usage skill, and the slash commands
|
|
480
|
-
`/clast:deadcode`, `/clast:impact`, `/clast:audit` and `/clast:status`. The old VS Code extension
|
|
481
|
-
scaffold that wrote the server into `.claude/settings.local.json` is removed.
|
|
482
|
-
- The navigation tools ask Claude Code to keep them loaded instead of deferring them behind tool
|
|
483
|
-
search; deferred, the agent never called them.
|
|
484
|
-
- Ruby classes and their ancestors are compared by qualified name (`Rails::Generators::Base` is not
|
|
485
|
-
`ActiveRecord::Base`; `Thor::Group` is not a project class called `Group`).
|
|
486
|
-
- C++ functions declared through a head-building macro (`FN(jstring, nativeStart)(args)`) are indexed
|
|
487
|
-
under their real name.
|
|
488
|
-
- C / C++ function pointers whose calling convention is a macro (`err_t (MY_CALL* find)(args)`) are
|
|
489
|
-
indexed as a field called `find`, not as a method called `err_t`.
|
|
490
|
-
|
|
491
|
-
### Upgrading from 0.1
|
|
492
|
-
|
|
493
|
-
- Node.js 22.22+ is required (was 18).
|
|
494
|
-
- The index moved from `.clast/clast.db` (sql.js) to `.clast/index.db` (`node:sqlite`). Nothing is
|
|
495
|
-
migrated: the index is a cache and is rebuilt on first start. The old file is ignored and can be
|
|
496
|
-
deleted.
|
|
497
|
-
- A later schema change deletes and rebuilds the index automatically (the schema is at version 26).
|
|
498
|
-
- Tool output is compact text instead of JSON.
|
|
499
|
-
- `clast_call_graph` defaults to depth 1 and gained `within` and `includeTests`.
|
|
500
|
-
- The binary entry point is `dist/cli.js`; `dist/server/index.js` still starts the server for old configs.
|
|
501
|
-
|
|
502
|
-
---
|
|
503
|
-
|
|
504
|
-
## Known limitations
|
|
403
|
+
</details>
|
|
404
|
+
|
|
405
|
+
<details>
|
|
406
|
+
<summary><strong>How it works</strong></summary>
|
|
407
|
+
|
|
408
|
+
1. **Scan.** Walk the tree honouring `.gitignore` and `ignoredPaths`; compare size and modification
|
|
409
|
+
time with the index, so an unchanged file costs one `stat`.
|
|
410
|
+
2. **Parse.** Changed files are parsed by a pool of worker threads (tree-sitter WebAssembly). A
|
|
411
|
+
per-language spec turns the syntax tree into symbols, calls (with the receiver and its declared
|
|
412
|
+
type), imports, inheritance and identifier occurrences.
|
|
413
|
+
3. **Store.** SQLite (`node:sqlite`, WAL) with a full-text index over names, signatures and docs.
|
|
414
|
+
4. **Link at query time.** A call is stored as "symbol A calls name N on receiver R of type T" and
|
|
415
|
+
resolved when asked, so nothing goes stale when another file changes. Resolution uses the declared
|
|
416
|
+
receiver type, then imports and same-file definitions, then `this` / `self` and inheritance, then
|
|
417
|
+
evidence scoring for untyped receivers. Argument counts separate overloads. A call through an
|
|
418
|
+
interface or base type counts as a caller of the implementations it can reach; a call bound to a
|
|
419
|
+
subclass override does not count for the base method.
|
|
420
|
+
5. **Stay fresh.** A watcher re-indexes saved files, and every tool re-checks the files it quotes.
|
|
421
|
+
|
|
422
|
+
The index lives in `.clast/index.db` and takes about 12-25 MB per 1,000 source files. A cold index of
|
|
423
|
+
vscode (15.7k files) takes about 26 s; a restart with nothing changed about 2 s; most queries are
|
|
424
|
+
under 50 ms.
|
|
425
|
+
|
|
426
|
+
</details>
|
|
427
|
+
|
|
428
|
+
<details>
|
|
429
|
+
<summary><strong>Known limitations</strong></summary>
|
|
505
430
|
|
|
506
431
|
- **Untyped receivers**: `obj.save()` in dynamic code cannot be attributed. Such calls are listed as
|
|
507
432
|
"receiver type unknown", not guessed.
|
|
433
|
+
- **Dead code is judged by name**: a member whose name is also used by other symbols (`get`, `run`,
|
|
434
|
+
`close`) is never listed, so the list is incomplete for common method names. Calls built from
|
|
435
|
+
strings at run time are only caught when the prefix is a literal (`getattr(x, "as_" + vendor)`).
|
|
436
|
+
Code found by convention (Django `AppConfig` classes inside Django itself) can be listed wrongly.
|
|
437
|
+
Treat the list as candidates and let the compiler or the tests confirm a deletion.
|
|
508
438
|
- **Same-named types**: classes with the same short name in different PHP namespaces or Java packages
|
|
509
439
|
can be confused when the calling file gives no hint.
|
|
510
|
-
- **Ruby**: the least exercised language. The rails audit found 4 wrong edges of 18 with one cause
|
|
511
|
-
(classes called `Base` in different namespaces treated as one family); classes are now compared by
|
|
512
|
-
qualified name, which fixes that case, but Ruby has not been re-audited since. Methods defined by
|
|
513
|
-
metaprogramming are invisible.
|
|
514
440
|
- **Overloads**: those that differ only in parameter types, not count, are one target.
|
|
515
441
|
- **Generics and inference**: no generic substitution; TypeScript return types are known only when
|
|
516
442
|
annotated.
|
|
517
443
|
- **Framework conventions**: Django managers, Laravel facades and container lookups, and services
|
|
518
|
-
injected by string are not modelled.
|
|
444
|
+
injected by string are not modelled. Endpoint and database statistics are pattern-based.
|
|
519
445
|
- **Python**: a local variable that holds a class is not followed.
|
|
520
|
-
- **Go**:
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
keeping its members. Swift and Dart are new and less exercised.
|
|
524
|
-
- **Dead code is judged by name**: a member whose name is also used by other symbols (`get`, `run`,
|
|
525
|
-
`close`) is never listed, so the list is incomplete for common method names; calls built from
|
|
526
|
-
strings at run time are only caught when the prefix is a literal (`getattr(x, "as_" + vendor)`).
|
|
527
|
-
In a spot check of 8-12 random findings per repository (kafka, aspnetcore, vscode, django, tokio,
|
|
528
|
-
hugo, OnAir) against a word grep, the remaining wrong ones were convention-based discovery (Django
|
|
529
|
-
`AppConfig` classes inside Django itself). Treat the list as candidates and let the compiler or the
|
|
530
|
-
tests confirm a deletion.
|
|
446
|
+
- **Go**: the module path is inferred from imports, not from go.mod.
|
|
447
|
+
- **Ruby, Swift, Dart**: the least exercised languages. Ruby methods defined by metaprogramming are
|
|
448
|
+
invisible; the Swift grammar fails on some files.
|
|
531
449
|
- **Search is lexical**: a description that shares no word with the code finds the neighbourhood, not
|
|
532
450
|
always the function.
|
|
533
451
|
- **Not indexed**: Kotlin, Scala and other languages without a bundled grammar. `clast_grep` still
|
|
534
452
|
searches those files as text.
|
|
535
|
-
- **
|
|
536
|
-
|
|
453
|
+
- **Prompt cost**: the tool definitions add about 5k tokens to every session.
|
|
454
|
+
|
|
455
|
+
More detail and examples: [docs/v0.2-summary.md](docs/v0.2-summary.md).
|
|
537
456
|
|
|
538
|
-
|
|
457
|
+
</details>
|
|
539
458
|
|
|
540
|
-
|
|
459
|
+
## Try it. Tell us what you think.
|
|
541
460
|
|
|
542
|
-
|
|
461
|
+
Point clast at a code base you know well and ask it something you already know the answer to. If an
|
|
462
|
+
answer is wrong, a language construct is missed, or an agent you use needs something different,
|
|
463
|
+
[open an issue](https://github.com/xls/clast/issues). Include the language, the question you asked and
|
|
464
|
+
the clast version (`clast-ai --version`).
|
|
543
465
|
|
|
544
|
-
|
|
466
|
+
<details>
|
|
467
|
+
<summary><strong>Working on clast</strong></summary>
|
|
468
|
+
|
|
469
|
+
```sh
|
|
545
470
|
npm install
|
|
546
471
|
npm run build # tsc
|
|
547
|
-
npm test # node:test,
|
|
472
|
+
npm test # node:test, incl. an end-to-end MCP session
|
|
548
473
|
npm run bench -- /path/to/dir-with-repos
|
|
549
474
|
```
|
|
550
475
|
|
|
551
476
|
Adding a language: create `src/parser/langs/<name>.ts` exporting a `LangSpec` (see `typescript.ts` and
|
|
552
|
-
`python.ts`
|
|
553
|
-
|
|
554
|
-
|
|
477
|
+
`python.ts`), register it in `src/parser/registry.ts`, and add a test under `test/langs/`.
|
|
478
|
+
`clast-ai ast <file>` dumps the syntax tree and `clast-ai parse <file>` shows what your spec extracts.
|
|
479
|
+
The grammar must be available in `@repomix/tree-sitter-wasms`.
|
|
480
|
+
|
|
481
|
+
The A/B harness that produced the figures above is `bench/ab.mjs`; the question sets are in `bench/ab/`.
|
|
482
|
+
|
|
483
|
+
</details>
|
|
555
484
|
|
|
556
485
|
## License
|
|
557
486
|
|
|
558
|
-
MIT
|
|
487
|
+
[MIT](LICENSE). Free to use, modify and share.
|