composto-ai 0.8.0 → 0.8.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 CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  **Token-efficient code context for AI agents. Your file's full structure in a fraction of the tokens, with its causal history baked in.**
4
4
 
5
+ > Send your agent the structure, not the noise.
6
+
5
7
  Composto compresses any source file into a Health-Aware IR that keeps exactly what your agent needs — signatures, types, control flow, dependencies — at 60-95% fewer tokens than raw code. On top of that, it surfaces the file's causal history (what historically changed and broke alongside the code you're touching) as advisory context. Local-first, MIT. Works with Claude Code, Cursor, and Gemini CLI.
6
8
 
7
9
  ```
package/dist/index.js CHANGED
@@ -1010,6 +1010,17 @@ function emitTier3(node) {
1010
1010
  if (expr.type === "await_expression") {
1011
1011
  return null;
1012
1012
  }
1013
+ if (expr.type === "assignment_expression") {
1014
+ if (node.parent?.type === "statement_block") return null;
1015
+ const left = expr.childForFieldName("left");
1016
+ const right = expr.childForFieldName("right");
1017
+ if (left?.type === "member_expression" && right && (right.type === "function_expression" || right.type === "arrow_function")) {
1018
+ const asyncPrefix = isAsync(right) ? "ASYNC " : "";
1019
+ const params = right.childForFieldName("parameters")?.text ?? "()";
1020
+ return `${asyncPrefix}FN:${left.text}${collapseText(params, 60)}`;
1021
+ }
1022
+ return null;
1023
+ }
1013
1024
  if (expr.type === "call_expression") {
1014
1025
  const callee = expr.child(0)?.text ?? "";
1015
1026
  if (callee === "ObjectSetPrototypeOf" || callee === "Object.setPrototypeOf") {
@@ -1515,6 +1526,17 @@ function summarize(results) {
1515
1526
  return { fileCount: results.length, totalRaw, totalIRL0, totalIRL1, totalSavedPercent, astCount, fpCount };
1516
1527
  }
1517
1528
 
1529
+ // src/version.ts
1530
+ import { createRequire } from "module";
1531
+ var VERSION = (() => {
1532
+ try {
1533
+ const req = createRequire(import.meta.url);
1534
+ return req("../package.json").version;
1535
+ } catch {
1536
+ return "0.0.0";
1537
+ }
1538
+ })();
1539
+
1518
1540
  // src/benchmark/quality.ts
1519
1541
  var BENCHMARK_PROMPTS = [
1520
1542
  {
@@ -2681,7 +2703,8 @@ function statFileSize(path) {
2681
2703
  function runScan(projectPath) {
2682
2704
  const adapter = new CLIAdapter();
2683
2705
  const config = loadConfig(projectPath);
2684
- console.log("composto v0.4.2 \u2014 scanning...\n");
2706
+ console.log(`composto v${VERSION} \u2014 scanning...
2707
+ `);
2685
2708
  const files = collectFiles(projectPath, [".ts", ".tsx", ".js", ".jsx"]);
2686
2709
  console.log(` Found ${files.length} files
2687
2710
  `);
@@ -2708,7 +2731,8 @@ function runScan(projectPath) {
2708
2731
  function runTrends(projectPath) {
2709
2732
  const adapter = new CLIAdapter();
2710
2733
  const config = loadConfig(projectPath);
2711
- console.log("composto v0.4.2 \u2014 trend analysis...\n");
2734
+ console.log(`composto v${VERSION} \u2014 trend analysis...
2735
+ `);
2712
2736
  const entries = getGitLog(projectPath, 100);
2713
2737
  if (entries.length === 0) {
2714
2738
  console.log(" No git history found.\n");
@@ -2750,7 +2774,8 @@ async function runIR(projectPath, filePath, layer) {
2750
2774
  }
2751
2775
  var ALL_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".py", ".go", ".rs"];
2752
2776
  async function runBenchmark(projectPath) {
2753
- console.log("composto v0.4.2 \u2014 benchmark\n");
2777
+ console.log(`composto v${VERSION} \u2014 benchmark
2778
+ `);
2754
2779
  const files = collectFiles(projectPath, ALL_EXTENSIONS);
2755
2780
  console.log(` ${files.length} files
