@nxuss/lemma 1.6.0 → 1.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 +114 -5
- package/bin/checkpoint.js +49 -0
- package/bin/init.js +21 -2
- package/dist/cjs/mcp/prompts.d.ts +1 -2
- package/dist/cjs/mcp/prompts.d.ts.map +1 -1
- package/dist/cjs/mcp/prompts.js +29 -126
- package/dist/cjs/mcp/prompts.js.map +1 -1
- package/dist/cjs/mcp/resources.d.ts.map +1 -1
- package/dist/cjs/mcp/resources.js +4 -4
- package/dist/cjs/mcp/resources.js.map +1 -1
- package/dist/cjs/mcp/tool-profiles.d.ts +55 -0
- package/dist/cjs/mcp/tool-profiles.d.ts.map +1 -0
- package/dist/cjs/mcp/tool-profiles.js +204 -0
- package/dist/cjs/mcp/tool-profiles.js.map +1 -0
- package/dist/cjs/mcp/tools.d.ts +12 -0
- package/dist/cjs/mcp/tools.d.ts.map +1 -1
- package/dist/cjs/mcp/tools.js +175 -26
- package/dist/cjs/mcp/tools.js.map +1 -1
- package/dist/cjs/subconscious/TheBrainV2.d.ts +36 -2
- package/dist/cjs/subconscious/TheBrainV2.d.ts.map +1 -1
- package/dist/cjs/subconscious/TheBrainV2.js +104 -23
- package/dist/cjs/subconscious/TheBrainV2.js.map +1 -1
- package/dist/cjs/utils/AutoContextBundle.d.ts.map +1 -1
- package/dist/cjs/utils/AutoContextBundle.js +17 -20
- package/dist/cjs/utils/AutoContextBundle.js.map +1 -1
- package/dist/cjs/utils/ConversationCheckpoint.d.ts +13 -0
- package/dist/cjs/utils/ConversationCheckpoint.d.ts.map +1 -1
- package/dist/cjs/utils/ConversationCheckpoint.js +58 -0
- package/dist/cjs/utils/ConversationCheckpoint.js.map +1 -1
- package/dist/cjs/utils/TestAutofixInterceptor.d.ts.map +1 -1
- package/dist/cjs/utils/TestAutofixInterceptor.js +15 -26
- package/dist/cjs/utils/TestAutofixInterceptor.js.map +1 -1
- package/dist/esm/mcp/prompts.d.ts +1 -2
- package/dist/esm/mcp/prompts.d.ts.map +1 -1
- package/dist/esm/mcp/prompts.js +28 -125
- package/dist/esm/mcp/prompts.js.map +1 -1
- package/dist/esm/mcp/resources.d.ts.map +1 -1
- package/dist/esm/mcp/resources.js +4 -4
- package/dist/esm/mcp/resources.js.map +1 -1
- package/dist/esm/mcp/tool-profiles.d.ts +55 -0
- package/dist/esm/mcp/tool-profiles.d.ts.map +1 -0
- package/dist/esm/mcp/tool-profiles.js +196 -0
- package/dist/esm/mcp/tool-profiles.js.map +1 -0
- package/dist/esm/mcp/tools.d.ts +12 -0
- package/dist/esm/mcp/tools.d.ts.map +1 -1
- package/dist/esm/mcp/tools.js +173 -27
- package/dist/esm/mcp/tools.js.map +1 -1
- package/dist/esm/subconscious/TheBrainV2.d.ts +36 -2
- package/dist/esm/subconscious/TheBrainV2.d.ts.map +1 -1
- package/dist/esm/subconscious/TheBrainV2.js +103 -23
- package/dist/esm/subconscious/TheBrainV2.js.map +1 -1
- package/dist/esm/utils/AutoContextBundle.d.ts.map +1 -1
- package/dist/esm/utils/AutoContextBundle.js +17 -20
- package/dist/esm/utils/AutoContextBundle.js.map +1 -1
- package/dist/esm/utils/ConversationCheckpoint.d.ts +13 -0
- package/dist/esm/utils/ConversationCheckpoint.d.ts.map +1 -1
- package/dist/esm/utils/ConversationCheckpoint.js +57 -0
- package/dist/esm/utils/ConversationCheckpoint.js.map +1 -1
- package/dist/esm/utils/TestAutofixInterceptor.d.ts.map +1 -1
- package/dist/esm/utils/TestAutofixInterceptor.js +15 -26
- package/dist/esm/utils/TestAutofixInterceptor.js.map +1 -1
- package/package.json +4 -2
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
/**
|
|
4
|
+
* Tools that pay for their schema in an ordinary coding session: reading, searching,
|
|
5
|
+
* patching, AST context, exact-hash caching, and the ledger. Everything else is
|
|
6
|
+
* situational and reachable through `lemma_toolbox`.
|
|
7
|
+
*/
|
|
8
|
+
export const CORE_TOOL_NAMES = [
|
|
9
|
+
// Workspace read & navigation
|
|
10
|
+
"read_workspace_file",
|
|
11
|
+
"list_workspace_dir",
|
|
12
|
+
"search_workspace",
|
|
13
|
+
"smart_file_slice",
|
|
14
|
+
"run_workspace_command",
|
|
15
|
+
"output_region",
|
|
16
|
+
// Write path — patch, then validate before it lands
|
|
17
|
+
"apply_workspace_patch",
|
|
18
|
+
"write_workspace_file",
|
|
19
|
+
"create_workspace_file",
|
|
20
|
+
"surgical_ast_insert",
|
|
21
|
+
"validate_patch_sandbox",
|
|
22
|
+
// Structure without reading whole files
|
|
23
|
+
"get_ast_hologram",
|
|
24
|
+
"get_symbol_surgical_context",
|
|
25
|
+
"get_project_onboarding",
|
|
26
|
+
// Avoid re-deriving what was already derived
|
|
27
|
+
"state_hash_cache",
|
|
28
|
+
"smarter_cache",
|
|
29
|
+
"search_memory",
|
|
30
|
+
"store_memory",
|
|
31
|
+
// Accounting & routing
|
|
32
|
+
"token_receipt",
|
|
33
|
+
"get_routing_advice",
|
|
34
|
+
];
|
|
35
|
+
const exists = (cwd, ...rel) => rel.some((r) => {
|
|
36
|
+
try {
|
|
37
|
+
return fs.existsSync(path.join(cwd, r));
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
/** Shallow scan for an extension — deep walks at startup would cost more than they save. */
|
|
44
|
+
function hasExtension(cwd, ext, depth = 2) {
|
|
45
|
+
const walk = (dir, left) => {
|
|
46
|
+
let entries;
|
|
47
|
+
try {
|
|
48
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
for (const entry of entries) {
|
|
54
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules")
|
|
55
|
+
continue;
|
|
56
|
+
if (entry.isFile() && entry.name.endsWith(ext))
|
|
57
|
+
return true;
|
|
58
|
+
if (entry.isDirectory() && left > 0 && walk(path.join(dir, entry.name), left - 1))
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
return false;
|
|
62
|
+
};
|
|
63
|
+
return walk(cwd, depth);
|
|
64
|
+
}
|
|
65
|
+
const TOOL_GROUPS = [
|
|
66
|
+
{
|
|
67
|
+
name: "infra",
|
|
68
|
+
tools: [
|
|
69
|
+
"infra_discover", "infra_connect", "infra_status", "infra_health_check",
|
|
70
|
+
"infra_diagnose", "infra_analyze_monolith", "infra_suggest_split",
|
|
71
|
+
"jenkins_generate_pipeline", "jenkins_trigger_build", "jenkins_build_status", "jenkins_logs",
|
|
72
|
+
"okd_generate_manifests", "okd_apply", "okd_status", "okd_logs", "okd_events",
|
|
73
|
+
"okd_describe", "okd_route_url",
|
|
74
|
+
"terraform_plan", "terraform_apply", "terraform_state_list", "terraform_drift",
|
|
75
|
+
],
|
|
76
|
+
applies: (cwd) =>
|
|
77
|
+
// A provider already connected, or the repo actually contains infra to act on.
|
|
78
|
+
exists(process.env.HOME || "", ".lemma-cache/infra/config.json") ||
|
|
79
|
+
exists(cwd, "Jenkinsfile", "kustomization.yaml", ".openshift", "openshift") ||
|
|
80
|
+
hasExtension(cwd, ".tf", 2),
|
|
81
|
+
},
|
|
82
|
+
];
|
|
83
|
+
/**
|
|
84
|
+
* Code review is a mode the user enters, not a property of the repo — and it cannot be
|
|
85
|
+
* detected at startup. Gating these on `.git` advertised them in every git repo, which is
|
|
86
|
+
* every repo: ~670 tokens per turn for tools most sessions never touch. They stay
|
|
87
|
+
* registered and `lemma_toolbox` finds them by query the moment a review actually starts.
|
|
88
|
+
*/
|
|
89
|
+
export const REVIEW_TOOL_NAMES = ["review_diff", "review_pr", "pr_status", "generate_pr_workflow"];
|
|
90
|
+
function readConfiguredProfile(cwd) {
|
|
91
|
+
try {
|
|
92
|
+
const configPath = path.join(cwd, "lemma.config.json");
|
|
93
|
+
if (fs.existsSync(configPath)) {
|
|
94
|
+
const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
95
|
+
const profile = config?.mcp?.toolProfile;
|
|
96
|
+
if (profile === "core" || profile === "full" || profile === "auto")
|
|
97
|
+
return profile;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
// Unreadable config must never change which tools work — fall through to the default.
|
|
102
|
+
}
|
|
103
|
+
const env = process.env.LEMMA_TOOL_PROFILE;
|
|
104
|
+
if (env === "core" || env === "full" || env === "auto")
|
|
105
|
+
return env;
|
|
106
|
+
return "auto";
|
|
107
|
+
}
|
|
108
|
+
export function resolveToolSurface(allDefinitions, cwd = process.cwd()) {
|
|
109
|
+
const profile = readConfiguredProfile(cwd);
|
|
110
|
+
if (profile === "full") {
|
|
111
|
+
return { profile, advertised: allDefinitions, hidden: [], activeGroups: ["*"] };
|
|
112
|
+
}
|
|
113
|
+
// Always advertised: it is the only route back to a withheld schema, so hiding it
|
|
114
|
+
// would turn a deferred tool into a lost one.
|
|
115
|
+
const allowed = new Set([...CORE_TOOL_NAMES, "lemma_toolbox"]);
|
|
116
|
+
const activeGroups = [];
|
|
117
|
+
if (profile === "auto") {
|
|
118
|
+
for (const group of TOOL_GROUPS) {
|
|
119
|
+
let matched = false;
|
|
120
|
+
try {
|
|
121
|
+
matched = group.applies(cwd);
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
// Detection must never be the reason a tool disappears; on error, include it.
|
|
125
|
+
matched = true;
|
|
126
|
+
}
|
|
127
|
+
if (matched) {
|
|
128
|
+
activeGroups.push(group.name);
|
|
129
|
+
group.tools.forEach((t) => allowed.add(t));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const advertised = [];
|
|
134
|
+
const hidden = [];
|
|
135
|
+
for (const def of allDefinitions) {
|
|
136
|
+
(allowed.has(def.name) ? advertised : hidden).push(def);
|
|
137
|
+
}
|
|
138
|
+
return { profile, advertised, hidden, activeGroups };
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* One-line-per-tool catalog of everything not advertised, so the model can find and
|
|
142
|
+
* invoke a hidden tool without its schema having been resident all session.
|
|
143
|
+
*/
|
|
144
|
+
export function buildToolboxCatalog(hidden, query) {
|
|
145
|
+
const q = (query || "").trim().toLowerCase();
|
|
146
|
+
const terms = q ? q.split(/\s+/).filter(Boolean) : [];
|
|
147
|
+
let matches = hidden;
|
|
148
|
+
if (terms.length) {
|
|
149
|
+
// Plain substring matching floods the result: "pr" hits scrub_privacy, precrime_static
|
|
150
|
+
// and compress_context, so a 3-word query returns the whole catalog and costs more
|
|
151
|
+
// than the schemas it was meant to replace. Match on word boundaries and rank by how
|
|
152
|
+
// many distinct terms a tool hits, so the tools the user meant come first.
|
|
153
|
+
const scored = hidden
|
|
154
|
+
.map((t) => {
|
|
155
|
+
const haystack = `${t.name} ${t.description || ""}`.toLowerCase();
|
|
156
|
+
const words = new Set(haystack.split(/[^a-z0-9]+/).filter(Boolean));
|
|
157
|
+
let score = 0;
|
|
158
|
+
for (const term of terms) {
|
|
159
|
+
if (words.has(term))
|
|
160
|
+
score += 2; // whole word — the strong signal
|
|
161
|
+
else if (term.length >= 4 && haystack.includes(term))
|
|
162
|
+
score += 1; // prefix/partial
|
|
163
|
+
}
|
|
164
|
+
return { tool: t, score };
|
|
165
|
+
})
|
|
166
|
+
.filter((s) => s.score > 0)
|
|
167
|
+
.sort((a, b) => b.score - a.score);
|
|
168
|
+
// A whole-word hit is a real match; partials only get shown when nothing matched a
|
|
169
|
+
// whole word, so a weak substring never crowds out the tool the user asked for.
|
|
170
|
+
const hasWordHit = scored.some((s) => s.score >= 2);
|
|
171
|
+
matches = scored.filter((s) => (hasWordHit ? s.score >= 2 : true)).map((s) => s.tool);
|
|
172
|
+
}
|
|
173
|
+
const CATALOG_LIMIT = 15;
|
|
174
|
+
const truncated = Math.max(0, matches.length - CATALOG_LIMIT);
|
|
175
|
+
const shown = matches.slice(0, CATALOG_LIMIT);
|
|
176
|
+
if (matches.length === 0) {
|
|
177
|
+
return terms.length
|
|
178
|
+
? `No hidden Lemma tool matches "${query}". ${hidden.length} tools available; call lemma_toolbox with no query to list all.`
|
|
179
|
+
: "No hidden tools — every Lemma tool is already advertised in this session.";
|
|
180
|
+
}
|
|
181
|
+
const lines = shown.map((t) => {
|
|
182
|
+
// One line per tool: enough to choose, not enough to be a second schema listing.
|
|
183
|
+
const desc = (t.description || "").split(/\.\s|\n/)[0].slice(0, 100);
|
|
184
|
+
return `- ${t.name}: ${desc}`;
|
|
185
|
+
});
|
|
186
|
+
return [
|
|
187
|
+
`${matches.length} Lemma tool(s) available but not advertised (schemas withheld to keep per-turn context small).`,
|
|
188
|
+
`Invoke one with: lemma_toolbox { action: "call", tool: "<name>", args: { ... } }`,
|
|
189
|
+
"",
|
|
190
|
+
...lines,
|
|
191
|
+
...(truncated > 0 ? [`… and ${truncated} more — narrow the query to see them.`] : []),
|
|
192
|
+
"",
|
|
193
|
+
`Call lemma_toolbox { action: "schema", tool: "<name>" } for a tool's full input schema before calling it.`,
|
|
194
|
+
].join("\n");
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=tool-profiles.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-profiles.js","sourceRoot":"","sources":["../../../src/mcp/tool-profiles.ts"],"names":[],"mappings":"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"}
|
package/dist/esm/mcp/tools.d.ts
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
2
|
/** Serialized size of every tool schema sent to the client, for cost accounting. */
|
|
3
3
|
export declare function getToolSchemaChars(): number;
|
|
4
|
+
/** Every tool name with a registered handler, advertised or not. */
|
|
5
|
+
export declare function getRegisteredToolNames(): string[];
|
|
6
|
+
/** Tool names whose schemas this session sends to the client. */
|
|
7
|
+
export declare function getAdvertisedToolNames(): string[];
|
|
8
|
+
/** Which tools this session advertises, and why — surfaced by `token_budget`. */
|
|
9
|
+
export declare function getToolSurfaceSummary(): {
|
|
10
|
+
profile: string;
|
|
11
|
+
advertised: number;
|
|
12
|
+
hidden: number;
|
|
13
|
+
total: number;
|
|
14
|
+
activeGroups: string[];
|
|
15
|
+
};
|
|
4
16
|
export interface ToolCallEvent {
|
|
5
17
|
tool: string;
|
|
6
18
|
args: Record<string, any>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../../src/mcp/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../../src/mcp/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AA2hCnE,oFAAoF;AACpF,wBAAgB,kBAAkB,IAAI,MAAM,CAM3C;AAED,oEAAoE;AACpE,wBAAgB,sBAAsB,IAAI,MAAM,EAAE,CAEjD;AAED,iEAAiE;AACjE,wBAAgB,sBAAsB,IAAI,MAAM,EAAE,CAEjD;AAED,iFAAiF;AACjF,wBAAgB,qBAAqB,IAAI;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB,CAQA;AAoHD,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC1B,MAAM,EAAE,IAAI,GAAG,OAAO,GAAG,KAAK,GAAG,MAAM,CAAC;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,EACd,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,GAC1C,IAAI,CA+DN"}
|
package/dist/esm/mcp/tools.js
CHANGED
|
@@ -24,13 +24,14 @@ import { findPattern, storePattern, getPatternStats } from "../utils/PromptPatte
|
|
|
24
24
|
import { runAnalysis, parseDiff } from "../pr-review/PRReviewEngine";
|
|
25
25
|
import { ReviewStore } from "../pr-review/ReviewStore";
|
|
26
26
|
import { runPRReview } from "../pr-review/reviewRunner";
|
|
27
|
-
import { getBrain } from "../subconscious/TheBrainV2";
|
|
27
|
+
import { getBrain, deriveProjectId } from "../subconscious/TheBrainV2";
|
|
28
28
|
import { collapseContext } from "../utils/ContextCollapser";
|
|
29
29
|
import { getSymbolSurgicalContext } from "../utils/SymbolSurgicalContext";
|
|
30
30
|
import { compressWormhole } from "../utils/WormholeCompressor";
|
|
31
31
|
import { generateExecutiveReport } from "../utils/ExecutiveReport";
|
|
32
32
|
import { surgicalASTInsert } from "../utils/SurgicalASTInsert";
|
|
33
33
|
import { getInfraToolDefinitions, getInfraToolHandlers, INFRA_TOOL_NAMES } from "../infra/mcp-tools";
|
|
34
|
+
import { resolveToolSurface, buildToolboxCatalog } from "./tool-profiles";
|
|
34
35
|
import { lookupStateHash, storeStateHash } from "../utils/StateHashCache";
|
|
35
36
|
import { recordReceiptEvent, getReceiptSummary } from "../utils/TokenReceipt";
|
|
36
37
|
import { distillCommandOutput, buildDistillFooter, readRegion } from "../utils/CommandOutputDistiller";
|
|
@@ -62,6 +63,8 @@ const FREE_TOOLS = new Set([
|
|
|
62
63
|
"token_budget", "squeeze_prompt", "turbosqueeze",
|
|
63
64
|
// Utility — just enough to function
|
|
64
65
|
"read_workspace_file", "scrub_privacy", "summarize_long_text",
|
|
66
|
+
// Discovery of withheld schemas — gating this would hide tools with no way back
|
|
67
|
+
"lemma_toolbox",
|
|
65
68
|
// Phase 1 zero-cost analysis tools
|
|
66
69
|
"file_fingerprint", "git_blame_heat", "type_coverage_report", "import_tree_context",
|
|
67
70
|
// Phase 2 smart reading & dedup tools
|
|
@@ -990,8 +993,32 @@ const toolDefinitions = [
|
|
|
990
993
|
];
|
|
991
994
|
// ── Infrastructure Tool Definitions ──────────────────────────────────────────
|
|
992
995
|
const _infraToolDefs = getInfraToolDefinitions();
|
|
993
|
-
|
|
994
|
-
|
|
996
|
+
// `lemma_toolbox` keeps every non-advertised tool reachable. It is defined here rather
|
|
997
|
+
// than in the main array because it must never itself be hidden.
|
|
998
|
+
const TOOLBOX_TOOL_DEFINITION = {
|
|
999
|
+
name: "lemma_toolbox",
|
|
1000
|
+
description: "Discover and invoke Lemma tools whose schemas are not loaded this session. Use action='list' (optionally with a query) to see what exists, 'schema' to read one tool's inputs, and 'call' to run it. Every Lemma capability is reachable here.",
|
|
1001
|
+
inputSchema: {
|
|
1002
|
+
type: "object",
|
|
1003
|
+
properties: {
|
|
1004
|
+
action: { type: "string", enum: ["list", "schema", "call"], description: "list | schema | call", default: "list" },
|
|
1005
|
+
query: { type: "string", description: "Filter terms for action='list' (e.g. 'terraform drift')" },
|
|
1006
|
+
tool: { type: "string", description: "Tool name, for action='schema' or 'call'" },
|
|
1007
|
+
args: { type: "object", description: "Arguments passed through to the tool, for action='call'" },
|
|
1008
|
+
},
|
|
1009
|
+
required: ["action"],
|
|
1010
|
+
},
|
|
1011
|
+
};
|
|
1012
|
+
const allToolDefinitions = [
|
|
1013
|
+
...toolDefinitions,
|
|
1014
|
+
..._infraToolDefs,
|
|
1015
|
+
TOOLBOX_TOOL_DEFINITION,
|
|
1016
|
+
];
|
|
1017
|
+
// Narrow what gets advertised to what this project can actually use. Hidden tools stay
|
|
1018
|
+
// registered in `toolHandlers` and callable — both directly and through `lemma_toolbox`.
|
|
1019
|
+
const _toolSurface = resolveToolSurface(allToolDefinitions, process.cwd());
|
|
1020
|
+
const toolDefinitionsArray = _toolSurface.advertised;
|
|
1021
|
+
const hiddenToolDefinitions = _toolSurface.hidden;
|
|
995
1022
|
/** Serialized size of every tool schema sent to the client, for cost accounting. */
|
|
996
1023
|
export function getToolSchemaChars() {
|
|
997
1024
|
try {
|
|
@@ -1001,6 +1028,24 @@ export function getToolSchemaChars() {
|
|
|
1001
1028
|
return 0;
|
|
1002
1029
|
}
|
|
1003
1030
|
}
|
|
1031
|
+
/** Every tool name with a registered handler, advertised or not. */
|
|
1032
|
+
export function getRegisteredToolNames() {
|
|
1033
|
+
return Object.keys(toolHandlers);
|
|
1034
|
+
}
|
|
1035
|
+
/** Tool names whose schemas this session sends to the client. */
|
|
1036
|
+
export function getAdvertisedToolNames() {
|
|
1037
|
+
return toolDefinitionsArray.map((d) => d.name);
|
|
1038
|
+
}
|
|
1039
|
+
/** Which tools this session advertises, and why — surfaced by `token_budget`. */
|
|
1040
|
+
export function getToolSurfaceSummary() {
|
|
1041
|
+
return {
|
|
1042
|
+
profile: _toolSurface.profile,
|
|
1043
|
+
advertised: toolDefinitionsArray.length,
|
|
1044
|
+
hidden: hiddenToolDefinitions.length,
|
|
1045
|
+
total: allToolDefinitions.length,
|
|
1046
|
+
activeGroups: _toolSurface.activeGroups,
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1004
1049
|
const toolHandlers = {
|
|
1005
1050
|
scrub_privacy: handleScrubPrivacy,
|
|
1006
1051
|
search_memory: handleSearchMemory,
|
|
@@ -1073,8 +1118,44 @@ const toolHandlers = {
|
|
|
1073
1118
|
conversation_checkpoint: handleConversationCheckpoint,
|
|
1074
1119
|
test_autofix_interceptor: handleTestAutofixInterceptor,
|
|
1075
1120
|
prompt_pattern_cache: handlePromptPatternCache,
|
|
1121
|
+
lemma_toolbox: handleToolbox,
|
|
1076
1122
|
...getInfraToolHandlers(),
|
|
1077
1123
|
};
|
|
1124
|
+
/**
|
|
1125
|
+
* Reaches the tools whose schemas were withheld from the session listing.
|
|
1126
|
+
*
|
|
1127
|
+
* `call` proxies through this server rather than relying on the client to emit a name it
|
|
1128
|
+
* never saw advertised — some clients validate outgoing tool names against tools/list, so
|
|
1129
|
+
* the proxy is what makes hiding a schema safe rather than a silent capability loss.
|
|
1130
|
+
*/
|
|
1131
|
+
async function handleToolbox(args) {
|
|
1132
|
+
const action = args?.action || "list";
|
|
1133
|
+
if (action === "list") {
|
|
1134
|
+
return {
|
|
1135
|
+
content: [{ type: "text", text: buildToolboxCatalog(hiddenToolDefinitions, args?.query) }],
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
1138
|
+
const toolName = args?.tool;
|
|
1139
|
+
if (!toolName)
|
|
1140
|
+
throw new Error("`tool` is required for action '" + action + "'");
|
|
1141
|
+
if (action === "schema") {
|
|
1142
|
+
const def = allToolDefinitions.find((d) => d.name === toolName);
|
|
1143
|
+
if (!def)
|
|
1144
|
+
throw new Error(`Unknown tool: ${toolName}`);
|
|
1145
|
+
return { content: [{ type: "text", text: JSON.stringify(def, null, 2) }] };
|
|
1146
|
+
}
|
|
1147
|
+
if (action === "call") {
|
|
1148
|
+
const handler = toolHandlers[toolName];
|
|
1149
|
+
if (!handler)
|
|
1150
|
+
throw new Error(`Unknown tool: ${toolName}`);
|
|
1151
|
+
// The proxy must not become a way around the license gate the direct path enforces.
|
|
1152
|
+
if (!isProUser() && !FREE_TOOLS.has(toolName)) {
|
|
1153
|
+
return { content: [{ type: "text", text: PRO_GATE_MESSAGE }] };
|
|
1154
|
+
}
|
|
1155
|
+
return handler((args?.args || {}));
|
|
1156
|
+
}
|
|
1157
|
+
throw new Error(`Unknown action: ${action}. Use 'list', 'schema', or 'call'.`);
|
|
1158
|
+
}
|
|
1078
1159
|
export function setupToolsHandlers(server, onToolCall) {
|
|
1079
1160
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
1080
1161
|
tools: toolDefinitionsArray,
|
|
@@ -1151,7 +1232,7 @@ async function handleSearchMemory(args) {
|
|
|
1151
1232
|
throw new Error("Query is required");
|
|
1152
1233
|
try {
|
|
1153
1234
|
const brain = getBrain();
|
|
1154
|
-
const results = brain.search(query, limit);
|
|
1235
|
+
const results = brain.search(query, limit, 0, { projectId: deriveProjectId() });
|
|
1155
1236
|
if (results.length === 0) {
|
|
1156
1237
|
return { content: [{ type: "text", text: "No relevant memories found in Lemma's Brain." }] };
|
|
1157
1238
|
}
|
|
@@ -1636,30 +1717,88 @@ async function handleSqueezePrompt(args) {
|
|
|
1636
1717
|
return { content: [{ type: "text", text: `Error squeezing prompt: ${err.message}` }] };
|
|
1637
1718
|
}
|
|
1638
1719
|
}
|
|
1720
|
+
/**
|
|
1721
|
+
* Onboarding built from the filesystem alone.
|
|
1722
|
+
*
|
|
1723
|
+
* The proxy produces a richer map, but this tool is in the core surface and CLAUDE.md tells
|
|
1724
|
+
* the model to call it first — so when the proxy is down, the first call of every session
|
|
1725
|
+
* used to return an error and the session started with no context at all. A local answer is
|
|
1726
|
+
* strictly better than that, and it never fails.
|
|
1727
|
+
*/
|
|
1728
|
+
function buildLocalOnboarding(cwd) {
|
|
1729
|
+
const lines = [];
|
|
1730
|
+
const projectName = path.basename(cwd);
|
|
1731
|
+
lines.push(`# ${projectName}`, "");
|
|
1732
|
+
let pkg = null;
|
|
1733
|
+
try {
|
|
1734
|
+
pkg = JSON.parse(fs.readFileSync(path.join(cwd, "package.json"), "utf8"));
|
|
1735
|
+
}
|
|
1736
|
+
catch { /* not a node project */ }
|
|
1737
|
+
if (pkg) {
|
|
1738
|
+
if (pkg.description)
|
|
1739
|
+
lines.push(pkg.description, "");
|
|
1740
|
+
lines.push(`**Version:** ${pkg.version || "unknown"}`);
|
|
1741
|
+
const deps = Object.keys(pkg.dependencies || {});
|
|
1742
|
+
if (deps.length)
|
|
1743
|
+
lines.push(`**Dependencies:** ${deps.slice(0, 12).join(", ")}${deps.length > 12 ? ` (+${deps.length - 12})` : ""}`);
|
|
1744
|
+
const scripts = Object.keys(pkg.scripts || {});
|
|
1745
|
+
if (scripts.length)
|
|
1746
|
+
lines.push(`**Scripts:** ${scripts.slice(0, 12).join(", ")}`);
|
|
1747
|
+
}
|
|
1748
|
+
// Current branch straight from .git/HEAD — no subprocess, works on a detached HEAD too.
|
|
1749
|
+
try {
|
|
1750
|
+
const head = fs.readFileSync(path.join(cwd, ".git", "HEAD"), "utf8").trim();
|
|
1751
|
+
const branch = head.startsWith("ref: ") ? head.replace("ref: refs/heads/", "") : `detached @ ${head.slice(0, 8)}`;
|
|
1752
|
+
lines.push(`**Branch:** ${branch}`);
|
|
1753
|
+
}
|
|
1754
|
+
catch { /* not a git repo */ }
|
|
1755
|
+
try {
|
|
1756
|
+
const entries = fs
|
|
1757
|
+
.readdirSync(cwd, { withFileTypes: true })
|
|
1758
|
+
.filter((e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules")
|
|
1759
|
+
.map((e) => e.name);
|
|
1760
|
+
if (entries.length)
|
|
1761
|
+
lines.push(`**Top-level directories:** ${entries.slice(0, 15).join(", ")}`);
|
|
1762
|
+
}
|
|
1763
|
+
catch { /* unreadable cwd */ }
|
|
1764
|
+
try {
|
|
1765
|
+
const memories = getBrain().getEntriesForProject(deriveProjectId(cwd));
|
|
1766
|
+
lines.push("", `**Brain memories for this project:** ${memories.length}`);
|
|
1767
|
+
if (memories.length) {
|
|
1768
|
+
const recent = memories
|
|
1769
|
+
.slice()
|
|
1770
|
+
.sort((a, b) => (a.timestamp < b.timestamp ? 1 : -1))
|
|
1771
|
+
.slice(0, 5);
|
|
1772
|
+
lines.push("", "Most recent:");
|
|
1773
|
+
for (const m of recent)
|
|
1774
|
+
lines.push(`- ${m.query.slice(0, 100)}`);
|
|
1775
|
+
lines.push("", "Use `search_memory` to retrieve any of these in full.");
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
catch { /* brain unavailable */ }
|
|
1779
|
+
return lines.join("\n");
|
|
1780
|
+
}
|
|
1639
1781
|
async function handleGetProjectOnboarding(_args) {
|
|
1782
|
+
const cwd = process.cwd();
|
|
1640
1783
|
try {
|
|
1641
1784
|
const port = getProxyPort();
|
|
1642
|
-
const response = await axios.get(`http://localhost:${port}/api/project/onboarding
|
|
1643
|
-
|
|
1644
|
-
content: [
|
|
1645
|
-
|
|
1646
|
-
type: "text",
|
|
1647
|
-
text: response.data.markdown || "# Onboarding mental model could not be generated.",
|
|
1648
|
-
},
|
|
1649
|
-
],
|
|
1650
|
-
};
|
|
1785
|
+
const response = await axios.get(`http://localhost:${port}/api/project/onboarding`, { timeout: 2000 });
|
|
1786
|
+
if (response.data?.markdown) {
|
|
1787
|
+
return { content: [{ type: "text", text: response.data.markdown }] };
|
|
1788
|
+
}
|
|
1651
1789
|
}
|
|
1652
1790
|
catch (error) {
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
content: [
|
|
1656
|
-
{
|
|
1657
|
-
type: "text",
|
|
1658
|
-
text: `Failed to fetch codebase onboarding mental model: ${error.message}. Is Lemma proxy running?`,
|
|
1659
|
-
},
|
|
1660
|
-
],
|
|
1661
|
-
};
|
|
1791
|
+
// Proxy down or slow — fall through. Not worth surfacing as an error to the model.
|
|
1792
|
+
logError("get_project_onboarding (falling back to local)", error);
|
|
1662
1793
|
}
|
|
1794
|
+
return {
|
|
1795
|
+
content: [
|
|
1796
|
+
{
|
|
1797
|
+
type: "text",
|
|
1798
|
+
text: `${buildLocalOnboarding(cwd)}\n\n---\n*Generated locally. Start the Lemma proxy (\`lemma start\`) for the full indexed architecture map.*`,
|
|
1799
|
+
},
|
|
1800
|
+
],
|
|
1801
|
+
};
|
|
1663
1802
|
}
|
|
1664
1803
|
function extractSymbolsWithTsCompiler(filePath, relPath) {
|
|
1665
1804
|
try {
|
|
@@ -1887,7 +2026,7 @@ async function handleQueryHybridConsensus(args) {
|
|
|
1887
2026
|
const fullQuery = context ? `${query}\n\nContext: ${context}` : query;
|
|
1888
2027
|
try {
|
|
1889
2028
|
const brain = getBrain();
|
|
1890
|
-
const results = brain.search(fullQuery, 3);
|
|
2029
|
+
const results = brain.search(fullQuery, 3, 0, { projectId: deriveProjectId() });
|
|
1891
2030
|
const topHit = results[0];
|
|
1892
2031
|
if (topHit && topHit.similarity >= threshold) {
|
|
1893
2032
|
const savedTokens = Math.floor((topHit.response?.length || 500) / 4);
|
|
@@ -1945,7 +2084,7 @@ async function handleGetTelepathicHints(args) {
|
|
|
1945
2084
|
const telepathicQuery = `${basename} ${dirContext} ${ext} patterns solutions architecture`;
|
|
1946
2085
|
try {
|
|
1947
2086
|
const brain = getBrain();
|
|
1948
|
-
const results = brain.search(telepathicQuery, limit);
|
|
2087
|
+
const results = brain.search(telepathicQuery, limit, 0, { projectId: deriveProjectId() });
|
|
1949
2088
|
if (results.length === 0) {
|
|
1950
2089
|
return {
|
|
1951
2090
|
content: [
|
|
@@ -2191,7 +2330,7 @@ async function handleSmarterCache(args) {
|
|
|
2191
2330
|
const fullQuery = context ? `${query}\n\nContext: ${context}` : query;
|
|
2192
2331
|
try {
|
|
2193
2332
|
const brain = getBrain();
|
|
2194
|
-
const results = brain.search(fullQuery, 5);
|
|
2333
|
+
const results = brain.search(fullQuery, 5, 0, { projectId: deriveProjectId() });
|
|
2195
2334
|
const topHit = results[0];
|
|
2196
2335
|
if (topHit && topHit.similarity >= threshold) {
|
|
2197
2336
|
const responseText = topHit.response;
|
|
@@ -2344,6 +2483,12 @@ async function handleTokenBudget(_args) {
|
|
|
2344
2483
|
astNavigation: true,
|
|
2345
2484
|
privacyScrubbing: true,
|
|
2346
2485
|
},
|
|
2486
|
+
// The fixed cost of being connected, re-sent on every request of the session.
|
|
2487
|
+
perTurnOverhead: {
|
|
2488
|
+
...getToolSurfaceSummary(),
|
|
2489
|
+
schemaTokens: Math.round(getToolSchemaChars() / 4),
|
|
2490
|
+
note: "Hidden tools stay callable via lemma_toolbox. Set mcp.toolProfile='full' in lemma.config.json to advertise all of them.",
|
|
2491
|
+
},
|
|
2347
2492
|
recommendations: toolCallCount === 0
|
|
2348
2493
|
? ["No se han usado tools de Lemma todavía. Asegúrate de que el agente esté usando el prompt lemma-turbomode."]
|
|
2349
2494
|
: ["Sigue usando turbosqueeze antes de cada code block grande.", "Usa smarter_cache antes de razonar."],
|
|
@@ -2957,7 +3102,8 @@ async function handleSemanticDedupGuard(args) {
|
|
|
2957
3102
|
throw new Error("query and response are required");
|
|
2958
3103
|
try {
|
|
2959
3104
|
const brain = getBrain();
|
|
2960
|
-
|
|
3105
|
+
// A dedup check is not a question the user asked — it must not register as a cache hit.
|
|
3106
|
+
const results = brain.search(query, 3, 0, { countStats: false, projectId: deriveProjectId() });
|
|
2961
3107
|
const topHit = results[0];
|
|
2962
3108
|
if (!topHit) {
|
|
2963
3109
|
return {
|
|
@@ -4063,7 +4209,7 @@ async function handleLocalSemanticAutofix(args) {
|
|
|
4063
4209
|
throw new Error("errorMessage is required");
|
|
4064
4210
|
try {
|
|
4065
4211
|
const brain = getBrain();
|
|
4066
|
-
const results = brain.search(errorMessage, 3, 0.70);
|
|
4212
|
+
const results = brain.search(errorMessage, 3, 0.70, { projectId: deriveProjectId() });
|
|
4067
4213
|
const topHit = results[0];
|
|
4068
4214
|
if (topHit) {
|
|
4069
4215
|
const responseText = topHit.response;
|