composto-ai 0.6.0 → 0.7.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 +39 -26
- package/dist/index.js +60 -29
- package/dist/mcp/server.js +5 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,24 +1,23 @@
|
|
|
1
1
|
# Composto
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**Causal memory layer for coding agents. Catches the bug your agent is about to reintroduce.**
|
|
4
4
|
|
|
5
|
-
Composto
|
|
5
|
+
Composto is a repo-local graph of your git history that your AI coding agent consults before every edit. When a file was reverted recently, has a fix cluster in its history, or was last touched by someone who left the team, Composto surfaces that signal as in-context guidance before the agent writes the code. Hook-enforced on Claude Code, Cursor, and Gemini CLI. Local-first, MIT.
|
|
6
6
|
|
|
7
7
|
```
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
CASE:"L3" → RET options.code
|
|
8
|
+
$ composto impact src/memory/signals/hotspot.ts
|
|
9
|
+
|
|
10
|
+
verdict: medium
|
|
11
|
+
score: 0.52
|
|
12
|
+
confidence: 0.50
|
|
13
|
+
signals:
|
|
14
|
+
revert_match ■■■■■■■■■■ strength=1.00 precision=1.00
|
|
15
|
+
hotspot ■ strength=0.10 precision=0.54
|
|
16
|
+
fix_ratio ■ strength=0.07 precision=0.54
|
|
17
|
+
author_churn · strength=0.00 precision=0.16
|
|
18
|
+
|
|
19
|
+
# This file was touched by a Revert commit in history.
|
|
20
|
+
# blastradius remembers. Your LLM couldn't.
|
|
22
21
|
```
|
|
23
22
|
|
|
24
23
|
---
|
|
@@ -29,21 +28,35 @@ OUT FN:generateLayer(layer: IRLayer, options: {...})
|
|
|
29
28
|
# Install
|
|
30
29
|
npm install -g composto-ai
|
|
31
30
|
|
|
32
|
-
#
|
|
33
|
-
|
|
31
|
+
# One-command setup, wires MCP + PreToolUse hook into your AI client
|
|
32
|
+
cd your-project
|
|
33
|
+
composto init --client=claude-code # or cursor, or gemini-cli
|
|
34
34
|
|
|
35
|
-
#
|
|
36
|
-
|
|
35
|
+
# Restart your AI client. Hook fires on every Edit / Write / MultiEdit.
|
|
36
|
+
# On medium|high|unknown verdicts, the agent gets a composto_blastradius
|
|
37
|
+
# block in context before it acts. Passthrough on low.
|
|
37
38
|
|
|
38
|
-
#
|
|
39
|
-
composto
|
|
39
|
+
# Observe
|
|
40
|
+
composto stats # hook invocations, verdict distribution, latency
|
|
41
|
+
composto stats --disable # local-only opt-out (writes .composto/telemetry-disabled)
|
|
40
42
|
|
|
41
|
-
#
|
|
42
|
-
COMPOSTO_BLASTRADIUS=1 composto index
|
|
43
|
+
# Query on demand
|
|
43
44
|
composto impact src/auth/login.ts
|
|
44
|
-
composto index --status
|
|
45
|
+
composto index --status # diagnostics: schema, freshness, calibration
|
|
45
46
|
```
|
|
46
47
|
|
|
48
|
+
### Also in the box: AST compression tools
|
|
49
|
+
|
|
50
|
+
Composto also ships a tree-sitter based AST compressor (about 89% token savings) and a smart context packer for bug-fix tasks. These are separate from the causal layer but live in the same binary.
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
composto ir src/app.ts # compress a file to IR (L0/L1/L2/L3)
|
|
54
|
+
composto context src/ --budget 2000 # smart context within a token budget
|
|
55
|
+
composto benchmark . # see compression stats
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
See the [IR Layers](#ir-layers), [Health-Aware IR](#health-aware-ir), and [Context Budget](#context-budget) sections below for details.
|
|
59
|
+
|
|
47
60
|
### MCP plugin (Claude Code, Cursor, Claude Desktop)
|
|
48
61
|
|
|
49
62
|
The MCP server is bundled inside `composto-ai`. Install the package globally first, then register the server with your client:
|
|
@@ -175,7 +188,7 @@ composto index --status # diagnostics: schema, freshness, calibration
|
|
|
175
188
|
|
|
176
189
|
---
|
|
177
190
|
|
|
178
|
-
## BlastRadius
|
|
191
|
+
## BlastRadius
|
|
179
192
|
|
|
180
193
|
Beyond compression, Composto indexes your repo's git history into a local SQLite graph and exposes it as a queryable risk surface. Before your agent edits a file, it can ask: *"has this region been reverted? who fixed the last similar bug? is the last author still around?"* — signals no LLM can infer from current code alone.
|
|
181
194
|
|
package/dist/index.js
CHANGED
|
@@ -3062,30 +3062,45 @@ function writeCursorHooks(projectPath, result) {
|
|
|
3062
3062
|
if (existed) result.merged.push(relPath);
|
|
3063
3063
|
else result.written.push(relPath);
|
|
3064
3064
|
}
|
|
3065
|
-
function initCursor(projectPath, result) {
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3065
|
+
function initCursor(projectPath, result, options) {
|
|
3066
|
+
if (options.withMcp) {
|
|
3067
|
+
writeJsonMerged(
|
|
3068
|
+
join7(projectPath, ".cursor", "mcp.json"),
|
|
3069
|
+
{
|
|
3070
|
+
mcpServers: {
|
|
3071
|
+
composto: {
|
|
3072
|
+
command: "composto-mcp",
|
|
3073
|
+
env: { COMPOSTO_BLASTRADIUS: "1" }
|
|
3074
|
+
}
|
|
3075
|
+
}
|
|
3076
|
+
},
|
|
3077
|
+
result,
|
|
3078
|
+
".cursor/mcp.json"
|
|
3079
|
+
);
|
|
3080
|
+
}
|
|
3081
|
+
if (options.withRules) {
|
|
3082
|
+
writeFileSkipIfExists(
|
|
3083
|
+
join7(projectPath, ".cursor", "rules", "composto.mdc"),
|
|
3084
|
+
CURSOR_RULES_MDC,
|
|
3085
|
+
result,
|
|
3086
|
+
".cursor/rules/composto.mdc"
|
|
3087
|
+
);
|
|
3088
|
+
}
|
|
3078
3089
|
writeCursorHooks(projectPath, result);
|
|
3079
3090
|
}
|
|
3080
|
-
function initClaudeCode(projectPath, result) {
|
|
3091
|
+
function initClaudeCode(projectPath, result, options) {
|
|
3081
3092
|
const settingsPath = join7(projectPath, ".claude", "settings.json");
|
|
3082
3093
|
const relPath = ".claude/settings.json";
|
|
3083
3094
|
const existed = existsSync4(settingsPath);
|
|
3084
3095
|
const existing = readJsonIfExists(settingsPath);
|
|
3085
|
-
const
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3096
|
+
const baseExistingMcp = existing.mcpServers ?? {};
|
|
3097
|
+
const mcpServers = options.withMcp ? {
|
|
3098
|
+
...baseExistingMcp,
|
|
3099
|
+
composto: {
|
|
3100
|
+
command: "composto-mcp",
|
|
3101
|
+
env: { COMPOSTO_BLASTRADIUS: "1" }
|
|
3102
|
+
}
|
|
3103
|
+
} : baseExistingMcp;
|
|
3089
3104
|
const compostoHookEntry = {
|
|
3090
3105
|
matcher: "Edit|Write|MultiEdit",
|
|
3091
3106
|
hooks: [
|
|
@@ -3100,9 +3115,11 @@ function initClaudeCode(projectPath, result) {
|
|
|
3100
3115
|
);
|
|
3101
3116
|
const merged = {
|
|
3102
3117
|
...existing,
|
|
3103
|
-
mcpServers,
|
|
3104
3118
|
hooks: { ...existingHooks, PreToolUse: preToolUse }
|
|
3105
3119
|
};
|
|
3120
|
+
if (Object.keys(mcpServers).length > 0) {
|
|
3121
|
+
merged.mcpServers = mcpServers;
|
|
3122
|
+
}
|
|
3106
3123
|
ensureDir(settingsPath);
|
|
3107
3124
|
writeFileSync2(settingsPath, JSON.stringify(merged, null, 2) + "\n");
|
|
3108
3125
|
if (existed) result.merged.push(relPath);
|
|
@@ -3114,10 +3131,14 @@ function initGeminiCli(_projectPath, result, options) {
|
|
|
3114
3131
|
try {
|
|
3115
3132
|
const existed = existsSync4(settingsPath);
|
|
3116
3133
|
const existing = readJsonIfExists(settingsPath);
|
|
3117
|
-
const
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3134
|
+
const baseExistingMcp = existing.mcpServers ?? {};
|
|
3135
|
+
const mcpServers = options.withMcp ? {
|
|
3136
|
+
...baseExistingMcp,
|
|
3137
|
+
composto: {
|
|
3138
|
+
command: "composto-mcp",
|
|
3139
|
+
env: { COMPOSTO_BLASTRADIUS: "1" }
|
|
3140
|
+
}
|
|
3141
|
+
} : baseExistingMcp;
|
|
3121
3142
|
const compostoHookEntry = {
|
|
3122
3143
|
matcher: "edit_file|write_file|replace",
|
|
3123
3144
|
hooks: [
|
|
@@ -3132,9 +3153,11 @@ function initGeminiCli(_projectPath, result, options) {
|
|
|
3132
3153
|
);
|
|
3133
3154
|
const merged = {
|
|
3134
3155
|
...existing,
|
|
3135
|
-
mcpServers,
|
|
3136
3156
|
hooks: { ...existingHooks, BeforeTool: beforeTool }
|
|
3137
3157
|
};
|
|
3158
|
+
if (Object.keys(mcpServers).length > 0) {
|
|
3159
|
+
merged.mcpServers = mcpServers;
|
|
3160
|
+
}
|
|
3138
3161
|
ensureDir(settingsPath);
|
|
3139
3162
|
writeFileSync2(settingsPath, JSON.stringify(merged, null, 2) + "\n");
|
|
3140
3163
|
if (existed) result.merged.push(relPath);
|
|
@@ -3147,9 +3170,9 @@ function initGeminiCli(_projectPath, result, options) {
|
|
|
3147
3170
|
function runInit(projectPath, options) {
|
|
3148
3171
|
const client = options.client ?? "cursor";
|
|
3149
3172
|
const result = { client, written: [], skipped: [], merged: [] };
|
|
3150
|
-
if (client === "claude-code") initClaudeCode(projectPath, result);
|
|
3173
|
+
if (client === "claude-code") initClaudeCode(projectPath, result, options);
|
|
3151
3174
|
else if (client === "gemini-cli") initGeminiCli(projectPath, result, options);
|
|
3152
|
-
else initCursor(projectPath, result);
|
|
3175
|
+
else initCursor(projectPath, result, options);
|
|
3153
3176
|
return result;
|
|
3154
3177
|
}
|
|
3155
3178
|
|
|
@@ -3662,7 +3685,13 @@ switch (command) {
|
|
|
3662
3685
|
console.error(`Unknown --client=${clientArg}. Valid: ${valid.join(", ")}`);
|
|
3663
3686
|
process.exit(1);
|
|
3664
3687
|
}
|
|
3665
|
-
const
|
|
3688
|
+
const withRules = args.includes("--with-rules");
|
|
3689
|
+
const withMcp = args.includes("--with-mcp");
|
|
3690
|
+
const result = runInit(resolve2("."), {
|
|
3691
|
+
client: clientArg,
|
|
3692
|
+
withRules,
|
|
3693
|
+
withMcp
|
|
3694
|
+
});
|
|
3666
3695
|
console.log(`composto init \u2014 configured for ${result.client}
|
|
3667
3696
|
`);
|
|
3668
3697
|
for (const f of result.written) console.log(` wrote ${f}`);
|
|
@@ -3740,8 +3769,10 @@ switch (command) {
|
|
|
3740
3769
|
console.log(" impact <file> Show historical blast radius for a file");
|
|
3741
3770
|
console.log(" index [--since=YYYY-MM-DD] Build or refresh the memory index (--since bounds work for huge repos)");
|
|
3742
3771
|
console.log(" index --status Show memory index diagnostics");
|
|
3743
|
-
console.log(" init [--client=<name>]
|
|
3744
|
-
console.log("
|
|
3772
|
+
console.log(" init [--client=<name>] [--with-mcp] [--with-rules]");
|
|
3773
|
+
console.log(" Lean Hook init (clients: cursor, claude-code, gemini-cli)");
|
|
3774
|
+
console.log(" --with-mcp register the composto MCP server (5 tools)");
|
|
3775
|
+
console.log(" --with-rules write .cursor/rules/composto.mdc (cursor only)");
|
|
3745
3776
|
console.log(" hook <platform> <event> Run BlastRadius hook (reads tool JSON from stdin)");
|
|
3746
3777
|
console.log(" stats [--json] [--disable] Show hook telemetry (last 7d); --disable opts out");
|
|
3747
3778
|
console.log(" version Show version");
|
package/dist/mcp/server.js
CHANGED
|
@@ -2387,7 +2387,7 @@ var server = new McpServer({
|
|
|
2387
2387
|
});
|
|
2388
2388
|
server.tool(
|
|
2389
2389
|
"composto_ir",
|
|
2390
|
-
"
|
|
2390
|
+
"Compressed AST-based IR for a file. ~89% fewer tokens than raw read.",
|
|
2391
2391
|
{
|
|
2392
2392
|
file: z.string().describe("Path to the source file"),
|
|
2393
2393
|
layer: z.enum(["L0", "L1", "L2", "L3"]).default("L1").describe("L0=structure only, L1=full IR (default), L2=delta context, L3=raw source")
|
|
@@ -2423,7 +2423,7 @@ ${result}` }]
|
|
|
2423
2423
|
);
|
|
2424
2424
|
server.tool(
|
|
2425
2425
|
"composto_benchmark",
|
|
2426
|
-
"
|
|
2426
|
+
"Per-file token-savings benchmark across a directory.",
|
|
2427
2427
|
{
|
|
2428
2428
|
path: z.string().default(".").describe("Directory to benchmark")
|
|
2429
2429
|
},
|
|
@@ -2455,7 +2455,7 @@ server.tool(
|
|
|
2455
2455
|
);
|
|
2456
2456
|
server.tool(
|
|
2457
2457
|
"composto_context",
|
|
2458
|
-
"Pack
|
|
2458
|
+
"Pack code into a token budget; target raw, neighbors as IR.",
|
|
2459
2459
|
{
|
|
2460
2460
|
path: z.string().default(".").describe("Directory to pack"),
|
|
2461
2461
|
budget: z.number().default(4e3).describe("Maximum tokens to use"),
|
|
@@ -2518,7 +2518,7 @@ Files: ${parts.join(", ")}`);
|
|
|
2518
2518
|
);
|
|
2519
2519
|
server.tool(
|
|
2520
2520
|
"composto_scan",
|
|
2521
|
-
"Scan
|
|
2521
|
+
"Scan for hardcoded secrets and debug artifacts. Local-only.",
|
|
2522
2522
|
{
|
|
2523
2523
|
path: z.string().default(".").describe("Directory to scan")
|
|
2524
2524
|
},
|
|
@@ -2551,7 +2551,7 @@ server.tool(
|
|
|
2551
2551
|
);
|
|
2552
2552
|
server.tool(
|
|
2553
2553
|
"composto_blastradius",
|
|
2554
|
-
|
|
2554
|
+
"Risk verdict (low/medium/high/unknown) for editing a file, from git history.",
|
|
2555
2555
|
{
|
|
2556
2556
|
file: z.string().describe("Repo-relative path of the file the agent intends to modify."),
|
|
2557
2557
|
intent: z.enum(["refactor", "bugfix", "feature", "test", "docs", "unknown"]).default("unknown").optional(),
|