@ajdev0/token-shrink 2.0.2 → 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-KN4YKUZM.js → chunk-RI64PBQ5.js} +17 -7
- 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 +391 -119
- 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 +1189 -152
- 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 +1126 -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 +2 -1
- package/dist/chunk-HBYIAFR4.js +0 -204
- package/dist/chunk-HBYIAFR4.js.map +0 -1
- package/dist/chunk-HRF3BIOV.js +0 -518
- package/dist/chunk-HRF3BIOV.js.map +0 -1
- package/dist/chunk-KN4YKUZM.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,104 +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
|
|
192
|
-
var
|
|
191
|
+
var import_node_fs5 = __toESM(require("fs"), 1);
|
|
192
|
+
var import_node_path7 = __toESM(require("path"), 1);
|
|
193
193
|
|
|
194
194
|
// src/server/assembler.ts
|
|
195
195
|
var import_node_fs = __toESM(require("fs"), 1);
|
|
196
196
|
var import_node_path = __toESM(require("path"), 1);
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
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
|
+
});
|
|
202
218
|
const included = [];
|
|
203
219
|
const unresolved = [];
|
|
204
220
|
const seen = /* @__PURE__ */ new Set();
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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);
|
|
211
251
|
}
|
|
212
|
-
if (seen.has(imp)) continue;
|
|
213
|
-
seen.add(imp);
|
|
214
|
-
included.push({ filePath: imp, language: entry.language });
|
|
215
|
-
if (included.length >= maxSkeletons) break;
|
|
216
252
|
}
|
|
217
253
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
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;
|
|
231
266
|
}
|
|
267
|
+
selected.push(cand);
|
|
268
|
+
dependencyTokens += cost;
|
|
232
269
|
}
|
|
270
|
+
const activeTokens = ring0Texts.reduce(
|
|
271
|
+
(acc, t) => acc + approximateTokens(t),
|
|
272
|
+
0
|
|
273
|
+
);
|
|
233
274
|
const lines = [];
|
|
234
275
|
lines.push("# Compressed Code Context", "");
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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
|
+
}
|
|
241
292
|
lines.push(
|
|
242
|
-
`## Ring 1 \u2014 Pruned dependencies (${
|
|
293
|
+
`## Ring 1 \u2014 Pruned dependencies (${selected.length})`,
|
|
243
294
|
"",
|
|
244
295
|
"Implementation bodies removed; type signatures, interfaces and exports retained.",
|
|
245
296
|
""
|
|
246
297
|
);
|
|
247
|
-
if (
|
|
298
|
+
if (selected.length === 0) {
|
|
248
299
|
lines.push("_No local dependency skeletons available._", "");
|
|
249
300
|
}
|
|
250
|
-
for (const inc of
|
|
301
|
+
for (const inc of selected) {
|
|
251
302
|
const entry = cache.get(inc.filePath);
|
|
252
303
|
const label = rel(inc.filePath);
|
|
253
304
|
lines.push(`### \`${label}\``, "");
|
|
254
305
|
if (entry) {
|
|
255
|
-
lines.push(`\`\`\`${ext(inc.filePath)}
|
|
256
|
-
lines.push(entry.skeleton.trim());
|
|
257
|
-
lines.push("```", "");
|
|
306
|
+
lines.push(`\`\`\`${ext(inc.filePath)}`, entry.skeleton.trim(), "```", "");
|
|
258
307
|
} else {
|
|
259
308
|
lines.push("_Unindexed file._", "");
|
|
260
309
|
}
|
|
261
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
|
+
}
|
|
262
317
|
if (unresolved.length > 0) {
|
|
263
318
|
lines.push("## Unresolved imports", "");
|
|
264
319
|
for (const u of unresolved) lines.push(`- \`${u}\``);
|
|
265
320
|
lines.push("");
|
|
266
321
|
}
|
|
267
322
|
if (includeStats) {
|
|
268
|
-
const
|
|
269
|
-
(acc, inc) => {
|
|
270
|
-
const e = cache.get(inc.filePath);
|
|
271
|
-
return e ? acc + approximateTokens(e.skeleton) : acc;
|
|
272
|
-
},
|
|
273
|
-
0
|
|
274
|
-
);
|
|
275
|
-
lines.push("---", "");
|
|
323
|
+
const budgetNote = maxTokens !== void 0 ? ` \xB7 budget ${maxTokens} (${trimmed} deps trimmed)` : "";
|
|
276
324
|
lines.push(
|
|
277
|
-
|
|
325
|
+
"---",
|
|
326
|
+
"",
|
|
327
|
+
`_Token estimate \u2014 active: ${activeTokens} \xB7 pruned deps: ${dependencyTokens}${budgetNote}._`,
|
|
278
328
|
""
|
|
279
329
|
);
|
|
280
330
|
}
|
|
281
331
|
return {
|
|
282
332
|
markdown: lines.join("\n"),
|
|
283
|
-
activeFilePath:
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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
|
+
}
|
|
287
345
|
};
|
|
288
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
|
+
}
|
|
289
365
|
function extractSpecifiers(source) {
|
|
290
366
|
const found = [];
|
|
291
367
|
const re = /(?:from\s+['"`]|import\s+['"`]|require\s*\(\s*['"`])([^'"`]+)['"`]/g;
|
|
@@ -342,12 +418,12 @@ function approximateTokens(text) {
|
|
|
342
418
|
}
|
|
343
419
|
|
|
344
420
|
// src/watcher/sync.ts
|
|
345
|
-
var
|
|
346
|
-
var
|
|
421
|
+
var import_node_fs4 = __toESM(require("fs"), 1);
|
|
422
|
+
var import_node_path6 = __toESM(require("path"), 1);
|
|
347
423
|
var import_node_crypto = require("crypto");
|
|
348
424
|
var import_chokidar = __toESM(require("chokidar"), 1);
|
|
349
425
|
|
|
350
|
-
// src/parser/
|
|
426
|
+
// src/parser/analyze.ts
|
|
351
427
|
var import_web_tree_sitter = __toESM(require("web-tree-sitter"), 1);
|
|
352
428
|
var import_node_module = require("module");
|
|
353
429
|
var import_node_path4 = __toESM(require("path"), 1);
|
|
@@ -432,7 +508,85 @@ async function warmGrammars(specs = []) {
|
|
|
432
508
|
return results.filter((r) => r.status === "fulfilled").length;
|
|
433
509
|
}
|
|
434
510
|
|
|
435
|
-
// 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
|
|
436
590
|
var languageCache = /* @__PURE__ */ new Map();
|
|
437
591
|
var initPromise = null;
|
|
438
592
|
function ensureParserInit() {
|
|
@@ -465,35 +619,6 @@ function loadLanguage(spec, force = false) {
|
|
|
465
619
|
return language;
|
|
466
620
|
})();
|
|
467
621
|
}
|
|
468
|
-
async function prune(filePath, source, opts = {}) {
|
|
469
|
-
const spec = languageForFile(filePath);
|
|
470
|
-
if (!spec || spec.rules.length === 0) {
|
|
471
|
-
return { code: source, language: null, removed: 0 };
|
|
472
|
-
}
|
|
473
|
-
const language = await loadLanguage(spec, opts.forceDownload);
|
|
474
|
-
const parser = new import_web_tree_sitter.default();
|
|
475
|
-
parser.setLanguage(language);
|
|
476
|
-
const tree = parser.parse(source);
|
|
477
|
-
const ranges = [];
|
|
478
|
-
for (const rule of spec.rules) {
|
|
479
|
-
const query = language.query(rule.query);
|
|
480
|
-
const captures = query.captures(tree.rootNode);
|
|
481
|
-
for (const cap of captures) {
|
|
482
|
-
if (cap.node.startIndex === cap.node.endIndex) continue;
|
|
483
|
-
if (rule.replacement.keepIf?.test(cap.node.text)) continue;
|
|
484
|
-
ranges.push({
|
|
485
|
-
start: cap.node.startIndex,
|
|
486
|
-
end: cap.node.endIndex,
|
|
487
|
-
token: rule.replacement.token
|
|
488
|
-
});
|
|
489
|
-
}
|
|
490
|
-
query.delete();
|
|
491
|
-
}
|
|
492
|
-
tree.delete();
|
|
493
|
-
parser.delete();
|
|
494
|
-
const { code, removed } = spliceRanges(source, ranges);
|
|
495
|
-
return { code, language: spec.name, removed };
|
|
496
|
-
}
|
|
497
622
|
function spliceRanges(source, ranges) {
|
|
498
623
|
let removed = 0;
|
|
499
624
|
const sorted = [...ranges].sort((a, b) => b.start - a.start);
|
|
@@ -505,6 +630,128 @@ function spliceRanges(source, ranges) {
|
|
|
505
630
|
}
|
|
506
631
|
return { code: out, removed };
|
|
507
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
|
+
}
|
|
508
755
|
|
|
509
756
|
// src/watcher/sync.ts
|
|
510
757
|
var DEFAULT_IGNORED = [
|
|
@@ -524,7 +771,7 @@ var ContextCache = class {
|
|
|
524
771
|
}
|
|
525
772
|
/** Returns the cached skeleton for a file, if present. */
|
|
526
773
|
getSkeleton(filePath) {
|
|
527
|
-
return this.entries.get(
|
|
774
|
+
return this.entries.get(import_node_path6.default.resolve(filePath)) ?? null;
|
|
528
775
|
}
|
|
529
776
|
get imports() {
|
|
530
777
|
return this.entries;
|
|
@@ -546,18 +793,18 @@ function extractImports(_filePath, source) {
|
|
|
546
793
|
function resolveImport(importer, specifier, root) {
|
|
547
794
|
if (!/^[.~@]/.test(specifier)) {
|
|
548
795
|
if (specifier.startsWith("@/")) {
|
|
549
|
-
return resolveCandidate(
|
|
796
|
+
return resolveCandidate(import_node_path6.default.join(root, specifier.slice(2)));
|
|
550
797
|
}
|
|
551
798
|
if (specifier.startsWith("@")) {
|
|
552
799
|
return null;
|
|
553
800
|
}
|
|
554
801
|
if (specifier.startsWith("~")) {
|
|
555
|
-
return resolveCandidate(
|
|
802
|
+
return resolveCandidate(import_node_path6.default.join(root, specifier.slice(1)));
|
|
556
803
|
}
|
|
557
804
|
return null;
|
|
558
805
|
}
|
|
559
|
-
const base =
|
|
560
|
-
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));
|
|
561
808
|
}
|
|
562
809
|
function resolveCandidate(p) {
|
|
563
810
|
const candidates = [
|
|
@@ -580,29 +827,34 @@ function resolveCandidate(p) {
|
|
|
580
827
|
`${p}.h`,
|
|
581
828
|
`${p}.hpp`,
|
|
582
829
|
`${p}.php`,
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
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")
|
|
588
835
|
];
|
|
589
836
|
for (const c of candidates) {
|
|
590
|
-
if (
|
|
837
|
+
if (import_node_fs4.default.existsSync(c) && import_node_fs4.default.statSync(c).isFile()) return import_node_path6.default.resolve(c);
|
|
591
838
|
}
|
|
592
839
|
return null;
|
|
593
840
|
}
|
|
594
841
|
function createWatcher(opts) {
|
|
595
842
|
const cache = new ContextCache();
|
|
596
|
-
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
|
+
];
|
|
597
849
|
const pending = /* @__PURE__ */ new Map();
|
|
598
850
|
const DEBOUNCE_MS = 100;
|
|
599
851
|
async function handle(filePath) {
|
|
600
|
-
const abs =
|
|
852
|
+
const abs = import_node_path6.default.resolve(filePath);
|
|
601
853
|
let source;
|
|
602
854
|
try {
|
|
603
|
-
const st =
|
|
855
|
+
const st = import_node_fs4.default.statSync(abs);
|
|
604
856
|
if (!st.isFile()) return;
|
|
605
|
-
source =
|
|
857
|
+
source = import_node_fs4.default.readFileSync(abs, "utf8");
|
|
606
858
|
} catch {
|
|
607
859
|
return;
|
|
608
860
|
}
|
|
@@ -610,13 +862,18 @@ function createWatcher(opts) {
|
|
|
610
862
|
const cached = cache.entries.get(abs);
|
|
611
863
|
if (cached && cached.hash === hash) return;
|
|
612
864
|
await cache.ensureInit();
|
|
613
|
-
const
|
|
614
|
-
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);
|
|
615
871
|
const entry = {
|
|
616
872
|
hash,
|
|
617
873
|
skeleton: code,
|
|
618
874
|
language,
|
|
619
|
-
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 } : {}
|
|
620
877
|
};
|
|
621
878
|
cache.entries.set(abs, entry);
|
|
622
879
|
onIndexed?.(abs, entry);
|
|
@@ -626,7 +883,7 @@ function createWatcher(opts) {
|
|
|
626
883
|
// sockets) are never opened with fs.watch (which raises UVException).
|
|
627
884
|
ignored(_p, stats) {
|
|
628
885
|
if (stats && !stats.isFile() && !stats.isDirectory()) return true;
|
|
629
|
-
return isIgnored(
|
|
886
|
+
return isIgnored(import_node_path6.default.resolve(String(_p)), effectiveIgnored);
|
|
630
887
|
},
|
|
631
888
|
alwaysStat: true,
|
|
632
889
|
ignoreInitial: true,
|
|
@@ -634,7 +891,7 @@ function createWatcher(opts) {
|
|
|
634
891
|
awaitWriteFinish: { stabilityThreshold: 50, pollInterval: 10 }
|
|
635
892
|
});
|
|
636
893
|
function debounce(filePath) {
|
|
637
|
-
const abs =
|
|
894
|
+
const abs = import_node_path6.default.resolve(filePath);
|
|
638
895
|
const existing = pending.get(abs);
|
|
639
896
|
if (existing) clearTimeout(existing);
|
|
640
897
|
if (!existing) void handle(abs);
|
|
@@ -646,8 +903,9 @@ function createWatcher(opts) {
|
|
|
646
903
|
watcher.on("add", debounce);
|
|
647
904
|
watcher.on("change", debounce);
|
|
648
905
|
watcher.on("unlink", (filePath) => {
|
|
649
|
-
const abs =
|
|
906
|
+
const abs = import_node_path6.default.resolve(filePath);
|
|
650
907
|
cache.entries.delete(abs);
|
|
908
|
+
onRemoved?.(abs);
|
|
651
909
|
});
|
|
652
910
|
return {
|
|
653
911
|
cache,
|
|
@@ -660,7 +918,7 @@ function createWatcher(opts) {
|
|
|
660
918
|
*/
|
|
661
919
|
async indexAll() {
|
|
662
920
|
const files = [];
|
|
663
|
-
await walk(root, (f) => files.push(f),
|
|
921
|
+
await walk(root, (f) => files.push(f), effectiveIgnored);
|
|
664
922
|
let ok = 0;
|
|
665
923
|
for (const f of files) {
|
|
666
924
|
try {
|
|
@@ -675,9 +933,9 @@ function createWatcher(opts) {
|
|
|
675
933
|
};
|
|
676
934
|
}
|
|
677
935
|
async function walk(root, push, ignored) {
|
|
678
|
-
const entries = await
|
|
936
|
+
const entries = await import_node_fs4.default.promises.readdir(root, { withFileTypes: true });
|
|
679
937
|
for (const e of entries) {
|
|
680
|
-
const abs =
|
|
938
|
+
const abs = import_node_path6.default.join(root, e.name);
|
|
681
939
|
if (isIgnored(abs, ignored)) continue;
|
|
682
940
|
if (e.isDirectory()) {
|
|
683
941
|
await walk(abs, push, ignored);
|
|
@@ -688,6 +946,10 @@ async function walk(root, push, ignored) {
|
|
|
688
946
|
}
|
|
689
947
|
function isIgnored(abs, ignored) {
|
|
690
948
|
for (const m of ignored) {
|
|
949
|
+
if (typeof m === "function") {
|
|
950
|
+
if (m(abs)) return true;
|
|
951
|
+
continue;
|
|
952
|
+
}
|
|
691
953
|
if (typeof m === "string" && abs.includes(m)) return true;
|
|
692
954
|
if (m instanceof RegExp && m.test(abs)) return true;
|
|
693
955
|
}
|
|
@@ -716,8 +978,9 @@ async function startServer(opts = {}) {
|
|
|
716
978
|
const args = parseArgv();
|
|
717
979
|
const port = opts.port ?? Number(args.port ?? process.env.PORT ?? 3e3);
|
|
718
980
|
const host = opts.host ?? args.host ?? "0.0.0.0";
|
|
719
|
-
const root =
|
|
981
|
+
const root = import_node_path7.default.resolve(opts.root ?? args.root ?? process.cwd());
|
|
720
982
|
const silent = opts.silent ?? args.silent === "true";
|
|
983
|
+
const maxTokensDefault = args["max-tokens"] ? Number(args["max-tokens"]) : void 0;
|
|
721
984
|
const log = (msg) => {
|
|
722
985
|
if (!silent) console.log(import_picocolors.default.dim(msg));
|
|
723
986
|
};
|
|
@@ -736,18 +999,27 @@ async function startServer(opts = {}) {
|
|
|
736
999
|
}));
|
|
737
1000
|
app.post("/v1/context", async (req, reply) => {
|
|
738
1001
|
const body = req.body ?? {};
|
|
739
|
-
|
|
740
|
-
|
|
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" });
|
|
741
1009
|
}
|
|
742
|
-
const
|
|
1010
|
+
const maxTokens = body.maxTokens !== void 0 ? body.maxTokens : maxTokensDefault;
|
|
1011
|
+
const result = assembleMany(paths, watcher.cache.entries, {
|
|
743
1012
|
maxSkeletons: body.maxSkeletons,
|
|
1013
|
+
maxTokens: Number.isFinite(maxTokens) ? maxTokens : void 0,
|
|
744
1014
|
includeStats: body.includeStats
|
|
745
1015
|
});
|
|
746
1016
|
return {
|
|
747
1017
|
markdown: result.markdown,
|
|
748
1018
|
activeFilePath: result.activeFilePath,
|
|
1019
|
+
activeFilePaths: result.activeFilePaths,
|
|
749
1020
|
dependencies: result.included.map((i) => i.filePath),
|
|
750
|
-
unresolved: result.unresolved
|
|
1021
|
+
unresolved: result.unresolved,
|
|
1022
|
+
tokenStats: result.tokenStats
|
|
751
1023
|
};
|
|
752
1024
|
});
|
|
753
1025
|
app.setNotFoundHandler(async (req, reply) => {
|
|
@@ -758,10 +1030,10 @@ async function startServer(opts = {}) {
|
|
|
758
1030
|
log(`Listening on http://${host}:${port}`);
|
|
759
1031
|
return { app, watcher };
|
|
760
1032
|
}
|
|
761
|
-
var argv1 = process.argv[1] ?
|
|
1033
|
+
var argv1 = process.argv[1] ? import_node_path7.default.basename(process.argv[1]) : "";
|
|
762
1034
|
var argv1Real = "";
|
|
763
1035
|
try {
|
|
764
|
-
argv1Real = process.argv[1] ?
|
|
1036
|
+
argv1Real = process.argv[1] ? import_node_path7.default.basename(import_node_fs5.default.realpathSync(process.argv[1])) : "";
|
|
765
1037
|
} catch {
|
|
766
1038
|
}
|
|
767
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") {
|