composto-ai 0.6.1 → 0.7.1
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/dist/index.js +105 -57
- package/dist/mcp/server.js +63 -29
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -843,8 +843,9 @@ function emitTier1(node) {
|
|
|
843
843
|
const outPrefix = exported ? "OUT " : "";
|
|
844
844
|
switch (node.type) {
|
|
845
845
|
case "import_statement": {
|
|
846
|
-
const
|
|
847
|
-
return `USE:${
|
|
846
|
+
const source = node.childForFieldName("source")?.text;
|
|
847
|
+
if (source) return `USE:${source.slice(1, -1)}`;
|
|
848
|
+
return `USE:${collapseText(node.text, 80)}`;
|
|
848
849
|
}
|
|
849
850
|
case "function_declaration": {
|
|
850
851
|
const name = node.childForFieldName("name")?.text ?? "anonymous";
|
|
@@ -974,9 +975,30 @@ function emitTier3(node) {
|
|
|
974
975
|
}
|
|
975
976
|
if (node.parent?.type === "statement_block") return null;
|
|
976
977
|
const vt = value.type;
|
|
977
|
-
if (vt === "number" || vt === "true" || vt === "false")
|
|
978
|
-
|
|
979
|
-
|
|
978
|
+
if (vt === "number" || vt === "true" || vt === "false") {
|
|
979
|
+
return `VAR:${name} = ${value.text}`;
|
|
980
|
+
}
|
|
981
|
+
if (vt === "array") {
|
|
982
|
+
return `VAR:${name}[${value.namedChildCount}]`;
|
|
983
|
+
}
|
|
984
|
+
if (vt === "object") {
|
|
985
|
+
const keys = [];
|
|
986
|
+
for (let i = 0; i < value.namedChildCount; i++) {
|
|
987
|
+
const member = value.namedChild(i);
|
|
988
|
+
keys.push(member.childForFieldName("key")?.text ?? member.text);
|
|
989
|
+
if (keys.length >= 6) break;
|
|
990
|
+
}
|
|
991
|
+
const more = value.namedChildCount > keys.length ? ", ..." : "";
|
|
992
|
+
return `VAR:${name}{${collapseText(keys.join(", "), 50)}${more}}`;
|
|
993
|
+
}
|
|
994
|
+
if (vt === "new_expression") {
|
|
995
|
+
const ctor = value.childForFieldName("constructor")?.text ?? "?";
|
|
996
|
+
return `VAR:${name} = new ${ctor}(...)`;
|
|
997
|
+
}
|
|
998
|
+
if (vt === "call_expression") {
|
|
999
|
+
const callee = value.childForFieldName("function")?.text ?? "?";
|
|
1000
|
+
return `VAR:${name} = ${collapseText(callee, 40)}(...)`;
|
|
1001
|
+
}
|
|
980
1002
|
const valText = value.text.replace(/"[^"]*"/g, '""').replace(/'[^']*'/g, "''").replace(/`[^`]*`/g, "``");
|
|
981
1003
|
return `VAR:${name} = ${collapseText(valText, 50)}`;
|
|
982
1004
|
}
|
|
@@ -1137,29 +1159,27 @@ async function astWalkIR(code, filePath) {
|
|
|
1137
1159
|
}
|
|
1138
1160
|
const merged = [];
|
|
1139
1161
|
let guardBlock = [];
|
|
1162
|
+
const guardValue = (ret) => ret.replace(/^RET\s+/, "");
|
|
1163
|
+
const flushGuards = () => {
|
|
1164
|
+
if (guardBlock.length === 0) return;
|
|
1165
|
+
if (guardBlock.length < 3) {
|
|
1166
|
+
for (const g of guardBlock) merged.push(`${g.indent}IF:${g.cond} \u2192 ${g.ret}`);
|
|
1167
|
+
} else {
|
|
1168
|
+
const entries = guardBlock.map((g) => `${g.cond} \u2192 ${guardValue(g.ret)}`);
|
|
1169
|
+
merged.push(`${guardBlock[0].indent}GUARD:[${entries.join(", ")}]`);
|
|
1170
|
+
}
|
|
1171
|
+
guardBlock = [];
|
|
1172
|
+
};
|
|
1140
1173
|
for (const line of pass1) {
|
|
1141
|
-
const guardMatch = line.match(/^(\s*)IF:(.+?) \u2192
|
|
1174
|
+
const guardMatch = line.match(/^(\s*)IF:(.+?) \u2192 (.+)$/);
|
|
1142
1175
|
if (guardMatch) {
|
|
1143
|
-
guardBlock.push(guardMatch[2].trim());
|
|
1176
|
+
guardBlock.push({ indent: guardMatch[1], cond: guardMatch[2].trim(), ret: guardMatch[3].trim() });
|
|
1144
1177
|
continue;
|
|
1145
1178
|
}
|
|
1146
|
-
|
|
1147
|
-
if (guardBlock.length < 3) {
|
|
1148
|
-
for (const g of guardBlock) merged.push(` IF:${g} \u2192 RET`);
|
|
1149
|
-
} else {
|
|
1150
|
-
merged.push(` GUARD:[${guardBlock.join(", ")}]`);
|
|
1151
|
-
}
|
|
1152
|
-
guardBlock = [];
|
|
1153
|
-
}
|
|
1179
|
+
flushGuards();
|
|
1154
1180
|
merged.push(line);
|
|
1155
1181
|
}
|
|
1156
|
-
|
|
1157
|
-
if (guardBlock.length < 3) {
|
|
1158
|
-
for (const g of guardBlock) merged.push(` IF:${g} \u2192 RET`);
|
|
1159
|
-
} else {
|
|
1160
|
-
merged.push(` GUARD:[${guardBlock.join(", ")}]`);
|
|
1161
|
-
}
|
|
1162
|
-
}
|
|
1182
|
+
flushGuards();
|
|
1163
1183
|
return merged.join("\n");
|
|
1164
1184
|
}
|
|
1165
1185
|
|
|
@@ -3062,40 +3082,45 @@ function writeCursorHooks(projectPath, result) {
|
|
|
3062
3082
|
if (existed) result.merged.push(relPath);
|
|
3063
3083
|
else result.written.push(relPath);
|
|
3064
3084
|
}
|
|
3065
|
-
function initCursor(projectPath, result) {
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3085
|
+
function initCursor(projectPath, result, options) {
|
|
3086
|
+
if (options.withMcp) {
|
|
3087
|
+
writeJsonMerged(
|
|
3088
|
+
join7(projectPath, ".cursor", "mcp.json"),
|
|
3089
|
+
{
|
|
3090
|
+
mcpServers: {
|
|
3091
|
+
composto: {
|
|
3092
|
+
command: "composto-mcp",
|
|
3093
|
+
env: { COMPOSTO_BLASTRADIUS: "1" }
|
|
3094
|
+
}
|
|
3073
3095
|
}
|
|
3074
|
-
}
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3096
|
+
},
|
|
3097
|
+
result,
|
|
3098
|
+
".cursor/mcp.json"
|
|
3099
|
+
);
|
|
3100
|
+
}
|
|
3101
|
+
if (options.withRules) {
|
|
3102
|
+
writeFileSkipIfExists(
|
|
3103
|
+
join7(projectPath, ".cursor", "rules", "composto.mdc"),
|
|
3104
|
+
CURSOR_RULES_MDC,
|
|
3105
|
+
result,
|
|
3106
|
+
".cursor/rules/composto.mdc"
|
|
3107
|
+
);
|
|
3108
|
+
}
|
|
3085
3109
|
writeCursorHooks(projectPath, result);
|
|
3086
3110
|
}
|
|
3087
|
-
function initClaudeCode(projectPath, result) {
|
|
3111
|
+
function initClaudeCode(projectPath, result, options) {
|
|
3088
3112
|
const settingsPath = join7(projectPath, ".claude", "settings.json");
|
|
3089
3113
|
const relPath = ".claude/settings.json";
|
|
3090
3114
|
const existed = existsSync4(settingsPath);
|
|
3091
3115
|
const existing = readJsonIfExists(settingsPath);
|
|
3092
|
-
const
|
|
3093
|
-
|
|
3116
|
+
const baseExistingMcp = existing.mcpServers ?? {};
|
|
3117
|
+
const mcpServers = options.withMcp ? {
|
|
3118
|
+
...baseExistingMcp,
|
|
3094
3119
|
composto: {
|
|
3095
3120
|
command: "composto-mcp",
|
|
3096
3121
|
env: { COMPOSTO_BLASTRADIUS: "1" }
|
|
3097
3122
|
}
|
|
3098
|
-
};
|
|
3123
|
+
} : baseExistingMcp;
|
|
3099
3124
|
const compostoHookEntry = {
|
|
3100
3125
|
matcher: "Edit|Write|MultiEdit",
|
|
3101
3126
|
hooks: [
|
|
@@ -3110,9 +3135,11 @@ function initClaudeCode(projectPath, result) {
|
|
|
3110
3135
|
);
|
|
3111
3136
|
const merged = {
|
|
3112
3137
|
...existing,
|
|
3113
|
-
mcpServers,
|
|
3114
3138
|
hooks: { ...existingHooks, PreToolUse: preToolUse }
|
|
3115
3139
|
};
|
|
3140
|
+
if (Object.keys(mcpServers).length > 0) {
|
|
3141
|
+
merged.mcpServers = mcpServers;
|
|
3142
|
+
}
|
|
3116
3143
|
ensureDir(settingsPath);
|
|
3117
3144
|
writeFileSync2(settingsPath, JSON.stringify(merged, null, 2) + "\n");
|
|
3118
3145
|
if (existed) result.merged.push(relPath);
|
|
@@ -3124,13 +3151,14 @@ function initGeminiCli(_projectPath, result, options) {
|
|
|
3124
3151
|
try {
|
|
3125
3152
|
const existed = existsSync4(settingsPath);
|
|
3126
3153
|
const existing = readJsonIfExists(settingsPath);
|
|
3127
|
-
const
|
|
3128
|
-
|
|
3154
|
+
const baseExistingMcp = existing.mcpServers ?? {};
|
|
3155
|
+
const mcpServers = options.withMcp ? {
|
|
3156
|
+
...baseExistingMcp,
|
|
3129
3157
|
composto: {
|
|
3130
3158
|
command: "composto-mcp",
|
|
3131
3159
|
env: { COMPOSTO_BLASTRADIUS: "1" }
|
|
3132
3160
|
}
|
|
3133
|
-
};
|
|
3161
|
+
} : baseExistingMcp;
|
|
3134
3162
|
const compostoHookEntry = {
|
|
3135
3163
|
matcher: "edit_file|write_file|replace",
|
|
3136
3164
|
hooks: [
|
|
@@ -3145,9 +3173,11 @@ function initGeminiCli(_projectPath, result, options) {
|
|
|
3145
3173
|
);
|
|
3146
3174
|
const merged = {
|
|
3147
3175
|
...existing,
|
|
3148
|
-
mcpServers,
|
|
3149
3176
|
hooks: { ...existingHooks, BeforeTool: beforeTool }
|
|
3150
3177
|
};
|
|
3178
|
+
if (Object.keys(mcpServers).length > 0) {
|
|
3179
|
+
merged.mcpServers = mcpServers;
|
|
3180
|
+
}
|
|
3151
3181
|
ensureDir(settingsPath);
|
|
3152
3182
|
writeFileSync2(settingsPath, JSON.stringify(merged, null, 2) + "\n");
|
|
3153
3183
|
if (existed) result.merged.push(relPath);
|
|
@@ -3160,9 +3190,9 @@ function initGeminiCli(_projectPath, result, options) {
|
|
|
3160
3190
|
function runInit(projectPath, options) {
|
|
3161
3191
|
const client = options.client ?? "cursor";
|
|
3162
3192
|
const result = { client, written: [], skipped: [], merged: [] };
|
|
3163
|
-
if (client === "claude-code") initClaudeCode(projectPath, result);
|
|
3193
|
+
if (client === "claude-code") initClaudeCode(projectPath, result, options);
|
|
3164
3194
|
else if (client === "gemini-cli") initGeminiCli(projectPath, result, options);
|
|
3165
|
-
else initCursor(projectPath, result);
|
|
3195
|
+
else initCursor(projectPath, result, options);
|
|
3166
3196
|
return result;
|
|
3167
3197
|
}
|
|
3168
3198
|
|
|
@@ -3583,7 +3613,16 @@ function renderSummary(s) {
|
|
|
3583
3613
|
}
|
|
3584
3614
|
|
|
3585
3615
|
// src/index.ts
|
|
3616
|
+
import { createRequire } from "module";
|
|
3586
3617
|
import { join as join13, resolve as resolve2 } from "path";
|
|
3618
|
+
var PKG_VERSION = (() => {
|
|
3619
|
+
try {
|
|
3620
|
+
const req = createRequire(import.meta.url);
|
|
3621
|
+
return req("../package.json").version;
|
|
3622
|
+
} catch {
|
|
3623
|
+
return "0.0.0";
|
|
3624
|
+
}
|
|
3625
|
+
})();
|
|
3587
3626
|
async function readStdin() {
|
|
3588
3627
|
if (process.stdin.isTTY) return "";
|
|
3589
3628
|
const chunks = [];
|
|
@@ -3675,7 +3714,13 @@ switch (command) {
|
|
|
3675
3714
|
console.error(`Unknown --client=${clientArg}. Valid: ${valid.join(", ")}`);
|
|
3676
3715
|
process.exit(1);
|
|
3677
3716
|
}
|
|
3678
|
-
const
|
|
3717
|
+
const withRules = args.includes("--with-rules");
|
|
3718
|
+
const withMcp = args.includes("--with-mcp");
|
|
3719
|
+
const result = runInit(resolve2("."), {
|
|
3720
|
+
client: clientArg,
|
|
3721
|
+
withRules,
|
|
3722
|
+
withMcp
|
|
3723
|
+
});
|
|
3679
3724
|
console.log(`composto init \u2014 configured for ${result.client}
|
|
3680
3725
|
`);
|
|
3681
3726
|
for (const f of result.written) console.log(` wrote ${f}`);
|
|
@@ -3738,10 +3783,11 @@ switch (command) {
|
|
|
3738
3783
|
break;
|
|
3739
3784
|
}
|
|
3740
3785
|
case "version":
|
|
3741
|
-
console.log(
|
|
3786
|
+
console.log(`composto v${PKG_VERSION}`);
|
|
3742
3787
|
break;
|
|
3743
3788
|
default:
|
|
3744
|
-
console.log(
|
|
3789
|
+
console.log(`composto v${PKG_VERSION} \u2014 less tokens, more insight
|
|
3790
|
+
`);
|
|
3745
3791
|
console.log("Commands:");
|
|
3746
3792
|
console.log(" scan [path] Scan codebase for issues");
|
|
3747
3793
|
console.log(" trends [path] Analyze codebase health trends");
|
|
@@ -3753,8 +3799,10 @@ switch (command) {
|
|
|
3753
3799
|
console.log(" impact <file> Show historical blast radius for a file");
|
|
3754
3800
|
console.log(" index [--since=YYYY-MM-DD] Build or refresh the memory index (--since bounds work for huge repos)");
|
|
3755
3801
|
console.log(" index --status Show memory index diagnostics");
|
|
3756
|
-
console.log(" init [--client=<name>]
|
|
3757
|
-
console.log("
|
|
3802
|
+
console.log(" init [--client=<name>] [--with-mcp] [--with-rules]");
|
|
3803
|
+
console.log(" Lean Hook init (clients: cursor, claude-code, gemini-cli)");
|
|
3804
|
+
console.log(" --with-mcp register the composto MCP server (5 tools)");
|
|
3805
|
+
console.log(" --with-rules write .cursor/rules/composto.mdc (cursor only)");
|
|
3758
3806
|
console.log(" hook <platform> <event> Run BlastRadius hook (reads tool JSON from stdin)");
|
|
3759
3807
|
console.log(" stats [--json] [--disable] Show hook telemetry (last 7d); --disable opts out");
|
|
3760
3808
|
console.log(" version Show version");
|
package/dist/mcp/server.js
CHANGED
|
@@ -5,7 +5,8 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
5
5
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
import { readFileSync as readFileSync4 } from "fs";
|
|
8
|
-
import {
|
|
8
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
9
|
+
import { resolve as resolve2, relative as relative2, join as join6, dirname as dirname5 } from "path";
|
|
9
10
|
|
|
10
11
|
// src/ir/structure.ts
|
|
11
12
|
var CLASSIFIERS = [
|
|
@@ -650,8 +651,9 @@ function emitTier1(node) {
|
|
|
650
651
|
const outPrefix = exported ? "OUT " : "";
|
|
651
652
|
switch (node.type) {
|
|
652
653
|
case "import_statement": {
|
|
653
|
-
const
|
|
654
|
-
return `USE:${
|
|
654
|
+
const source = node.childForFieldName("source")?.text;
|
|
655
|
+
if (source) return `USE:${source.slice(1, -1)}`;
|
|
656
|
+
return `USE:${collapseText(node.text, 80)}`;
|
|
655
657
|
}
|
|
656
658
|
case "function_declaration": {
|
|
657
659
|
const name = node.childForFieldName("name")?.text ?? "anonymous";
|
|
@@ -781,9 +783,30 @@ function emitTier3(node) {
|
|
|
781
783
|
}
|
|
782
784
|
if (node.parent?.type === "statement_block") return null;
|
|
783
785
|
const vt = value.type;
|
|
784
|
-
if (vt === "number" || vt === "true" || vt === "false")
|
|
785
|
-
|
|
786
|
-
|
|
786
|
+
if (vt === "number" || vt === "true" || vt === "false") {
|
|
787
|
+
return `VAR:${name} = ${value.text}`;
|
|
788
|
+
}
|
|
789
|
+
if (vt === "array") {
|
|
790
|
+
return `VAR:${name}[${value.namedChildCount}]`;
|
|
791
|
+
}
|
|
792
|
+
if (vt === "object") {
|
|
793
|
+
const keys = [];
|
|
794
|
+
for (let i = 0; i < value.namedChildCount; i++) {
|
|
795
|
+
const member = value.namedChild(i);
|
|
796
|
+
keys.push(member.childForFieldName("key")?.text ?? member.text);
|
|
797
|
+
if (keys.length >= 6) break;
|
|
798
|
+
}
|
|
799
|
+
const more = value.namedChildCount > keys.length ? ", ..." : "";
|
|
800
|
+
return `VAR:${name}{${collapseText(keys.join(", "), 50)}${more}}`;
|
|
801
|
+
}
|
|
802
|
+
if (vt === "new_expression") {
|
|
803
|
+
const ctor = value.childForFieldName("constructor")?.text ?? "?";
|
|
804
|
+
return `VAR:${name} = new ${ctor}(...)`;
|
|
805
|
+
}
|
|
806
|
+
if (vt === "call_expression") {
|
|
807
|
+
const callee = value.childForFieldName("function")?.text ?? "?";
|
|
808
|
+
return `VAR:${name} = ${collapseText(callee, 40)}(...)`;
|
|
809
|
+
}
|
|
787
810
|
const valText = value.text.replace(/"[^"]*"/g, '""').replace(/'[^']*'/g, "''").replace(/`[^`]*`/g, "``");
|
|
788
811
|
return `VAR:${name} = ${collapseText(valText, 50)}`;
|
|
789
812
|
}
|
|
@@ -944,29 +967,27 @@ async function astWalkIR(code, filePath) {
|
|
|
944
967
|
}
|
|
945
968
|
const merged = [];
|
|
946
969
|
let guardBlock = [];
|
|
970
|
+
const guardValue = (ret) => ret.replace(/^RET\s+/, "");
|
|
971
|
+
const flushGuards = () => {
|
|
972
|
+
if (guardBlock.length === 0) return;
|
|
973
|
+
if (guardBlock.length < 3) {
|
|
974
|
+
for (const g of guardBlock) merged.push(`${g.indent}IF:${g.cond} \u2192 ${g.ret}`);
|
|
975
|
+
} else {
|
|
976
|
+
const entries = guardBlock.map((g) => `${g.cond} \u2192 ${guardValue(g.ret)}`);
|
|
977
|
+
merged.push(`${guardBlock[0].indent}GUARD:[${entries.join(", ")}]`);
|
|
978
|
+
}
|
|
979
|
+
guardBlock = [];
|
|
980
|
+
};
|
|
947
981
|
for (const line of pass1) {
|
|
948
|
-
const guardMatch = line.match(/^(\s*)IF:(.+?) \u2192
|
|
982
|
+
const guardMatch = line.match(/^(\s*)IF:(.+?) \u2192 (.+)$/);
|
|
949
983
|
if (guardMatch) {
|
|
950
|
-
guardBlock.push(guardMatch[2].trim());
|
|
984
|
+
guardBlock.push({ indent: guardMatch[1], cond: guardMatch[2].trim(), ret: guardMatch[3].trim() });
|
|
951
985
|
continue;
|
|
952
986
|
}
|
|
953
|
-
|
|
954
|
-
if (guardBlock.length < 3) {
|
|
955
|
-
for (const g of guardBlock) merged.push(` IF:${g} \u2192 RET`);
|
|
956
|
-
} else {
|
|
957
|
-
merged.push(` GUARD:[${guardBlock.join(", ")}]`);
|
|
958
|
-
}
|
|
959
|
-
guardBlock = [];
|
|
960
|
-
}
|
|
987
|
+
flushGuards();
|
|
961
988
|
merged.push(line);
|
|
962
989
|
}
|
|
963
|
-
|
|
964
|
-
if (guardBlock.length < 3) {
|
|
965
|
-
for (const g of guardBlock) merged.push(` IF:${g} \u2192 RET`);
|
|
966
|
-
} else {
|
|
967
|
-
merged.push(` GUARD:[${guardBlock.join(", ")}]`);
|
|
968
|
-
}
|
|
969
|
-
}
|
|
990
|
+
flushGuards();
|
|
970
991
|
return merged.join("\n");
|
|
971
992
|
}
|
|
972
993
|
|
|
@@ -2381,13 +2402,26 @@ var MemoryAPI = class {
|
|
|
2381
2402
|
|
|
2382
2403
|
// src/mcp/server.ts
|
|
2383
2404
|
var ALL_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".py", ".go", ".rs"];
|
|
2405
|
+
var PKG_VERSION = (() => {
|
|
2406
|
+
try {
|
|
2407
|
+
const pkgPath = join6(
|
|
2408
|
+
dirname5(fileURLToPath3(import.meta.url)),
|
|
2409
|
+
"..",
|
|
2410
|
+
"..",
|
|
2411
|
+
"package.json"
|
|
2412
|
+
);
|
|
2413
|
+
return JSON.parse(readFileSync4(pkgPath, "utf-8")).version;
|
|
2414
|
+
} catch {
|
|
2415
|
+
return "0.0.0";
|
|
2416
|
+
}
|
|
2417
|
+
})();
|
|
2384
2418
|
var server = new McpServer({
|
|
2385
2419
|
name: "composto",
|
|
2386
|
-
version:
|
|
2420
|
+
version: PKG_VERSION
|
|
2387
2421
|
});
|
|
2388
2422
|
server.tool(
|
|
2389
2423
|
"composto_ir",
|
|
2390
|
-
"
|
|
2424
|
+
"Compressed AST-based IR for a file. ~89% fewer tokens than raw read.",
|
|
2391
2425
|
{
|
|
2392
2426
|
file: z.string().describe("Path to the source file"),
|
|
2393
2427
|
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 +2457,7 @@ ${result}` }]
|
|
|
2423
2457
|
);
|
|
2424
2458
|
server.tool(
|
|
2425
2459
|
"composto_benchmark",
|
|
2426
|
-
"
|
|
2460
|
+
"Per-file token-savings benchmark across a directory.",
|
|
2427
2461
|
{
|
|
2428
2462
|
path: z.string().default(".").describe("Directory to benchmark")
|
|
2429
2463
|
},
|
|
@@ -2455,7 +2489,7 @@ server.tool(
|
|
|
2455
2489
|
);
|
|
2456
2490
|
server.tool(
|
|
2457
2491
|
"composto_context",
|
|
2458
|
-
"Pack
|
|
2492
|
+
"Pack code into a token budget; target raw, neighbors as IR.",
|
|
2459
2493
|
{
|
|
2460
2494
|
path: z.string().default(".").describe("Directory to pack"),
|
|
2461
2495
|
budget: z.number().default(4e3).describe("Maximum tokens to use"),
|
|
@@ -2518,7 +2552,7 @@ Files: ${parts.join(", ")}`);
|
|
|
2518
2552
|
);
|
|
2519
2553
|
server.tool(
|
|
2520
2554
|
"composto_scan",
|
|
2521
|
-
"Scan
|
|
2555
|
+
"Scan for hardcoded secrets and debug artifacts. Local-only.",
|
|
2522
2556
|
{
|
|
2523
2557
|
path: z.string().default(".").describe("Directory to scan")
|
|
2524
2558
|
},
|
|
@@ -2551,7 +2585,7 @@ server.tool(
|
|
|
2551
2585
|
);
|
|
2552
2586
|
server.tool(
|
|
2553
2587
|
"composto_blastradius",
|
|
2554
|
-
|
|
2588
|
+
"Risk verdict (low/medium/high/unknown) for editing a file, from git history.",
|
|
2555
2589
|
{
|
|
2556
2590
|
file: z.string().describe("Repo-relative path of the file the agent intends to modify."),
|
|
2557
2591
|
intent: z.enum(["refactor", "bugfix", "feature", "test", "docs", "unknown"]).default("unknown").optional(),
|