agent-enderun 0.1.10 → 0.2.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/.enderun/BRAIN_DASHBOARD.md +43 -0
- package/.enderun/ENDERUN.md +203 -0
- package/.enderun/PROJECT_MEMORY.md +137 -36
- package/.enderun/agents/analyst.md +21 -10
- package/.enderun/agents/backend.md +12 -11
- package/.enderun/agents/explorer.md +10 -7
- package/.enderun/agents/frontend.md +9 -20
- package/.enderun/agents/git.md +16 -12
- package/.enderun/agents/manager.md +14 -15
- package/.enderun/agents/mobile.md +5 -5
- package/.enderun/agents/native.md +5 -5
- package/.enderun/benchmarks/.gitkeep +0 -0
- package/.enderun/cli-commands.json +13 -1
- package/.enderun/config.json +1 -1
- package/.enderun/docs/api/README.md +10 -9
- package/.enderun/docs/api/auth.md +11 -0
- package/.enderun/docs/api/errors.md +7 -0
- package/.enderun/docs/error-handling.md +12 -0
- package/.enderun/docs/privacy.md +3 -0
- package/.enderun/docs/security.md +12 -0
- package/.enderun/docs/tech-stack.md +1 -0
- package/.enderun/docs/troubleshooting.md +7 -0
- package/.enderun/knowledge/api_design_rules.md +6 -0
- package/.enderun/knowledge/async_error_handling.md +18 -0
- package/.enderun/knowledge/branded_types_pattern.md +1 -0
- package/.enderun/knowledge/code_review_checklist.md +7 -0
- package/.enderun/knowledge/contract_versioning.md +7 -0
- package/.enderun/knowledge/database_migration.md +6 -0
- package/.enderun/knowledge/deployment_checklist.md +7 -0
- package/.enderun/knowledge/git_commit_strategy.md +10 -0
- package/.enderun/knowledge/legacy_onboarding.md +7 -0
- package/.enderun/knowledge/monitoring_setup.md +5 -0
- package/.enderun/knowledge/performance_guidelines.md +11 -0
- package/.enderun/knowledge/repository_patterns.md +9 -0
- package/.enderun/knowledge/security_scanning.md +6 -0
- package/.enderun/knowledge/testing_standards.md +7 -0
- package/.enderun/knowledge/troubleshooting_guide.md +5 -0
- package/.enderun/knowledge/zero_ui_library_policy.md +1 -0
- package/.enderun/logs/analyst.json +1 -0
- package/.enderun/logs/backend.json +1 -0
- package/.enderun/logs/explorer.json +1 -0
- package/.enderun/logs/frontend.json +1 -0
- package/.enderun/logs/git.json +1 -0
- package/.enderun/logs/manager.json +363 -0
- package/.enderun/logs/mobile.json +1 -0
- package/.enderun/logs/native.json +1 -0
- package/.enderun/monitoring/.gitkeep +0 -0
- package/ENDERUN.md +8 -8
- package/LICENSE +21 -0
- package/README.md +595 -195
- package/bin/cli.js +292 -79
- package/package.json +42 -2
- package/packages/framework-mcp/README.md +47 -81
- package/packages/framework-mcp/dist/index.js +13 -971
- package/packages/framework-mcp/dist/schemas.js +84 -0
- package/packages/framework-mcp/dist/tools/academy.js +184 -0
- package/packages/framework-mcp/dist/tools/codebase.js +294 -0
- package/packages/framework-mcp/dist/tools/contract.js +95 -0
- package/packages/framework-mcp/dist/tools/database.js +52 -0
- package/packages/framework-mcp/dist/tools/framework.js +161 -0
- package/packages/framework-mcp/dist/tools/git.js +53 -0
- package/packages/framework-mcp/dist/tools/index.js +42 -0
- package/packages/framework-mcp/dist/tools/knowledge.js +69 -0
- package/packages/framework-mcp/dist/tools/memory.js +94 -0
- package/packages/framework-mcp/dist/tools/messages.js +71 -0
- package/packages/framework-mcp/dist/tools/repository.js +76 -0
- package/packages/framework-mcp/dist/tools/security.js +122 -0
- package/packages/framework-mcp/dist/utils.js +82 -0
- package/packages/framework-mcp/package.json +1 -1
- package/packages/framework-mcp/src/index.ts +20 -970
- package/packages/framework-mcp/src/schemas.ts +106 -0
- package/packages/framework-mcp/src/tools/academy.ts +178 -0
- package/packages/framework-mcp/src/tools/codebase.ts +284 -0
- package/packages/framework-mcp/src/tools/contract.ts +91 -0
- package/packages/framework-mcp/src/tools/database.ts +49 -0
- package/packages/framework-mcp/src/tools/framework.ts +157 -0
- package/packages/framework-mcp/src/tools/git.ts +43 -0
- package/packages/framework-mcp/src/tools/index.ts +45 -0
- package/packages/framework-mcp/src/tools/knowledge.ts +68 -0
- package/packages/framework-mcp/src/tools/memory.ts +88 -0
- package/packages/framework-mcp/src/tools/messages.ts +70 -0
- package/packages/framework-mcp/src/tools/repository.ts +76 -0
- package/packages/framework-mcp/src/tools/security.ts +122 -0
- package/packages/framework-mcp/src/utils.ts +90 -0
- package/packages/shared-types/README.md +28 -51
- package/packages/shared-types/dist/index.d.ts +80 -48
- package/packages/shared-types/dist/index.d.ts.map +1 -1
- package/packages/shared-types/dist/index.js +5 -8
- package/packages/shared-types/dist/index.js.map +1 -1
- package/packages/shared-types/package.json +1 -1
- package/packages/shared-types/src/index.ts +79 -51
- package/CHANGELOG.md +0 -97
- package/CLAUDE.md +0 -7
- package/CODEX.md +0 -7
- package/CURSOR.md +0 -7
- package/GEMINI.md +0 -7
- package/docs/tech-stack.md +0 -10
- package/gemini-extension.json +0 -5
- package/packages/framework-mcp/tsconfig.json +0 -15
- package/packages/shared-types/contract.version.json +0 -9
- package/packages/shared-types/tsconfig.json +0 -17
- package/panda.config.ts +0 -20
- /package/{docs → .enderun/docs}/project-docs.md +0 -0
|
@@ -1,991 +1,33 @@
|
|
|
1
1
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
2
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
-
import { CallToolRequestSchema, ListToolsRequestSchema
|
|
4
|
-
import {
|
|
5
|
-
import path from "path";
|
|
6
|
-
import fs from "fs";
|
|
7
|
-
import crypto from "crypto";
|
|
8
|
-
import { Project, SyntaxKind } from "ts-morph";
|
|
3
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
4
|
+
import { allTools, allHandlers } from "./tools/index.js";
|
|
9
5
|
const server = new Server({
|
|
10
6
|
name: "ai-enderun-mcp",
|
|
11
|
-
version: "0.0
|
|
7
|
+
version: "0.2.0",
|
|
12
8
|
}, {
|
|
13
9
|
capabilities: {
|
|
14
10
|
tools: {},
|
|
15
11
|
},
|
|
16
12
|
});
|
|
17
|
-
const SECURITY_AUDIT_ARGS_SCHEMA = z.object({
|
|
18
|
-
path: z.string().default("."),
|
|
19
|
-
});
|
|
20
|
-
const SEARCH_CODEBASE_ARGS_SCHEMA = z.object({
|
|
21
|
-
query: z.string().min(1).max(300),
|
|
22
|
-
extension: z
|
|
23
|
-
.string()
|
|
24
|
-
.regex(/^[a-z0-9]+$/i)
|
|
25
|
-
.default("ts"),
|
|
26
|
-
});
|
|
27
|
-
const UPDATE_MEMORY_ARGS_SCHEMA = z.object({
|
|
28
|
-
section: z.enum(["CURRENT STATUS", "HISTORY", "ACTIVE TASKS"]),
|
|
29
|
-
content: z.string().min(1),
|
|
30
|
-
});
|
|
31
|
-
const ANALYZE_DEPENDENCIES_ARGS_SCHEMA = z.object({
|
|
32
|
-
path: z.string().min(1),
|
|
33
|
-
});
|
|
34
|
-
const LOG_AGENT_ACTION_ARGS_SCHEMA = z.object({
|
|
35
|
-
agent: z.string().min(1),
|
|
36
|
-
action: z.string().min(1),
|
|
37
|
-
requestId: z.string().min(1),
|
|
38
|
-
files: z.array(z.string()).default([]),
|
|
39
|
-
status: z.enum(["SUCCESS", "FAILURE"]),
|
|
40
|
-
summary: z.string().min(1),
|
|
41
|
-
details: z.record(z.any()).default({}),
|
|
42
|
-
});
|
|
43
|
-
const FRAMEWORK_VERSION = "0.0.9";
|
|
44
|
-
function getFrameworkDir(projectRoot) {
|
|
45
|
-
const adapters = [".gemini", ".claude", ".cursor", ".codex", ".enderun"];
|
|
46
|
-
for (const adp of adapters) {
|
|
47
|
-
const fullPath = path.join(projectRoot, adp);
|
|
48
|
-
if (fs.existsSync(fullPath) && fs.lstatSync(fullPath).isDirectory()) {
|
|
49
|
-
return adp;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
return ".enderun";
|
|
53
|
-
}
|
|
54
|
-
function resolveSafePath(projectRoot, targetPath) {
|
|
55
|
-
const resolved = path.resolve(projectRoot, targetPath);
|
|
56
|
-
const relative = path.relative(projectRoot, resolved);
|
|
57
|
-
if (relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
58
|
-
throw new Error("Path escapes project root.");
|
|
59
|
-
}
|
|
60
|
-
return resolved;
|
|
61
|
-
}
|
|
62
|
-
function collectFilesRecursively(targetPath, extensions) {
|
|
63
|
-
const results = [];
|
|
64
|
-
const entries = fs.readdirSync(targetPath, { withFileTypes: true });
|
|
65
|
-
for (const entry of entries) {
|
|
66
|
-
const fullPath = path.join(targetPath, entry.name);
|
|
67
|
-
if (entry.isDirectory()) {
|
|
68
|
-
if (entry.name === "node_modules" || entry.name === ".git")
|
|
69
|
-
continue;
|
|
70
|
-
results.push(...collectFilesRecursively(fullPath, extensions));
|
|
71
|
-
continue;
|
|
72
|
-
}
|
|
73
|
-
const ext = path.extname(entry.name).slice(1).toLowerCase();
|
|
74
|
-
if (extensions.has(ext))
|
|
75
|
-
results.push(fullPath);
|
|
76
|
-
}
|
|
77
|
-
return results;
|
|
78
|
-
}
|
|
79
|
-
function buildLineMatches(files, matcher, maxResults, projectRoot) {
|
|
80
|
-
const matches = [];
|
|
81
|
-
for (const filePath of files) {
|
|
82
|
-
if (matches.length >= maxResults)
|
|
83
|
-
break;
|
|
84
|
-
const content = fs.readFileSync(filePath, "utf-8");
|
|
85
|
-
const lines = content.split("\n");
|
|
86
|
-
for (let i = 0; i < lines.length; i++) {
|
|
87
|
-
if (matches.length >= maxResults)
|
|
88
|
-
break;
|
|
89
|
-
const line = lines[i];
|
|
90
|
-
if (!matcher(line))
|
|
91
|
-
continue;
|
|
92
|
-
const relativePath = path.relative(projectRoot, filePath);
|
|
93
|
-
matches.push(`${relativePath}:${i + 1}:${line}`);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
return matches;
|
|
97
|
-
}
|
|
98
|
-
function collectMarkdownArtifacts(projectRoot) {
|
|
99
|
-
const docsRoot = path.join(projectRoot, "docs");
|
|
100
|
-
if (!fs.existsSync(docsRoot))
|
|
101
|
-
return [];
|
|
102
|
-
return collectFilesRecursively(docsRoot, new Set(["md"])).map((filePath) => path.relative(projectRoot, filePath));
|
|
103
|
-
}
|
|
104
|
-
function replaceSectionContent(markdown, sectionTitle, newBody) {
|
|
105
|
-
const escaped = sectionTitle.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
106
|
-
const sectionRegex = new RegExp(`## ${escaped}[\\s\\S]*?(?=\\n## |$)`, "m");
|
|
107
|
-
if (!sectionRegex.test(markdown)) {
|
|
108
|
-
throw new Error(`Section not found: ${sectionTitle}`);
|
|
109
|
-
}
|
|
110
|
-
return markdown.replace(sectionRegex, `## ${sectionTitle}\n\n${newBody.trim()}\n`);
|
|
111
|
-
}
|
|
112
|
-
function prependToSection(markdown, sectionTitle, contentToPrepend) {
|
|
113
|
-
const escaped = sectionTitle.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
114
|
-
const sectionRegex = new RegExp(`(## ${escaped}\\n)([\\s\\S]*?)(?=\\n## |$)`, "m");
|
|
115
|
-
const match = markdown.match(sectionRegex);
|
|
116
|
-
if (!match) {
|
|
117
|
-
throw new Error(`Section not found: ${sectionTitle}`);
|
|
118
|
-
}
|
|
119
|
-
const currentBody = match[2].trimStart();
|
|
120
|
-
const updatedBody = `${contentToPrepend.trim()}\n\n${currentBody}`.trim();
|
|
121
|
-
return markdown.replace(sectionRegex, `$1\n${updatedBody}\n`);
|
|
122
|
-
}
|
|
123
|
-
/**
|
|
124
|
-
* Tool definitions
|
|
125
|
-
*/
|
|
126
13
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
127
14
|
return {
|
|
128
|
-
tools:
|
|
129
|
-
{
|
|
130
|
-
name: "get_framework_status",
|
|
131
|
-
description: "Get the current status of the AI Agent Framework, including active phase and agent states.",
|
|
132
|
-
inputSchema: { type: "object", properties: {} },
|
|
133
|
-
},
|
|
134
|
-
{
|
|
135
|
-
name: "search_codebase",
|
|
136
|
-
description: "Semantic search across the codebase using grep for exact matches and context. Ideal for finding logic and patterns.",
|
|
137
|
-
inputSchema: {
|
|
138
|
-
type: "object",
|
|
139
|
-
properties: {
|
|
140
|
-
query: {
|
|
141
|
-
type: "string",
|
|
142
|
-
description: "Search query or regex pattern",
|
|
143
|
-
},
|
|
144
|
-
extension: {
|
|
145
|
-
type: "string",
|
|
146
|
-
description: "File extension filter (e.g., ts, md)",
|
|
147
|
-
default: "ts",
|
|
148
|
-
},
|
|
149
|
-
},
|
|
150
|
-
required: ["query"],
|
|
151
|
-
},
|
|
152
|
-
},
|
|
153
|
-
{
|
|
154
|
-
name: "codebase_search",
|
|
155
|
-
description: "Compatibility alias for search_codebase. Use when older agent prompts still reference codebase_search.",
|
|
156
|
-
inputSchema: {
|
|
157
|
-
type: "object",
|
|
158
|
-
properties: {
|
|
159
|
-
query: {
|
|
160
|
-
type: "string",
|
|
161
|
-
description: "Search query or regex pattern",
|
|
162
|
-
},
|
|
163
|
-
extension: {
|
|
164
|
-
type: "string",
|
|
165
|
-
description: "File extension filter (e.g., ts, md)",
|
|
166
|
-
default: "ts",
|
|
167
|
-
},
|
|
168
|
-
},
|
|
169
|
-
required: ["query"],
|
|
170
|
-
},
|
|
171
|
-
},
|
|
172
|
-
{
|
|
173
|
-
name: "analyze_dependencies",
|
|
174
|
-
description: "Analyze code dependencies for a specific file or folder using import tracking.",
|
|
175
|
-
inputSchema: {
|
|
176
|
-
type: "object",
|
|
177
|
-
properties: {
|
|
178
|
-
path: {
|
|
179
|
-
type: "string",
|
|
180
|
-
description: "Path to analyze (relative to project root)",
|
|
181
|
-
},
|
|
182
|
-
},
|
|
183
|
-
required: ["path"],
|
|
184
|
-
},
|
|
185
|
-
},
|
|
186
|
-
{
|
|
187
|
-
name: "codebase_graph_query",
|
|
188
|
-
description: "Compatibility alias for analyze_dependencies. Returns import-level dependency information for a file or folder.",
|
|
189
|
-
inputSchema: {
|
|
190
|
-
type: "object",
|
|
191
|
-
properties: {
|
|
192
|
-
path: {
|
|
193
|
-
type: "string",
|
|
194
|
-
description: "Path to analyze (relative to project root)",
|
|
195
|
-
},
|
|
196
|
-
},
|
|
197
|
-
required: ["path"],
|
|
198
|
-
},
|
|
199
|
-
},
|
|
200
|
-
{
|
|
201
|
-
name: "get_memory_insights",
|
|
202
|
-
description: "Analyze PROJECT_MEMORY.md and BRAIN_DASHBOARD.md to provide insights on what was done, what is being done, and the history.",
|
|
203
|
-
inputSchema: { type: "object", properties: {} },
|
|
204
|
-
},
|
|
205
|
-
{
|
|
206
|
-
name: "codebase_context",
|
|
207
|
-
description: "Compatibility helper for non-code context discovery. Lists known markdown artifacts under docs/ and memory files.",
|
|
208
|
-
inputSchema: { type: "object", properties: {} },
|
|
209
|
-
},
|
|
210
|
-
{
|
|
211
|
-
name: "codebase_context_search",
|
|
212
|
-
description: "Compatibility alias for search_codebase focused on markdown artifacts. Defaults to md files.",
|
|
213
|
-
inputSchema: {
|
|
214
|
-
type: "object",
|
|
215
|
-
properties: {
|
|
216
|
-
query: {
|
|
217
|
-
type: "string",
|
|
218
|
-
description: "Search query or regex pattern",
|
|
219
|
-
},
|
|
220
|
-
extension: {
|
|
221
|
-
type: "string",
|
|
222
|
-
description: "File extension filter (defaults to md)",
|
|
223
|
-
default: "md",
|
|
224
|
-
},
|
|
225
|
-
},
|
|
226
|
-
required: ["query"],
|
|
227
|
-
},
|
|
228
|
-
},
|
|
229
|
-
{
|
|
230
|
-
name: "get_project_gaps",
|
|
231
|
-
description: "Scans the project structure against the defined standards in ENDERUN.md and identifies missing files, folders, or documentation.",
|
|
232
|
-
inputSchema: { type: "object", properties: {} },
|
|
233
|
-
},
|
|
234
|
-
{
|
|
235
|
-
name: "codebase_status",
|
|
236
|
-
description: "Compatibility alias for get_framework_status.",
|
|
237
|
-
inputSchema: { type: "object", properties: {} },
|
|
238
|
-
},
|
|
239
|
-
{
|
|
240
|
-
name: "security_audit_scan",
|
|
241
|
-
description: "Scans the codebase for security vulnerabilities like hardcoded secrets, raw SQL, and unsafe async patterns.",
|
|
242
|
-
inputSchema: {
|
|
243
|
-
type: "object",
|
|
244
|
-
properties: {
|
|
245
|
-
path: {
|
|
246
|
-
type: "string",
|
|
247
|
-
description: "Path to scan (relative to project root)",
|
|
248
|
-
default: ".",
|
|
249
|
-
},
|
|
250
|
-
},
|
|
251
|
-
},
|
|
252
|
-
},
|
|
253
|
-
{
|
|
254
|
-
name: "update_project_memory",
|
|
255
|
-
description: "Update a specific section of PROJECT_MEMORY.md (CURRENT STATUS, HISTORY, or ACTIVE TASKS) with new content.",
|
|
256
|
-
inputSchema: {
|
|
257
|
-
type: "object",
|
|
258
|
-
properties: {
|
|
259
|
-
section: {
|
|
260
|
-
type: "string",
|
|
261
|
-
enum: ["CURRENT STATUS", "HISTORY", "ACTIVE TASKS"],
|
|
262
|
-
description: "The section to update.",
|
|
263
|
-
},
|
|
264
|
-
content: {
|
|
265
|
-
type: "string",
|
|
266
|
-
description: "The new content to append or replace in that section.",
|
|
267
|
-
},
|
|
268
|
-
},
|
|
269
|
-
required: ["section", "content"],
|
|
270
|
-
},
|
|
271
|
-
},
|
|
272
|
-
{
|
|
273
|
-
name: "verify_api_contract",
|
|
274
|
-
description: "Verify if the shared-types match the stored contract hash in contract.version.json.",
|
|
275
|
-
inputSchema: { type: "object", properties: {} },
|
|
276
|
-
},
|
|
277
|
-
{
|
|
278
|
-
name: "update_contract_hash",
|
|
279
|
-
description: "Generate a new hash for shared-types and update contract.version.json.",
|
|
280
|
-
inputSchema: { type: "object", properties: {} },
|
|
281
|
-
},
|
|
282
|
-
{
|
|
283
|
-
name: "log_agent_action",
|
|
284
|
-
description: "Safely append a structured log entry to the agent's log file.",
|
|
285
|
-
inputSchema: {
|
|
286
|
-
type: "object",
|
|
287
|
-
properties: {
|
|
288
|
-
agent: {
|
|
289
|
-
type: "string",
|
|
290
|
-
description: "Agent name (e.g. analyst, backend)",
|
|
291
|
-
},
|
|
292
|
-
action: {
|
|
293
|
-
type: "string",
|
|
294
|
-
description: "Action performed (e.g. CREATE, MODIFY)",
|
|
295
|
-
},
|
|
296
|
-
requestId: {
|
|
297
|
-
type: "string",
|
|
298
|
-
description: "Trace ID or Request ID",
|
|
299
|
-
},
|
|
300
|
-
files: {
|
|
301
|
-
type: "array",
|
|
302
|
-
items: { type: "string" },
|
|
303
|
-
description: "Files affected",
|
|
304
|
-
},
|
|
305
|
-
status: {
|
|
306
|
-
type: "string",
|
|
307
|
-
enum: ["SUCCESS", "FAILURE"],
|
|
308
|
-
description: "Action status",
|
|
309
|
-
},
|
|
310
|
-
summary: { type: "string", description: "Short English summary" },
|
|
311
|
-
details: { type: "object", description: "Additional details" },
|
|
312
|
-
},
|
|
313
|
-
required: ["agent", "action", "requestId", "status", "summary"],
|
|
314
|
-
},
|
|
315
|
-
},
|
|
316
|
-
{
|
|
317
|
-
name: "get_system_time",
|
|
318
|
-
description: "Get the current system time in ISO-8601 format (UTC). Use this instead of Shell 'date' commands.",
|
|
319
|
-
inputSchema: { type: "object", properties: {} },
|
|
320
|
-
},
|
|
321
|
-
{
|
|
322
|
-
name: "read_project_memory",
|
|
323
|
-
description: "Read the entire content of PROJECT_MEMORY.md safely. Use this instead of direct ReadFile tools to ensure framework compatibility.",
|
|
324
|
-
inputSchema: { type: "object", properties: {} },
|
|
325
|
-
},
|
|
326
|
-
],
|
|
15
|
+
tools: allTools,
|
|
327
16
|
};
|
|
328
17
|
});
|
|
329
|
-
/**
|
|
330
|
-
* Tool execution logic
|
|
331
|
-
*/
|
|
332
18
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
333
19
|
const { name, arguments: args } = request.params;
|
|
334
20
|
const projectRoot = process.cwd();
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
const phase = statusRowMatch?.[1]?.trim() ?? "UNKNOWN";
|
|
344
|
-
const profile = statusRowMatch?.[2]?.trim() ?? "UNKNOWN";
|
|
345
|
-
return {
|
|
346
|
-
content: [
|
|
347
|
-
{
|
|
348
|
-
type: "text",
|
|
349
|
-
text: `Framework active (v${FRAMEWORK_VERSION}). Phase: ${phase}. Profile: ${profile}.`,
|
|
350
|
-
},
|
|
351
|
-
],
|
|
352
|
-
};
|
|
353
|
-
}
|
|
354
|
-
catch (error) {
|
|
355
|
-
return {
|
|
356
|
-
content: [
|
|
357
|
-
{ type: "text", text: "Framework active. Memory unreadable." },
|
|
358
|
-
],
|
|
359
|
-
};
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
case "security_audit_scan": {
|
|
363
|
-
const parsed = SECURITY_AUDIT_ARGS_SCHEMA.safeParse(args ?? {});
|
|
364
|
-
if (!parsed.success) {
|
|
365
|
-
return {
|
|
366
|
-
content: [{ type: "text", text: "Invalid path argument." }],
|
|
367
|
-
};
|
|
368
|
-
}
|
|
369
|
-
const vulnerabilities = [];
|
|
370
|
-
let safeTargetPath;
|
|
371
|
-
const scanRules = [
|
|
372
|
-
{
|
|
373
|
-
pattern: /sql`/,
|
|
374
|
-
message: "Potential Raw SQL usage detected (check Kysely usage)",
|
|
375
|
-
severity: "HIGH",
|
|
376
|
-
},
|
|
377
|
-
{
|
|
378
|
-
pattern: /(?<!\/\/\s*)console\.log/,
|
|
379
|
-
message: "console.log found in production code",
|
|
380
|
-
severity: "LOW",
|
|
381
|
-
},
|
|
382
|
-
{
|
|
383
|
-
pattern: /(password|secret|api_?key)\s*[:=]\s*['"][^'"]+['"]/i,
|
|
384
|
-
message: "Potential hardcoded secret/password detected",
|
|
385
|
-
severity: "CRITICAL",
|
|
386
|
-
},
|
|
387
|
-
{
|
|
388
|
-
pattern: /:\s*any(?!\w)/,
|
|
389
|
-
message: "Usage of 'any' type detected",
|
|
390
|
-
severity: "MEDIUM",
|
|
391
|
-
},
|
|
392
|
-
{
|
|
393
|
-
pattern: /(?<!\w)eval\s*\(/,
|
|
394
|
-
message: "Dangerous 'eval()' usage detected",
|
|
395
|
-
severity: "HIGH",
|
|
396
|
-
},
|
|
397
|
-
{
|
|
398
|
-
pattern: /\.innerHTML\s*=/,
|
|
399
|
-
message: "Unsafe innerHTML assignment detected (XSS risk)",
|
|
400
|
-
severity: "MEDIUM",
|
|
401
|
-
},
|
|
402
|
-
{
|
|
403
|
-
pattern: /dangerouslySetInnerHTML/,
|
|
404
|
-
message: "React dangerouslySetInnerHTML detected",
|
|
405
|
-
severity: "MEDIUM",
|
|
406
|
-
},
|
|
407
|
-
{
|
|
408
|
-
pattern: /TODO:/,
|
|
409
|
-
message: "Outstanding TODO item found",
|
|
410
|
-
severity: "LOW",
|
|
411
|
-
},
|
|
412
|
-
];
|
|
413
|
-
try {
|
|
414
|
-
safeTargetPath = resolveSafePath(projectRoot, parsed.data.path);
|
|
415
|
-
if (!fs.existsSync(safeTargetPath)) {
|
|
416
|
-
return {
|
|
417
|
-
content: [{ type: "text", text: "Target path not found." }],
|
|
418
|
-
};
|
|
419
|
-
}
|
|
420
|
-
const files = collectFilesRecursively(safeTargetPath, new Set(["ts", "tsx"]));
|
|
421
|
-
// Regex-based scans (for simple patterns)
|
|
422
|
-
for (const rule of scanRules) {
|
|
423
|
-
const ruleMatches = buildLineMatches(files, (line) => {
|
|
424
|
-
if (typeof rule.pattern === "string") {
|
|
425
|
-
return line.includes(rule.pattern);
|
|
426
|
-
}
|
|
427
|
-
else {
|
|
428
|
-
return rule.pattern.test(line);
|
|
429
|
-
}
|
|
430
|
-
}, 5, projectRoot);
|
|
431
|
-
if (ruleMatches.length > 0) {
|
|
432
|
-
vulnerabilities.push(`[${rule.severity}] ${rule.message}:\n${ruleMatches.join("\n")}`);
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
// AST-based scans (for deeper structural analysis)
|
|
436
|
-
const tsProject = new Project({
|
|
437
|
-
compilerOptions: { allowJs: true },
|
|
438
|
-
});
|
|
439
|
-
tsProject.addSourceFilesAtPaths(path.join(safeTargetPath, "**/*.{ts,tsx}"));
|
|
440
|
-
for (const sourceFile of tsProject.getSourceFiles()) {
|
|
441
|
-
const relativePath = path.relative(projectRoot, sourceFile.getFilePath());
|
|
442
|
-
// 1. Precise 'any' detection
|
|
443
|
-
sourceFile.forEachDescendant((node) => {
|
|
444
|
-
if (node.getKindName() === "AnyKeyword") {
|
|
445
|
-
const line = node.getStartLineNumber();
|
|
446
|
-
vulnerabilities.push(`[MEDIUM] Precise 'any' type detected in AST at ${relativePath}:${line}`);
|
|
447
|
-
}
|
|
448
|
-
// 2. Precise 'console.log' detection
|
|
449
|
-
if (node.getKind() === SyntaxKind.CallExpression) {
|
|
450
|
-
const callExp = node.asKind(SyntaxKind.CallExpression);
|
|
451
|
-
const expression = callExp?.getExpression();
|
|
452
|
-
if (expression?.getText() === "console.log") {
|
|
453
|
-
const line = node.getStartLineNumber();
|
|
454
|
-
vulnerabilities.push(`[LOW] Production 'console.log' detected in AST at ${relativePath}:${line}`);
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
});
|
|
458
|
-
}
|
|
459
|
-
return {
|
|
460
|
-
content: [
|
|
461
|
-
{
|
|
462
|
-
type: "text",
|
|
463
|
-
text: vulnerabilities.length > 0
|
|
464
|
-
? `### ADVANCED SECURITY AUDIT RESULTS\n\n${Array.from(new Set(vulnerabilities)).join("\n\n")}`
|
|
465
|
-
: "No security patterns or rule violations detected (Regex & AST verified).",
|
|
466
|
-
},
|
|
467
|
-
],
|
|
468
|
-
};
|
|
469
|
-
}
|
|
470
|
-
catch (error) {
|
|
471
|
-
return { content: [{ type: "text", text: "Security scan failed." }] };
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
case "get_memory_insights": {
|
|
475
|
-
try {
|
|
476
|
-
const frameworkDir = getFrameworkDir(projectRoot);
|
|
477
|
-
const memoryPath = path.join(projectRoot, frameworkDir, "PROJECT_MEMORY.md");
|
|
478
|
-
const dashboardPath = path.join(projectRoot, frameworkDir, "BRAIN_DASHBOARD.md");
|
|
479
|
-
const memory = fs.existsSync(memoryPath)
|
|
480
|
-
? fs.readFileSync(memoryPath, "utf-8")
|
|
481
|
-
: "Memory file missing.";
|
|
482
|
-
const dashboard = fs.existsSync(dashboardPath)
|
|
483
|
-
? fs.readFileSync(dashboardPath, "utf-8")
|
|
484
|
-
: "Dashboard file missing.";
|
|
485
|
-
const history = memory.split("## HISTORY")[1] || "No history found.";
|
|
486
|
-
const activeTasks = memory.split("## ACTIVE TASKS")[1]?.split("##")[0] ||
|
|
487
|
-
"No active tasks.";
|
|
488
|
-
const dashboardAgents = dashboard.split("## 📈 Visualizations")[0] || dashboard;
|
|
489
|
-
return {
|
|
490
|
-
content: [
|
|
491
|
-
{
|
|
492
|
-
type: "text",
|
|
493
|
-
text: `### LIVE MEMORY INSIGHTS\n\n**Active Tasks:**\n${activeTasks.trim()}\n\n**Recent History:**\n${history.trim().substring(0, 1000)}...\n\n**Brain Snapshot:**\n${dashboardAgents.trim().substring(0, 500)}...`,
|
|
494
|
-
},
|
|
495
|
-
],
|
|
496
|
-
};
|
|
497
|
-
}
|
|
498
|
-
catch (error) {
|
|
499
|
-
return {
|
|
500
|
-
content: [
|
|
501
|
-
{ type: "text", text: "Failed to gather memory insights." },
|
|
502
|
-
],
|
|
503
|
-
};
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
case "codebase_context": {
|
|
507
|
-
try {
|
|
508
|
-
const artifacts = collectMarkdownArtifacts(projectRoot);
|
|
509
|
-
const frameworkDir = getFrameworkDir(projectRoot);
|
|
510
|
-
const memoryPath = path.join(projectRoot, frameworkDir, "PROJECT_MEMORY.md");
|
|
511
|
-
const dashboardPath = path.join(projectRoot, frameworkDir, "BRAIN_DASHBOARD.md");
|
|
512
|
-
return {
|
|
513
|
-
content: [
|
|
514
|
-
{
|
|
515
|
-
type: "text",
|
|
516
|
-
text: "### CONTEXT ARTIFACTS\n\n" +
|
|
517
|
-
`PROJECT_MEMORY: ${fs.existsSync(memoryPath) ? "present" : "missing"}\n` +
|
|
518
|
-
`BRAIN_DASHBOARD: ${fs.existsSync(dashboardPath) ? "present" : "missing"}\n` +
|
|
519
|
-
`Docs:\n${artifacts.length > 0 ? artifacts.join("\n") : "No markdown artifacts found."}`,
|
|
520
|
-
},
|
|
521
|
-
],
|
|
522
|
-
};
|
|
523
|
-
}
|
|
524
|
-
catch (error) {
|
|
525
|
-
return {
|
|
526
|
-
content: [{ type: "text", text: "Context discovery failed." }],
|
|
527
|
-
};
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
case "get_project_gaps": {
|
|
531
|
-
const missing = [];
|
|
532
|
-
const frameworkDir = getFrameworkDir(projectRoot);
|
|
533
|
-
const checkPaths = [
|
|
534
|
-
{ path: "apps", type: "folder", optional: true },
|
|
535
|
-
{ path: "packages/shared-types/src", type: "folder" },
|
|
536
|
-
{ path: path.join(frameworkDir, "docs/api"), type: "folder", optional: true },
|
|
537
|
-
{ path: ".env", type: "file", optional: true },
|
|
538
|
-
{ path: ".env.example", type: "file" },
|
|
539
|
-
{ path: path.join(frameworkDir, "PROJECT_MEMORY.md"), type: "file" },
|
|
540
|
-
{ path: path.join(frameworkDir, "BRAIN_DASHBOARD.md"), type: "file" },
|
|
541
|
-
{ path: "docs/tech-stack.md", type: "file" },
|
|
542
|
-
{ path: "docs/project-docs.md", type: "file" },
|
|
543
|
-
];
|
|
544
|
-
for (const item of checkPaths) {
|
|
545
|
-
const fullPath = path.join(projectRoot, item.path);
|
|
546
|
-
if (!fs.existsSync(fullPath) && !item.optional) {
|
|
547
|
-
missing.push(`[MISSING ${item.type.toUpperCase()}] ${item.path}`);
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
// Additional dynamic checks
|
|
551
|
-
const agentsDir = path.join(projectRoot, frameworkDir, "agents");
|
|
552
|
-
if (!fs.existsSync(agentsDir)) {
|
|
553
|
-
missing.push(`[MISSING FOLDER] ${frameworkDir}/agents`);
|
|
554
|
-
}
|
|
555
|
-
else {
|
|
556
|
-
const agents = fs
|
|
557
|
-
.readdirSync(agentsDir)
|
|
558
|
-
.filter((f) => f.endsWith(".md"))
|
|
559
|
-
.map((f) => f.replace(".md", ""));
|
|
560
|
-
const logsDir = path.join(projectRoot, frameworkDir, "logs");
|
|
561
|
-
for (const agentName of agents) {
|
|
562
|
-
const logFile = path.join(logsDir, `${agentName}.json`);
|
|
563
|
-
if (!fs.existsSync(logFile)) {
|
|
564
|
-
missing.push(`[MISSING LOG FILE] ${frameworkDir}/logs/${agentName}.json (Expected for agent ${agentName})`);
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
return {
|
|
569
|
-
content: [
|
|
570
|
-
{
|
|
571
|
-
type: "text",
|
|
572
|
-
text: missing.length > 0
|
|
573
|
-
? `Detected Gaps:\n${missing.join("\n")}`
|
|
574
|
-
: "No structural gaps detected based on core standards.",
|
|
575
|
-
},
|
|
576
|
-
],
|
|
577
|
-
};
|
|
578
|
-
}
|
|
579
|
-
case "search_codebase":
|
|
580
|
-
case "codebase_search":
|
|
581
|
-
case "codebase_context_search": {
|
|
582
|
-
const mergedArgs = name === "codebase_context_search"
|
|
583
|
-
? { extension: "md", ...(args ?? {}) }
|
|
584
|
-
: (args ?? {});
|
|
585
|
-
const parsed = SEARCH_CODEBASE_ARGS_SCHEMA.safeParse(mergedArgs);
|
|
586
|
-
if (!parsed.success) {
|
|
587
|
-
return {
|
|
588
|
-
content: [
|
|
589
|
-
{ type: "text", text: "Invalid query/extension argument." },
|
|
590
|
-
],
|
|
591
|
-
};
|
|
592
|
-
}
|
|
593
|
-
const { query, extension } = parsed.data;
|
|
594
|
-
try {
|
|
595
|
-
const files = collectFilesRecursively(projectRoot, new Set([extension]));
|
|
596
|
-
let queryRegex;
|
|
597
|
-
try {
|
|
598
|
-
queryRegex = new RegExp(query, "i"); // make search case-insensitive by default
|
|
599
|
-
}
|
|
600
|
-
catch (error) {
|
|
601
|
-
return {
|
|
602
|
-
content: [
|
|
603
|
-
{
|
|
604
|
-
type: "text",
|
|
605
|
-
text: "Invalid regex pattern in query.",
|
|
606
|
-
},
|
|
607
|
-
],
|
|
608
|
-
};
|
|
609
|
-
}
|
|
610
|
-
// Fast search with limited results
|
|
611
|
-
const matches = [];
|
|
612
|
-
const MAX_RESULTS = 30;
|
|
613
|
-
for (const filePath of files) {
|
|
614
|
-
if (matches.length >= MAX_RESULTS)
|
|
615
|
-
break;
|
|
616
|
-
const content = fs.readFileSync(filePath, "utf-8");
|
|
617
|
-
const lines = content.split("\n");
|
|
618
|
-
for (let i = 0; i < lines.length; i++) {
|
|
619
|
-
if (matches.length >= MAX_RESULTS)
|
|
620
|
-
break;
|
|
621
|
-
const line = lines[i];
|
|
622
|
-
if (queryRegex.test(line)) {
|
|
623
|
-
const relativePath = path.relative(projectRoot, filePath);
|
|
624
|
-
matches.push(`- ${relativePath}:${i + 1}: ${line.trim()}`);
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
const result = matches.join("\n");
|
|
629
|
-
return {
|
|
630
|
-
content: [{ type: "text", text: result || "No matches found." }],
|
|
631
|
-
};
|
|
632
|
-
}
|
|
633
|
-
catch (error) {
|
|
634
|
-
return {
|
|
635
|
-
content: [
|
|
636
|
-
{ type: "text", text: "Search found no matches or failed." },
|
|
637
|
-
],
|
|
638
|
-
};
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
case "analyze_dependencies":
|
|
642
|
-
case "codebase_graph_query": {
|
|
643
|
-
const parsed = ANALYZE_DEPENDENCIES_ARGS_SCHEMA.safeParse(args ?? {});
|
|
644
|
-
if (!parsed.success) {
|
|
645
|
-
return {
|
|
646
|
-
content: [{ type: "text", text: "Invalid path argument." }],
|
|
647
|
-
};
|
|
648
|
-
}
|
|
649
|
-
const targetPath = parsed.data.path;
|
|
650
|
-
try {
|
|
651
|
-
const fullPath = resolveSafePath(projectRoot, targetPath);
|
|
652
|
-
if (!fs.existsSync(fullPath))
|
|
653
|
-
return {
|
|
654
|
-
content: [{ type: "text", text: `Path not found: ${targetPath}` }],
|
|
655
|
-
};
|
|
656
|
-
const stats = fs.statSync(fullPath);
|
|
657
|
-
const tsProject = new Project({
|
|
658
|
-
compilerOptions: { allowJs: true },
|
|
659
|
-
});
|
|
660
|
-
if (stats.isDirectory()) {
|
|
661
|
-
tsProject.addSourceFilesAtPaths(path.join(fullPath, "**/*.{ts,tsx,js,jsx}"));
|
|
662
|
-
const sourceFiles = tsProject.getSourceFiles();
|
|
663
|
-
return {
|
|
664
|
-
content: [
|
|
665
|
-
{
|
|
666
|
-
type: "text",
|
|
667
|
-
text: `Directory contains ${sourceFiles.length} source files. Use a specific file path for deep dependency analysis.`,
|
|
668
|
-
},
|
|
669
|
-
],
|
|
670
|
-
};
|
|
671
|
-
}
|
|
672
|
-
else {
|
|
673
|
-
const sourceFile = tsProject.addSourceFileAtPath(fullPath);
|
|
674
|
-
const imports = sourceFile.getImportDeclarations();
|
|
675
|
-
const importDetails = imports.map((imp) => {
|
|
676
|
-
const moduleSpecifier = imp.getModuleSpecifierValue();
|
|
677
|
-
const source = imp.getModuleSpecifierSourceFile();
|
|
678
|
-
const resolvedPath = source
|
|
679
|
-
? path.relative(projectRoot, source.getFilePath())
|
|
680
|
-
: "unresolved/external";
|
|
681
|
-
return `- ${moduleSpecifier} (${resolvedPath})`;
|
|
682
|
-
});
|
|
683
|
-
return {
|
|
684
|
-
content: [
|
|
685
|
-
{
|
|
686
|
-
type: "text",
|
|
687
|
-
text: `Dependencies for ${targetPath}:\n${importDetails.length > 0 ? importDetails.join("\n") : "No imports found."}`,
|
|
688
|
-
},
|
|
689
|
-
],
|
|
690
|
-
};
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
catch (error) {
|
|
694
|
-
return {
|
|
695
|
-
content: [
|
|
696
|
-
{
|
|
697
|
-
type: "text",
|
|
698
|
-
text: "Analysis failed: " +
|
|
699
|
-
(error instanceof Error ? error.message : String(error)),
|
|
700
|
-
},
|
|
701
|
-
],
|
|
702
|
-
};
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
case "update_project_memory": {
|
|
706
|
-
const parsed = UPDATE_MEMORY_ARGS_SCHEMA.safeParse(args ?? {});
|
|
707
|
-
if (!parsed.success) {
|
|
708
|
-
return {
|
|
709
|
-
content: [{ type: "text", text: "Invalid section or content." }],
|
|
710
|
-
};
|
|
711
|
-
}
|
|
712
|
-
const { section, content } = parsed.data;
|
|
713
|
-
const frameworkDir = getFrameworkDir(projectRoot);
|
|
714
|
-
const memoryPath = path.join(projectRoot, frameworkDir, "PROJECT_MEMORY.md");
|
|
715
|
-
const lockPath = memoryPath + ".lock";
|
|
716
|
-
const lockOwner = `lock-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
717
|
-
try {
|
|
718
|
-
// Lock protocol (simplified for MCP)
|
|
719
|
-
if (fs.existsSync(lockPath)) {
|
|
720
|
-
const stats = fs.statSync(lockPath);
|
|
721
|
-
const now = Date.now();
|
|
722
|
-
if (now - stats.mtimeMs > 120000) {
|
|
723
|
-
// 2 minutes stale
|
|
724
|
-
fs.unlinkSync(lockPath); // Override stale lock
|
|
725
|
-
}
|
|
726
|
-
else {
|
|
727
|
-
return {
|
|
728
|
-
content: [
|
|
729
|
-
{ type: "text", text: "Memory is locked. Try again later." },
|
|
730
|
-
],
|
|
731
|
-
};
|
|
732
|
-
}
|
|
733
|
-
}
|
|
734
|
-
fs.writeFileSync(lockPath, JSON.stringify({
|
|
735
|
-
owner: lockOwner,
|
|
736
|
-
createdAt: new Date().toISOString(),
|
|
737
|
-
}));
|
|
738
|
-
let memoryContent = fs.readFileSync(memoryPath, "utf-8");
|
|
739
|
-
if (section === "HISTORY") {
|
|
740
|
-
let updated = false;
|
|
741
|
-
const headers = ["HISTORY (Persistent Memory)", "HISTORY"];
|
|
742
|
-
for (const h of headers) {
|
|
743
|
-
try {
|
|
744
|
-
memoryContent = prependToSection(memoryContent, h, content);
|
|
745
|
-
updated = true;
|
|
746
|
-
break;
|
|
747
|
-
}
|
|
748
|
-
catch (e) {
|
|
749
|
-
// try next header
|
|
750
|
-
}
|
|
751
|
-
}
|
|
752
|
-
if (!updated)
|
|
753
|
-
throw new Error("HISTORY section not found.");
|
|
754
|
-
}
|
|
755
|
-
else if (section === "CURRENT STATUS") {
|
|
756
|
-
memoryContent = replaceSectionContent(memoryContent, "CURRENT STATUS", content);
|
|
757
|
-
}
|
|
758
|
-
else if (section === "ACTIVE TASKS") {
|
|
759
|
-
memoryContent = replaceSectionContent(memoryContent, "ACTIVE TASKS", content);
|
|
760
|
-
}
|
|
761
|
-
fs.writeFileSync(memoryPath, memoryContent);
|
|
762
|
-
if (fs.existsSync(lockPath)) {
|
|
763
|
-
const lockContent = fs.readFileSync(lockPath, "utf-8");
|
|
764
|
-
if (lockContent.includes(lockOwner)) {
|
|
765
|
-
fs.unlinkSync(lockPath);
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
return {
|
|
769
|
-
content: [
|
|
770
|
-
{ type: "text", text: `Section ${section} updated successfully.` },
|
|
771
|
-
],
|
|
772
|
-
};
|
|
773
|
-
}
|
|
774
|
-
catch (error) {
|
|
775
|
-
if (fs.existsSync(lockPath)) {
|
|
776
|
-
const lockContent = fs.readFileSync(lockPath, "utf-8");
|
|
777
|
-
if (lockContent.includes(lockOwner)) {
|
|
778
|
-
fs.unlinkSync(lockPath);
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
782
|
-
return {
|
|
783
|
-
content: [{ type: "text", text: `Memory update failed: ${message}` }],
|
|
784
|
-
};
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
case "verify_api_contract": {
|
|
788
|
-
try {
|
|
789
|
-
const sharedTypesDir = path.join(projectRoot, "packages/shared-types/src");
|
|
790
|
-
const contractJsonPath = path.join(projectRoot, "packages/shared-types/contract.version.json");
|
|
791
|
-
if (!fs.existsSync(sharedTypesDir) ||
|
|
792
|
-
!fs.existsSync(contractJsonPath)) {
|
|
793
|
-
return {
|
|
794
|
-
content: [
|
|
795
|
-
{
|
|
796
|
-
type: "text",
|
|
797
|
-
text: "Missing shared-types directory or contract.version.json",
|
|
798
|
-
},
|
|
799
|
-
],
|
|
800
|
-
};
|
|
801
|
-
}
|
|
802
|
-
const files = collectFilesRecursively(sharedTypesDir, new Set(["ts"]));
|
|
803
|
-
files.sort(); // Sort to ensure consistent hashing
|
|
804
|
-
const hash = crypto.createHash("sha256");
|
|
805
|
-
for (const file of files) {
|
|
806
|
-
const fileContent = fs.readFileSync(file);
|
|
807
|
-
hash.update(fileContent);
|
|
808
|
-
}
|
|
809
|
-
const currentHash = hash.digest("hex");
|
|
810
|
-
const contractJson = JSON.parse(fs.readFileSync(contractJsonPath, "utf-8"));
|
|
811
|
-
const storedHash = contractJson.contract_hash;
|
|
812
|
-
if (currentHash === storedHash) {
|
|
813
|
-
return {
|
|
814
|
-
content: [
|
|
815
|
-
{
|
|
816
|
-
type: "text",
|
|
817
|
-
text: "✅ MATCH: Contract is valid and synchronized.",
|
|
818
|
-
},
|
|
819
|
-
],
|
|
820
|
-
};
|
|
821
|
-
}
|
|
822
|
-
else {
|
|
823
|
-
return {
|
|
824
|
-
content: [
|
|
825
|
-
{
|
|
826
|
-
type: "text",
|
|
827
|
-
text: `❌ MISMATCH: Current hash (${currentHash.slice(0, 8)}...) does not match stored hash (${storedHash.slice(0, 8)}...).
|
|
828
|
-
Contract is invalid or out of date!
|
|
829
|
-
|
|
830
|
-
**How to fix:**
|
|
831
|
-
1. Review changes in 'packages/shared-types/src/'.
|
|
832
|
-
2. Run the MCP tool 'update_contract_hash' or use CLI:
|
|
833
|
-
\`npm run update-contract\` (if configured)`,
|
|
834
|
-
},
|
|
835
|
-
],
|
|
836
|
-
};
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
catch (error) {
|
|
840
|
-
return {
|
|
841
|
-
content: [
|
|
842
|
-
{
|
|
843
|
-
type: "text",
|
|
844
|
-
text: "Failed to verify contract: " +
|
|
845
|
-
(error instanceof Error ? error.message : String(error)),
|
|
846
|
-
},
|
|
847
|
-
],
|
|
848
|
-
};
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
|
-
case "update_contract_hash": {
|
|
852
|
-
try {
|
|
853
|
-
const sharedTypesDir = path.join(projectRoot, "packages/shared-types/src");
|
|
854
|
-
const contractJsonPath = path.join(projectRoot, "packages/shared-types/contract.version.json");
|
|
855
|
-
if (!fs.existsSync(sharedTypesDir)) {
|
|
856
|
-
return {
|
|
857
|
-
content: [{ type: "text", text: "Missing shared-types directory" }],
|
|
858
|
-
};
|
|
859
|
-
}
|
|
860
|
-
const files = collectFilesRecursively(sharedTypesDir, new Set(["ts"]));
|
|
861
|
-
if (files.length === 0) {
|
|
862
|
-
return {
|
|
863
|
-
content: [
|
|
864
|
-
{
|
|
865
|
-
type: "text",
|
|
866
|
-
text: "⚠️ WARNING: No TypeScript files found in shared-types/src. Hash not updated.",
|
|
867
|
-
},
|
|
868
|
-
],
|
|
869
|
-
};
|
|
870
|
-
}
|
|
871
|
-
files.sort();
|
|
872
|
-
const hash = crypto.createHash("sha256");
|
|
873
|
-
for (const file of files) {
|
|
874
|
-
const fileContent = fs.readFileSync(file);
|
|
875
|
-
hash.update(fileContent);
|
|
876
|
-
}
|
|
877
|
-
const currentHash = hash.digest("hex");
|
|
878
|
-
let contractJson = {};
|
|
879
|
-
if (fs.existsSync(contractJsonPath)) {
|
|
880
|
-
contractJson = JSON.parse(fs.readFileSync(contractJsonPath, "utf-8"));
|
|
881
|
-
}
|
|
882
|
-
contractJson.contract_hash = currentHash;
|
|
883
|
-
contractJson.last_updated = new Date().toISOString();
|
|
884
|
-
fs.writeFileSync(contractJsonPath, JSON.stringify(contractJson, null, 2));
|
|
885
|
-
return {
|
|
886
|
-
content: [
|
|
887
|
-
{
|
|
888
|
-
type: "text",
|
|
889
|
-
text: `SUCCESS: Contract hash updated to ${currentHash}`,
|
|
890
|
-
},
|
|
891
|
-
],
|
|
892
|
-
};
|
|
893
|
-
}
|
|
894
|
-
catch (error) {
|
|
895
|
-
return {
|
|
896
|
-
content: [
|
|
897
|
-
{
|
|
898
|
-
type: "text",
|
|
899
|
-
text: "Failed to update contract hash: " +
|
|
900
|
-
(error instanceof Error ? error.message : String(error)),
|
|
901
|
-
},
|
|
902
|
-
],
|
|
903
|
-
};
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
case "log_agent_action": {
|
|
907
|
-
const parsed = LOG_AGENT_ACTION_ARGS_SCHEMA.safeParse(args ?? {});
|
|
908
|
-
if (!parsed.success) {
|
|
909
|
-
return {
|
|
910
|
-
content: [
|
|
911
|
-
{ type: "text", text: "Invalid arguments for log_agent_action." },
|
|
912
|
-
],
|
|
913
|
-
};
|
|
914
|
-
}
|
|
915
|
-
try {
|
|
916
|
-
const frameworkDir = getFrameworkDir(projectRoot);
|
|
917
|
-
const logsDir = path.join(projectRoot, frameworkDir, "logs");
|
|
918
|
-
if (!fs.existsSync(logsDir)) {
|
|
919
|
-
fs.mkdirSync(logsDir, { recursive: true });
|
|
920
|
-
}
|
|
921
|
-
const logPath = path.join(logsDir, `${parsed.data.agent}.json`);
|
|
922
|
-
let logs = [];
|
|
923
|
-
if (fs.existsSync(logPath)) {
|
|
924
|
-
try {
|
|
925
|
-
logs = JSON.parse(fs.readFileSync(logPath, "utf-8"));
|
|
926
|
-
if (!Array.isArray(logs))
|
|
927
|
-
logs = [];
|
|
928
|
-
}
|
|
929
|
-
catch {
|
|
930
|
-
logs = [];
|
|
931
|
-
}
|
|
932
|
-
}
|
|
933
|
-
const requestId = parsed.data.requestId;
|
|
934
|
-
const ulidRegex = /^[0-9A-Z]{26}$/;
|
|
935
|
-
if (requestId !== "—" && !ulidRegex.test(requestId)) {
|
|
936
|
-
return {
|
|
937
|
-
content: [{ type: "text", text: "ERROR: requestId MUST be a 26-character ULID (or '—' for initialization)." }],
|
|
938
|
-
};
|
|
939
|
-
}
|
|
940
|
-
const newEntry = {
|
|
941
|
-
timestamp: new Date().toISOString(),
|
|
942
|
-
...parsed.data,
|
|
943
|
-
};
|
|
944
|
-
logs.push(newEntry);
|
|
945
|
-
fs.writeFileSync(logPath, JSON.stringify(logs, null, 2));
|
|
946
|
-
return {
|
|
947
|
-
content: [
|
|
948
|
-
{
|
|
949
|
-
type: "text",
|
|
950
|
-
text: `SUCCESS: Logged action to ${parsed.data.agent}.json`,
|
|
951
|
-
},
|
|
952
|
-
],
|
|
953
|
-
};
|
|
954
|
-
}
|
|
955
|
-
catch (error) {
|
|
956
|
-
return {
|
|
957
|
-
content: [
|
|
958
|
-
{
|
|
959
|
-
type: "text",
|
|
960
|
-
text: "Failed to log action: " +
|
|
961
|
-
(error instanceof Error ? error.message : String(error)),
|
|
962
|
-
},
|
|
963
|
-
],
|
|
964
|
-
};
|
|
965
|
-
}
|
|
966
|
-
}
|
|
967
|
-
case "get_system_time": {
|
|
968
|
-
return {
|
|
969
|
-
content: [{ type: "text", text: new Date().toISOString() }],
|
|
970
|
-
};
|
|
971
|
-
}
|
|
972
|
-
case "read_project_memory": {
|
|
973
|
-
try {
|
|
974
|
-
const frameworkDir = getFrameworkDir(projectRoot);
|
|
975
|
-
const memoryPath = path.join(projectRoot, frameworkDir, "PROJECT_MEMORY.md");
|
|
976
|
-
if (!fs.existsSync(memoryPath)) {
|
|
977
|
-
return { content: [{ type: "text", text: `ERROR: ${frameworkDir}/PROJECT_MEMORY.md not found.` }] };
|
|
978
|
-
}
|
|
979
|
-
const content = fs.readFileSync(memoryPath, "utf-8");
|
|
980
|
-
return { content: [{ type: "text", text: content }] };
|
|
981
|
-
}
|
|
982
|
-
catch (error) {
|
|
983
|
-
return { content: [{ type: "text", text: "ERROR: Failed to read PROJECT_MEMORY.md" }] };
|
|
984
|
-
}
|
|
985
|
-
}
|
|
986
|
-
default:
|
|
987
|
-
throw new Error(`Tool not found: ${name}`);
|
|
21
|
+
// Handle compatibility for context search
|
|
22
|
+
let effectiveArgs = args;
|
|
23
|
+
if (name === "codebase_context_search") {
|
|
24
|
+
effectiveArgs = { extension: "md", ...(args ?? {}) };
|
|
25
|
+
}
|
|
26
|
+
const handler = allHandlers[name];
|
|
27
|
+
if (handler) {
|
|
28
|
+
return await handler(effectiveArgs, projectRoot);
|
|
988
29
|
}
|
|
30
|
+
throw new Error(`Tool not found: ${name}`);
|
|
989
31
|
});
|
|
990
32
|
async function main() {
|
|
991
33
|
const transport = new StdioServerTransport();
|