make-laten 1.1.1 → 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 CHANGED
@@ -18,10 +18,10 @@ User → AI Agent → make-laten intercept → compress (60-90% savings) → ret
18
18
 
19
19
  | Command | Savings | Description |
20
20
  |---------|---------|-------------|
21
- | `read` | **89%** | Compressed file read — exports, classes, functions only |
21
+ | `read` | **65-92%** | Compressed file read — exports, classes, functions only |
22
22
  | `grep` | **70%** | Grouped by file with line numbers |
23
- | `git diff` | **60%** | Condensed hunks, only changes shown |
24
- | `git status` | **65%** | Status summary |
23
+ | `git diff` | **61%** | Condensed hunks, only changes shown |
24
+ | `git status` | **50-65%** | Status summary |
25
25
  | `fetch` | **75%** | Web content with semantic extraction |
26
26
 
27
27
  ## Quick Start
package/dist/cli/index.js CHANGED
@@ -58,29 +58,145 @@ function isKnownPattern(content) {
58
58
  return patterns.some((p) => p.test(content));
59
59
  }
60
60
 
61
+ // src/compress/strip.ts
62
+ function stripImports(content) {
63
+ 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");
64
+ }
65
+ function stripComments(content) {
66
+ let result = content.replace(/(?<!["'`])\/\/.*$/gm, "");
67
+ result = result.replace(/\/\*[\s\S]*?\*\//g, "");
68
+ return result.split("\n").filter((l) => l.trim() !== "").join("\n");
69
+ }
70
+ function stripBlankLines(content) {
71
+ return content.replace(/\n{3,}/g, "\n\n");
72
+ }
73
+ function stripWhitespace(content) {
74
+ return content.replace(/[ \t]+$/gm, "");
75
+ }
76
+ function stripAll(content) {
77
+ let result = content;
78
+ result = stripImports(result);
79
+ result = stripComments(result);
80
+ result = stripBlankLines(result);
81
+ result = stripWhitespace(result);
82
+ return result.trim();
83
+ }
84
+
85
+ // src/compress/structure.ts
86
+ function extractExports(content) {
87
+ const lines = content.split("\n");
88
+ const exports2 = [];
89
+ for (const line of lines) {
90
+ const trimmed = line.trim();
91
+ if (/^export\s+(default\s+)?/.test(trimmed)) {
92
+ if (trimmed.includes("{") && !trimmed.includes("}")) {
93
+ exports2.push(trimmed.replace(/\{[\s\S]*$/, "{ ... }"));
94
+ } else {
95
+ exports2.push(trimmed);
96
+ }
97
+ }
98
+ }
99
+ return exports2.join("\n");
100
+ }
101
+ function extractClassMethods(content) {
102
+ const classRegex = /class\s+\w+[^{]*\{([\s\S]*?)\n\}/g;
103
+ const methods = [];
104
+ let match;
105
+ while ((match = classRegex.exec(content)) !== null) {
106
+ const classBody = match[1];
107
+ const methodRegex = /(^\s*(?:public|private|protected|static|async|abstract|\s)*\w+\s*\([^)]*\)(?:\s*:\s*[^{]+)?)\s*\{/gm;
108
+ let methodMatch;
109
+ while ((methodMatch = methodRegex.exec(classBody)) !== null) {
110
+ methods.push(methodMatch[1].trim());
111
+ }
112
+ }
113
+ return methods.join("\n");
114
+ }
115
+ function extractFunctionSignatures(content) {
116
+ const funcRegex = /(export\s+)?(async\s+)?function\s+\w+\s*\([^)]*\)(?:\s*:\s*[^{]+)?\s*\{/g;
117
+ const signatures = [];
118
+ let match;
119
+ while ((match = funcRegex.exec(content)) !== null) {
120
+ signatures.push(match[0].replace(/\{$/, ""));
121
+ }
122
+ return signatures.join("\n");
123
+ }
124
+ function extractTypeDefinitions(content) {
125
+ const typeRegex = /(export\s+)?(interface|type)\s+\w+[^{]*\{[^}]*\}|(export\s+)?type\s+\w+\s*=[^;]+;?/g;
126
+ const types = [];
127
+ let match;
128
+ while ((match = typeRegex.exec(content)) !== null) {
129
+ types.push(match[0].trim());
130
+ }
131
+ return types.join("\n");
132
+ }
133
+
134
+ // src/compress/truncate.ts
135
+ function smartTruncate(content, maxTokens) {
136
+ const lines = content.split("\n");
137
+ const estimatedTokens = Math.ceil(content.length / 4);
138
+ if (estimatedTokens <= maxTokens) return content;
139
+ const targetLines = Math.round(maxTokens * 4 / (content.length / lines.length));
140
+ const headerLines = Math.floor(targetLines * 0.3);
141
+ const footerLines = Math.floor(targetLines * 0.3);
142
+ const omitted = lines.length - headerLines - footerLines;
143
+ const header = lines.slice(0, headerLines);
144
+ const footer = lines.slice(-footerLines);
145
+ return [...header, `
146
+ ... [${omitted} lines omitted] ...
147
+ `, ...footer].join("\n");
148
+ }
149
+
61
150
  // src/compress/file-read.ts
62
151
  var FileReadCompressor = class {
63
152
  async compress(input) {
64
153
  const { content, filePath, language } = input;
65
- const signatures = this.extractSignatures(content, language);
66
- const exports2 = this.extractExports(content);
67
- const lines = [
154
+ const lines = content.split("\n").length;
155
+ const tokens = Math.ceil(content.length / 4);
156
+ if (tokens < 200) {
157
+ return {
158
+ content,
159
+ original: content,
160
+ confidence: 1,
161
+ metadata: {
162
+ strategy: "passthrough",
163
+ originalLines: lines,
164
+ compressedLines: lines,
165
+ savings: 0
166
+ }
167
+ };
168
+ }
169
+ const stripped = stripAll(content);
170
+ const exports2 = extractExports(stripped);
171
+ const classes = extractClassMethods(stripped);
172
+ const functions = extractFunctionSignatures(stripped);
173
+ const types = extractTypeDefinitions(stripped);
174
+ const sections = [
68
175
  `// ${filePath} (${content.split("\n").length} lines)`,
69
176
  "",
70
- ...signatures,
71
- "",
72
177
  "// Exports:",
73
- ...exports2
74
- ];
75
- const compressed = lines.join("\n");
178
+ exports2,
179
+ "",
180
+ "// Classes:",
181
+ classes,
182
+ "",
183
+ "// Functions:",
184
+ functions,
185
+ "",
186
+ "// Types:",
187
+ types
188
+ ].filter((l) => l !== "" && !l.endsWith(":"));
189
+ let compressed = sections.join("\n");
190
+ const maxTokens = 2e3;
191
+ compressed = smartTruncate(compressed, maxTokens);
76
192
  return {
77
193
  content: compressed,
78
194
  original: content,
79
195
  confidence: calculateConfidence(content, compressed),
80
196
  metadata: {
81
- strategy: "signatures",
197
+ strategy: "hybrid",
82
198
  originalLines: content.split("\n").length,
83
- compressedLines: lines.length,
199
+ compressedLines: compressed.split("\n").length,
84
200
  savings: 1 - compressed.length / content.length
85
201
  }
86
202
  };
@@ -88,32 +204,6 @@ var FileReadCompressor = class {
88
204
  async decompress(compressed) {
89
205
  return compressed.original;
90
206
  }
91
- extractSignatures(content, language) {
92
- const signatures = [];
93
- const lines = content.split("\n");
94
- for (const line of lines) {
95
- const trimmed = line.trim();
96
- if (/^(export\s+)?(async\s+)?function\s+\w+/.test(trimmed)) {
97
- signatures.push(trimmed.replace(/\{[\s\S]*$/, "{ ... }"));
98
- }
99
- if (/^(export\s+)?class\s+\w+/.test(trimmed)) {
100
- signatures.push(trimmed);
101
- }
102
- if (/^(export\s+)?(type|interface)\s+\w+/.test(trimmed)) {
103
- signatures.push(trimmed);
104
- }
105
- }
106
- return signatures;
107
- }
108
- extractExports(content) {
109
- const exports2 = [];
110
- const exportRegex = /export\s+(default\s+)?(function|class|const|let|var|type|interface)\s+(\w+)/g;
111
- let match;
112
- while ((match = exportRegex.exec(content)) !== null) {
113
- exports2.push(` - ${match[3]} (${match[2]})`);
114
- }
115
- return exports2;
116
- }
117
207
  };
118
208
 
119
209
  // src/cli/commands/read.ts
@@ -272,18 +362,21 @@ var import_util2 = require("util");
272
362
  var GitDiffCompressor = class {
273
363
  async compress(input) {
274
364
  const { diff } = input;
275
- const hunks = this.parseDiff(diff);
276
- const condensed = hunks.map((hunk) => ({
277
- file: hunk.file,
278
- changes: this.condenseHunk(hunk),
279
- stats: { additions: hunk.additions, deletions: hunk.deletions }
280
- }));
365
+ const files = this.parseDiff(diff);
281
366
  const lines = [];
282
- for (const hunk of condensed) {
283
- if (hunk.changes.length > 0) {
284
- lines.push(`${hunk.file} (+${hunk.stats.additions} -${hunk.stats.deletions})`);
285
- lines.push(...hunk.changes);
367
+ const totalAdd = files.reduce((s, f) => s + f.additions, 0);
368
+ const totalDel = files.reduce((s, f) => s + f.deletions, 0);
369
+ lines.push(`# ${files.length} files changed: +${totalAdd} -${totalDel}`);
370
+ lines.push("");
371
+ for (const file of files) {
372
+ lines.push(`${file.path} (+${file.additions} -${file.deletions})`);
373
+ const changes = file.lines.filter((l) => l.type === "add" || l.type === "del").map((l) => `${l.type === "add" ? "+" : "-"}${l.content}`);
374
+ const limited = changes.slice(0, 20);
375
+ lines.push(...limited);
376
+ if (changes.length > 20) {
377
+ lines.push(`... [${changes.length - 20} more changes]`);
286
378
  }
379
+ lines.push("");
287
380
  }
288
381
  const compressed = lines.join("\n");
289
382
  return {
@@ -292,7 +385,7 @@ var GitDiffCompressor = class {
292
385
  confidence: calculateConfidence(diff, compressed),
293
386
  metadata: {
294
387
  strategy: "condensed",
295
- filesChanged: condensed.length,
388
+ filesChanged: files.length,
296
389
  savings: 1 - compressed.length / diff.length
297
390
  }
298
391
  };
@@ -301,34 +394,31 @@ var GitDiffCompressor = class {
301
394
  return compressed.original;
302
395
  }
303
396
  parseDiff(diff) {
304
- const hunks = [];
397
+ const files = [];
305
398
  const lines = diff.split("\n");
306
- let currentHunk = null;
399
+ let current = null;
307
400
  for (const line of lines) {
308
401
  if (line.startsWith("diff --git")) {
309
- if (currentHunk) hunks.push(currentHunk);
310
- const fileMatch = line.match(/b\/(.+)/);
311
- currentHunk = {
312
- file: fileMatch?.[1] || "unknown",
313
- lines: [],
402
+ if (current) files.push(current);
403
+ const match = line.match(/b\/(.+)/);
404
+ current = {
405
+ path: match?.[1] || "unknown",
314
406
  additions: 0,
315
- deletions: 0
407
+ deletions: 0,
408
+ lines: []
316
409
  };
317
- } else if (currentHunk) {
410
+ } else if (current) {
318
411
  if (line.startsWith("+")) {
319
- currentHunk.additions++;
320
- currentHunk.lines.push({ type: "addition", content: line.slice(1) });
412
+ current.additions++;
413
+ current.lines.push({ type: "add", content: line.slice(1) });
321
414
  } else if (line.startsWith("-")) {
322
- currentHunk.deletions++;
323
- currentHunk.lines.push({ type: "deletion", content: line.slice(1) });
415
+ current.deletions++;
416
+ current.lines.push({ type: "del", content: line.slice(1) });
324
417
  }
325
418
  }
326
419
  }
327
- if (currentHunk) hunks.push(currentHunk);
328
- return hunks;
329
- }
330
- condenseHunk(hunk) {
331
- return hunk.lines.filter((l) => l.type === "addition" || l.type === "deletion").map((l) => `${l.type === "addition" ? "+" : "-"} ${l.content}`);
420
+ if (current) files.push(current);
421
+ return files;
332
422
  }
333
423
  };
334
424
 
@@ -1513,6 +1603,156 @@ async function initCommand(options) {
1513
1603
  console.log("");
1514
1604
  }
1515
1605
 
1606
+ // src/compress/git-status.ts
1607
+ var GitStatusCompressor = class {
1608
+ async compress(input) {
1609
+ const { status } = input;
1610
+ const statusLines = status.split("\n").filter(Boolean).length;
1611
+ const tokens = Math.ceil(status.length / 4);
1612
+ if (tokens < 10) {
1613
+ return {
1614
+ content: status,
1615
+ original: status,
1616
+ confidence: 1,
1617
+ metadata: {
1618
+ strategy: "passthrough",
1619
+ totalFiles: statusLines,
1620
+ savings: 0
1621
+ }
1622
+ };
1623
+ }
1624
+ const files = this.parseStatus(status);
1625
+ const grouped = this.groupByStatus(files);
1626
+ const lines = [];
1627
+ for (const [statusType, statusFiles] of grouped) {
1628
+ const label = this.statusLabel(statusType);
1629
+ lines.push(`${label} (${statusFiles.length})`);
1630
+ const compressed2 = this.compressPaths(statusFiles.map((f) => f.path));
1631
+ lines.push(...compressed2);
1632
+ lines.push("");
1633
+ }
1634
+ const compressed = lines.join("\n");
1635
+ return {
1636
+ content: compressed,
1637
+ original: status,
1638
+ confidence: calculateConfidence(status, compressed),
1639
+ metadata: {
1640
+ strategy: "grouped",
1641
+ totalFiles: files.length,
1642
+ savings: 1 - compressed.length / status.length
1643
+ }
1644
+ };
1645
+ }
1646
+ async decompress(compressed) {
1647
+ return compressed.original;
1648
+ }
1649
+ parseStatus(status) {
1650
+ return status.split("\n").filter(Boolean).map((line) => {
1651
+ const match = line.match(/^(\?\?|[MADRC]+\s+)(.+)$/);
1652
+ if (match) {
1653
+ return { status: match[1].trim(), path: match[2] };
1654
+ }
1655
+ return { status: line[0], path: line.slice(3) };
1656
+ });
1657
+ }
1658
+ groupByStatus(files) {
1659
+ const grouped = /* @__PURE__ */ new Map();
1660
+ for (const file of files) {
1661
+ const existing = grouped.get(file.status) || [];
1662
+ existing.push(file);
1663
+ grouped.set(file.status, existing);
1664
+ }
1665
+ return grouped;
1666
+ }
1667
+ statusLabel(status) {
1668
+ const labels = {
1669
+ "M": "Modified",
1670
+ "A": "Added",
1671
+ "D": "Deleted",
1672
+ "??": "Untracked",
1673
+ "R": "Renamed"
1674
+ };
1675
+ return labels[status] || `Status ${status}`;
1676
+ }
1677
+ compressPaths(paths) {
1678
+ if (paths.length <= 3) return paths.map((p) => ` ${p}`);
1679
+ const parts = paths.map((p) => p.split("/"));
1680
+ const minLength = Math.min(...parts.map((p) => p.length));
1681
+ let commonIndex = 0;
1682
+ for (let i = 0; i < minLength; i++) {
1683
+ if (parts.every((p) => p[i] === parts[0][i])) {
1684
+ commonIndex = i;
1685
+ } else {
1686
+ break;
1687
+ }
1688
+ }
1689
+ if (commonIndex > 0) {
1690
+ const prefix = parts[0].slice(0, commonIndex).join("/");
1691
+ const suffixes = paths.map((p) => p.slice(prefix.length + 1));
1692
+ return [` ${prefix}/`, ...suffixes.map((s) => ` ${s}`)];
1693
+ }
1694
+ return paths.map((p) => ` ${p}`);
1695
+ }
1696
+ };
1697
+
1698
+ // src/cli/commands/benchmark.ts
1699
+ var import_fs = __toESM(require("fs"));
1700
+ var import_path3 = __toESM(require("path"));
1701
+ var import_child_process5 = require("child_process");
1702
+ async function benchmark() {
1703
+ const fileCompressor = new FileReadCompressor();
1704
+ const diffCompressor = new GitDiffCompressor();
1705
+ const statusCompressor = new GitStatusCompressor();
1706
+ const projectPath = process.cwd();
1707
+ console.log("");
1708
+ console.log(" make-laten benchmark");
1709
+ console.log(" ===================");
1710
+ console.log("");
1711
+ const results = [];
1712
+ const testFiles = ["src/index.ts", "package.json", "README.md", "src/mcp/server.ts"];
1713
+ for (const file of testFiles) {
1714
+ const full = import_path3.default.join(projectPath, file);
1715
+ if (!import_fs.default.existsSync(full)) continue;
1716
+ const content = import_fs.default.readFileSync(full, "utf-8");
1717
+ const raw = Math.round(content.length / 4);
1718
+ const result = await fileCompressor.compress({ content, filePath: file });
1719
+ const compressed = Math.round(result.content.length / 4);
1720
+ results.push({ name: `read ${file}`, raw, compressed, savings: Math.round((1 - compressed / raw) * 100) });
1721
+ }
1722
+ try {
1723
+ const diff = (0, import_child_process5.execSync)('git diff HEAD~1 2>/dev/null || echo ""', { cwd: projectPath, encoding: "utf-8" }).trim();
1724
+ if (diff) {
1725
+ const raw = Math.round(diff.length / 4);
1726
+ const result = await diffCompressor.compress({ diff });
1727
+ const compressed = Math.round(result.content.length / 4);
1728
+ results.push({ name: "git-diff", raw, compressed, savings: Math.round((1 - compressed / raw) * 100) });
1729
+ }
1730
+ } catch {
1731
+ }
1732
+ try {
1733
+ const status = (0, import_child_process5.execSync)('git status --porcelain 2>/dev/null || echo ""', { cwd: projectPath, encoding: "utf-8" }).trim();
1734
+ if (status) {
1735
+ const raw = Math.round(status.length / 4);
1736
+ const result = await statusCompressor.compress({ status });
1737
+ const compressed = Math.round(result.content.length / 4);
1738
+ results.push({ name: "git-status", raw, compressed, savings: Math.round((1 - compressed / raw) * 100) });
1739
+ }
1740
+ } catch {
1741
+ }
1742
+ console.log(" Command".padEnd(28) + "Raw".padEnd(10) + "Compressed".padEnd(12) + "Savings");
1743
+ console.log(" " + "\u2500".repeat(60));
1744
+ for (const r of results) {
1745
+ console.log(` ${r.name}`.padEnd(28) + `${r.raw}`.padEnd(10) + `${r.compressed}`.padEnd(12) + `${r.savings}%`);
1746
+ }
1747
+ const totalRaw = results.reduce((s, r) => s + r.raw, 0);
1748
+ const totalCompressed = results.reduce((s, r) => s + r.compressed, 0);
1749
+ const savings = Math.round((1 - totalCompressed / totalRaw) * 100);
1750
+ console.log("");
1751
+ console.log(` Total: ${totalRaw} \u2192 ${totalCompressed} tokens (${savings}% saved)`);
1752
+ console.log("");
1753
+ }
1754
+ benchmark();
1755
+
1516
1756
  // src/cli/index.ts
1517
1757
  var program = new import_commander.Command();
1518
1758
  program.name("make-laten").description("Universal efficiency toolkit for AI coding agents").version("1.0.3");
@@ -1528,5 +1768,6 @@ program.command("search").description("Search the web").argument("<query>", "Sea
1528
1768
  program.command("fetch").description("Fetch and compress web content").argument("<url>", "URL to fetch").option("--no-compress", "Disable compression").option("--no-extract", "Disable semantic extraction").action(fetchCommand);
1529
1769
  program.command("install").description("Install make-laten across all detected platforms").option("-u, --uninstall", "Remove adapters").option("-s, --status", "Show installation status").action(installCommand);
1530
1770
  program.command("init").description("Interactive setup wizard \u2014 detect agents & configure MCP").option("-a, --all", "Configure all detected agents (no prompts)").option("-p, --project", "Create .mcp.json in current directory only").action(initCommand);
1771
+ program.command("benchmark").description("Compare raw vs compressed output").action(void 0);
1531
1772
  program.parse();
1532
1773
  //# sourceMappingURL=index.js.map