make-laten 1.1.0 → 1.2.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 +198 -51
- package/dist/cli/index.js +376 -175
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +376 -175
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.js +288 -68
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +274 -67
- package/dist/index.mjs.map +1 -1
- package/dist/mcp/server.js +250 -68
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/server.mjs +250 -68
- package/dist/mcp/server.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -180,29 +180,157 @@ function isKnownPattern(content) {
|
|
|
180
180
|
return patterns.some((p) => p.test(content));
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
+
// src/compress/strip.ts
|
|
184
|
+
function stripImports(content) {
|
|
185
|
+
return content.replace(/^import\s+[\s\S]*?from\s+['"][^'"]+['"]\s*;?\s*$/gm, "").replace(/^import\s+['"][^'"]+['"]\s*;?\s*$/gm, "").replace(/^import\s+\{[^}]*\}\s+from\s+['"][^'"]+['"]\s*;?\s*$/gm, "").split("\n").filter((l) => l.trim() !== "").join("\n");
|
|
186
|
+
}
|
|
187
|
+
function stripComments(content) {
|
|
188
|
+
let result = content.replace(/(?<!["'`])\/\/.*$/gm, "");
|
|
189
|
+
result = result.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
190
|
+
return result.split("\n").filter((l) => l.trim() !== "").join("\n");
|
|
191
|
+
}
|
|
192
|
+
function stripBlankLines(content) {
|
|
193
|
+
return content.replace(/\n{3,}/g, "\n\n");
|
|
194
|
+
}
|
|
195
|
+
function stripWhitespace(content) {
|
|
196
|
+
return content.replace(/[ \t]+$/gm, "");
|
|
197
|
+
}
|
|
198
|
+
function stripAll(content) {
|
|
199
|
+
let result = content;
|
|
200
|
+
result = stripImports(result);
|
|
201
|
+
result = stripComments(result);
|
|
202
|
+
result = stripBlankLines(result);
|
|
203
|
+
result = stripWhitespace(result);
|
|
204
|
+
return result.trim();
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// src/compress/structure.ts
|
|
208
|
+
function extractExports(content) {
|
|
209
|
+
const lines = content.split("\n");
|
|
210
|
+
const exports = [];
|
|
211
|
+
for (const line of lines) {
|
|
212
|
+
const trimmed = line.trim();
|
|
213
|
+
if (/^export\s+(default\s+)?/.test(trimmed)) {
|
|
214
|
+
if (trimmed.includes("{") && !trimmed.includes("}")) {
|
|
215
|
+
exports.push(trimmed.replace(/\{[\s\S]*$/, "{ ... }"));
|
|
216
|
+
} else {
|
|
217
|
+
exports.push(trimmed);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return exports.join("\n");
|
|
222
|
+
}
|
|
223
|
+
function extractClassMethods(content) {
|
|
224
|
+
const classRegex = /class\s+\w+[^{]*\{([\s\S]*?)\n\}/g;
|
|
225
|
+
const methods = [];
|
|
226
|
+
let match;
|
|
227
|
+
while ((match = classRegex.exec(content)) !== null) {
|
|
228
|
+
const classBody = match[1];
|
|
229
|
+
const methodRegex = /(^\s*(?:public|private|protected|static|async|abstract|\s)*\w+\s*\([^)]*\)(?:\s*:\s*[^{]+)?)\s*\{/gm;
|
|
230
|
+
let methodMatch;
|
|
231
|
+
while ((methodMatch = methodRegex.exec(classBody)) !== null) {
|
|
232
|
+
methods.push(methodMatch[1].trim());
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return methods.join("\n");
|
|
236
|
+
}
|
|
237
|
+
function extractFunctionSignatures(content) {
|
|
238
|
+
const funcRegex = /(export\s+)?(async\s+)?function\s+\w+\s*\([^)]*\)(?:\s*:\s*[^{]+)?\s*\{/g;
|
|
239
|
+
const signatures = [];
|
|
240
|
+
let match;
|
|
241
|
+
while ((match = funcRegex.exec(content)) !== null) {
|
|
242
|
+
signatures.push(match[0].replace(/\{$/, ""));
|
|
243
|
+
}
|
|
244
|
+
return signatures.join("\n");
|
|
245
|
+
}
|
|
246
|
+
function extractTypeDefinitions(content) {
|
|
247
|
+
const typeRegex = /(export\s+)?(interface|type)\s+\w+[^{]*\{[^}]*\}|(export\s+)?type\s+\w+\s*=[^;]+;?/g;
|
|
248
|
+
const types = [];
|
|
249
|
+
let match;
|
|
250
|
+
while ((match = typeRegex.exec(content)) !== null) {
|
|
251
|
+
types.push(match[0].trim());
|
|
252
|
+
}
|
|
253
|
+
return types.join("\n");
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// src/compress/truncate.ts
|
|
257
|
+
function smartTruncate(content, maxTokens) {
|
|
258
|
+
const lines = content.split("\n");
|
|
259
|
+
const estimatedTokens = Math.ceil(content.length / 4);
|
|
260
|
+
if (estimatedTokens <= maxTokens) return content;
|
|
261
|
+
const targetLines = Math.round(maxTokens * 4 / (content.length / lines.length));
|
|
262
|
+
const headerLines = Math.floor(targetLines * 0.3);
|
|
263
|
+
const footerLines = Math.floor(targetLines * 0.3);
|
|
264
|
+
const omitted = lines.length - headerLines - footerLines;
|
|
265
|
+
const header = lines.slice(0, headerLines);
|
|
266
|
+
const footer = lines.slice(-footerLines);
|
|
267
|
+
return [...header, `
|
|
268
|
+
... [${omitted} lines omitted] ...
|
|
269
|
+
`, ...footer].join("\n");
|
|
270
|
+
}
|
|
271
|
+
function truncateByTokens(text, maxTokens) {
|
|
272
|
+
const maxChars = maxTokens * 4;
|
|
273
|
+
if (text.length <= maxChars) return text;
|
|
274
|
+
const truncated = text.slice(0, maxChars);
|
|
275
|
+
const lastNewline = truncated.lastIndexOf("\n");
|
|
276
|
+
return truncated.slice(0, lastNewline > 0 ? lastNewline : maxChars) + "\n\n... (truncated)";
|
|
277
|
+
}
|
|
278
|
+
function preserveContext(lines, targetIndex, before, after) {
|
|
279
|
+
const start = Math.max(0, targetIndex - before);
|
|
280
|
+
const end = Math.min(lines.length, targetIndex + after + 1);
|
|
281
|
+
return lines.slice(start, end);
|
|
282
|
+
}
|
|
283
|
+
|
|
183
284
|
// src/compress/file-read.ts
|
|
184
285
|
var FileReadCompressor = class {
|
|
185
286
|
async compress(input) {
|
|
186
287
|
const { content, filePath, language } = input;
|
|
187
|
-
const
|
|
188
|
-
const
|
|
189
|
-
|
|
288
|
+
const lines = content.split("\n").length;
|
|
289
|
+
const tokens = Math.ceil(content.length / 4);
|
|
290
|
+
if (tokens < 200) {
|
|
291
|
+
return {
|
|
292
|
+
content,
|
|
293
|
+
original: content,
|
|
294
|
+
confidence: 1,
|
|
295
|
+
metadata: {
|
|
296
|
+
strategy: "passthrough",
|
|
297
|
+
originalLines: lines,
|
|
298
|
+
compressedLines: lines,
|
|
299
|
+
savings: 0
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
const stripped = stripAll(content);
|
|
304
|
+
const exports = extractExports(stripped);
|
|
305
|
+
const classes = extractClassMethods(stripped);
|
|
306
|
+
const functions = extractFunctionSignatures(stripped);
|
|
307
|
+
const types = extractTypeDefinitions(stripped);
|
|
308
|
+
const sections = [
|
|
190
309
|
`// ${filePath} (${content.split("\n").length} lines)`,
|
|
191
310
|
"",
|
|
192
|
-
...signatures,
|
|
193
|
-
"",
|
|
194
311
|
"// Exports:",
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
312
|
+
exports,
|
|
313
|
+
"",
|
|
314
|
+
"// Classes:",
|
|
315
|
+
classes,
|
|
316
|
+
"",
|
|
317
|
+
"// Functions:",
|
|
318
|
+
functions,
|
|
319
|
+
"",
|
|
320
|
+
"// Types:",
|
|
321
|
+
types
|
|
322
|
+
].filter((l) => l !== "" && !l.endsWith(":"));
|
|
323
|
+
let compressed = sections.join("\n");
|
|
324
|
+
const maxTokens = 2e3;
|
|
325
|
+
compressed = smartTruncate(compressed, maxTokens);
|
|
198
326
|
return {
|
|
199
327
|
content: compressed,
|
|
200
328
|
original: content,
|
|
201
329
|
confidence: calculateConfidence(content, compressed),
|
|
202
330
|
metadata: {
|
|
203
|
-
strategy: "
|
|
331
|
+
strategy: "hybrid",
|
|
204
332
|
originalLines: content.split("\n").length,
|
|
205
|
-
compressedLines:
|
|
333
|
+
compressedLines: compressed.split("\n").length,
|
|
206
334
|
savings: 1 - compressed.length / content.length
|
|
207
335
|
}
|
|
208
336
|
};
|
|
@@ -210,50 +338,27 @@ var FileReadCompressor = class {
|
|
|
210
338
|
async decompress(compressed) {
|
|
211
339
|
return compressed.original;
|
|
212
340
|
}
|
|
213
|
-
extractSignatures(content, language) {
|
|
214
|
-
const signatures = [];
|
|
215
|
-
const lines = content.split("\n");
|
|
216
|
-
for (const line of lines) {
|
|
217
|
-
const trimmed = line.trim();
|
|
218
|
-
if (/^(export\s+)?(async\s+)?function\s+\w+/.test(trimmed)) {
|
|
219
|
-
signatures.push(trimmed.replace(/\{[\s\S]*$/, "{ ... }"));
|
|
220
|
-
}
|
|
221
|
-
if (/^(export\s+)?class\s+\w+/.test(trimmed)) {
|
|
222
|
-
signatures.push(trimmed);
|
|
223
|
-
}
|
|
224
|
-
if (/^(export\s+)?(type|interface)\s+\w+/.test(trimmed)) {
|
|
225
|
-
signatures.push(trimmed);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
return signatures;
|
|
229
|
-
}
|
|
230
|
-
extractExports(content) {
|
|
231
|
-
const exports = [];
|
|
232
|
-
const exportRegex = /export\s+(default\s+)?(function|class|const|let|var|type|interface)\s+(\w+)/g;
|
|
233
|
-
let match;
|
|
234
|
-
while ((match = exportRegex.exec(content)) !== null) {
|
|
235
|
-
exports.push(` - ${match[3]} (${match[2]})`);
|
|
236
|
-
}
|
|
237
|
-
return exports;
|
|
238
|
-
}
|
|
239
341
|
};
|
|
240
342
|
|
|
241
343
|
// src/compress/git-diff.ts
|
|
242
344
|
var GitDiffCompressor = class {
|
|
243
345
|
async compress(input) {
|
|
244
346
|
const { diff } = input;
|
|
245
|
-
const
|
|
246
|
-
const condensed = hunks.map((hunk) => ({
|
|
247
|
-
file: hunk.file,
|
|
248
|
-
changes: this.condenseHunk(hunk),
|
|
249
|
-
stats: { additions: hunk.additions, deletions: hunk.deletions }
|
|
250
|
-
}));
|
|
347
|
+
const files = this.parseDiff(diff);
|
|
251
348
|
const lines = [];
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
349
|
+
const totalAdd = files.reduce((s, f) => s + f.additions, 0);
|
|
350
|
+
const totalDel = files.reduce((s, f) => s + f.deletions, 0);
|
|
351
|
+
lines.push(`# ${files.length} files changed: +${totalAdd} -${totalDel}`);
|
|
352
|
+
lines.push("");
|
|
353
|
+
for (const file of files) {
|
|
354
|
+
lines.push(`${file.path} (+${file.additions} -${file.deletions})`);
|
|
355
|
+
const changes = file.lines.filter((l) => l.type === "add" || l.type === "del").map((l) => `${l.type === "add" ? "+" : "-"}${l.content}`);
|
|
356
|
+
const limited = changes.slice(0, 20);
|
|
357
|
+
lines.push(...limited);
|
|
358
|
+
if (changes.length > 20) {
|
|
359
|
+
lines.push(`... [${changes.length - 20} more changes]`);
|
|
256
360
|
}
|
|
361
|
+
lines.push("");
|
|
257
362
|
}
|
|
258
363
|
const compressed = lines.join("\n");
|
|
259
364
|
return {
|
|
@@ -262,7 +367,7 @@ var GitDiffCompressor = class {
|
|
|
262
367
|
confidence: calculateConfidence(diff, compressed),
|
|
263
368
|
metadata: {
|
|
264
369
|
strategy: "condensed",
|
|
265
|
-
filesChanged:
|
|
370
|
+
filesChanged: files.length,
|
|
266
371
|
savings: 1 - compressed.length / diff.length
|
|
267
372
|
}
|
|
268
373
|
};
|
|
@@ -271,34 +376,123 @@ var GitDiffCompressor = class {
|
|
|
271
376
|
return compressed.original;
|
|
272
377
|
}
|
|
273
378
|
parseDiff(diff) {
|
|
274
|
-
const
|
|
379
|
+
const files = [];
|
|
275
380
|
const lines = diff.split("\n");
|
|
276
|
-
let
|
|
381
|
+
let current = null;
|
|
277
382
|
for (const line of lines) {
|
|
278
383
|
if (line.startsWith("diff --git")) {
|
|
279
|
-
if (
|
|
280
|
-
const
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
lines: [],
|
|
384
|
+
if (current) files.push(current);
|
|
385
|
+
const match = line.match(/b\/(.+)/);
|
|
386
|
+
current = {
|
|
387
|
+
path: match?.[1] || "unknown",
|
|
284
388
|
additions: 0,
|
|
285
|
-
deletions: 0
|
|
389
|
+
deletions: 0,
|
|
390
|
+
lines: []
|
|
286
391
|
};
|
|
287
|
-
} else if (
|
|
392
|
+
} else if (current) {
|
|
288
393
|
if (line.startsWith("+")) {
|
|
289
|
-
|
|
290
|
-
|
|
394
|
+
current.additions++;
|
|
395
|
+
current.lines.push({ type: "add", content: line.slice(1) });
|
|
291
396
|
} else if (line.startsWith("-")) {
|
|
292
|
-
|
|
293
|
-
|
|
397
|
+
current.deletions++;
|
|
398
|
+
current.lines.push({ type: "del", content: line.slice(1) });
|
|
294
399
|
}
|
|
295
400
|
}
|
|
296
401
|
}
|
|
297
|
-
if (
|
|
298
|
-
return
|
|
402
|
+
if (current) files.push(current);
|
|
403
|
+
return files;
|
|
299
404
|
}
|
|
300
|
-
|
|
301
|
-
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
// src/compress/git-status.ts
|
|
408
|
+
var GitStatusCompressor = class {
|
|
409
|
+
async compress(input) {
|
|
410
|
+
const { status } = input;
|
|
411
|
+
const statusLines = status.split("\n").filter(Boolean).length;
|
|
412
|
+
const tokens = Math.ceil(status.length / 4);
|
|
413
|
+
if (tokens < 10) {
|
|
414
|
+
return {
|
|
415
|
+
content: status,
|
|
416
|
+
original: status,
|
|
417
|
+
confidence: 1,
|
|
418
|
+
metadata: {
|
|
419
|
+
strategy: "passthrough",
|
|
420
|
+
totalFiles: statusLines,
|
|
421
|
+
savings: 0
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
const files = this.parseStatus(status);
|
|
426
|
+
const grouped = this.groupByStatus(files);
|
|
427
|
+
const lines = [];
|
|
428
|
+
for (const [statusType, statusFiles] of grouped) {
|
|
429
|
+
const label = this.statusLabel(statusType);
|
|
430
|
+
lines.push(`${label} (${statusFiles.length})`);
|
|
431
|
+
const compressed2 = this.compressPaths(statusFiles.map((f) => f.path));
|
|
432
|
+
lines.push(...compressed2);
|
|
433
|
+
lines.push("");
|
|
434
|
+
}
|
|
435
|
+
const compressed = lines.join("\n");
|
|
436
|
+
return {
|
|
437
|
+
content: compressed,
|
|
438
|
+
original: status,
|
|
439
|
+
confidence: calculateConfidence(status, compressed),
|
|
440
|
+
metadata: {
|
|
441
|
+
strategy: "grouped",
|
|
442
|
+
totalFiles: files.length,
|
|
443
|
+
savings: 1 - compressed.length / status.length
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
async decompress(compressed) {
|
|
448
|
+
return compressed.original;
|
|
449
|
+
}
|
|
450
|
+
parseStatus(status) {
|
|
451
|
+
return status.split("\n").filter(Boolean).map((line) => {
|
|
452
|
+
const match = line.match(/^(\?\?|[MADRC]+\s+)(.+)$/);
|
|
453
|
+
if (match) {
|
|
454
|
+
return { status: match[1].trim(), path: match[2] };
|
|
455
|
+
}
|
|
456
|
+
return { status: line[0], path: line.slice(3) };
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
groupByStatus(files) {
|
|
460
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
461
|
+
for (const file of files) {
|
|
462
|
+
const existing = grouped.get(file.status) || [];
|
|
463
|
+
existing.push(file);
|
|
464
|
+
grouped.set(file.status, existing);
|
|
465
|
+
}
|
|
466
|
+
return grouped;
|
|
467
|
+
}
|
|
468
|
+
statusLabel(status) {
|
|
469
|
+
const labels = {
|
|
470
|
+
"M": "Modified",
|
|
471
|
+
"A": "Added",
|
|
472
|
+
"D": "Deleted",
|
|
473
|
+
"??": "Untracked",
|
|
474
|
+
"R": "Renamed"
|
|
475
|
+
};
|
|
476
|
+
return labels[status] || `Status ${status}`;
|
|
477
|
+
}
|
|
478
|
+
compressPaths(paths) {
|
|
479
|
+
if (paths.length <= 3) return paths.map((p) => ` ${p}`);
|
|
480
|
+
const parts = paths.map((p) => p.split("/"));
|
|
481
|
+
const minLength = Math.min(...parts.map((p) => p.length));
|
|
482
|
+
let commonIndex = 0;
|
|
483
|
+
for (let i = 0; i < minLength; i++) {
|
|
484
|
+
if (parts.every((p) => p[i] === parts[0][i])) {
|
|
485
|
+
commonIndex = i;
|
|
486
|
+
} else {
|
|
487
|
+
break;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
if (commonIndex > 0) {
|
|
491
|
+
const prefix = parts[0].slice(0, commonIndex).join("/");
|
|
492
|
+
const suffixes = paths.map((p) => p.slice(prefix.length + 1));
|
|
493
|
+
return [` ${prefix}/`, ...suffixes.map((s) => ` ${s}`)];
|
|
494
|
+
}
|
|
495
|
+
return paths.map((p) => ` ${p}`);
|
|
302
496
|
}
|
|
303
497
|
};
|
|
304
498
|
|
|
@@ -2031,7 +2225,7 @@ function compressWebContent(semantic, options = {}) {
|
|
|
2031
2225
|
let result = lines.join("\n");
|
|
2032
2226
|
const estimatedTokens = estimateTokens(result);
|
|
2033
2227
|
if (estimatedTokens > maxTokens) {
|
|
2034
|
-
result =
|
|
2228
|
+
result = truncateByTokens2(result, maxTokens);
|
|
2035
2229
|
}
|
|
2036
2230
|
return result;
|
|
2037
2231
|
}
|
|
@@ -2047,7 +2241,7 @@ function truncateContent(content, maxLength) {
|
|
|
2047
2241
|
function estimateTokens(text) {
|
|
2048
2242
|
return Math.ceil(text.length / 4);
|
|
2049
2243
|
}
|
|
2050
|
-
function
|
|
2244
|
+
function truncateByTokens2(text, maxTokens) {
|
|
2051
2245
|
const maxChars = maxTokens * 4;
|
|
2052
2246
|
if (text.length <= maxChars) return text;
|
|
2053
2247
|
const truncated = text.slice(0, maxChars);
|
|
@@ -2184,6 +2378,7 @@ export {
|
|
|
2184
2378
|
FileReadCompressor,
|
|
2185
2379
|
GeminiCliAdapter,
|
|
2186
2380
|
GitDiffCompressor,
|
|
2381
|
+
GitStatusCompressor,
|
|
2187
2382
|
GrepCompressor,
|
|
2188
2383
|
HookAdapter,
|
|
2189
2384
|
Installer,
|
|
@@ -2218,12 +2413,24 @@ export {
|
|
|
2218
2413
|
createRouter,
|
|
2219
2414
|
createRulesAdapter,
|
|
2220
2415
|
createToolRegistry,
|
|
2416
|
+
extractClassMethods,
|
|
2417
|
+
extractExports,
|
|
2418
|
+
extractFunctionSignatures,
|
|
2221
2419
|
extractSemantic,
|
|
2420
|
+
extractTypeDefinitions,
|
|
2222
2421
|
getAdapter,
|
|
2223
2422
|
getAdapters,
|
|
2224
2423
|
getSemanticTools,
|
|
2225
2424
|
makeLatenCache,
|
|
2425
|
+
preserveContext,
|
|
2226
2426
|
registerAdapter,
|
|
2227
|
-
selectBackend
|
|
2427
|
+
selectBackend,
|
|
2428
|
+
smartTruncate,
|
|
2429
|
+
stripAll,
|
|
2430
|
+
stripBlankLines,
|
|
2431
|
+
stripComments,
|
|
2432
|
+
stripImports,
|
|
2433
|
+
stripWhitespace,
|
|
2434
|
+
truncateByTokens
|
|
2228
2435
|
};
|
|
2229
2436
|
//# sourceMappingURL=index.mjs.map
|