@crowley/rag-mcp 1.5.0 → 1.6.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/dist/__tests__/tool-middleware.test.js +51 -51
- package/dist/__tests__/tools/memory.test.js +78 -63
- package/dist/api-client.d.ts +49 -2
- package/dist/api-client.js +139 -7
- package/dist/connection-pool.d.ts +15 -0
- package/dist/connection-pool.js +24 -0
- package/dist/context-enrichment.js +5 -3
- package/dist/formatters.js +12 -8
- package/dist/http-transport.d.ts +15 -0
- package/dist/http-transport.js +109 -0
- package/dist/index.js +27 -4
- package/dist/schemas.js +3 -12
- package/dist/tool-middleware.js +13 -8
- package/dist/tool-registry.js +11 -4
- package/dist/tools/advanced.js +64 -19
- package/dist/tools/agents.js +42 -13
- package/dist/tools/analytics.js +17 -5
- package/dist/tools/architecture.js +115 -31
- package/dist/tools/ask.js +23 -8
- package/dist/tools/cache.js +12 -3
- package/dist/tools/clustering.js +53 -17
- package/dist/tools/confluence.js +26 -8
- package/dist/tools/database.js +87 -24
- package/dist/tools/feedback.js +22 -6
- package/dist/tools/guidelines.js +15 -2
- package/dist/tools/indexing.js +34 -8
- package/dist/tools/memory.js +196 -39
- package/dist/tools/pm.js +38 -11
- package/dist/tools/quality.js +7 -2
- package/dist/tools/review.js +25 -7
- package/dist/tools/search.js +92 -31
- package/dist/tools/session.js +58 -26
- package/dist/tools/suggestions.js +75 -22
- package/dist/types.d.ts +2 -2
- package/dist/validation-hooks.js +27 -11
- package/package.json +2 -2
package/dist/formatters.js
CHANGED
|
@@ -25,7 +25,9 @@ export function formatCodeResults(results, contentLimit = PREVIEW.LONG) {
|
|
|
25
25
|
return results
|
|
26
26
|
.map((r) => `**${r.file}** (${pct(r.score)} match)\n` +
|
|
27
27
|
(r.startLine ? `Lines ${r.startLine}-${r.endLine || "?"}\n` : "") +
|
|
28
|
-
"```" +
|
|
28
|
+
"```" +
|
|
29
|
+
(r.language || "") +
|
|
30
|
+
"\n" +
|
|
29
31
|
truncate(r.content, contentLimit) +
|
|
30
32
|
"\n```")
|
|
31
33
|
.join("\n\n---\n\n");
|
|
@@ -46,7 +48,7 @@ export function formatMemoryResults(results, emptyMessage = "No memories found."
|
|
|
46
48
|
results.forEach((r, i) => {
|
|
47
49
|
const m = r.memory;
|
|
48
50
|
const type = m.type;
|
|
49
|
-
const emoji = type === "todo" && m.status === "done" ? "✅" :
|
|
51
|
+
const emoji = type === "todo" && m.status === "done" ? "✅" : typeEmojis[type] || "📝";
|
|
50
52
|
result += `### ${i + 1}. ${emoji} ${(type || "note").toUpperCase()}\n`;
|
|
51
53
|
result += `**Relevance:** ${pct(r.score)}\n`;
|
|
52
54
|
result += `${m.content}\n`;
|
|
@@ -64,21 +66,23 @@ export function formatMemoryResults(results, emptyMessage = "No memories found."
|
|
|
64
66
|
export function formatNavigationResults(results) {
|
|
65
67
|
if (!results?.length)
|
|
66
68
|
return "No results found.";
|
|
67
|
-
return results
|
|
68
|
-
|
|
69
|
+
return results
|
|
70
|
+
.map((r) => {
|
|
71
|
+
const loc = r.lines ? `:${r.lines[0]}-${r.lines[1]}` : "";
|
|
69
72
|
let out = `**${r.file}${loc}** (${pct(r.score)})`;
|
|
70
73
|
if (r.layer)
|
|
71
74
|
out += ` [${r.layer}]`;
|
|
72
75
|
if (r.graphExpanded)
|
|
73
|
-
out +=
|
|
76
|
+
out += " _(graph)_";
|
|
74
77
|
if (r.preview)
|
|
75
78
|
out += `\n\`${truncate(r.preview, 100)}\``;
|
|
76
79
|
if (r.symbols?.length)
|
|
77
|
-
out += `\nSymbols: ${r.symbols.join(
|
|
80
|
+
out += `\nSymbols: ${r.symbols.join(", ")}`;
|
|
78
81
|
if (r.connections?.length)
|
|
79
|
-
out += `\nConnections: ${r.connections.map(c =>
|
|
82
|
+
out += `\nConnections: ${r.connections.map((c) => "`" + c + "`").join(", ")}`;
|
|
80
83
|
return out;
|
|
81
|
-
})
|
|
84
|
+
})
|
|
85
|
+
.join("\n\n");
|
|
82
86
|
}
|
|
83
87
|
/** Format pagination footer for list tools */
|
|
84
88
|
export function paginationFooter(count, limit, offset) {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Streamable HTTP transport for MCP server.
|
|
3
|
+
* Enables dashboard and remote clients to connect over HTTP.
|
|
4
|
+
*
|
|
5
|
+
* Env:
|
|
6
|
+
* MCP_TRANSPORT — stdio | http | both (default: stdio)
|
|
7
|
+
* MCP_HTTP_PORT — port for HTTP transport (default: 3101)
|
|
8
|
+
* RAG_API_KEY — required for Bearer auth when HTTP is enabled
|
|
9
|
+
*/
|
|
10
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
11
|
+
export interface HttpTransportConfig {
|
|
12
|
+
port: number;
|
|
13
|
+
apiKey?: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function startHttpTransport(server: McpServer, config: HttpTransportConfig): Promise<void>;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Streamable HTTP transport for MCP server.
|
|
3
|
+
* Enables dashboard and remote clients to connect over HTTP.
|
|
4
|
+
*
|
|
5
|
+
* Env:
|
|
6
|
+
* MCP_TRANSPORT — stdio | http | both (default: stdio)
|
|
7
|
+
* MCP_HTTP_PORT — port for HTTP transport (default: 3101)
|
|
8
|
+
* RAG_API_KEY — required for Bearer auth when HTTP is enabled
|
|
9
|
+
*/
|
|
10
|
+
import { createServer, } from "node:http";
|
|
11
|
+
import { randomUUID } from "node:crypto";
|
|
12
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
13
|
+
export async function startHttpTransport(server, config) {
|
|
14
|
+
// Per-session transport instances
|
|
15
|
+
const transports = new Map();
|
|
16
|
+
function checkAuth(req, res) {
|
|
17
|
+
if (!config.apiKey)
|
|
18
|
+
return true;
|
|
19
|
+
const auth = req.headers.authorization;
|
|
20
|
+
if (auth !== `Bearer ${config.apiKey}`) {
|
|
21
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
22
|
+
res.end(JSON.stringify({ error: "Unauthorized" }));
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
function parseBody(req) {
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
const chunks = [];
|
|
30
|
+
req.on("data", (chunk) => chunks.push(chunk));
|
|
31
|
+
req.on("end", () => {
|
|
32
|
+
try {
|
|
33
|
+
const body = Buffer.concat(chunks).toString();
|
|
34
|
+
resolve(body ? JSON.parse(body) : undefined);
|
|
35
|
+
}
|
|
36
|
+
catch (e) {
|
|
37
|
+
reject(e);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
req.on("error", reject);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
const httpServer = createServer(async (req, res) => {
|
|
44
|
+
// Only handle /mcp path
|
|
45
|
+
const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
|
|
46
|
+
if (url.pathname !== "/mcp") {
|
|
47
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
48
|
+
res.end(JSON.stringify({ error: "Not found" }));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (!checkAuth(req, res))
|
|
52
|
+
return;
|
|
53
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
54
|
+
if (req.method === "POST") {
|
|
55
|
+
const body = await parseBody(req).catch(() => undefined);
|
|
56
|
+
let transport = sessionId ? transports.get(sessionId) : undefined;
|
|
57
|
+
if (!transport) {
|
|
58
|
+
// New session — create transport and connect to MCP server
|
|
59
|
+
transport = new StreamableHTTPServerTransport({
|
|
60
|
+
sessionIdGenerator: () => randomUUID(),
|
|
61
|
+
});
|
|
62
|
+
transport.onclose = () => {
|
|
63
|
+
if (transport.sessionId) {
|
|
64
|
+
transports.delete(transport.sessionId);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
await server.connect(transport);
|
|
68
|
+
if (transport.sessionId) {
|
|
69
|
+
transports.set(transport.sessionId, transport);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
await transport.handleRequest(req, res, body);
|
|
73
|
+
}
|
|
74
|
+
else if (req.method === "GET") {
|
|
75
|
+
// SSE stream for server-initiated messages
|
|
76
|
+
if (!sessionId) {
|
|
77
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
78
|
+
res.end(JSON.stringify({ error: "Missing mcp-session-id header" }));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const transport = transports.get(sessionId);
|
|
82
|
+
if (!transport) {
|
|
83
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
84
|
+
res.end(JSON.stringify({ error: "Session not found" }));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
await transport.handleRequest(req, res);
|
|
88
|
+
}
|
|
89
|
+
else if (req.method === "DELETE") {
|
|
90
|
+
// Close session
|
|
91
|
+
if (sessionId) {
|
|
92
|
+
const transport = transports.get(sessionId);
|
|
93
|
+
if (transport) {
|
|
94
|
+
await transport.close();
|
|
95
|
+
transports.delete(sessionId);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
res.writeHead(200);
|
|
99
|
+
res.end();
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
res.writeHead(405, { "Content-Type": "application/json" });
|
|
103
|
+
res.end(JSON.stringify({ error: "Method not allowed" }));
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
httpServer.listen(config.port, "127.0.0.1", () => {
|
|
107
|
+
console.error(`MCP HTTP transport listening on http://127.0.0.1:${config.port}/mcp`);
|
|
108
|
+
});
|
|
109
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -13,8 +13,16 @@
|
|
|
13
13
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
14
14
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
15
15
|
import { createApiClient } from "./api-client.js";
|
|
16
|
+
import { configureConnectionPool } from "./connection-pool.js";
|
|
16
17
|
import { ContextEnricher } from "./context-enrichment.js";
|
|
18
|
+
import { startHttpTransport } from "./http-transport.js";
|
|
17
19
|
import { wrapHandler } from "./tool-middleware.js";
|
|
20
|
+
// Phase 3: Configure undici connection pool for RAG API communication
|
|
21
|
+
configureConnectionPool({
|
|
22
|
+
connections: parseInt(process.env.MCP_POOL_CONNECTIONS || "10"),
|
|
23
|
+
keepAliveTimeout: parseInt(process.env.MCP_POOL_KEEPALIVE || "30000"),
|
|
24
|
+
pipelining: parseInt(process.env.MCP_POOL_PIPELINING || "1"),
|
|
25
|
+
});
|
|
18
26
|
// Tool modules
|
|
19
27
|
import { createSearchTools } from "./tools/search.js";
|
|
20
28
|
import { createAskTools } from "./tools/ask.js";
|
|
@@ -51,6 +59,11 @@ const ctx = {
|
|
|
51
59
|
collectionPrefix: COLLECTION_PREFIX,
|
|
52
60
|
enrichmentEnabled: true,
|
|
53
61
|
};
|
|
62
|
+
// If session ID was injected by SessionStart hook, use it
|
|
63
|
+
const hookSessionId = process.env.RAG_SESSION_ID;
|
|
64
|
+
if (hookSessionId) {
|
|
65
|
+
ctx.activeSessionId = hookSessionId;
|
|
66
|
+
}
|
|
54
67
|
// Context enrichment middleware
|
|
55
68
|
const enricher = new ContextEnricher({
|
|
56
69
|
maxAutoRecall: 3,
|
|
@@ -169,11 +182,21 @@ async function cleanup() {
|
|
|
169
182
|
}
|
|
170
183
|
process.on("SIGINT", cleanup);
|
|
171
184
|
process.on("SIGTERM", cleanup);
|
|
172
|
-
//
|
|
185
|
+
// Phase 4: Transport selection — stdio | http | both
|
|
186
|
+
const MCP_TRANSPORT = process.env.MCP_TRANSPORT || "stdio";
|
|
187
|
+
const MCP_HTTP_PORT = parseInt(process.env.MCP_HTTP_PORT || "3101");
|
|
173
188
|
async function main() {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
189
|
+
if (MCP_TRANSPORT === "stdio" || MCP_TRANSPORT === "both") {
|
|
190
|
+
const transport = new StdioServerTransport();
|
|
191
|
+
await server.connect(transport);
|
|
192
|
+
}
|
|
193
|
+
if (MCP_TRANSPORT === "http" || MCP_TRANSPORT === "both") {
|
|
194
|
+
await startHttpTransport(server, {
|
|
195
|
+
port: MCP_HTTP_PORT,
|
|
196
|
+
apiKey: RAG_API_KEY,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
console.error(`${PROJECT_NAME} RAG MCP server running (transport: ${MCP_TRANSPORT}, prefix: ${COLLECTION_PREFIX})`);
|
|
177
200
|
console.error(`Registered ${coreSpecs.length}/${allSpecs.length} core tools (${allSpecs.length - coreSpecs.length} hidden, accessible via run_agent)`);
|
|
178
201
|
}
|
|
179
202
|
main().catch(console.error);
|
package/dist/schemas.js
CHANGED
|
@@ -22,10 +22,7 @@ export function zodToInputSchema(schema) {
|
|
|
22
22
|
};
|
|
23
23
|
}
|
|
24
24
|
// ── Primitives ──────────────────────────────────────────────
|
|
25
|
-
export const QueryStr = z
|
|
26
|
-
.string()
|
|
27
|
-
.min(1)
|
|
28
|
-
.describe("Search query or question");
|
|
25
|
+
export const QueryStr = z.string().min(1).describe("Search query or question");
|
|
29
26
|
export const Limit = z
|
|
30
27
|
.number()
|
|
31
28
|
.int()
|
|
@@ -47,10 +44,7 @@ export const FilePaths = z
|
|
|
47
44
|
.array(FilePath)
|
|
48
45
|
.min(1)
|
|
49
46
|
.describe("List of file paths");
|
|
50
|
-
export const Content = z
|
|
51
|
-
.string()
|
|
52
|
-
.min(1)
|
|
53
|
-
.describe("Text content");
|
|
47
|
+
export const Content = z.string().min(1).describe("Text content");
|
|
54
48
|
export const CollectionSuffix = z
|
|
55
49
|
.string()
|
|
56
50
|
.min(1)
|
|
@@ -93,10 +87,7 @@ export const SearchFilters = z
|
|
|
93
87
|
.string()
|
|
94
88
|
.optional()
|
|
95
89
|
.describe("Filter by file extension (e.g. 'ts', 'py')"),
|
|
96
|
-
directory: z
|
|
97
|
-
.string()
|
|
98
|
-
.optional()
|
|
99
|
-
.describe("Filter by directory prefix"),
|
|
90
|
+
directory: z.string().optional().describe("Filter by directory prefix"),
|
|
100
91
|
})
|
|
101
92
|
.optional()
|
|
102
93
|
.describe("Search filters");
|
package/dist/tool-middleware.js
CHANGED
|
@@ -69,7 +69,12 @@ export const SESSION_TOOLS = new Set([
|
|
|
69
69
|
// ── Helpers ─────────────────────────────────────────────────
|
|
70
70
|
/** Summarize tool args into a short string for analytics */
|
|
71
71
|
export function summarizeInput(name, args) {
|
|
72
|
-
const q = args.query ||
|
|
72
|
+
const q = args.query ||
|
|
73
|
+
args.question ||
|
|
74
|
+
args.feature ||
|
|
75
|
+
args.description ||
|
|
76
|
+
args.task ||
|
|
77
|
+
"";
|
|
73
78
|
if (q && typeof q === "string")
|
|
74
79
|
return q.slice(0, 200);
|
|
75
80
|
const content = args.content || args.code || args.diff || "";
|
|
@@ -159,15 +164,15 @@ export function trackUsage(name, args, startTime, success, result, errorMessage,
|
|
|
159
164
|
/** Extract file paths from tool args */
|
|
160
165
|
function extractFiles(args) {
|
|
161
166
|
const files = [];
|
|
162
|
-
for (const key of [
|
|
167
|
+
for (const key of ["file", "filePath", "currentFile", "path"]) {
|
|
163
168
|
const v = args[key];
|
|
164
|
-
if (typeof v ===
|
|
169
|
+
if (typeof v === "string" && v.length > 0)
|
|
165
170
|
files.push(v);
|
|
166
171
|
}
|
|
167
172
|
const arr = args.affectedFiles || args.files;
|
|
168
173
|
if (Array.isArray(arr)) {
|
|
169
174
|
for (const f of arr) {
|
|
170
|
-
if (typeof f ===
|
|
175
|
+
if (typeof f === "string")
|
|
171
176
|
files.push(f);
|
|
172
177
|
}
|
|
173
178
|
}
|
|
@@ -223,12 +228,12 @@ export function wrapHandler(name, handler, deps) {
|
|
|
223
228
|
// Validate: run PreToolUse hooks
|
|
224
229
|
const validation = await validationPipeline.validate(name, args, ctx);
|
|
225
230
|
if (!validation.allowed) {
|
|
226
|
-
return `Blocked: ${validation.reason ||
|
|
231
|
+
return `Blocked: ${validation.reason || "validation failed"}`;
|
|
227
232
|
}
|
|
228
233
|
const validatedArgs = validation.modifiedArgs || args;
|
|
229
234
|
const warningPrefix = validation.warnings?.length
|
|
230
|
-
? `⚠️ ${validation.warnings.join(
|
|
231
|
-
:
|
|
235
|
+
? `⚠️ ${validation.warnings.join(" | ")}\n\n`
|
|
236
|
+
: "";
|
|
232
237
|
// Before: auto-enrich context
|
|
233
238
|
const contextPrefix = ctx.enrichmentEnabled && deps.enricher
|
|
234
239
|
? await deps.enricher.before(name, validatedArgs, ctx)
|
|
@@ -247,7 +252,7 @@ export function wrapHandler(name, handler, deps) {
|
|
|
247
252
|
// Capture in sensory buffer (fire-and-forget)
|
|
248
253
|
appendToSensoryBuffer(name, args, startTime, true, text, ctx);
|
|
249
254
|
// Prepend context/warnings if available
|
|
250
|
-
const prefix = [warningPrefix, contextPrefix].filter(Boolean).join(
|
|
255
|
+
const prefix = [warningPrefix, contextPrefix].filter(Boolean).join("");
|
|
251
256
|
if (prefix) {
|
|
252
257
|
if (typeof result === "string") {
|
|
253
258
|
return prefix + result;
|
package/dist/tool-registry.js
CHANGED
|
@@ -21,7 +21,12 @@ const SESSION_TOOLS = new Set([
|
|
|
21
21
|
/** Summarize tool args into a short string for analytics */
|
|
22
22
|
function summarizeInput(name, args) {
|
|
23
23
|
// Common patterns: query, question, content, feature, code, file
|
|
24
|
-
const q = args.query ||
|
|
24
|
+
const q = args.query ||
|
|
25
|
+
args.question ||
|
|
26
|
+
args.feature ||
|
|
27
|
+
args.description ||
|
|
28
|
+
args.task ||
|
|
29
|
+
"";
|
|
25
30
|
if (q && typeof q === "string")
|
|
26
31
|
return q.slice(0, 200);
|
|
27
32
|
const content = args.content || args.code || args.diff || "";
|
|
@@ -40,7 +45,9 @@ function summarizeInput(name, args) {
|
|
|
40
45
|
/** Count results from a tool response string */
|
|
41
46
|
function countResults(result) {
|
|
42
47
|
// Heuristic: count numbered list items, file matches, or "No results" = 0
|
|
43
|
-
if (result.includes("No results") ||
|
|
48
|
+
if (result.includes("No results") ||
|
|
49
|
+
result.includes("No matches") ||
|
|
50
|
+
result.includes("not found"))
|
|
44
51
|
return 0;
|
|
45
52
|
const numbered = result.match(/^\d+\./gm);
|
|
46
53
|
if (numbered)
|
|
@@ -166,8 +173,8 @@ export class ToolRegistry {
|
|
|
166
173
|
// Track failed usage (fire-and-forget)
|
|
167
174
|
this.trackUsage(name, args, startTime, false, "", errorMessage, ctx);
|
|
168
175
|
if (err.code === "ECONNREFUSED") {
|
|
169
|
-
return `Error: Cannot connect to RAG API at ${ctx.api.defaults.baseURL}. Is it running?\n` +
|
|
170
|
-
`Start with: cd docker && docker-compose up -d
|
|
176
|
+
return (`Error: Cannot connect to RAG API at ${ctx.api.defaults.baseURL}. Is it running?\n` +
|
|
177
|
+
`Start with: cd docker && docker-compose up -d`);
|
|
171
178
|
}
|
|
172
179
|
if (err.response) {
|
|
173
180
|
return `API Error (${err.response.status}): ${JSON.stringify(err.response.data)}`;
|
package/dist/tools/advanced.js
CHANGED
|
@@ -14,15 +14,28 @@ export function createAdvancedTools(projectName) {
|
|
|
14
14
|
name: "merge_memories",
|
|
15
15
|
description: `Consolidate duplicate memories for ${projectName}. Finds similar memories and merges them using LLM to reduce clutter.`,
|
|
16
16
|
schema: z.object({
|
|
17
|
-
type: z
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
type: z
|
|
18
|
+
.string()
|
|
19
|
+
.optional()
|
|
20
|
+
.describe("Filter by memory type (decision, insight, context, todo, conversation, note, or all). Default: all"),
|
|
21
|
+
threshold: z.coerce
|
|
22
|
+
.number()
|
|
23
|
+
.optional()
|
|
24
|
+
.describe("Similarity threshold for merging (0.5-1.0, default: 0.9). Lower = more aggressive merging."),
|
|
25
|
+
dryRun: z
|
|
26
|
+
.boolean()
|
|
27
|
+
.optional()
|
|
28
|
+
.describe("If true, preview merge candidates without making changes (default: true)."),
|
|
29
|
+
limit: z.coerce
|
|
30
|
+
.number()
|
|
31
|
+
.optional()
|
|
32
|
+
.describe("Max clusters to process (default: 50)."),
|
|
21
33
|
}),
|
|
22
34
|
annotations: TOOL_ANNOTATIONS["merge_memories"],
|
|
23
35
|
handler: async (args, ctx) => {
|
|
24
|
-
const { type = "all", threshold = 0.9, dryRun = true, limit = 50 } = args;
|
|
36
|
+
const { type = "all", threshold = 0.9, dryRun = true, limit = 50, } = args;
|
|
25
37
|
const response = await ctx.api.post("/api/memory/merge", {
|
|
38
|
+
projectName: ctx.projectName,
|
|
26
39
|
type,
|
|
27
40
|
threshold,
|
|
28
41
|
dryRun,
|
|
@@ -63,13 +76,21 @@ export function createAdvancedTools(projectName) {
|
|
|
63
76
|
description: `Get code completion context for ${projectName}. Finds similar patterns, imports, and symbols from the codebase to aid code completion.`,
|
|
64
77
|
schema: z.object({
|
|
65
78
|
currentFile: z.string().describe("Path of the file being edited"),
|
|
66
|
-
currentCode: z
|
|
67
|
-
|
|
68
|
-
|
|
79
|
+
currentCode: z
|
|
80
|
+
.string()
|
|
81
|
+
.describe("Current code snippet or file content"),
|
|
82
|
+
language: z
|
|
83
|
+
.string()
|
|
84
|
+
.optional()
|
|
85
|
+
.describe("Programming language filter (optional)"),
|
|
86
|
+
limit: z.coerce
|
|
87
|
+
.number()
|
|
88
|
+
.optional()
|
|
89
|
+
.describe("Max results (default: 5)"),
|
|
69
90
|
}),
|
|
70
91
|
annotations: TOOL_ANNOTATIONS["get_completion_context"],
|
|
71
92
|
handler: async (args, ctx) => {
|
|
72
|
-
const { currentFile, currentCode, language, limit = 5 } = args;
|
|
93
|
+
const { currentFile, currentCode, language, limit = 5, } = args;
|
|
73
94
|
const response = await ctx.api.post("/api/code/completion-context", {
|
|
74
95
|
currentFile,
|
|
75
96
|
currentCode,
|
|
@@ -107,12 +128,18 @@ export function createAdvancedTools(projectName) {
|
|
|
107
128
|
schema: z.object({
|
|
108
129
|
currentFile: z.string().describe("Path of the file being edited"),
|
|
109
130
|
currentCode: z.string().describe("Current code content"),
|
|
110
|
-
language: z
|
|
111
|
-
|
|
131
|
+
language: z
|
|
132
|
+
.string()
|
|
133
|
+
.optional()
|
|
134
|
+
.describe("Programming language filter (optional)"),
|
|
135
|
+
limit: z.coerce
|
|
136
|
+
.number()
|
|
137
|
+
.optional()
|
|
138
|
+
.describe("Max suggestions (default: 10)"),
|
|
112
139
|
}),
|
|
113
140
|
annotations: TOOL_ANNOTATIONS["get_import_suggestions"],
|
|
114
141
|
handler: async (args, ctx) => {
|
|
115
|
-
const { currentFile, currentCode, language, limit = 10 } = args;
|
|
142
|
+
const { currentFile, currentCode, language, limit = 10, } = args;
|
|
116
143
|
const response = await ctx.api.post("/api/code/import-suggestions", {
|
|
117
144
|
currentFile,
|
|
118
145
|
currentCode,
|
|
@@ -144,14 +171,26 @@ export function createAdvancedTools(projectName) {
|
|
|
144
171
|
name: "get_type_context",
|
|
145
172
|
description: `Look up type/interface/class definitions and usage in ${projectName}. Finds where a type is defined and how it's used across the codebase.`,
|
|
146
173
|
schema: z.object({
|
|
147
|
-
typeName: z
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
174
|
+
typeName: z
|
|
175
|
+
.string()
|
|
176
|
+
.optional()
|
|
177
|
+
.describe("Name of the type/interface/class to look up"),
|
|
178
|
+
code: z
|
|
179
|
+
.string()
|
|
180
|
+
.optional()
|
|
181
|
+
.describe("Code containing types to look up (alternative to typeName)"),
|
|
182
|
+
currentFile: z
|
|
183
|
+
.string()
|
|
184
|
+
.optional()
|
|
185
|
+
.describe("Current file to exclude from results"),
|
|
186
|
+
limit: z.coerce
|
|
187
|
+
.number()
|
|
188
|
+
.optional()
|
|
189
|
+
.describe("Max results per category (default: 5)"),
|
|
151
190
|
}),
|
|
152
191
|
annotations: TOOL_ANNOTATIONS["get_type_context"],
|
|
153
192
|
handler: async (args, ctx) => {
|
|
154
|
-
const { typeName, code, currentFile, limit = 5 } = args;
|
|
193
|
+
const { typeName, code, currentFile, limit = 5, } = args;
|
|
155
194
|
if (!typeName && !code) {
|
|
156
195
|
return "Error: Either typeName or code is required.";
|
|
157
196
|
}
|
|
@@ -187,8 +226,14 @@ export function createAdvancedTools(projectName) {
|
|
|
187
226
|
name: "get_behavior_patterns",
|
|
188
227
|
description: `Analyze user workflow patterns for ${projectName}. Shows peak hours, tool preferences, common sequences, and session statistics.`,
|
|
189
228
|
schema: z.object({
|
|
190
|
-
days: z.coerce
|
|
191
|
-
|
|
229
|
+
days: z.coerce
|
|
230
|
+
.number()
|
|
231
|
+
.optional()
|
|
232
|
+
.describe("Number of days to analyze (default: 7)"),
|
|
233
|
+
sessionId: z
|
|
234
|
+
.string()
|
|
235
|
+
.optional()
|
|
236
|
+
.describe("Filter to a specific session (optional)"),
|
|
192
237
|
}),
|
|
193
238
|
annotations: TOOL_ANNOTATIONS["get_behavior_patterns"],
|
|
194
239
|
handler: async (args, ctx) => {
|
package/dist/tools/agents.js
CHANGED
|
@@ -12,10 +12,18 @@ export function createAgentTools(projectName) {
|
|
|
12
12
|
name: "run_agent",
|
|
13
13
|
description: `Run a specialized agent for ${projectName}. Agents autonomously research, review, or analyze using multiple tool calls. Returns result + reasoning trace.`,
|
|
14
14
|
schema: z.object({
|
|
15
|
-
type: z
|
|
15
|
+
type: z
|
|
16
|
+
.enum(["research", "review", "documentation", "refactor", "test"])
|
|
17
|
+
.describe("Agent type: research, review, documentation, refactor, or test"),
|
|
16
18
|
task: z.string().describe("The task for the agent to perform"),
|
|
17
|
-
context: z
|
|
18
|
-
|
|
19
|
+
context: z
|
|
20
|
+
.string()
|
|
21
|
+
.optional()
|
|
22
|
+
.describe("Optional additional context (code, requirements, etc.)"),
|
|
23
|
+
maxIterations: z.coerce
|
|
24
|
+
.number()
|
|
25
|
+
.optional()
|
|
26
|
+
.describe("Maximum ReAct iterations (default: varies by agent type)"),
|
|
19
27
|
}),
|
|
20
28
|
annotations: TOOL_ANNOTATIONS["run_agent"],
|
|
21
29
|
handler: async (args, ctx) => {
|
|
@@ -63,16 +71,37 @@ export function createAgentTools(projectName) {
|
|
|
63
71
|
name: "tribunal_debate",
|
|
64
72
|
description: `Run an adversarial debate on a topic for ${projectName}. Multiple advocates argue positions, a judge renders a verdict. Use for architecture decisions, tech choices, or code approach trade-offs.`,
|
|
65
73
|
schema: z.object({
|
|
66
|
-
topic: z
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
74
|
+
topic: z
|
|
75
|
+
.string()
|
|
76
|
+
.describe("The debate topic (e.g., 'Should we use REST or gRPC for the new API?')"),
|
|
77
|
+
positions: z
|
|
78
|
+
.array(z.string())
|
|
79
|
+
.min(2)
|
|
80
|
+
.max(4)
|
|
81
|
+
.describe("Positions to debate (2-4 options, e.g., ['REST', 'gRPC'])"),
|
|
82
|
+
context: z
|
|
83
|
+
.string()
|
|
84
|
+
.optional()
|
|
85
|
+
.describe("Additional context for the debate"),
|
|
86
|
+
maxRounds: z.coerce
|
|
87
|
+
.number()
|
|
88
|
+
.optional()
|
|
89
|
+
.describe("Number of rebuttal rounds (default: 1, max: 3)"),
|
|
90
|
+
useCodeContext: z
|
|
91
|
+
.boolean()
|
|
92
|
+
.optional()
|
|
93
|
+
.describe("Fetch relevant code, ADRs, and patterns as evidence (default: false)"),
|
|
94
|
+
autoRecord: z
|
|
95
|
+
.boolean()
|
|
96
|
+
.optional()
|
|
97
|
+
.describe("Save verdict as a decision in project memory (default: false)"),
|
|
72
98
|
}),
|
|
73
|
-
annotations: TOOL_ANNOTATIONS["tribunal_debate"] || {
|
|
99
|
+
annotations: TOOL_ANNOTATIONS["tribunal_debate"] || {
|
|
100
|
+
priority: 0.4,
|
|
101
|
+
readOnlyHint: true,
|
|
102
|
+
},
|
|
74
103
|
handler: async (args, ctx) => {
|
|
75
|
-
const { topic, positions, context, maxRounds, useCodeContext, autoRecord } = args;
|
|
104
|
+
const { topic, positions, context, maxRounds, useCodeContext, autoRecord, } = args;
|
|
76
105
|
const response = await ctx.api.post("/api/tribunal/debate", {
|
|
77
106
|
projectName: ctx.projectName,
|
|
78
107
|
topic,
|
|
@@ -87,7 +116,7 @@ export function createAgentTools(projectName) {
|
|
|
87
116
|
let result = `## Tribunal Debate: ${data.topic}\n`;
|
|
88
117
|
result += `**Status:** ${data.status}`;
|
|
89
118
|
result += ` | **Duration:** ${Math.round(data.durationMs / 1000)}s`;
|
|
90
|
-
result += ` | **Cost:** ~$${data.cost?.estimatedUsd?.toFixed(3) ||
|
|
119
|
+
result += ` | **Cost:** ~$${data.cost?.estimatedUsd?.toFixed(3) || "?"}\n\n`;
|
|
91
120
|
// Phases summary
|
|
92
121
|
if (data.phases) {
|
|
93
122
|
result += `### Phases\n`;
|
|
@@ -100,7 +129,7 @@ export function createAgentTools(projectName) {
|
|
|
100
129
|
if (data.arguments && data.arguments.length > 0) {
|
|
101
130
|
result += `### Arguments\n`;
|
|
102
131
|
for (const arg of data.arguments) {
|
|
103
|
-
const label = arg.round === 0 ?
|
|
132
|
+
const label = arg.round === 0 ? "Initial" : `Rebuttal R${arg.round}`;
|
|
104
133
|
result += `#### ${arg.position} (${label})\n${arg.content}\n\n`;
|
|
105
134
|
}
|
|
106
135
|
}
|
package/dist/tools/analytics.js
CHANGED
|
@@ -72,7 +72,9 @@ export function createAnalyticsTools(projectName) {
|
|
|
72
72
|
name: "get_analytics",
|
|
73
73
|
description: `Get detailed analytics for a ${projectName} collection. Shows vectors, storage, language breakdown, and more.`,
|
|
74
74
|
schema: z.object({
|
|
75
|
-
collectionName: z
|
|
75
|
+
collectionName: z
|
|
76
|
+
.string()
|
|
77
|
+
.describe("Collection name to get analytics for (e.g., 'codebase', 'docs', 'memory')"),
|
|
76
78
|
}),
|
|
77
79
|
annotations: TOOL_ANNOTATIONS["get_analytics"],
|
|
78
80
|
handler: async (args, ctx) => {
|
|
@@ -125,7 +127,9 @@ export function createAnalyticsTools(projectName) {
|
|
|
125
127
|
name: "list_backups",
|
|
126
128
|
description: `List backup snapshots for a ${projectName} collection.`,
|
|
127
129
|
schema: z.object({
|
|
128
|
-
collectionName: z
|
|
130
|
+
collectionName: z
|
|
131
|
+
.string()
|
|
132
|
+
.describe("Collection name to list backups for"),
|
|
129
133
|
}),
|
|
130
134
|
annotations: TOOL_ANNOTATIONS["list_backups"],
|
|
131
135
|
handler: async (args, ctx) => {
|
|
@@ -153,8 +157,13 @@ export function createAnalyticsTools(projectName) {
|
|
|
153
157
|
name: "enable_quantization",
|
|
154
158
|
description: `Enable scalar quantization on a ${projectName} collection to reduce memory usage.`,
|
|
155
159
|
schema: z.object({
|
|
156
|
-
collectionName: z
|
|
157
|
-
|
|
160
|
+
collectionName: z
|
|
161
|
+
.string()
|
|
162
|
+
.describe("Collection name to enable quantization on"),
|
|
163
|
+
quantile: z.coerce
|
|
164
|
+
.number()
|
|
165
|
+
.optional()
|
|
166
|
+
.describe("Quantile for quantization (0-1, default: 0.99)"),
|
|
158
167
|
}),
|
|
159
168
|
annotations: TOOL_ANNOTATIONS["enable_quantization"],
|
|
160
169
|
handler: async (args, ctx) => {
|
|
@@ -195,7 +204,10 @@ export function createAnalyticsTools(projectName) {
|
|
|
195
204
|
name: "get_prediction_stats",
|
|
196
205
|
description: `Get predictive loader stats for ${projectName}. Shows prediction accuracy, hit rates, and strategy breakdown.`,
|
|
197
206
|
schema: z.object({
|
|
198
|
-
sessionId: z
|
|
207
|
+
sessionId: z
|
|
208
|
+
.string()
|
|
209
|
+
.optional()
|
|
210
|
+
.describe("Session ID to get stats for. If omitted, returns aggregate stats."),
|
|
199
211
|
}),
|
|
200
212
|
annotations: TOOL_ANNOTATIONS["get_prediction_stats"],
|
|
201
213
|
handler: async (args, ctx) => {
|