2756
2781
  `);
@@ -2788,6 +2813,25 @@ async function runBenchmark(projectPath) {
2788
2813
  console.log(` L1 (full IR): ${summary.totalRaw} \u2192 ${summary.totalIRL1} tokens (${summary.totalSavedPercent.toFixed(1)}% reduction)`);
2789
2814
  console.log(` Files analyzed: ${summary.fileCount}`);
2790
2815
  console.log(` Engine: ${summary.astCount} AST, ${summary.fpCount} FP`);
2816
+ printSavingsCard(summary);
2817
+ }
2818
+ function printSavingsCard(summary) {
2819
+ if (summary.totalRaw <= 0) return;
2820
+ const saved = summary.totalRaw - summary.totalIRL1;
2821
+ const pct2 = summary.totalSavedPercent.toFixed(1);
2822
+ const SONNET_PER_MTOK = 3;
2823
+ const LOADS_PER_DAY = 50;
2824
+ const DAYS = 30;
2825
+ const rawCost = summary.totalRaw / 1e6 * SONNET_PER_MTOK;
2826
+ const irCost = summary.totalIRL1 / 1e6 * SONNET_PER_MTOK;
2827
+ const monthly = (rawCost - irCost) * LOADS_PER_DAY * DAYS;
2828
+ console.log("\n \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
2829
+ console.log(` \u{1F4B8} Every full-context load of this project: $${rawCost.toFixed(2)} raw \u2192 $${irCost.toFixed(2)} with Composto`);
2830
+ console.log(` (Claude Sonnet input, $3/Mtok). At ${LOADS_PER_DAY} loads/day that is ~$${monthly.toFixed(0)}/month saved.`);
2831
+ console.log("\n \u{1F4CB} Share your result:");
2832
+ console.log(` Composto compressed my ${summary.fileCount}-file project ${pct2}% (${summary.totalRaw.toLocaleString()} \u2192 ${summary.totalIRL1.toLocaleString()} tokens).`);
2833
+ console.log(" Try yours: npm i -g composto-ai && composto benchmark .");
2834
+ console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
2791
2835
  }
2792
2836
  async function runBenchmarkQuality(projectPath, filePath) {
2793
2837
  const apiKey = process.env.ANTHROPIC_API_KEY;
@@ -2797,7 +2841,8 @@ async function runBenchmarkQuality(projectPath, filePath) {
2797
2841
  }
2798
2842
  const code = readFileSync4(filePath, "utf-8");
2799
2843
  const relPath = relative2(projectPath, filePath);
2800
- console.log("composto v0.4.2 \u2014 quality benchmark\n");
2844
+ console.log(`composto v${VERSION} \u2014 quality benchmark
2845
+ `);
2801
2846
  console.log(` File: ${relPath}
2802
2847
  `);
2803
2848
  console.log(" Sending to Claude Haiku...\n");
@@ -2827,8 +2872,8 @@ ${result.ir.response}
2827
2872
  }
2828
2873
  }
2829
2874
  async function runContext(projectPath, budget, target) {
2830
- const header = target ? `composto v0.4.2 \u2014 context (target: ${target}, budget: ${budget} tokens)
2831
- ` : `composto v0.4.2 \u2014 context (budget: ${budget} tokens)
2875
+ const header = target ? `composto v${VERSION} \u2014 context (target: ${target}, budget: ${budget} tokens)
2876
+ ` : `composto v${VERSION} \u2014 context (budget: ${budget} tokens)
2832
2877
  `;
2833
2878
  console.log(header);
2834
2879
  const files = collectFiles(projectPath, ALL_EXTENSIONS);
@@ -3649,11 +3694,11 @@ function renderSummary(s) {
3649
3694
  }
3650
3695
 
3651
3696
  // src/index.ts
3652
- import { createRequire } from "module";
3697
+ import { createRequire as createRequire2 } from "module";
3653
3698
  import { join as join13, resolve as resolve2 } from "path";
3654
3699
  var PKG_VERSION = (() => {
3655
3700
  try {
3656
- const req = createRequire(import.meta.url);
3701
+ const req = createRequire2(import.meta.url);
3657
3702
  return req("../package.json").version;
3658
3703
  } catch {
3659
3704
  return "0.0.0";
@@ -818,6 +818,17 @@ function emitTier3(node) {
818
818
  if (expr.type === "await_expression") {
819
819
  return null;
820
820
  }
821
+ if (expr.type === "assignment_expression") {
822
+ if (node.parent?.type === "statement_block") return null;
823
+ const left = expr.childForFieldName("left");
824
+ const right = expr.childForFieldName("right");
825
+ if (left?.type === "member_expression" && right && (right.type === "function_expression" || right.type === "arrow_function")) {
826
+ const asyncPrefix = isAsync(right) ? "ASYNC " : "";
827
+ const params = right.childForFieldName("parameters")?.text ?? "()";
828
+ return `${asyncPrefix}FN:${left.text}${collapseText(params, 60)}`;
829
+ }
830
+ return null;
831
+ }
821
832
  if (expr.type === "call_expression") {
822
833
  const callee = expr.child(0)?.text ?? "";
823
834
  if (callee === "ObjectSetPrototypeOf" || callee === "Object.setPrototypeOf") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "composto-ai",
3
- "version": "0.8.0",
3
+ "version": "0.8.3",
4
4
  "description": "Proactive AI team companion — less tokens, more insight",
5
5
  "type": "module",
6
6
  "bin": {
@@ -18,6 +18,7 @@
18
18
  },
19
19
  "scripts": {
20
20
  "build": "tsup",
21
+ "prepublishOnly": "npm run build",
21
22
  "test": "vitest run",
22
23
  "test:watch": "vitest",
23
24
  "dev": "tsx src/index.ts"