@ajdev0/token-shrink 2.0.1 → 2.0.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/README.md +52 -14
- package/dist/chunk-FYCLLG7F.js +847 -0
- package/dist/chunk-FYCLLG7F.js.map +1 -0
- package/dist/{chunk-7H6PGILN.js → chunk-RI64PBQ5.js} +24 -8
- package/dist/chunk-RI64PBQ5.js.map +1 -0
- package/dist/chunk-RLHEIKPR.js +894 -0
- package/dist/chunk-RLHEIKPR.js.map +1 -0
- package/dist/cli-DzY7l7Rr.d.cts +182 -0
- package/dist/cli-DzY7l7Rr.d.ts +182 -0
- package/dist/cli.cjs +396 -118
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.cts +2 -1
- package/dist/cli.d.ts +2 -1
- package/dist/cli.js +2 -2
- package/dist/index.cjs +1200 -151
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +160 -20
- package/dist/index.d.ts +160 -20
- package/dist/index.js +37 -3
- package/dist/index.js.map +1 -1
- package/dist/mcp.cjs +1132 -136
- package/dist/mcp.cjs.map +1 -1
- package/dist/mcp.d.cts +21 -6
- package/dist/mcp.d.ts +21 -6
- package/dist/mcp.js +6 -2
- package/hooks/claude/README.md +29 -0
- package/hooks/claude/git-impact.mjs +47 -0
- package/hooks/claude/settings.example.json +24 -0
- package/package.json +3 -1
- package/dist/chunk-7H6PGILN.js.map +0 -1
- package/dist/chunk-ERJ3LBJ4.js +0 -198
- package/dist/chunk-ERJ3LBJ4.js.map +0 -1
- package/dist/chunk-HRF3BIOV.js +0 -518
- package/dist/chunk-HRF3BIOV.js.map +0 -1
- package/dist/cli-EpVqinpB.d.cts +0 -96
- package/dist/cli-EpVqinpB.d.ts +0 -96
package/dist/cli.cjs
CHANGED
|
@@ -188,103 +188,180 @@ __export(cli_exports, {
|
|
|
188
188
|
module.exports = __toCommonJS(cli_exports);
|
|
189
189
|
var import_fastify = __toESM(require("fastify"), 1);
|
|
190
190
|
var import_picocolors = __toESM(require("picocolors"), 1);
|
|
191
|
-
var
|
|
191
|
+
var import_node_fs5 = __toESM(require("fs"), 1);
|
|
192
|
+
var import_node_path7 = __toESM(require("path"), 1);
|
|
192
193
|
|
|
193
194
|
// src/server/assembler.ts
|
|
194
195
|
var import_node_fs = __toESM(require("fs"), 1);
|
|
195
196
|
var import_node_path = __toESM(require("path"), 1);
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
197
|
+
var MARKDOWN_OVERHEAD_TOKENS = 40;
|
|
198
|
+
function assembleMany(activeFiles, cache, opts = {}) {
|
|
199
|
+
const {
|
|
200
|
+
includeStats = false,
|
|
201
|
+
maxSkeletons = 50,
|
|
202
|
+
maxTokens,
|
|
203
|
+
pruneActiveFile = false
|
|
204
|
+
} = opts;
|
|
205
|
+
const absList = activeFiles.map((p) => import_node_path.default.resolve(p));
|
|
206
|
+
const sources = absList.map((abs) => {
|
|
207
|
+
try {
|
|
208
|
+
return import_node_fs.default.readFileSync(abs, "utf8");
|
|
209
|
+
} catch {
|
|
210
|
+
return "";
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
const activeSet = new Set(absList);
|
|
214
|
+
const ring0Texts = absList.map((abs, i) => {
|
|
215
|
+
const entry = cache.get(abs);
|
|
216
|
+
return pruneActiveFile && entry ? entry.skeleton : sources[i];
|
|
217
|
+
});
|
|
201
218
|
const included = [];
|
|
202
219
|
const unresolved = [];
|
|
203
220
|
const seen = /* @__PURE__ */ new Set();
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
221
|
+
const addUnresolved = (p) => {
|
|
222
|
+
if (!unresolved.includes(p)) unresolved.push(p);
|
|
223
|
+
};
|
|
224
|
+
const addIncluded = (p, allowUncached) => {
|
|
225
|
+
if (seen.has(p)) return;
|
|
226
|
+
const entry = cache.get(p);
|
|
227
|
+
if (!entry && !allowUncached) {
|
|
228
|
+
addUnresolved(p);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
seen.add(p);
|
|
232
|
+
included.push({ filePath: p, language: entry?.language ?? null });
|
|
233
|
+
};
|
|
234
|
+
for (let i = 0; i < absList.length; i++) {
|
|
235
|
+
const abs = absList[i];
|
|
236
|
+
const entry = cache.get(abs);
|
|
237
|
+
if (entry) {
|
|
238
|
+
for (const imp of entry.imports) {
|
|
239
|
+
if (activeSet.has(imp)) continue;
|
|
240
|
+
addIncluded(imp, false);
|
|
241
|
+
}
|
|
242
|
+
} else if (sources[i]) {
|
|
243
|
+
for (const spec of extractSpecifiers(sources[i])) {
|
|
244
|
+
const resolved = resolveLocal(abs, spec);
|
|
245
|
+
if (!resolved) {
|
|
246
|
+
addUnresolved(spec);
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
if (activeSet.has(resolved)) continue;
|
|
250
|
+
addIncluded(resolved, true);
|
|
210
251
|
}
|
|
211
|
-
if (seen.has(imp)) continue;
|
|
212
|
-
seen.add(imp);
|
|
213
|
-
included.push({ filePath: imp, language: entry.language });
|
|
214
|
-
if (included.length >= maxSkeletons) break;
|
|
215
252
|
}
|
|
216
253
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
if (included.length >= maxSkeletons) break;
|
|
254
|
+
const ordered = maxTokens === void 0 ? included : rankByRelevance(included, cache);
|
|
255
|
+
const budget = maxTokens === void 0 ? void 0 : Math.max(0, maxTokens - MARKDOWN_OVERHEAD_TOKENS);
|
|
256
|
+
const selected = [];
|
|
257
|
+
let trimmed = 0;
|
|
258
|
+
let dependencyTokens = 0;
|
|
259
|
+
for (const cand of ordered) {
|
|
260
|
+
if (selected.length >= maxSkeletons) break;
|
|
261
|
+
const entry = cache.get(cand.filePath);
|
|
262
|
+
const cost = entry ? approximateTokens(entry.skeleton) : 8;
|
|
263
|
+
if (budget !== void 0 && dependencyTokens + cost > budget) {
|
|
264
|
+
trimmed++;
|
|
265
|
+
continue;
|
|
230
266
|
}
|
|
267
|
+
selected.push(cand);
|
|
268
|
+
dependencyTokens += cost;
|
|
231
269
|
}
|
|
270
|
+
const activeTokens = ring0Texts.reduce(
|
|
271
|
+
(acc, t) => acc + approximateTokens(t),
|
|
272
|
+
0
|
|
273
|
+
);
|
|
232
274
|
const lines = [];
|
|
233
275
|
lines.push("# Compressed Code Context", "");
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
276
|
+
if (absList.length === 1) {
|
|
277
|
+
lines.push(`Active file: \`${rel(absList[0])}\``, "");
|
|
278
|
+
} else {
|
|
279
|
+
lines.push("Active files:", "");
|
|
280
|
+
for (const p of absList) lines.push(`- \`${rel(p)}\``);
|
|
281
|
+
lines.push("");
|
|
282
|
+
}
|
|
283
|
+
lines.push(
|
|
284
|
+
absList.length === 1 ? "## Ring 0 \u2014 Active file (full text)" : "## Ring 0 \u2014 Active files (full text)",
|
|
285
|
+
""
|
|
286
|
+
);
|
|
287
|
+
for (let i = 0; i < absList.length; i++) {
|
|
288
|
+
if (absList.length > 1) lines.push(`### \`${rel(absList[i])}\``, "");
|
|
289
|
+
const body = ring0Texts[i].trim() || "(empty or unreadable file)";
|
|
290
|
+
lines.push(`\`\`\`${ext(absList[i])}`, body, "```", "");
|
|
291
|
+
}
|
|
240
292
|
lines.push(
|
|
241
|
-
`## Ring 1 \u2014 Pruned dependencies (${
|
|
293
|
+
`## Ring 1 \u2014 Pruned dependencies (${selected.length})`,
|
|
242
294
|
"",
|
|
243
295
|
"Implementation bodies removed; type signatures, interfaces and exports retained.",
|
|
244
296
|
""
|
|
245
297
|
);
|
|
246
|
-
if (
|
|
298
|
+
if (selected.length === 0) {
|
|
247
299
|
lines.push("_No local dependency skeletons available._", "");
|
|
248
300
|
}
|
|
249
|
-
for (const inc of
|
|
301
|
+
for (const inc of selected) {
|
|
250
302
|
const entry = cache.get(inc.filePath);
|
|
251
303
|
const label = rel(inc.filePath);
|
|
252
304
|
lines.push(`### \`${label}\``, "");
|
|
253
305
|
if (entry) {
|
|
254
|
-
lines.push(`\`\`\`${ext(inc.filePath)}
|
|
255
|
-
lines.push(entry.skeleton.trim());
|
|
256
|
-
lines.push("```", "");
|
|
306
|
+
lines.push(`\`\`\`${ext(inc.filePath)}`, entry.skeleton.trim(), "```", "");
|
|
257
307
|
} else {
|
|
258
308
|
lines.push("_Unindexed file._", "");
|
|
259
309
|
}
|
|
260
310
|
}
|
|
311
|
+
if (maxTokens !== void 0 && trimmed > 0) {
|
|
312
|
+
lines.push(
|
|
313
|
+
`_Note: token budget of ${maxTokens} excluded ${trimmed} lower-priority dependenc${trimmed === 1 ? "y" : "ies"}._`,
|
|
314
|
+
""
|
|
315
|
+
);
|
|
316
|
+
}
|
|
261
317
|
if (unresolved.length > 0) {
|
|
262
318
|
lines.push("## Unresolved imports", "");
|
|
263
319
|
for (const u of unresolved) lines.push(`- \`${u}\``);
|
|
264
320
|
lines.push("");
|
|
265
321
|
}
|
|
266
322
|
if (includeStats) {
|
|
267
|
-
const
|
|
268
|
-
(acc, inc) => {
|
|
269
|
-
const e = cache.get(inc.filePath);
|
|
270
|
-
return e ? acc + approximateTokens(e.skeleton) : acc;
|
|
271
|
-
},
|
|
272
|
-
0
|
|
273
|
-
);
|
|
274
|
-
lines.push("---", "");
|
|
323
|
+
const budgetNote = maxTokens !== void 0 ? ` \xB7 budget ${maxTokens} (${trimmed} deps trimmed)` : "";
|
|
275
324
|
lines.push(
|
|
276
|
-
|
|
325
|
+
"---",
|
|
326
|
+
"",
|
|
327
|
+
`_Token estimate \u2014 active: ${activeTokens} \xB7 pruned deps: ${dependencyTokens}${budgetNote}._`,
|
|
277
328
|
""
|
|
278
329
|
);
|
|
279
330
|
}
|
|
280
331
|
return {
|
|
281
332
|
markdown: lines.join("\n"),
|
|
282
|
-
activeFilePath:
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
333
|
+
activeFilePath: absList[0] ?? "",
|
|
334
|
+
activeFilePaths: absList,
|
|
335
|
+
activeSource: sources.join("\n"),
|
|
336
|
+
included: selected,
|
|
337
|
+
unresolved,
|
|
338
|
+
tokenStats: {
|
|
339
|
+
activeTokens,
|
|
340
|
+
dependencyTokens,
|
|
341
|
+
totalTokens: activeTokens + dependencyTokens,
|
|
342
|
+
...maxTokens !== void 0 ? { budget: maxTokens } : {},
|
|
343
|
+
trimmed
|
|
344
|
+
}
|
|
286
345
|
};
|
|
287
346
|
}
|
|
347
|
+
function rankByRelevance(candidates, cache) {
|
|
348
|
+
const fanIn = /* @__PURE__ */ new Map();
|
|
349
|
+
for (const entry of cache.values()) {
|
|
350
|
+
for (const imp of entry.imports) fanIn.set(imp, (fanIn.get(imp) ?? 0) + 1);
|
|
351
|
+
}
|
|
352
|
+
return candidates.map((cand, idx) => ({ cand, idx })).sort((a, b) => {
|
|
353
|
+
const fa = fanIn.get(a.cand.filePath) ?? 0;
|
|
354
|
+
const fb = fanIn.get(b.cand.filePath) ?? 0;
|
|
355
|
+
if (fa !== fb) return fb - fa;
|
|
356
|
+
const da = pathDepth(a.cand.filePath);
|
|
357
|
+
const db = pathDepth(b.cand.filePath);
|
|
358
|
+
if (da !== db) return da - db;
|
|
359
|
+
return a.idx - b.idx;
|
|
360
|
+
}).map((x) => x.cand);
|
|
361
|
+
}
|
|
362
|
+
function pathDepth(p) {
|
|
363
|
+
return p.split(/[/\\]+/).filter(Boolean).length;
|
|
364
|
+
}
|
|
288
365
|
function extractSpecifiers(source) {
|
|
289
366
|
const found = [];
|
|
290
367
|
const re = /(?:from\s+['"`]|import\s+['"`]|require\s*\(\s*['"`])([^'"`]+)['"`]/g;
|
|
@@ -341,12 +418,12 @@ function approximateTokens(text) {
|
|
|
341
418
|
}
|
|
342
419
|
|
|
343
420
|
// src/watcher/sync.ts
|
|
344
|
-
var
|
|
345
|
-
var
|
|
421
|
+
var import_node_fs4 = __toESM(require("fs"), 1);
|
|
422
|
+
var import_node_path6 = __toESM(require("path"), 1);
|
|
346
423
|
var import_node_crypto = require("crypto");
|
|
347
424
|
var import_chokidar = __toESM(require("chokidar"), 1);
|
|
348
425
|
|
|
349
|
-
// src/parser/
|
|
426
|
+
// src/parser/analyze.ts
|
|
350
427
|
var import_web_tree_sitter = __toESM(require("web-tree-sitter"), 1);
|
|
351
428
|
var import_node_module = require("module");
|
|
352
429
|
var import_node_path4 = __toESM(require("path"), 1);
|
|
@@ -431,7 +508,85 @@ async function warmGrammars(specs = []) {
|
|
|
431
508
|
return results.filter((r) => r.status === "fulfilled").length;
|
|
432
509
|
}
|
|
433
510
|
|
|
434
|
-
// src/parser/
|
|
511
|
+
// src/parser/symbols.ts
|
|
512
|
+
var KIND_BY_TYPE = {
|
|
513
|
+
// TypeScript / JavaScript / TSX / JSX
|
|
514
|
+
function_declaration: "function",
|
|
515
|
+
generator_function_declaration: "function",
|
|
516
|
+
function_expression: "function",
|
|
517
|
+
function_signature_item: "function",
|
|
518
|
+
method_definition: "method",
|
|
519
|
+
method_signature: "method",
|
|
520
|
+
class_declaration: "class",
|
|
521
|
+
abstract_class_declaration: "class",
|
|
522
|
+
interface_declaration: "interface",
|
|
523
|
+
enum_declaration: "enum",
|
|
524
|
+
type_alias_declaration: "type",
|
|
525
|
+
// Python
|
|
526
|
+
function_definition: "function",
|
|
527
|
+
class_definition: "class",
|
|
528
|
+
// Go
|
|
529
|
+
method_declaration: "method",
|
|
530
|
+
type_spec: "type",
|
|
531
|
+
// Rust
|
|
532
|
+
function_item: "function",
|
|
533
|
+
struct_item: "class",
|
|
534
|
+
enum_item: "enum",
|
|
535
|
+
trait_item: "interface",
|
|
536
|
+
type_item: "type",
|
|
537
|
+
// Java / Kotlin / PHP / Dart / Swift (best-effort generic names)
|
|
538
|
+
constructor_declaration: "method",
|
|
539
|
+
module_declaration: "class",
|
|
540
|
+
protocol_declaration: "interface"
|
|
541
|
+
};
|
|
542
|
+
var SIGNATURE_MAX = 200;
|
|
543
|
+
function collectSymbols(root, source) {
|
|
544
|
+
const out = [];
|
|
545
|
+
const walk2 = (node) => {
|
|
546
|
+
const kind = KIND_BY_TYPE[node.type];
|
|
547
|
+
if (kind) {
|
|
548
|
+
const nameNode = node.childForFieldName("name");
|
|
549
|
+
if (nameNode && nameNode.text.trim()) {
|
|
550
|
+
out.push(symbolFrom(nameNode.text.trim(), kind, node.startIndex, node.endIndex, source));
|
|
551
|
+
}
|
|
552
|
+
} else if (node.type === "variable_declarator") {
|
|
553
|
+
const value = node.childForFieldName("value");
|
|
554
|
+
const valueType = value?.type;
|
|
555
|
+
if (valueType === "arrow_function" || valueType === "function_expression") {
|
|
556
|
+
const nameNode = node.childForFieldName("name");
|
|
557
|
+
if (nameNode && nameNode.text.trim()) {
|
|
558
|
+
out.push(
|
|
559
|
+
symbolFrom(nameNode.text.trim(), "arrow", node.startIndex, node.endIndex, source)
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
565
|
+
const child = node.child(i);
|
|
566
|
+
if (child) walk2(child);
|
|
567
|
+
}
|
|
568
|
+
};
|
|
569
|
+
walk2(root);
|
|
570
|
+
return out;
|
|
571
|
+
}
|
|
572
|
+
function symbolFrom(name, kind, start, end, source) {
|
|
573
|
+
return {
|
|
574
|
+
name,
|
|
575
|
+
kind,
|
|
576
|
+
line: source.slice(0, start).split("\n").length,
|
|
577
|
+
start,
|
|
578
|
+
end,
|
|
579
|
+
signature: signaturePreview(source, start, end)
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
function signaturePreview(source, start, end) {
|
|
583
|
+
const nl = source.indexOf("\n", start);
|
|
584
|
+
const endOfLine = nl === -1 ? end : nl;
|
|
585
|
+
const first = source.slice(start, Math.min(endOfLine, end)).trim();
|
|
586
|
+
return first.length > SIGNATURE_MAX ? `${first.slice(0, SIGNATURE_MAX)}\u2026` : first;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// src/parser/analyze.ts
|
|
435
590
|
var languageCache = /* @__PURE__ */ new Map();
|
|
436
591
|
var initPromise = null;
|
|
437
592
|
function ensureParserInit() {
|
|
@@ -464,35 +619,6 @@ function loadLanguage(spec, force = false) {
|
|
|
464
619
|
return language;
|
|
465
620
|
})();
|
|
466
621
|
}
|
|
467
|
-
async function prune(filePath, source, opts = {}) {
|
|
468
|
-
const spec = languageForFile(filePath);
|
|
469
|
-
if (!spec || spec.rules.length === 0) {
|
|
470
|
-
return { code: source, language: null, removed: 0 };
|
|
471
|
-
}
|
|
472
|
-
const language = await loadLanguage(spec, opts.forceDownload);
|
|
473
|
-
const parser = new import_web_tree_sitter.default();
|
|
474
|
-
parser.setLanguage(language);
|
|
475
|
-
const tree = parser.parse(source);
|
|
476
|
-
const ranges = [];
|
|
477
|
-
for (const rule of spec.rules) {
|
|
478
|
-
const query = language.query(rule.query);
|
|
479
|
-
const captures = query.captures(tree.rootNode);
|
|
480
|
-
for (const cap of captures) {
|
|
481
|
-
if (cap.node.startIndex === cap.node.endIndex) continue;
|
|
482
|
-
if (rule.replacement.keepIf?.test(cap.node.text)) continue;
|
|
483
|
-
ranges.push({
|
|
484
|
-
start: cap.node.startIndex,
|
|
485
|
-
end: cap.node.endIndex,
|
|
486
|
-
token: rule.replacement.token
|
|
487
|
-
});
|
|
488
|
-
}
|
|
489
|
-
query.delete();
|
|
490
|
-
}
|
|
491
|
-
tree.delete();
|
|
492
|
-
parser.delete();
|
|
493
|
-
const { code, removed } = spliceRanges(source, ranges);
|
|
494
|
-
return { code, language: spec.name, removed };
|
|
495
|
-
}
|
|
496
622
|
function spliceRanges(source, ranges) {
|
|
497
623
|
let removed = 0;
|
|
498
624
|
const sorted = [...ranges].sort((a, b) => b.start - a.start);
|
|
@@ -504,6 +630,128 @@ function spliceRanges(source, ranges) {
|
|
|
504
630
|
}
|
|
505
631
|
return { code: out, removed };
|
|
506
632
|
}
|
|
633
|
+
async function analyze(filePath, source, opts = {}) {
|
|
634
|
+
const spec = languageForFile(filePath);
|
|
635
|
+
if (!spec || spec.rules.length === 0) {
|
|
636
|
+
return { code: source, language: null, removed: 0, symbols: [] };
|
|
637
|
+
}
|
|
638
|
+
let language;
|
|
639
|
+
try {
|
|
640
|
+
language = await loadLanguage(spec, opts.forceDownload);
|
|
641
|
+
} catch {
|
|
642
|
+
return { code: source, language: null, removed: 0, symbols: [] };
|
|
643
|
+
}
|
|
644
|
+
const parser = new import_web_tree_sitter.default();
|
|
645
|
+
parser.setLanguage(language);
|
|
646
|
+
const tree = parser.parse(source);
|
|
647
|
+
try {
|
|
648
|
+
const symbols = collectSymbols(tree.rootNode, source);
|
|
649
|
+
if (opts.skipPrune) {
|
|
650
|
+
return { code: source, language: spec.name, removed: 0, symbols };
|
|
651
|
+
}
|
|
652
|
+
const keepRanges = [
|
|
653
|
+
...opts.keepBlocksInside ?? [],
|
|
654
|
+
...opts.preserveAnnotations?.length ? annotatedKeepRanges(symbols, source, opts.preserveAnnotations) : []
|
|
655
|
+
];
|
|
656
|
+
const isProtected = (start, end) => keepRanges.some((k) => k.start <= start && end <= k.end);
|
|
657
|
+
const ranges = [];
|
|
658
|
+
for (const rule of spec.rules) {
|
|
659
|
+
const query = language.query(rule.query);
|
|
660
|
+
const captures = query.captures(tree.rootNode);
|
|
661
|
+
for (const cap of captures) {
|
|
662
|
+
if (cap.node.startIndex === cap.node.endIndex) continue;
|
|
663
|
+
if (rule.replacement.keepIf?.test(cap.node.text)) continue;
|
|
664
|
+
if (isProtected(cap.node.startIndex, cap.node.endIndex)) continue;
|
|
665
|
+
ranges.push({
|
|
666
|
+
start: cap.node.startIndex,
|
|
667
|
+
end: cap.node.endIndex,
|
|
668
|
+
token: rule.replacement.token
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
query.delete();
|
|
672
|
+
}
|
|
673
|
+
const { code, removed } = spliceRanges(source, ranges);
|
|
674
|
+
return { code, language: spec.name, removed, symbols };
|
|
675
|
+
} finally {
|
|
676
|
+
tree.delete();
|
|
677
|
+
parser.delete();
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
var ANNOTATION_WINDOW = 240;
|
|
681
|
+
function annotatedKeepRanges(symbols, source, annotations) {
|
|
682
|
+
if (annotations.length === 0) return [];
|
|
683
|
+
const markers = annotations.filter(Boolean).map(escapeRegExp);
|
|
684
|
+
if (markers.length === 0) return [];
|
|
685
|
+
const re = new RegExp(`@(${markers.join("|")})\\b`, "i");
|
|
686
|
+
return symbols.filter((s) => {
|
|
687
|
+
const windowStart = Math.max(0, s.start - ANNOTATION_WINDOW);
|
|
688
|
+
const tail = source.slice(windowStart, s.start);
|
|
689
|
+
const cut = Math.max(tail.lastIndexOf("}\n"), tail.lastIndexOf(";\n"));
|
|
690
|
+
const relevant = cut === -1 ? tail : tail.slice(cut + 1);
|
|
691
|
+
return re.test(relevant);
|
|
692
|
+
}).map((s) => ({ start: s.start, end: s.end }));
|
|
693
|
+
}
|
|
694
|
+
function escapeRegExp(text) {
|
|
695
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// src/config.ts
|
|
699
|
+
var import_node_fs3 = __toESM(require("fs"), 1);
|
|
700
|
+
var import_node_path5 = __toESM(require("path"), 1);
|
|
701
|
+
function matchesAny(file, patterns) {
|
|
702
|
+
const rel2 = normalizeSlashes(file);
|
|
703
|
+
for (const raw of patterns) {
|
|
704
|
+
if (!raw) continue;
|
|
705
|
+
const pattern = normalizeSlashes(raw).replace(/^\.\//, "");
|
|
706
|
+
if (matchesGlob(rel2, pattern)) return true;
|
|
707
|
+
}
|
|
708
|
+
return false;
|
|
709
|
+
}
|
|
710
|
+
function matchesGlob(file, pattern) {
|
|
711
|
+
if (file === pattern) return true;
|
|
712
|
+
if (!/[?*[]/.test(pattern)) {
|
|
713
|
+
return file.endsWith(`/${pattern}`);
|
|
714
|
+
}
|
|
715
|
+
const re = globToRegExp(pattern);
|
|
716
|
+
return re.test(file);
|
|
717
|
+
}
|
|
718
|
+
function globToRegExp(glob) {
|
|
719
|
+
let re = "^";
|
|
720
|
+
for (let i = 0; i < glob.length; i++) {
|
|
721
|
+
const c = glob[i];
|
|
722
|
+
if (c === "*") {
|
|
723
|
+
if (glob[i + 1] === "*") {
|
|
724
|
+
i++;
|
|
725
|
+
if (glob[i + 1] === "/") {
|
|
726
|
+
i++;
|
|
727
|
+
re += "(?:.*/)?";
|
|
728
|
+
} else {
|
|
729
|
+
re += ".*";
|
|
730
|
+
}
|
|
731
|
+
} else {
|
|
732
|
+
re += "[^/]*";
|
|
733
|
+
}
|
|
734
|
+
} else if (c === "?") {
|
|
735
|
+
re += "[^/]";
|
|
736
|
+
} else if (c === "[") {
|
|
737
|
+
const close = glob.indexOf("]", i);
|
|
738
|
+
if (close === -1) {
|
|
739
|
+
re += "\\[";
|
|
740
|
+
} else {
|
|
741
|
+
const inner = glob.slice(i + 1, close).replace(/\\/g, "\\\\");
|
|
742
|
+
re += `[${inner}]`;
|
|
743
|
+
i = close;
|
|
744
|
+
}
|
|
745
|
+
} else {
|
|
746
|
+
re += c.replace(/[.+^${}()|\\]/g, "\\$&");
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
re += "$";
|
|
750
|
+
return new RegExp(re);
|
|
751
|
+
}
|
|
752
|
+
function normalizeSlashes(p) {
|
|
753
|
+
return p.split(import_node_path5.default.sep).join("/").replace(/^\.\//, "");
|
|
754
|
+
}
|
|
507
755
|
|
|
508
756
|
// src/watcher/sync.ts
|
|
509
757
|
var DEFAULT_IGNORED = [
|
|
@@ -523,7 +771,7 @@ var ContextCache = class {
|
|
|
523
771
|
}
|
|
524
772
|
/** Returns the cached skeleton for a file, if present. */
|
|
525
773
|
getSkeleton(filePath) {
|
|
526
|
-
return this.entries.get(
|
|
774
|
+
return this.entries.get(import_node_path6.default.resolve(filePath)) ?? null;
|
|
527
775
|
}
|
|
528
776
|
get imports() {
|
|
529
777
|
return this.entries;
|
|
@@ -545,18 +793,18 @@ function extractImports(_filePath, source) {
|
|
|
545
793
|
function resolveImport(importer, specifier, root) {
|
|
546
794
|
if (!/^[.~@]/.test(specifier)) {
|
|
547
795
|
if (specifier.startsWith("@/")) {
|
|
548
|
-
return resolveCandidate(
|
|
796
|
+
return resolveCandidate(import_node_path6.default.join(root, specifier.slice(2)));
|
|
549
797
|
}
|
|
550
798
|
if (specifier.startsWith("@")) {
|
|
551
799
|
return null;
|
|
552
800
|
}
|
|
553
801
|
if (specifier.startsWith("~")) {
|
|
554
|
-
return resolveCandidate(
|
|
802
|
+
return resolveCandidate(import_node_path6.default.join(root, specifier.slice(1)));
|
|
555
803
|
}
|
|
556
804
|
return null;
|
|
557
805
|
}
|
|
558
|
-
const base =
|
|
559
|
-
return resolveCandidate(
|
|
806
|
+
const base = import_node_path6.default.dirname(import_node_path6.default.resolve(importer));
|
|
807
|
+
return resolveCandidate(import_node_path6.default.resolve(base, specifier));
|
|
560
808
|
}
|
|
561
809
|
function resolveCandidate(p) {
|
|
562
810
|
const candidates = [
|
|
@@ -579,29 +827,34 @@ function resolveCandidate(p) {
|
|
|
579
827
|
`${p}.h`,
|
|
580
828
|
`${p}.hpp`,
|
|
581
829
|
`${p}.php`,
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
830
|
+
import_node_path6.default.join(p, "index.ts"),
|
|
831
|
+
import_node_path6.default.join(p, "index.js"),
|
|
832
|
+
import_node_path6.default.join(p, "index.tsx"),
|
|
833
|
+
import_node_path6.default.join(p, "index.jsx"),
|
|
834
|
+
import_node_path6.default.join(p, "index.py")
|
|
587
835
|
];
|
|
588
836
|
for (const c of candidates) {
|
|
589
|
-
if (
|
|
837
|
+
if (import_node_fs4.default.existsSync(c) && import_node_fs4.default.statSync(c).isFile()) return import_node_path6.default.resolve(c);
|
|
590
838
|
}
|
|
591
839
|
return null;
|
|
592
840
|
}
|
|
593
841
|
function createWatcher(opts) {
|
|
594
842
|
const cache = new ContextCache();
|
|
595
|
-
const { root, ignored = DEFAULT_IGNORED, onIndexed } = opts;
|
|
843
|
+
const { root, ignored = DEFAULT_IGNORED, config, onIndexed, onRemoved } = opts;
|
|
844
|
+
const rel2 = (abs) => import_node_path6.default.relative(root, abs);
|
|
845
|
+
const effectiveIgnored = [
|
|
846
|
+
...ignored,
|
|
847
|
+
...config?.ignorePatterns?.length ? [(_abs) => matchesAny(rel2(String(_abs)), config.ignorePatterns)] : []
|
|
848
|
+
];
|
|
596
849
|
const pending = /* @__PURE__ */ new Map();
|
|
597
850
|
const DEBOUNCE_MS = 100;
|
|
598
851
|
async function handle(filePath) {
|
|
599
|
-
const abs =
|
|
852
|
+
const abs = import_node_path6.default.resolve(filePath);
|
|
600
853
|
let source;
|
|
601
854
|
try {
|
|
602
|
-
const st =
|
|
855
|
+
const st = import_node_fs4.default.statSync(abs);
|
|
603
856
|
if (!st.isFile()) return;
|
|
604
|
-
source =
|
|
857
|
+
source = import_node_fs4.default.readFileSync(abs, "utf8");
|
|
605
858
|
} catch {
|
|
606
859
|
return;
|
|
607
860
|
}
|
|
@@ -609,13 +862,18 @@ function createWatcher(opts) {
|
|
|
609
862
|
const cached = cache.entries.get(abs);
|
|
610
863
|
if (cached && cached.hash === hash) return;
|
|
611
864
|
await cache.ensureInit();
|
|
612
|
-
const
|
|
613
|
-
const
|
|
865
|
+
const keepFull = config?.keepUnpruned?.length ? matchesAny(import_node_path6.default.relative(root, abs), config.keepUnpruned) : false;
|
|
866
|
+
const { code, language, symbols } = await analyze(abs, source, {
|
|
867
|
+
skipPrune: keepFull,
|
|
868
|
+
preserveAnnotations: config?.preserveAnnotations
|
|
869
|
+
});
|
|
870
|
+
const imports = extractImports(abs, source);
|
|
614
871
|
const entry = {
|
|
615
872
|
hash,
|
|
616
873
|
skeleton: code,
|
|
617
874
|
language,
|
|
618
|
-
imports: imports.map((spec) => resolveImport(abs, spec, root)).filter((p) => p !== null)
|
|
875
|
+
imports: imports.map((spec) => resolveImport(abs, spec, root)).filter((p) => p !== null),
|
|
876
|
+
...symbols.length > 0 ? { symbols } : {}
|
|
619
877
|
};
|
|
620
878
|
cache.entries.set(abs, entry);
|
|
621
879
|
onIndexed?.(abs, entry);
|
|
@@ -625,7 +883,7 @@ function createWatcher(opts) {
|
|
|
625
883
|
// sockets) are never opened with fs.watch (which raises UVException).
|
|
626
884
|
ignored(_p, stats) {
|
|
627
885
|
if (stats && !stats.isFile() && !stats.isDirectory()) return true;
|
|
628
|
-
return isIgnored(
|
|
886
|
+
return isIgnored(import_node_path6.default.resolve(String(_p)), effectiveIgnored);
|
|
629
887
|
},
|
|
630
888
|
alwaysStat: true,
|
|
631
889
|
ignoreInitial: true,
|
|
@@ -633,7 +891,7 @@ function createWatcher(opts) {
|
|
|
633
891
|
awaitWriteFinish: { stabilityThreshold: 50, pollInterval: 10 }
|
|
634
892
|
});
|
|
635
893
|
function debounce(filePath) {
|
|
636
|
-
const abs =
|
|
894
|
+
const abs = import_node_path6.default.resolve(filePath);
|
|
637
895
|
const existing = pending.get(abs);
|
|
638
896
|
if (existing) clearTimeout(existing);
|
|
639
897
|
if (!existing) void handle(abs);
|
|
@@ -645,8 +903,9 @@ function createWatcher(opts) {
|
|
|
645
903
|
watcher.on("add", debounce);
|
|
646
904
|
watcher.on("change", debounce);
|
|
647
905
|
watcher.on("unlink", (filePath) => {
|
|
648
|
-
const abs =
|
|
906
|
+
const abs = import_node_path6.default.resolve(filePath);
|
|
649
907
|
cache.entries.delete(abs);
|
|
908
|
+
onRemoved?.(abs);
|
|
650
909
|
});
|
|
651
910
|
return {
|
|
652
911
|
cache,
|
|
@@ -659,7 +918,7 @@ function createWatcher(opts) {
|
|
|
659
918
|
*/
|
|
660
919
|
async indexAll() {
|
|
661
920
|
const files = [];
|
|
662
|
-
await walk(root, (f) => files.push(f),
|
|
921
|
+
await walk(root, (f) => files.push(f), effectiveIgnored);
|
|
663
922
|
let ok = 0;
|
|
664
923
|
for (const f of files) {
|
|
665
924
|
try {
|
|
@@ -674,9 +933,9 @@ function createWatcher(opts) {
|
|
|
674
933
|
};
|
|
675
934
|
}
|
|
676
935
|
async function walk(root, push, ignored) {
|
|
677
|
-
const entries = await
|
|
936
|
+
const entries = await import_node_fs4.default.promises.readdir(root, { withFileTypes: true });
|
|
678
937
|
for (const e of entries) {
|
|
679
|
-
const abs =
|
|
938
|
+
const abs = import_node_path6.default.join(root, e.name);
|
|
680
939
|
if (isIgnored(abs, ignored)) continue;
|
|
681
940
|
if (e.isDirectory()) {
|
|
682
941
|
await walk(abs, push, ignored);
|
|
@@ -687,6 +946,10 @@ async function walk(root, push, ignored) {
|
|
|
687
946
|
}
|
|
688
947
|
function isIgnored(abs, ignored) {
|
|
689
948
|
for (const m of ignored) {
|
|
949
|
+
if (typeof m === "function") {
|
|
950
|
+
if (m(abs)) return true;
|
|
951
|
+
continue;
|
|
952
|
+
}
|
|
690
953
|
if (typeof m === "string" && abs.includes(m)) return true;
|
|
691
954
|
if (m instanceof RegExp && m.test(abs)) return true;
|
|
692
955
|
}
|
|
@@ -715,8 +978,9 @@ async function startServer(opts = {}) {
|
|
|
715
978
|
const args = parseArgv();
|
|
716
979
|
const port = opts.port ?? Number(args.port ?? process.env.PORT ?? 3e3);
|
|
717
980
|
const host = opts.host ?? args.host ?? "0.0.0.0";
|
|
718
|
-
const root =
|
|
981
|
+
const root = import_node_path7.default.resolve(opts.root ?? args.root ?? process.cwd());
|
|
719
982
|
const silent = opts.silent ?? args.silent === "true";
|
|
983
|
+
const maxTokensDefault = args["max-tokens"] ? Number(args["max-tokens"]) : void 0;
|
|
720
984
|
const log = (msg) => {
|
|
721
985
|
if (!silent) console.log(import_picocolors.default.dim(msg));
|
|
722
986
|
};
|
|
@@ -735,18 +999,27 @@ async function startServer(opts = {}) {
|
|
|
735
999
|
}));
|
|
736
1000
|
app.post("/v1/context", async (req, reply) => {
|
|
737
1001
|
const body = req.body ?? {};
|
|
738
|
-
|
|
739
|
-
|
|
1002
|
+
const hasMany = Array.isArray(body.activeFiles) && body.activeFiles.length > 0;
|
|
1003
|
+
if (hasMany && body.activeFilePath) {
|
|
1004
|
+
return reply.status(400).send({ error: "Pass either `activeFilePath` or `activeFiles`, not both." });
|
|
1005
|
+
}
|
|
1006
|
+
const paths = hasMany ? body.activeFiles : body.activeFilePath ? [body.activeFilePath] : [];
|
|
1007
|
+
if (paths.length === 0) {
|
|
1008
|
+
return reply.status(400).send({ error: "`activeFilePath` or `activeFiles` is required" });
|
|
740
1009
|
}
|
|
741
|
-
const
|
|
1010
|
+
const maxTokens = body.maxTokens !== void 0 ? body.maxTokens : maxTokensDefault;
|
|
1011
|
+
const result = assembleMany(paths, watcher.cache.entries, {
|
|
742
1012
|
maxSkeletons: body.maxSkeletons,
|
|
1013
|
+
maxTokens: Number.isFinite(maxTokens) ? maxTokens : void 0,
|
|
743
1014
|
includeStats: body.includeStats
|
|
744
1015
|
});
|
|
745
1016
|
return {
|
|
746
1017
|
markdown: result.markdown,
|
|
747
1018
|
activeFilePath: result.activeFilePath,
|
|
1019
|
+
activeFilePaths: result.activeFilePaths,
|
|
748
1020
|
dependencies: result.included.map((i) => i.filePath),
|
|
749
|
-
unresolved: result.unresolved
|
|
1021
|
+
unresolved: result.unresolved,
|
|
1022
|
+
tokenStats: result.tokenStats
|
|
750
1023
|
};
|
|
751
1024
|
});
|
|
752
1025
|
app.setNotFoundHandler(async (req, reply) => {
|
|
@@ -757,8 +1030,13 @@ async function startServer(opts = {}) {
|
|
|
757
1030
|
log(`Listening on http://${host}:${port}`);
|
|
758
1031
|
return { app, watcher };
|
|
759
1032
|
}
|
|
760
|
-
var argv1 = process.argv[1] ?
|
|
761
|
-
|
|
1033
|
+
var argv1 = process.argv[1] ? import_node_path7.default.basename(process.argv[1]) : "";
|
|
1034
|
+
var argv1Real = "";
|
|
1035
|
+
try {
|
|
1036
|
+
argv1Real = process.argv[1] ? import_node_path7.default.basename(import_node_fs5.default.realpathSync(process.argv[1])) : "";
|
|
1037
|
+
} catch {
|
|
1038
|
+
}
|
|
1039
|
+
if (argv1 === "cli.js" || argv1 === "cli.mjs" || argv1 === "cli.cjs" || argv1 === "cli.ts" || argv1Real === "cli.js" || argv1Real === "cli.mjs" || argv1Real === "cli.cjs" || argv1Real === "cli.ts") {
|
|
762
1040
|
startServer().catch((err) => {
|
|
763
1041
|
console.error(import_picocolors.default.red(`[token-shrink] ${err.message}`));
|
|
764
1042
|
process.exitCode = 1;
|