@ajdev0/token-shrink 2.0.0
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/README.md +345 -0
- package/dist/chunk-7H6PGILN.js +86 -0
- package/dist/chunk-7H6PGILN.js.map +1 -0
- package/dist/chunk-7SQ6HMWM.js +145 -0
- package/dist/chunk-7SQ6HMWM.js.map +1 -0
- package/dist/chunk-HRF3BIOV.js +518 -0
- package/dist/chunk-HRF3BIOV.js.map +1 -0
- package/dist/chunk-LPJMNP4N.js +179 -0
- package/dist/chunk-LPJMNP4N.js.map +1 -0
- package/dist/cli-EpVqinpB.d.cts +96 -0
- package/dist/cli-EpVqinpB.d.ts +96 -0
- package/dist/cli.cjs +771 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +4 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.js +10 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +988 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +169 -0
- package/dist/index.d.ts +169 -0
- package/dist/index.js +68 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp.cjs +856 -0
- package/dist/mcp.cjs.map +1 -0
- package/dist/mcp.d.cts +58 -0
- package/dist/mcp.d.ts +58 -0
- package/dist/mcp.js +26 -0
- package/dist/mcp.js.map +1 -0
- package/dist/registry-JLP6X4QB.js +14 -0
- package/dist/registry-JLP6X4QB.js.map +1 -0
- package/dist/tree-sitter-typescript.wasm +0 -0
- package/package.json +59 -0
- package/wasm/tree-sitter-typescript.wasm +0 -0
package/README.md
ADDED
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
# token-shrink
|
|
2
|
+
|
|
3
|
+
A **local-first, framework-aware token reduction engine** — a polyglot AST semantic proxy and MCP server. It prunes implementation bodies out of **dependency files** while preserving every type signature, interface, and module export, so LLM agents see the full shape of the code at a fraction of the tokens.
|
|
4
|
+
|
|
5
|
+
> **The 80–90% reduction target**: full type information, no implementation noise. Ring 0 (your active file) stays complete; Ring 1 (its direct imports) is delivered as pruned skeletons.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## How it works
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
active file imports (Ring 1)
|
|
13
|
+
┌──────────────────┐ ┌──────────────────────┐
|
|
14
|
+
│ src/page.ts │ ──► │ src/util.ts │
|
|
15
|
+
└──────────────────┘ └──────────────────────┘
|
|
16
|
+
▾ ▾
|
|
17
|
+
tree-sitter (WASM) ─────────────► prune impl blocks
|
|
18
|
+
parse & query keep interfaces · types ·
|
|
19
|
+
signatures · exports
|
|
20
|
+
▾
|
|
21
|
+
pruned skeleton (Ring 0 full source)
|
|
22
|
+
▾
|
|
23
|
+
Compressed Code Context (Markdown)
|
|
24
|
+
│ │
|
|
25
|
+
via MCP (stdio) via HTTP (Fastify)
|
|
26
|
+
get_compressed_code_context POST /v1/context
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Pipeline stages:
|
|
30
|
+
|
|
31
|
+
1. **Parse** — `web-tree-sitter` loads a `.wasm` grammar per language (auto-downloaded on first run).
|
|
32
|
+
2. **Prune** — an S-expression query matches implementation blocks (`statement_block`, `block`, `compound_statement`…), which are replaced with a short token (`/* ... */`, or `pass` for Python) using **descending-order splicing** so offsets stay valid.
|
|
33
|
+
3. **Watch** — `chokidar` watches the repo, `sha1`-hashes file contents, and refreshes the cache only on change.
|
|
34
|
+
4. **Assemble** — the active file's imports are resolved and merged into a Markdown context payload (Ring 0 + Ring 1).
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
## Install
|
|
41
|
+
|
|
42
|
+
Requires **Node.js 18+**.
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# run anywhere without installing
|
|
46
|
+
# --root project root --port http port --host bind address
|
|
47
|
+
npx @ajdev0/token-shrink --root /path/to/project
|
|
48
|
+
|
|
49
|
+
# or install locally
|
|
50
|
+
npm install @ajdev0/token-shrink
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
### Build from source
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# install deps
|
|
59
|
+
npm install
|
|
60
|
+
|
|
61
|
+
# compile (tsup -> dist/), typecheck, and run tests
|
|
62
|
+
npm run build
|
|
63
|
+
npm run typecheck
|
|
64
|
+
npm test
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The build produces three binaries:
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
| Binary | Entry | Purpose |
|
|
71
|
+
| -------------------- | --------------- | -------------------------------------------- |
|
|
72
|
+
| `@ajdev0/token-shrink` | `dist/cli.cjs` | Fastify HTTP server (`POST /v1/context`) |
|
|
73
|
+
| `@ajdev0/token-shrink-mcp` | `dist/mcp.cjs` | MCP stdio server for AI agents |
|
|
74
|
+
| library | `dist/index.js` | `prune()`, `assemble()`, `createWatcher()` … |
|
|
75
|
+
|
|
76
|
+
### Publish to npm
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
npm login
|
|
80
|
+
npm run build && npm test
|
|
81
|
+
npm pack --dry-run # preview tarball contents
|
|
82
|
+
npm publish
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
The package name on npm is **`@ajdev0/token-shrink`**. After publishing, users can run:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
npx @ajdev0/token-shrink-mcp --root /path/to/project
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
## WASM grammars (auto-download)
|
|
97
|
+
|
|
98
|
+
Grammars are fetched from the official tree-sitter GitHub releases on **first use** and cached in `wasm/`:
|
|
99
|
+
|
|
100
|
+
```text
|
|
101
|
+
wasm/
|
|
102
|
+
├── tree-sitter-typescript.wasm
|
|
103
|
+
├── tree-sitter-javascript.wasm
|
|
104
|
+
├── tree-sitter-tsx.wasm
|
|
105
|
+
├── tree-sitter-python.wasm
|
|
106
|
+
├── tree-sitter-go.wasm
|
|
107
|
+
├── ...
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
- First run requires network access; afterwards everything is offline and fast.
|
|
111
|
+
- Files are written atomically (`*.tmp` → rename) with an in-flight lock, so concurrent first-run parses never corrupt the cache.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
## Usage
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
### 1. MCP server (AI agents — Cursor, Claude, etc.)
|
|
122
|
+
|
|
123
|
+
Run the stdio MCP server and expose the `get_compressed_code_context` tool:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
# point it at your project
|
|
127
|
+
token-shrink-mcp --root /path/to/project
|
|
128
|
+
|
|
129
|
+
# root also works via env or cwd
|
|
130
|
+
ROOT=/path/to/project token-shrink-mcp
|
|
131
|
+
cd /path/to/project && token-shrink-mcp
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
**Cursor MCP config** (`.cursor/mcp.json`):
|
|
135
|
+
|
|
136
|
+
```json
|
|
137
|
+
{
|
|
138
|
+
"mcpServers": {
|
|
139
|
+
"token-shrink": {
|
|
140
|
+
"command": "token-shrink-mcp",
|
|
141
|
+
"args": ["--root", "/absolute/path/to/your/project"]
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**Claude Code MCP config** — add it to the project's `.mcp.json`, or register with the Claude CLI:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
# register the server for this project
|
|
151
|
+
claude mcp add token-shrink -- token-shrink-mcp --root /path/to/project
|
|
152
|
+
# persistent flag: -- transport stdio
|
|
153
|
+
claude mcp add token-shrink --transport stdio -- token-shrink-mcp --root /path/to/project
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
or place in `.claude/settings.json` / project `.mcp.json`:
|
|
157
|
+
|
|
158
|
+
```json
|
|
159
|
+
{
|
|
160
|
+
"mcpServers": {
|
|
161
|
+
"token-shrink": {
|
|
162
|
+
"command": "token-shrink-mcp",
|
|
163
|
+
"args": ["--root", "/path/to/project"]
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**Auto rule**: by default the server writes agent integration rules so the tool is used automatically on every prompt:
|
|
170
|
+
|
|
171
|
+
- **Cursor**: `.cursor/rules/token-shrink.mdc`
|
|
172
|
+
- **Claude Code**: `.claude/rules/token-shrink.md`
|
|
173
|
+
|
|
174
|
+
Both are sentinel-tagged and never rewrite a user-authored file at the same path. Repeated starts are no-ops. Choose the target(s) with `--rule-target=cursor|claude|all` (default `all`, comma-separated values allowed):
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
# only Claude Code
|
|
178
|
+
token-shrink-mcp --root /path/to/project --rule-target=claude
|
|
179
|
+
|
|
180
|
+
# completely disable auto-rules
|
|
181
|
+
token-shrink-mcp --root /path/to/project --no-create-rule
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Opt out also via `--create-rule=false` or `TOKEN_SHRINK_CREATE_RULE=0`.
|
|
185
|
+
|
|
186
|
+
**Tool:** `get_compressed_code_context`
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
| Argument | Type | Required | Description |
|
|
190
|
+
| ---------------- | --------- | -------- | --------------------------------------------- |
|
|
191
|
+
| `activeFilePath` | `string` | yes | The file the agent is working on |
|
|
192
|
+
| `maxSkeletons` | `number` | no | Cap on Ring-1 files (default `50`, max `200`) |
|
|
193
|
+
| `includeStats` | `boolean` | no | Append approximate token counts |
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
Returns a Markdown payload with the active file fully inlined (Ring 0) and the pruned skeletons of its direct imports (Ring 1).
|
|
197
|
+
|
|
198
|
+
### 2. HTTP server (Fastify)
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
token-shrink --root /path/to/project --port 3000
|
|
202
|
+
# env equivalents: ROOT=… PORT=… HOST=…
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
| Route | Method | Body | Returns |
|
|
207
|
+
| ------------- | ------ | -------------------------------------------------- | -------------------------------- |
|
|
208
|
+
| `/health` | `GET` | — | status, root, indexed file count |
|
|
209
|
+
| `/v1/context` | `POST` | `{ activeFilePath, maxSkeletons?, includeStats? }` | assembled Markdown + deps |
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
curl -s http://localhost:3000/health
|
|
214
|
+
# {"status":"ok","service":"token-shrink","version":"2.0.0","root":".","indexed":182}
|
|
215
|
+
|
|
216
|
+
curl -s -X POST http://localhost:3000/v1/context \
|
|
217
|
+
-H 'Content-Type: application/json' \
|
|
218
|
+
-d '{"activeFilePath":"./src/page.ts","includeStats":true}'
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
### 3. Library API
|
|
224
|
+
|
|
225
|
+
```ts
|
|
226
|
+
import { prune, assemble, createWatcher } from 'token-shrink';
|
|
227
|
+
|
|
228
|
+
// prune a single file -> skeleton (keeps signatures, strips bodies)
|
|
229
|
+
const { code, removed } = await prune('src/util.ts', sourceText);
|
|
230
|
+
|
|
231
|
+
// assemble context for an active file from a warm cache
|
|
232
|
+
const { markdown } = assemble('src/page.ts', watcher.cache.entries, {
|
|
233
|
+
includeStats: true,
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// incremental watcher
|
|
237
|
+
const watcher = createWatcher({ root: process.cwd(), ignored: ['node_modules'] });
|
|
238
|
+
await watcher.indexAll();
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
## Supported languages
|
|
246
|
+
|
|
247
|
+
S-expression queries match implementation blocks; interfaces, signatures, and exports are never touched. The `Block node` column shows the AST node that gets collapsed during pruning.
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
| Language | Extensions | Grammar wasm | Block node |
|
|
251
|
+
| --------------- | --------------------------------------- | ----------------------------- | -------------------- |
|
|
252
|
+
| TypeScript | `.ts` `.cts` `.mts` | `tree-sitter-typescript.wasm` | `statement_block` |
|
|
253
|
+
| JavaScript | `.js` `.cjs` `.mjs` | `tree-sitter-javascript.wasm` | `statement_block` |
|
|
254
|
+
| React / Next.js | `.tsx` | `tree-sitter-tsx.wasm` | `statement_block`¹ |
|
|
255
|
+
| React (JSX) | `.jsx` | `tree-sitter-javascript.wasm` | `statement_block`¹ |
|
|
256
|
+
| Python | `.py` `.pyi` | `tree-sitter-python.wasm` | `block` → `pass` |
|
|
257
|
+
| Dart / Flutter | `.dart` | `tree-sitter-dart.wasm` | `block` |
|
|
258
|
+
| Swift / SwiftUI | `.swift` | `tree-sitter-swift.wasm` | `statements` |
|
|
259
|
+
| Go | `.go` | `tree-sitter-go.wasm` | `block` |
|
|
260
|
+
| Rust | `.rs` | `tree-sitter-rust.wasm` | `block` |
|
|
261
|
+
| Java | `.java` | `tree-sitter-java.wasm` | `block` |
|
|
262
|
+
| Kotlin | `.kt` `.kts` | `tree-sitter-kotlin.wasm` | `block` |
|
|
263
|
+
| C | `.c` `.h` | `tree-sitter-c.wasm` | `compound_statement` |
|
|
264
|
+
| C++ | `.cc` `.cpp` `.cxx` `.hpp` `.hh` `.hxx` | `tree-sitter-cpp.wasm` | `compound_statement` |
|
|
265
|
+
| PHP | `.php` | `tree-sitter-php.wasm` | `compound_statement` |
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
> ¹ TSX/JSX also preserve `'use client'` / `'use server'` directive lines inside otherwise-pruned bodies (framework-aware).
|
|
269
|
+
|
|
270
|
+
Language IDs: `typescript · javascript · tsx · jsx · python · dart · swift · go · rust · java · kotlin · c · cpp · php`.
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
## Example
|
|
277
|
+
|
|
278
|
+
**Input** `src/util.ts`
|
|
279
|
+
|
|
280
|
+
```ts
|
|
281
|
+
export interface User {
|
|
282
|
+
id: number;
|
|
283
|
+
name: string;
|
|
284
|
+
}
|
|
285
|
+
export function buildGreeting(u: User) {
|
|
286
|
+
const parts = [u.name, u.email];
|
|
287
|
+
return parts.join(' | ');
|
|
288
|
+
}
|
|
289
|
+
export const formatEmail = (u: User) => {
|
|
290
|
+
return u.email.toLowerCase().trim();
|
|
291
|
+
};
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
**Pruned skeleton (Ring 1)** — signatures and the interface intact, bodies collapsed:
|
|
295
|
+
|
|
296
|
+
```ts
|
|
297
|
+
export interface User {
|
|
298
|
+
id: number;
|
|
299
|
+
name: string;
|
|
300
|
+
}
|
|
301
|
+
export function buildGreeting(u: User) /* ... */
|
|
302
|
+
export const formatEmail = (u: User) => /* ... */;
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
## Design notes
|
|
310
|
+
|
|
311
|
+
- **Bottom-up splicing** — ranges are sorted by start index descending and replaced in place, so earlier offsets never shift and the output stays a valid, parseable file.
|
|
312
|
+
- **Regex-based import extraction** — resilient across languages; resolves relative imports (`./x`, `../y`), aliases (`@/`, `~`), and skips bare package specifiers.
|
|
313
|
+
- **Incremental hashing** — files are re-pruned only when their `sha1` hash changes; the watcher is debounced (100 ms) and zero-CPU while idle.
|
|
314
|
+
- **Ram-safe watchers** — sockets / non-regular files are never opened with `fs.watch`, so stray unix sockets in the tree can't crash the server.
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
## Project layout
|
|
321
|
+
|
|
322
|
+
```
|
|
323
|
+
token-shrink/
|
|
324
|
+
├── package.json / tsconfig.json / tsup.config.ts / vitest.config.ts
|
|
325
|
+
├── src/
|
|
326
|
+
│ ├── index.ts # library entry (exports)
|
|
327
|
+
│ ├── cli.ts # Fastify HTTP server
|
|
328
|
+
│ ├── mcp.ts # MCP stdio server
|
|
329
|
+
│ ├── parser/
|
|
330
|
+
│ │ ├── registry.ts # extension → language spec + S-queries
|
|
331
|
+
│ │ ├── wasm.ts # auto-download + cache of .wasm files
|
|
332
|
+
│ │ └── pruner.ts # prune(filePath, source) → skeleton
|
|
333
|
+
│ ├── watcher/
|
|
334
|
+
│ │ └── sync.ts # chokidar watch + hash cache + import graph
|
|
335
|
+
│ └── server/
|
|
336
|
+
│ └── assembler.ts # Ring 0 + Ring 1 Markdown payload
|
|
337
|
+
├── tests/ # pruning integrity + token-reduction tests
|
|
338
|
+
└── wasm/ # auto-downloaded grammars (gitignored)
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
## License
|
|
344
|
+
|
|
345
|
+
MIT
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
assemble,
|
|
4
|
+
createWatcher,
|
|
5
|
+
warmGrammars
|
|
6
|
+
} from "./chunk-HRF3BIOV.js";
|
|
7
|
+
|
|
8
|
+
// src/cli.ts
|
|
9
|
+
import Fastify from "fastify";
|
|
10
|
+
import picocolors from "picocolors";
|
|
11
|
+
import path from "path";
|
|
12
|
+
function parseArgv(argv = process.argv) {
|
|
13
|
+
const out = {};
|
|
14
|
+
for (let i = 0; i < argv.length; i++) {
|
|
15
|
+
const cur = argv[i];
|
|
16
|
+
if (cur?.startsWith("--")) {
|
|
17
|
+
const key = cur.slice(2);
|
|
18
|
+
const next = argv[i + 1];
|
|
19
|
+
if (next && !next.startsWith("--")) {
|
|
20
|
+
out[key] = next;
|
|
21
|
+
i++;
|
|
22
|
+
} else {
|
|
23
|
+
out[key] = "true";
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
29
|
+
async function startServer(opts = {}) {
|
|
30
|
+
const args = parseArgv();
|
|
31
|
+
const port = opts.port ?? Number(args.port ?? process.env.PORT ?? 3e3);
|
|
32
|
+
const host = opts.host ?? args.host ?? "0.0.0.0";
|
|
33
|
+
const root = path.resolve(opts.root ?? args.root ?? process.cwd());
|
|
34
|
+
const silent = opts.silent ?? args.silent === "true";
|
|
35
|
+
const log = (msg) => {
|
|
36
|
+
if (!silent) console.log(picocolors.dim(msg));
|
|
37
|
+
};
|
|
38
|
+
void warmGrammars().catch(() => {
|
|
39
|
+
});
|
|
40
|
+
const watcher = createWatcher({ root, ignored: opts.ignored });
|
|
41
|
+
log(`Indexing ${root} in the background\u2026`);
|
|
42
|
+
void watcher.indexAll().then((n) => log(`Indexed ${n} files.`));
|
|
43
|
+
const app = Fastify({ logger: !silent });
|
|
44
|
+
app.get("/health", async () => ({
|
|
45
|
+
status: "ok",
|
|
46
|
+
service: "token-shrink",
|
|
47
|
+
version: "2.0.0",
|
|
48
|
+
root,
|
|
49
|
+
indexed: watcher.cache.entries.size
|
|
50
|
+
}));
|
|
51
|
+
app.post("/v1/context", async (req, reply) => {
|
|
52
|
+
const body = req.body ?? {};
|
|
53
|
+
if (!body.activeFilePath) {
|
|
54
|
+
return reply.status(400).send({ error: "`activeFilePath` is required" });
|
|
55
|
+
}
|
|
56
|
+
const result = assemble(body.activeFilePath, watcher.cache.entries, {
|
|
57
|
+
maxSkeletons: body.maxSkeletons,
|
|
58
|
+
includeStats: body.includeStats
|
|
59
|
+
});
|
|
60
|
+
return {
|
|
61
|
+
markdown: result.markdown,
|
|
62
|
+
activeFilePath: result.activeFilePath,
|
|
63
|
+
dependencies: result.included.map((i) => i.filePath),
|
|
64
|
+
unresolved: result.unresolved
|
|
65
|
+
};
|
|
66
|
+
});
|
|
67
|
+
app.setNotFoundHandler(async (req, reply) => {
|
|
68
|
+
void req;
|
|
69
|
+
return reply.status(404).send({ error: "Not found" });
|
|
70
|
+
});
|
|
71
|
+
await app.listen({ port, host });
|
|
72
|
+
log(`Listening on http://${host}:${port}`);
|
|
73
|
+
return { app, watcher };
|
|
74
|
+
}
|
|
75
|
+
var argv1 = process.argv[1] ? path.basename(process.argv[1]) : "";
|
|
76
|
+
if (argv1 === "cli.js" || argv1 === "cli.mjs" || argv1 === "cli.cjs" || argv1 === "cli.ts") {
|
|
77
|
+
startServer().catch((err) => {
|
|
78
|
+
console.error(picocolors.red(`[token-shrink] ${err.message}`));
|
|
79
|
+
process.exitCode = 1;
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export {
|
|
84
|
+
startServer
|
|
85
|
+
};
|
|
86
|
+
//# sourceMappingURL=chunk-7H6PGILN.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["/**\n * HTTP/CLI server. Exposes the context assembler as a small Fastify service:\n *\n * GET /health -> liveness\n * POST /v1/context -> { activeFilePath, maxSkeletons?, includeStats? } => markdown\n *\n * Run as `token-shrink` or `node dist/cli.js`. The server watches `--root`\n * (defaults to cwd) and keeps the import graph + skeletons warm.\n */\n\nimport Fastify from 'fastify';\nimport picocolors from 'picocolors';\nimport path from 'node:path';\n\nimport { assemble } from './server/assembler.js';\nimport { createWatcher, type Matcher } from './watcher/sync.js';\nimport { warmGrammars } from './parser/wasm.js';\n\nexport interface CliOptions {\n port?: number;\n host?: string;\n root?: string;\n ignored?: Matcher[];\n silent?: boolean;\n}\n\nfunction parseArgv(argv = process.argv): Record<string, string> {\n const out: Record<string, string> = {};\n for (let i = 0; i < argv.length; i++) {\n const cur = argv[i];\n if (cur?.startsWith('--')) {\n const key = cur.slice(2);\n const next = argv[i + 1];\n if (next && !next.startsWith('--')) {\n out[key] = next;\n i++;\n } else {\n out[key] = 'true';\n }\n }\n }\n return out;\n}\n\n/** Start the Fastify server; returns the running instance + watcher handle. */\nexport async function startServer(opts: CliOptions = {}) {\n const args = parseArgv();\n const port = opts.port ?? Number(args.port ?? process.env.PORT ?? 3000);\n const host = opts.host ?? args.host ?? '0.0.0.0';\n const root = path.resolve(opts.root ?? args.root ?? process.cwd());\n const silent = opts.silent ?? args.silent === 'true';\n\n const log = (msg: string) => {\n if (!silent) console.log(picocolors.dim(msg));\n };\n\n // Warm grammars in the background so the first prune isn't slow.\n void warmGrammars().catch(() => {});\n\n const watcher = createWatcher({ root, ignored: opts.ignored });\n log(`Indexing ${root} in the background…`);\n void watcher.indexAll().then((n) => log(`Indexed ${n} files.`));\n\n const app = Fastify({ logger: !silent });\n\n app.get('/health', async () => ({\n status: 'ok',\n service: 'token-shrink',\n version: '2.0.0',\n root,\n indexed: watcher.cache.entries.size,\n }));\n\n app.post('/v1/context', async (req, reply) => {\n const body = (req.body ?? {}) as {\n activeFilePath?: string;\n maxSkeletons?: number;\n includeStats?: boolean;\n };\n if (!body.activeFilePath) {\n return reply.status(400).send({ error: '`activeFilePath` is required' });\n }\n const result = assemble(body.activeFilePath, watcher.cache.entries, {\n maxSkeletons: body.maxSkeletons,\n includeStats: body.includeStats,\n });\n return {\n markdown: result.markdown,\n activeFilePath: result.activeFilePath,\n dependencies: result.included.map((i) => i.filePath),\n unresolved: result.unresolved,\n };\n });\n\n app.setNotFoundHandler(async (req, reply) => {\n void req;\n return reply.status(404).send({ error: 'Not found' });\n });\n\n await app.listen({ port, host });\n log(`Listening on http://${host}:${port}`);\n\n return { app, watcher };\n}\n\n// Start when invoked directly (`node dist/cli.cjs` / `token-shrink`).\nconst argv1 = process.argv[1] ? path.basename(process.argv[1]) : '';\nif (\n argv1 === 'cli.js' || argv1 === 'cli.mjs' ||\n argv1 === 'cli.cjs' || argv1 === 'cli.ts'\n) {\n startServer().catch((err) => {\n console.error(picocolors.red(`[token-shrink] ${err.message}`));\n process.exitCode = 1;\n });\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/parser/registry.ts
|
|
4
|
+
import path from "path";
|
|
5
|
+
var BLOCK = { query: "(statement_block) @block", replacement: { token: "/* ... */" } };
|
|
6
|
+
var SWIFT_BLOCK = { query: "(statements) @block", replacement: { token: "/* ... */" } };
|
|
7
|
+
var GO_BLOCK = { query: "(block) @block", replacement: { token: "/* ... */" } };
|
|
8
|
+
var RUST_BLOCK = { query: "(block) @block", replacement: { token: "/* ... */" } };
|
|
9
|
+
var PY_BLOCK = { query: "(block) @block", replacement: { token: "pass" } };
|
|
10
|
+
var C_BLOCK = { query: "(compound_statement) @block", replacement: { token: "/* ... */" } };
|
|
11
|
+
var JAVA_BLOCK = {
|
|
12
|
+
query: "(block) @block",
|
|
13
|
+
replacement: { token: "/* ... */" }
|
|
14
|
+
};
|
|
15
|
+
var PHP_BLOCK = {
|
|
16
|
+
query: "(compound_statement) @block",
|
|
17
|
+
replacement: { token: "/* ... */" }
|
|
18
|
+
};
|
|
19
|
+
var KOTLIN_BLOCK = { query: "(block) @block", replacement: { token: "/* ... */" } };
|
|
20
|
+
var DART_BLOCK = { query: "(block) @block", replacement: { token: "/* ... */" } };
|
|
21
|
+
var DIRECTIVE_KEEP = {
|
|
22
|
+
token: "/* ... */",
|
|
23
|
+
keepIf: /^\s*'use (client|server)'/
|
|
24
|
+
};
|
|
25
|
+
var registry = {
|
|
26
|
+
typescript: {
|
|
27
|
+
name: "TypeScript",
|
|
28
|
+
wasm: "tree-sitter-typescript.wasm",
|
|
29
|
+
url: "https://github.com/tree-sitter/tree-sitter-typescript/releases/latest/download/tree-sitter-typescript.wasm",
|
|
30
|
+
extensions: [".ts", ".cts", ".mts"],
|
|
31
|
+
rules: [BLOCK]
|
|
32
|
+
},
|
|
33
|
+
javascript: {
|
|
34
|
+
name: "JavaScript",
|
|
35
|
+
wasm: "tree-sitter-javascript.wasm",
|
|
36
|
+
url: "https://github.com/tree-sitter/tree-sitter-javascript/releases/latest/download/tree-sitter-javascript.wasm",
|
|
37
|
+
extensions: [".js", ".cjs", ".mjs"],
|
|
38
|
+
rules: [BLOCK]
|
|
39
|
+
},
|
|
40
|
+
tsx: {
|
|
41
|
+
name: "React / Next.js (TSX)",
|
|
42
|
+
wasm: "tree-sitter-tsx.wasm",
|
|
43
|
+
url: "https://github.com/tree-sitter/tree-sitter-typescript/releases/latest/download/tree-sitter-tsx.wasm",
|
|
44
|
+
extensions: [".tsx"],
|
|
45
|
+
rules: [{ query: "(statement_block) @block", replacement: DIRECTIVE_KEEP }]
|
|
46
|
+
},
|
|
47
|
+
jsx: {
|
|
48
|
+
name: "React (JSX)",
|
|
49
|
+
wasm: "tree-sitter-javascript.wasm",
|
|
50
|
+
url: "https://github.com/tree-sitter/tree-sitter-javascript/releases/latest/download/tree-sitter-javascript.wasm",
|
|
51
|
+
extensions: [".jsx"],
|
|
52
|
+
rules: [{ query: "(statement_block) @block", replacement: DIRECTIVE_KEEP }]
|
|
53
|
+
},
|
|
54
|
+
python: {
|
|
55
|
+
name: "Python",
|
|
56
|
+
wasm: "tree-sitter-python.wasm",
|
|
57
|
+
url: "https://github.com/tree-sitter/tree-sitter-python/releases/latest/download/tree-sitter-python.wasm",
|
|
58
|
+
extensions: [".py", ".pyi"],
|
|
59
|
+
rules: [PY_BLOCK]
|
|
60
|
+
},
|
|
61
|
+
dart: {
|
|
62
|
+
name: "Dart / Flutter",
|
|
63
|
+
wasm: "tree-sitter-dart.wasm",
|
|
64
|
+
url: "https://github.com/UserNobody14/tree-sitter-dart.wasm/releases/latest/download/tree-sitter-dart.wasm",
|
|
65
|
+
extensions: [".dart"],
|
|
66
|
+
rules: [DART_BLOCK]
|
|
67
|
+
},
|
|
68
|
+
swift: {
|
|
69
|
+
name: "Swift / SwiftUI",
|
|
70
|
+
wasm: "tree-sitter-swift.wasm",
|
|
71
|
+
url: "https://github.com/alex-pinkus/tree-sitter-swift/releases/latest/download/tree-sitter-swift.wasm",
|
|
72
|
+
extensions: [".swift"],
|
|
73
|
+
rules: [SWIFT_BLOCK]
|
|
74
|
+
},
|
|
75
|
+
go: {
|
|
76
|
+
name: "Go",
|
|
77
|
+
wasm: "tree-sitter-go.wasm",
|
|
78
|
+
url: "https://github.com/tree-sitter/tree-sitter-go/releases/latest/download/tree-sitter-go.wasm",
|
|
79
|
+
extensions: [".go"],
|
|
80
|
+
rules: [GO_BLOCK]
|
|
81
|
+
},
|
|
82
|
+
rust: {
|
|
83
|
+
name: "Rust",
|
|
84
|
+
wasm: "tree-sitter-rust.wasm",
|
|
85
|
+
url: "https://github.com/tree-sitter/tree-sitter-rust/releases/latest/download/tree-sitter-rust.wasm",
|
|
86
|
+
extensions: [".rs"],
|
|
87
|
+
rules: [RUST_BLOCK]
|
|
88
|
+
},
|
|
89
|
+
java: {
|
|
90
|
+
name: "Java",
|
|
91
|
+
wasm: "tree-sitter-java.wasm",
|
|
92
|
+
url: "https://github.com/tree-sitter/tree-sitter-java/releases/latest/download/tree-sitter-java.wasm",
|
|
93
|
+
extensions: [".java"],
|
|
94
|
+
rules: [JAVA_BLOCK]
|
|
95
|
+
},
|
|
96
|
+
kotlin: {
|
|
97
|
+
name: "Kotlin",
|
|
98
|
+
wasm: "tree-sitter-kotlin.wasm",
|
|
99
|
+
url: "https://github.com/fwcd/tree-sitter-kotlin/releases/latest/download/tree-sitter-kotlin.wasm",
|
|
100
|
+
extensions: [".kt", ".kts"],
|
|
101
|
+
rules: [KOTLIN_BLOCK]
|
|
102
|
+
},
|
|
103
|
+
c: {
|
|
104
|
+
name: "C",
|
|
105
|
+
wasm: "tree-sitter-c.wasm",
|
|
106
|
+
url: "https://github.com/tree-sitter/tree-sitter-c/releases/latest/download/tree-sitter-c.wasm",
|
|
107
|
+
extensions: [".c", ".h"],
|
|
108
|
+
rules: [C_BLOCK]
|
|
109
|
+
},
|
|
110
|
+
cpp: {
|
|
111
|
+
name: "C++",
|
|
112
|
+
wasm: "tree-sitter-cpp.wasm",
|
|
113
|
+
url: "https://github.com/tree-sitter/tree-sitter-cpp/releases/latest/download/tree-sitter-cpp.wasm",
|
|
114
|
+
extensions: [".cc", ".cpp", ".cxx", ".hpp", ".hh", ".hxx"],
|
|
115
|
+
rules: [C_BLOCK]
|
|
116
|
+
},
|
|
117
|
+
php: {
|
|
118
|
+
name: "PHP",
|
|
119
|
+
wasm: "tree-sitter-php.wasm",
|
|
120
|
+
url: "https://github.com/tree-sitter/tree-sitter-php/releases/latest/download/tree-sitter-php.wasm",
|
|
121
|
+
extensions: [".php"],
|
|
122
|
+
rules: [PHP_BLOCK]
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
var extensionIndex = /* @__PURE__ */ new Map();
|
|
126
|
+
for (const [id, spec] of Object.entries(registry)) {
|
|
127
|
+
for (const ext of spec.extensions) {
|
|
128
|
+
extensionIndex.set(ext, id);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function languageForFile(filePath) {
|
|
132
|
+
const langId = extensionIndex.get(path.extname(filePath).toLowerCase());
|
|
133
|
+
if (!langId) return null;
|
|
134
|
+
return registry[langId];
|
|
135
|
+
}
|
|
136
|
+
var supportedLanguageIds = Object.keys(registry);
|
|
137
|
+
var allExtensions = [...extensionIndex.keys()];
|
|
138
|
+
|
|
139
|
+
export {
|
|
140
|
+
registry,
|
|
141
|
+
languageForFile,
|
|
142
|
+
supportedLanguageIds,
|
|
143
|
+
allExtensions
|
|
144
|
+
};
|
|
145
|
+
//# sourceMappingURL=chunk-7SQ6HMWM.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/parser/registry.ts"],"sourcesContent":["/**\n * Language registry mapping file extensions to tree-sitter grammars and\n * pruning strategies. Each spec defines:\n * - the web-tree-sitter language name\n * - the `.wasm` file and the authoritative download URL\n * - one or more S-expression queries (the \"prune query\") that match the\n * implementation blocks to remove, plus the replacement token.\n *\n * The empty-query default of '' matches nothing, so the pruner leaves the\n * file untouched when a language has no registered prune rules yet.\n */\n\nimport path from 'node:path';\n\n/** Source of truth mirror in the PRD language matrix. */\nexport type Replacement = {\n /** Token used to replace a pruned block. */\n token: string;\n /**\n * Optional regex, run against the raw block text. If it matches, the block\n * is kept instead of pruned. Used to preserve directive/header lines\n * ('use client/server') even when they live inside an otherwise-pruned body.\n */\n keepIf?: RegExp;\n};\n\nexport interface LanguageSpec {\n /** Friendly name, used in logs and the report header. */\n name: string;\n /** web-tree-sitter `Language.load` file name. */\n wasm: string;\n /** Where to download the `.wasm` binary if not cached locally. */\n url: string;\n /** Extensions that map to this language: `.ts`, `.tsx`, etc. */\n extensions: string[];\n /**\n * One or more (query, replacement) pairs. Blocks matched by each query are\n * pruned bottom-up. Queries are combined; a block is pruned if it matches\n * any. This keeps small, frequently repeated constructs cheap to configure.\n */\n rules: { query: string; replacement: Replacement }[];\n}\n\n// ---------------------------------------------------------------------------\n// Pruning workhorse: prune every `statement_block` / `block` node.\n// ---------------------------------------------------------------------------\n\nconst BLOCK = { query: '(statement_block) @block', replacement: { token: '/* ... */' } };\nconst SWIFT_BLOCK = { query: '(statements) @block', replacement: { token: '/* ... */' } };\nconst GO_BLOCK = { query: '(block) @block', replacement: { token: '/* ... */' } };\nconst RUST_BLOCK = { query: '(block) @block', replacement: { token: '/* ... */' } };\nconst PY_BLOCK = { query: '(block) @block', replacement: { token: 'pass' } };\nconst C_BLOCK = { query: '(compound_statement) @block', replacement: { token: '/* ... */' } };\nconst JAVA_BLOCK = {\n query: '(block) @block',\n replacement: { token: '/* ... */' },\n};\nconst PHP_BLOCK = {\n query: '(compound_statement) @block',\n replacement: { token: '/* ... */' },\n};\nconst KOTLIN_BLOCK = { query: '(block) @block', replacement: { token: '/* ... */' } };\nconst DART_BLOCK = { query: '(block) @block', replacement: { token: '/* ... */' } };\n\n// Keeps `'use client'`/`'use server'` and top-level directive strings.\nconst DIRECTIVE_KEEP: Replacement = {\n token: '/* ... */',\n keepIf: /^\\s*'use (client|server)'/,\n};\n\nexport const registry: Record<string, LanguageSpec> = {\n typescript: {\n name: 'TypeScript',\n wasm: 'tree-sitter-typescript.wasm',\n url: 'https://github.com/tree-sitter/tree-sitter-typescript/releases/latest/download/tree-sitter-typescript.wasm',\n extensions: ['.ts', '.cts', '.mts'],\n rules: [BLOCK],\n },\n javascript: {\n name: 'JavaScript',\n wasm: 'tree-sitter-javascript.wasm',\n url: 'https://github.com/tree-sitter/tree-sitter-javascript/releases/latest/download/tree-sitter-javascript.wasm',\n extensions: ['.js', '.cjs', '.mjs'],\n rules: [BLOCK],\n },\n tsx: {\n name: 'React / Next.js (TSX)',\n wasm: 'tree-sitter-tsx.wasm',\n url: 'https://github.com/tree-sitter/tree-sitter-typescript/releases/latest/download/tree-sitter-tsx.wasm',\n extensions: ['.tsx'],\n rules: [{ query: '(statement_block) @block', replacement: DIRECTIVE_KEEP }],\n },\n jsx: {\n name: 'React (JSX)',\n wasm: 'tree-sitter-javascript.wasm',\n url: 'https://github.com/tree-sitter/tree-sitter-javascript/releases/latest/download/tree-sitter-javascript.wasm',\n extensions: ['.jsx'],\n rules: [{ query: '(statement_block) @block', replacement: DIRECTIVE_KEEP }],\n },\n python: {\n name: 'Python',\n wasm: 'tree-sitter-python.wasm',\n url: 'https://github.com/tree-sitter/tree-sitter-python/releases/latest/download/tree-sitter-python.wasm',\n extensions: ['.py', '.pyi'],\n rules: [PY_BLOCK],\n },\n dart: {\n name: 'Dart / Flutter',\n wasm: 'tree-sitter-dart.wasm',\n url: 'https://github.com/UserNobody14/tree-sitter-dart.wasm/releases/latest/download/tree-sitter-dart.wasm',\n extensions: ['.dart'],\n rules: [DART_BLOCK],\n },\n swift: {\n name: 'Swift / SwiftUI',\n wasm: 'tree-sitter-swift.wasm',\n url: 'https://github.com/alex-pinkus/tree-sitter-swift/releases/latest/download/tree-sitter-swift.wasm',\n extensions: ['.swift'],\n rules: [SWIFT_BLOCK],\n },\n go: {\n name: 'Go',\n wasm: 'tree-sitter-go.wasm',\n url: 'https://github.com/tree-sitter/tree-sitter-go/releases/latest/download/tree-sitter-go.wasm',\n extensions: ['.go'],\n rules: [GO_BLOCK],\n },\n rust: {\n name: 'Rust',\n wasm: 'tree-sitter-rust.wasm',\n url: 'https://github.com/tree-sitter/tree-sitter-rust/releases/latest/download/tree-sitter-rust.wasm',\n extensions: ['.rs'],\n rules: [RUST_BLOCK],\n },\n java: {\n name: 'Java',\n wasm: 'tree-sitter-java.wasm',\n url: 'https://github.com/tree-sitter/tree-sitter-java/releases/latest/download/tree-sitter-java.wasm',\n extensions: ['.java'],\n rules: [JAVA_BLOCK],\n },\n kotlin: {\n name: 'Kotlin',\n wasm: 'tree-sitter-kotlin.wasm',\n url: 'https://github.com/fwcd/tree-sitter-kotlin/releases/latest/download/tree-sitter-kotlin.wasm',\n extensions: ['.kt', '.kts'],\n rules: [KOTLIN_BLOCK],\n },\n c: {\n name: 'C',\n wasm: 'tree-sitter-c.wasm',\n url: 'https://github.com/tree-sitter/tree-sitter-c/releases/latest/download/tree-sitter-c.wasm',\n extensions: ['.c', '.h'],\n rules: [C_BLOCK],\n },\n cpp: {\n name: 'C++',\n wasm: 'tree-sitter-cpp.wasm',\n url: 'https://github.com/tree-sitter/tree-sitter-cpp/releases/latest/download/tree-sitter-cpp.wasm',\n extensions: ['.cc', '.cpp', '.cxx', '.hpp', '.hh', '.hxx'],\n rules: [C_BLOCK],\n },\n php: {\n name: 'PHP',\n wasm: 'tree-sitter-php.wasm',\n url: 'https://github.com/tree-sitter/tree-sitter-php/releases/latest/download/tree-sitter-php.wasm',\n extensions: ['.php'],\n rules: [PHP_BLOCK],\n },\n} satisfies Record<string, LanguageSpec>;\n\n/** Map of extension -> language spec id, built once. */\nconst extensionIndex = new Map<string, string>();\nfor (const [id, spec] of Object.entries(registry)) {\n for (const ext of spec.extensions) {\n extensionIndex.set(ext, id);\n }\n}\n\nexport type LanguageId = keyof typeof registry;\n\n/** Return the language spec for a file path, or null if unsupported. */\nexport function languageForFile(filePath: string): LanguageSpec | null {\n const langId = extensionIndex.get(path.extname(filePath).toLowerCase());\n if (!langId) return null;\n return registry[langId];\n}\n\n/** All languages that have at least one prune rule. */\nexport const supportedLanguageIds = Object.keys(registry) as LanguageId[];\n\n/** Convenience: list every known extension. */\nexport const allExtensions = [...extensionIndex.keys()];\n"],"mappings":"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","names":[]}
|