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.
Files changed (103) hide show
  1. package/.enderun/BRAIN_DASHBOARD.md +43 -0
  2. package/.enderun/ENDERUN.md +203 -0
  3. package/.enderun/PROJECT_MEMORY.md +137 -36
  4. package/.enderun/agents/analyst.md +21 -10
  5. package/.enderun/agents/backend.md +12 -11
  6. package/.enderun/agents/explorer.md +10 -7
  7. package/.enderun/agents/frontend.md +9 -20
  8. package/.enderun/agents/git.md +16 -12
  9. package/.enderun/agents/manager.md +14 -15
  10. package/.enderun/agents/mobile.md +5 -5
  11. package/.enderun/agents/native.md +5 -5
  12. package/.enderun/benchmarks/.gitkeep +0 -0
  13. package/.enderun/cli-commands.json +13 -1
  14. package/.enderun/config.json +1 -1
  15. package/.enderun/docs/api/README.md +10 -9
  16. package/.enderun/docs/api/auth.md +11 -0
  17. package/.enderun/docs/api/errors.md +7 -0
  18. package/.enderun/docs/error-handling.md +12 -0
  19. package/.enderun/docs/privacy.md +3 -0
  20. package/.enderun/docs/security.md +12 -0
  21. package/.enderun/docs/tech-stack.md +1 -0
  22. package/.enderun/docs/troubleshooting.md +7 -0
  23. package/.enderun/knowledge/api_design_rules.md +6 -0
  24. package/.enderun/knowledge/async_error_handling.md +18 -0
  25. package/.enderun/knowledge/branded_types_pattern.md +1 -0
  26. package/.enderun/knowledge/code_review_checklist.md +7 -0
  27. package/.enderun/knowledge/contract_versioning.md +7 -0
  28. package/.enderun/knowledge/database_migration.md +6 -0
  29. package/.enderun/knowledge/deployment_checklist.md +7 -0
  30. package/.enderun/knowledge/git_commit_strategy.md +10 -0
  31. package/.enderun/knowledge/legacy_onboarding.md +7 -0
  32. package/.enderun/knowledge/monitoring_setup.md +5 -0
  33. package/.enderun/knowledge/performance_guidelines.md +11 -0
  34. package/.enderun/knowledge/repository_patterns.md +9 -0
  35. package/.enderun/knowledge/security_scanning.md +6 -0
  36. package/.enderun/knowledge/testing_standards.md +7 -0
  37. package/.enderun/knowledge/troubleshooting_guide.md +5 -0
  38. package/.enderun/knowledge/zero_ui_library_policy.md +1 -0
  39. package/.enderun/logs/analyst.json +1 -0
  40. package/.enderun/logs/backend.json +1 -0
  41. package/.enderun/logs/explorer.json +1 -0
  42. package/.enderun/logs/frontend.json +1 -0
  43. package/.enderun/logs/git.json +1 -0
  44. package/.enderun/logs/manager.json +363 -0
  45. package/.enderun/logs/mobile.json +1 -0
  46. package/.enderun/logs/native.json +1 -0
  47. package/.enderun/monitoring/.gitkeep +0 -0
  48. package/ENDERUN.md +8 -8
  49. package/LICENSE +21 -0
  50. package/README.md +595 -195
  51. package/bin/cli.js +292 -79
  52. package/package.json +42 -2
  53. package/packages/framework-mcp/README.md +47 -81
  54. package/packages/framework-mcp/dist/index.js +13 -971
  55. package/packages/framework-mcp/dist/schemas.js +84 -0
  56. package/packages/framework-mcp/dist/tools/academy.js +184 -0
  57. package/packages/framework-mcp/dist/tools/codebase.js +294 -0
  58. package/packages/framework-mcp/dist/tools/contract.js +95 -0
  59. package/packages/framework-mcp/dist/tools/database.js +52 -0
  60. package/packages/framework-mcp/dist/tools/framework.js +161 -0
  61. package/packages/framework-mcp/dist/tools/git.js +53 -0
  62. package/packages/framework-mcp/dist/tools/index.js +42 -0
  63. package/packages/framework-mcp/dist/tools/knowledge.js +69 -0
  64. package/packages/framework-mcp/dist/tools/memory.js +94 -0
  65. package/packages/framework-mcp/dist/tools/messages.js +71 -0
  66. package/packages/framework-mcp/dist/tools/repository.js +76 -0
  67. package/packages/framework-mcp/dist/tools/security.js +122 -0
  68. package/packages/framework-mcp/dist/utils.js +82 -0
  69. package/packages/framework-mcp/package.json +1 -1
  70. package/packages/framework-mcp/src/index.ts +20 -970
  71. package/packages/framework-mcp/src/schemas.ts +106 -0
  72. package/packages/framework-mcp/src/tools/academy.ts +178 -0
  73. package/packages/framework-mcp/src/tools/codebase.ts +284 -0
  74. package/packages/framework-mcp/src/tools/contract.ts +91 -0
  75. package/packages/framework-mcp/src/tools/database.ts +49 -0
  76. package/packages/framework-mcp/src/tools/framework.ts +157 -0
  77. package/packages/framework-mcp/src/tools/git.ts +43 -0
  78. package/packages/framework-mcp/src/tools/index.ts +45 -0
  79. package/packages/framework-mcp/src/tools/knowledge.ts +68 -0
  80. package/packages/framework-mcp/src/tools/memory.ts +88 -0
  81. package/packages/framework-mcp/src/tools/messages.ts +70 -0
  82. package/packages/framework-mcp/src/tools/repository.ts +76 -0
  83. package/packages/framework-mcp/src/tools/security.ts +122 -0
  84. package/packages/framework-mcp/src/utils.ts +90 -0
  85. package/packages/shared-types/README.md +28 -51
  86. package/packages/shared-types/dist/index.d.ts +80 -48
  87. package/packages/shared-types/dist/index.d.ts.map +1 -1
  88. package/packages/shared-types/dist/index.js +5 -8
  89. package/packages/shared-types/dist/index.js.map +1 -1
  90. package/packages/shared-types/package.json +1 -1
  91. package/packages/shared-types/src/index.ts +79 -51
  92. package/CHANGELOG.md +0 -97
  93. package/CLAUDE.md +0 -7
  94. package/CODEX.md +0 -7
  95. package/CURSOR.md +0 -7
  96. package/GEMINI.md +0 -7
  97. package/docs/tech-stack.md +0 -10
  98. package/gemini-extension.json +0 -5
  99. package/packages/framework-mcp/tsconfig.json +0 -15
  100. package/packages/shared-types/contract.version.json +0 -9
  101. package/packages/shared-types/tsconfig.json +0 -17
  102. package/panda.config.ts +0 -20
  103. /package/{docs → .enderun/docs}/project-docs.md +0 -0
@@ -0,0 +1,49 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { Project } from "ts-morph";
4
+ import { resolveSafePath } from "../utils.js";
5
+ import { ANALYZE_DATABASE_SCHEMA_ARGS_SCHEMA } from "../schemas.js";
6
+
7
+ export const databaseTools = [
8
+ {
9
+ name: "analyze_database_schema",
10
+ description: "Scans the backend codebase (e.g., Kysely migrations or Prisma schemas) to extract and visualize the database schema as a Mermaid ER diagram.",
11
+ inputSchema: {
12
+ type: "object",
13
+ properties: {
14
+ path: { type: "string", description: "Path to scan for DB definitions", default: "apps/backend" },
15
+ },
16
+ },
17
+ },
18
+ ];
19
+
20
+ export const databaseHandlers = {
21
+ analyze_database_schema: async (args: any, projectRoot: string) => {
22
+ const parsed = ANALYZE_DATABASE_SCHEMA_ARGS_SCHEMA.safeParse(args ?? {});
23
+ const targetPath = parsed.success ? parsed.data.path : "apps/backend";
24
+ try {
25
+ const safeTargetPath = resolveSafePath(projectRoot, targetPath);
26
+ if (!fs.existsSync(safeTargetPath)) return { content: [{ type: "text", text: "Database path not found." }] };
27
+ const tsProject = new Project();
28
+ tsProject.addSourceFilesAtPaths(path.join(safeTargetPath, "**/*.{ts,tsx}"));
29
+ let mermaid = "erDiagram\n", tablesFound = 0;
30
+ for (const sourceFile of tsProject.getSourceFiles()) {
31
+ const text = sourceFile.getFullText();
32
+ const tableMatches = Array.from(text.matchAll(/\.createTable\(['"]([^'"]+)['"]\)/g));
33
+ for (const match of tableMatches) {
34
+ const tableName = match[1]; tablesFound++;
35
+ mermaid += ` ${tableName} {\n`;
36
+ const colRegex = /\.addColumn\(['"]([^'"]+)['"]\s*,\s*['"]([^'"]+)['"]/g;
37
+ let colMatch; while ((colMatch = colRegex.exec(text)) !== null) {
38
+ mermaid += ` ${colMatch[2].replace(/[^a-zA-Z0-9]/g, '_')} ${colMatch[1]}\n`;
39
+ }
40
+ mermaid += ` }\n`;
41
+ }
42
+ }
43
+ if (tablesFound === 0) return { content: [{ type: "text", text: "No database tables detected." }] };
44
+ return { content: [{ type: "text", text: `### DATABASE SCHEMA MAP\n\n\`\`\`mermaid\n${mermaid}\n\`\`\`\n\n**Total Tables Detected:** ${tablesFound}` }] };
45
+ } catch (error) {
46
+ return { content: [{ type: "text", text: "Database schema analysis failed." }] };
47
+ }
48
+ },
49
+ };
@@ -0,0 +1,157 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import {
4
+ getFrameworkDir,
5
+ collectMarkdownArtifacts,
6
+ FRAMEWORK_VERSION
7
+ } from "../utils.js";
8
+
9
+ export const frameworkTools = [
10
+ {
11
+ name: "get_framework_status",
12
+ description: "Get the current status of the AI Agent Framework, including active phase and agent states.",
13
+ inputSchema: { type: "object", properties: {} },
14
+ },
15
+ {
16
+ name: "get_project_gaps",
17
+ description: "Scans the project structure against the defined standards in ENDERUN.md and identifies missing files, folders, or documentation.",
18
+ inputSchema: { type: "object", properties: {} },
19
+ },
20
+ {
21
+ name: "codebase_status",
22
+ description: "Compatibility alias for get_framework_status.",
23
+ inputSchema: { type: "object", properties: {} },
24
+ },
25
+ {
26
+ name: "get_memory_insights",
27
+ description: "Analyze PROJECT_MEMORY.md and BRAIN_DASHBOARD.md to provide insights on what was done, what is being done, and the history.",
28
+ inputSchema: { type: "object", properties: {} },
29
+ },
30
+ {
31
+ name: "codebase_context",
32
+ description: "Compatibility helper for non-code context discovery. Lists known markdown artifacts under docs/ and memory files.",
33
+ inputSchema: { type: "object", properties: {} },
34
+ },
35
+ {
36
+ name: "get_system_time",
37
+ description: "Get the current system time in ISO-8601 format (UTC). Use this instead of Shell 'date' commands.",
38
+ inputSchema: { type: "object", properties: {} },
39
+ },
40
+ {
41
+ name: "bootstrap_legacy_memory",
42
+ description: "Analyzes a pre-existing codebase to automatically generate the initial project memory, learning its tech stack and architecture.",
43
+ inputSchema: { type: "object", properties: {} },
44
+ },
45
+ ];
46
+
47
+ export const frameworkHandlers = {
48
+ bootstrap_legacy_memory: async (args: any, projectRoot: string) => {
49
+ try {
50
+ const frameworkDir = getFrameworkDir(projectRoot);
51
+ const memoryPath = path.join(projectRoot, frameworkDir, "PROJECT_MEMORY.md");
52
+ const pkgPath = path.join(projectRoot, "package.json");
53
+ let techStack = "Unknown";
54
+ if (fs.existsSync(pkgPath)) {
55
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
56
+ const allDeps = Object.keys(pkg.dependencies || {}).concat(Object.keys(pkg.devDependencies || {}));
57
+ const detected = [];
58
+ if (allDeps.includes("react")) detected.push("React");
59
+ if (allDeps.includes("next")) detected.push("Next.js");
60
+ if (allDeps.includes("typescript")) detected.push("TypeScript");
61
+ if (detected.length > 0) techStack = detected.join(", ");
62
+ }
63
+ const rootDirs = fs.readdirSync(projectRoot).filter(f => fs.lstatSync(path.join(projectRoot, f)).isDirectory() && !f.startsWith("."));
64
+ const isMonorepo = rootDirs.includes("apps") || rootDirs.includes("packages");
65
+ const date = new Date().toISOString().split("T")[0];
66
+ const bootstrapContent = `# PROJECT MEMORY — Legacy Onboarding\n\n## CURRENT STATUS\n\n| Active Phase | Profile | Last Update | Active Trace ID | Blockers |\n| PHASE_1 | Full | ${date} | — | Legacy Onboarding |`;
67
+ if (fs.existsSync(memoryPath)) fs.copyFileSync(memoryPath, memoryPath + ".bak");
68
+ fs.writeFileSync(memoryPath, bootstrapContent);
69
+ return { content: [{ type: "text", text: `Legacy Codebase Bootstrap complete. Detected Stack: ${techStack}.` }] };
70
+ } catch (error) {
71
+ return { content: [{ type: "text", text: "Failed to bootstrap legacy memory." }] };
72
+ }
73
+ },
74
+ get_framework_status: async (args: any, projectRoot: string) => {
75
+ try {
76
+ const frameworkDir = getFrameworkDir(projectRoot);
77
+ const memoryPath = path.join(projectRoot, frameworkDir, "PROJECT_MEMORY.md");
78
+ const memoryContent = fs.readFileSync(memoryPath, "utf-8");
79
+ const statusRowMatch = memoryContent.match(/\| Active Phase \| Profile \| Last Update \| Active Trace ID \| Blockers \|\n\| :----------- \| :------ \| :---------- \| :-------------- \| :------- \|\n\| ([^|]+) \| ([^|]+) \| ([^|]+) \| ([^|]+) \| ([^|]+) \|/);
80
+ const phase = statusRowMatch?.[1]?.trim() ?? "UNKNOWN";
81
+ const profile = statusRowMatch?.[2]?.trim() ?? "UNKNOWN";
82
+ return {
83
+ content: [{ type: "text", text: `Framework active (v${FRAMEWORK_VERSION}). Phase: ${phase}. Profile: ${profile}.` }],
84
+ };
85
+ } catch (error) {
86
+ return { content: [{ type: "text", text: "Framework active. Memory unreadable." }] };
87
+ }
88
+ },
89
+ get_project_gaps: async (args: any, projectRoot: string) => {
90
+ const missing: string[] = [];
91
+ const frameworkDir = getFrameworkDir(projectRoot);
92
+ const checkPaths = [
93
+ { path: "apps", type: "folder", optional: true },
94
+ { path: "packages/shared-types/src", type: "folder" },
95
+ { path: path.join(frameworkDir, "docs/api"), type: "folder", optional: true },
96
+ { path: ".env", type: "file", optional: true },
97
+ { path: ".env.example", type: "file" },
98
+ { path: path.join(frameworkDir, "PROJECT_MEMORY.md"), type: "file" },
99
+ { path: path.join(frameworkDir, "BRAIN_DASHBOARD.md"), type: "file" },
100
+ { path: "docs/tech-stack.md", type: "file" },
101
+ { path: "docs/project-docs.md", type: "file" },
102
+ ];
103
+ for (const item of checkPaths) {
104
+ const fullPath = path.join(projectRoot, item.path);
105
+ if (!fs.existsSync(fullPath) && !item.optional) {
106
+ missing.push(`[MISSING ${item.type.toUpperCase()}] ${item.path}`);
107
+ }
108
+ }
109
+ const agentsDir = path.join(projectRoot, frameworkDir, "agents");
110
+ if (!fs.existsSync(agentsDir)) {
111
+ missing.push(`[MISSING FOLDER] ${frameworkDir}/agents`);
112
+ } else {
113
+ const agents = fs.readdirSync(agentsDir).filter((f) => f.endsWith(".md")).map((f) => f.replace(".md", ""));
114
+ const logsDir = path.join(projectRoot, frameworkDir, "logs");
115
+ for (const agentName of agents) {
116
+ const logFile = path.join(logsDir, `${agentName}.json`);
117
+ if (!fs.existsSync(logFile)) {
118
+ missing.push(`[MISSING LOG FILE] ${frameworkDir}/logs/${agentName}.json (Expected for agent ${agentName})`);
119
+ }
120
+ }
121
+ }
122
+ return { content: [{ type: "text", text: missing.length > 0 ? `Detected Gaps:\n${missing.join("\n")}` : "No structural gaps detected based on core standards." }] };
123
+ },
124
+ get_memory_insights: async (args: any, projectRoot: string) => {
125
+ try {
126
+ const frameworkDir = getFrameworkDir(projectRoot);
127
+ const memoryPath = path.join(projectRoot, frameworkDir, "PROJECT_MEMORY.md");
128
+ const dashboardPath = path.join(projectRoot, frameworkDir, "BRAIN_DASHBOARD.md");
129
+ const memory = fs.existsSync(memoryPath) ? fs.readFileSync(memoryPath, "utf-8") : "Memory file missing.";
130
+ const dashboard = fs.existsSync(dashboardPath) ? fs.readFileSync(dashboardPath, "utf-8") : "Dashboard file missing.";
131
+ const history = memory.split("## HISTORY")[1] || "No history found.";
132
+ const activeTasks = memory.split("## ACTIVE TASKS")[1]?.split("##")[0] || "No active tasks.";
133
+ const dashboardAgents = dashboard.split("## 📈 Visualizations")[0] || dashboard;
134
+ return {
135
+ content: [{ type: "text", 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)}...` }],
136
+ };
137
+ } catch (error) {
138
+ return { content: [{ type: "text", text: "Failed to gather memory insights." }] };
139
+ }
140
+ },
141
+ codebase_context: async (args: any, projectRoot: string) => {
142
+ try {
143
+ const artifacts = collectMarkdownArtifacts(projectRoot);
144
+ const frameworkDir = getFrameworkDir(projectRoot);
145
+ const memoryPath = path.join(projectRoot, frameworkDir, "PROJECT_MEMORY.md");
146
+ const dashboardPath = path.join(projectRoot, frameworkDir, "BRAIN_DASHBOARD.md");
147
+ return {
148
+ content: [{ type: "text", text: "### CONTEXT ARTIFACTS\n\n" + `PROJECT_MEMORY: ${fs.existsSync(memoryPath) ? "present" : "missing"}\n` + `BRAIN_DASHBOARD: ${fs.existsSync(dashboardPath) ? "present" : "missing"}\n` + `Docs:\n${artifacts.length > 0 ? artifacts.join("\n") : "No markdown artifacts found."}` }],
149
+ };
150
+ } catch (error) {
151
+ return { content: [{ type: "text", text: "Context discovery failed." }] };
152
+ }
153
+ },
154
+ get_system_time: async () => {
155
+ return { content: [{ type: "text", text: new Date().toISOString() }] };
156
+ },
157
+ };
@@ -0,0 +1,43 @@
1
+ import path from "path";
2
+ import { execSync } from "child_process";
3
+ import { GENERATE_SEMANTIC_COMMIT_MESSAGE_ARGS_SCHEMA } from "../schemas.js";
4
+
5
+ export const gitTools = [
6
+ {
7
+ name: "generate_semantic_commit_message",
8
+ description: "Analyzes staged git changes and generates a structured semantic commit message based on project standards.",
9
+ inputSchema: {
10
+ type: "object",
11
+ properties: {
12
+ traceId: { type: "string", description: "The active Trace ID (ULID)" },
13
+ },
14
+ required: ["traceId"],
15
+ },
16
+ },
17
+ ];
18
+
19
+ export const gitHandlers = {
20
+ generate_semantic_commit_message: async (args: any, projectRoot: string) => {
21
+ const parsed = GENERATE_SEMANTIC_COMMIT_MESSAGE_ARGS_SCHEMA.safeParse(args ?? {});
22
+ if (!parsed.success) return { content: [{ type: "text", text: "Invalid traceId argument." }] };
23
+ try {
24
+ const diff = execSync("git diff --staged", { encoding: "utf-8", cwd: projectRoot });
25
+ if (!diff) return { content: [{ type: "text", text: "No staged changes found." }] };
26
+ const files = execSync("git diff --staged --name-only", { encoding: "utf-8", cwd: projectRoot }).split("\n").filter(Boolean);
27
+ let type = "feat", scope = "code";
28
+ if (files.some(f => f.includes(".md"))) type = "docs";
29
+ else if (files.some(f => f.includes("shared-types"))) type = "arch";
30
+ else if (files.some(f => f.includes("framework-mcp"))) type = "chore";
31
+ else if (files.some(f => f.includes("bin/cli.js"))) type = "fix";
32
+ else if (files.some(f => f.includes("test"))) type = "test";
33
+ if (files.some(f => f.includes("apps/web"))) scope = "web";
34
+ else if (files.some(f => f.includes("apps/backend"))) scope = "backend";
35
+ else if (files.some(f => f.includes("packages/shared-types"))) scope = "contract";
36
+ else if (files.some(f => f.includes("packages/framework-mcp"))) scope = "mcp";
37
+ const summary = files.length === 1 ? `update ${path.basename(files[0])}` : `update ${files.length} files`;
38
+ return { content: [{ type: "text", text: `### SUGGESTED SEMANTIC COMMIT MESSAGE\n\n\`[${parsed.data.traceId}] ${type}(${scope}): ${summary}\`\n\n**Files Analyzed:**\n${files.map(f => `- ${f}`).join("\n")}` }] };
39
+ } catch (error) {
40
+ return { content: [{ type: "text", text: "Failed to generate commit message." }] };
41
+ }
42
+ },
43
+ };
@@ -0,0 +1,45 @@
1
+ import { codebaseTools, codebaseHandlers } from "./codebase.js";
2
+ import { frameworkTools, frameworkHandlers } from "./framework.js";
3
+ import { securityTools, securityHandlers } from "./security.js";
4
+ import { memoryTools, memoryHandlers } from "./memory.js";
5
+ import { contractTools, contractHandlers } from "./contract.js";
6
+ import { academyTools, academyHandlers } from "./academy.js";
7
+ import { messageTools, messageHandlers } from "./messages.js";
8
+ import { gitTools, gitHandlers } from "./git.js";
9
+ import { databaseTools, databaseHandlers } from "./database.js";
10
+ import { knowledgeTools, knowledgeHandlers } from "./knowledge.js";
11
+ import { repositoryTools, repositoryHandlers } from "./repository.js";
12
+
13
+ export const allTools = [
14
+ ...codebaseTools,
15
+ ...frameworkTools,
16
+ ...securityTools,
17
+ ...memoryTools,
18
+ ...contractTools,
19
+ ...academyTools,
20
+ ...messageTools,
21
+ ...gitTools,
22
+ ...databaseTools,
23
+ ...knowledgeTools,
24
+ ...repositoryTools,
25
+ ];
26
+
27
+ export const allHandlers: Record<string, (args: any, projectRoot: string) => Promise<any>> = {
28
+ ...codebaseHandlers,
29
+ ...frameworkHandlers,
30
+ ...securityHandlers,
31
+ ...memoryHandlers,
32
+ ...contractHandlers,
33
+ ...academyHandlers,
34
+ ...messageHandlers,
35
+ ...gitHandlers,
36
+ ...databaseHandlers,
37
+ ...knowledgeHandlers,
38
+ ...repositoryHandlers,
39
+ };
40
+
41
+ // Add compatibility aliases
42
+ allHandlers.codebase_search = allHandlers.search_codebase;
43
+ allHandlers.codebase_graph_query = allHandlers.analyze_dependencies;
44
+ allHandlers.codebase_status = allHandlers.get_framework_status;
45
+ allHandlers.codebase_context_search = allHandlers.search_codebase; // Modified in original but search_codebase handles extension
@@ -0,0 +1,68 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { getFrameworkDir } from "../utils.js";
4
+ import {
5
+ SEARCH_KNOWLEDGE_BASE_ARGS_SCHEMA,
6
+ UPDATE_KNOWLEDGE_BASE_ARGS_SCHEMA
7
+ } from "../schemas.js";
8
+
9
+ export const knowledgeTools = [
10
+ {
11
+ name: "search_knowledge_base",
12
+ description: "Searches the Academy's internal knowledge base for architectural patterns, troubleshooting guides, and FAQs.",
13
+ inputSchema: {
14
+ type: "object",
15
+ properties: {
16
+ query: { type: "string", description: "Search query or topic" },
17
+ },
18
+ required: ["query"],
19
+ },
20
+ },
21
+ {
22
+ name: "update_knowledge_base",
23
+ description: "Adds or updates an entry in the Academy's internal knowledge base.",
24
+ inputSchema: {
25
+ type: "object",
26
+ properties: {
27
+ topic: { type: "string", description: "The topic or title of the entry" },
28
+ content: { type: "string", description: "The technical content or guide" },
29
+ },
30
+ required: ["topic", "content"],
31
+ },
32
+ },
33
+ ];
34
+
35
+ export const knowledgeHandlers = {
36
+ search_knowledge_base: async (args: any, projectRoot: string) => {
37
+ const parsed = SEARCH_KNOWLEDGE_BASE_ARGS_SCHEMA.safeParse(args ?? {});
38
+ if (!parsed.success) return { content: [{ type: "text", text: "Invalid search query." }] };
39
+ try {
40
+ const frameworkDir = getFrameworkDir(projectRoot);
41
+ const kbDir = path.join(projectRoot, frameworkDir, "knowledge");
42
+ if (!fs.existsSync(kbDir)) return { content: [{ type: "text", text: "Knowledge base is empty." }] };
43
+ const results = fs.readdirSync(kbDir).filter(f => f.endsWith(".md")).map(file => {
44
+ const content = fs.readFileSync(path.join(kbDir, file), "utf-8");
45
+ if (content.toLowerCase().includes(parsed.data.query.toLowerCase()) || file.toLowerCase().includes(parsed.data.query.toLowerCase())) {
46
+ return `### ${file.replace(".md", "")}\n\n${content.slice(0, 300)}...`;
47
+ }
48
+ return null;
49
+ }).filter(Boolean);
50
+ return { content: [{ type: "text", text: results.length > 0 ? `### KNOWLEDGE BASE SEARCH RESULTS\n\n${results.join("\n\n---\n\n")}` : "No matching knowledge base entries found." }] };
51
+ } catch (error) {
52
+ return { content: [{ type: "text", text: "Knowledge base search failed." }] };
53
+ }
54
+ },
55
+ update_knowledge_base: async (args: any, projectRoot: string) => {
56
+ const parsed = UPDATE_KNOWLEDGE_BASE_ARGS_SCHEMA.safeParse(args ?? {});
57
+ if (!parsed.success) return { content: [{ type: "text", text: "Invalid topic or content." }] };
58
+ try {
59
+ const frameworkDir = getFrameworkDir(projectRoot);
60
+ const kbDir = path.join(projectRoot, frameworkDir, "knowledge");
61
+ if (!fs.existsSync(kbDir)) fs.mkdirSync(kbDir, { recursive: true });
62
+ fs.writeFileSync(path.join(kbDir, parsed.data.topic.replace(/[^a-z0-9]/gi, "_").toLowerCase() + ".md"), parsed.data.content);
63
+ return { content: [{ type: "text", text: `Knowledge base updated: ${parsed.data.topic}` }] };
64
+ } catch (error) {
65
+ return { content: [{ type: "text", text: "Failed to update knowledge base." }] };
66
+ }
67
+ },
68
+ };
@@ -0,0 +1,88 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import {
4
+ getFrameworkDir,
5
+ prependToSection,
6
+ replaceSectionContent
7
+ } from "../utils.js";
8
+ import { UPDATE_MEMORY_ARGS_SCHEMA } from "../schemas.js";
9
+
10
+ export const memoryTools = [
11
+ {
12
+ name: "update_project_memory",
13
+ description: "Update a specific section of PROJECT_MEMORY.md (CURRENT STATUS, HISTORY, or ACTIVE TASKS) with new content.",
14
+ inputSchema: {
15
+ type: "object",
16
+ properties: {
17
+ section: {
18
+ type: "string",
19
+ enum: ["CURRENT STATUS", "HISTORY", "ACTIVE TASKS"],
20
+ description: "The section to update.",
21
+ },
22
+ content: {
23
+ type: "string",
24
+ description: "The new content to append or replace in that section.",
25
+ },
26
+ },
27
+ required: ["section", "content"],
28
+ },
29
+ },
30
+ {
31
+ name: "read_project_memory",
32
+ description: "Read the entire content of PROJECT_MEMORY.md safely. Use this instead of direct ReadFile tools to ensure framework compatibility.",
33
+ inputSchema: { type: "object", properties: {} },
34
+ },
35
+ ];
36
+
37
+ export const memoryHandlers = {
38
+ update_project_memory: async (args: any, projectRoot: string) => {
39
+ const parsed = UPDATE_MEMORY_ARGS_SCHEMA.safeParse(args ?? {});
40
+ if (!parsed.success) return { content: [{ type: "text", text: "Invalid section or content." }] };
41
+ const { section, content } = parsed.data;
42
+ const frameworkDir = getFrameworkDir(projectRoot);
43
+ const memoryPath = path.join(projectRoot, frameworkDir, "PROJECT_MEMORY.md");
44
+ const lockPath = memoryPath + ".lock";
45
+ const lockOwner = `lock-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
46
+ try {
47
+ if (fs.existsSync(lockPath)) {
48
+ const stats = fs.statSync(lockPath);
49
+ if (Date.now() - stats.mtimeMs > 120000) fs.unlinkSync(lockPath);
50
+ else return { content: [{ type: "text", text: "Memory is locked. Try again later." }] };
51
+ }
52
+ fs.writeFileSync(lockPath, JSON.stringify({ owner: lockOwner, createdAt: new Date().toISOString() }));
53
+ let memoryContent = fs.readFileSync(memoryPath, "utf-8");
54
+ if (section === "HISTORY") {
55
+ let updated = false;
56
+ const headers = ["HISTORY (Persistent Memory)", "HISTORY"];
57
+ for (const h of headers) {
58
+ try {
59
+ memoryContent = prependToSection(memoryContent, h, content);
60
+ updated = true;
61
+ break;
62
+ } catch (e) {}
63
+ }
64
+ if (!updated) throw new Error("HISTORY section not found.");
65
+ } else if (section === "CURRENT STATUS") {
66
+ memoryContent = replaceSectionContent(memoryContent, "CURRENT STATUS", content);
67
+ } else if (section === "ACTIVE TASKS") {
68
+ memoryContent = replaceSectionContent(memoryContent, "ACTIVE TASKS", content);
69
+ }
70
+ fs.writeFileSync(memoryPath, memoryContent);
71
+ if (fs.existsSync(lockPath) && fs.readFileSync(lockPath, "utf-8").includes(lockOwner)) fs.unlinkSync(lockPath);
72
+ return { content: [{ type: "text", text: `Section ${section} updated successfully.` }] };
73
+ } catch (error) {
74
+ if (fs.existsSync(lockPath) && fs.readFileSync(lockPath, "utf-8").includes(lockOwner)) fs.unlinkSync(lockPath);
75
+ return { content: [{ type: "text", text: `Memory update failed: ${error instanceof Error ? error.message : "Unknown error"}` }] };
76
+ }
77
+ },
78
+ read_project_memory: async (args: any, projectRoot: string) => {
79
+ try {
80
+ const frameworkDir = getFrameworkDir(projectRoot);
81
+ const memoryPath = path.join(projectRoot, frameworkDir, "PROJECT_MEMORY.md");
82
+ if (!fs.existsSync(memoryPath)) return { content: [{ type: "text", text: `ERROR: ${frameworkDir}/PROJECT_MEMORY.md not found.` }] };
83
+ return { content: [{ type: "text", text: fs.readFileSync(memoryPath, "utf-8") }] };
84
+ } catch (error) {
85
+ return { content: [{ type: "text", text: "ERROR: Failed to read PROJECT_MEMORY.md" }] };
86
+ }
87
+ },
88
+ };
@@ -0,0 +1,70 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { getFrameworkDir } from "../utils.js";
4
+ import {
5
+ SEND_AGENT_MESSAGE_ARGS_SCHEMA,
6
+ READ_AGENT_MESSAGES_ARGS_SCHEMA
7
+ } from "../schemas.js";
8
+
9
+ export const messageTools = [
10
+ {
11
+ name: "send_agent_message",
12
+ description: "Sends a message to another specialized agent. Useful for collaboration and delegation.",
13
+ inputSchema: {
14
+ type: "object",
15
+ properties: {
16
+ to: { type: "string", description: "Recipient agent name" },
17
+ message: { type: "string", description: "The message content" },
18
+ traceId: { type: "string", description: "The active Trace ID" },
19
+ },
20
+ required: ["to", "message", "traceId"],
21
+ },
22
+ },
23
+ {
24
+ name: "read_agent_messages",
25
+ description: "Reads messages sent to the current agent.",
26
+ inputSchema: {
27
+ type: "object",
28
+ properties: {
29
+ agent: { type: "string", description: "Current agent name reading their messages" },
30
+ },
31
+ required: ["agent"],
32
+ },
33
+ },
34
+ ];
35
+
36
+ export const messageHandlers = {
37
+ send_agent_message: async (args: any, projectRoot: string) => {
38
+ const parsed = SEND_AGENT_MESSAGE_ARGS_SCHEMA.safeParse(args ?? {});
39
+ if (!parsed.success) return { content: [{ type: "text", text: "Invalid message arguments." }] };
40
+ try {
41
+ const frameworkDir = getFrameworkDir(projectRoot);
42
+ const recipient = parsed.data.to.replace(/^@/, "");
43
+ const messagesDir = path.join(projectRoot, frameworkDir, "messages");
44
+ if (!fs.existsSync(messagesDir)) fs.mkdirSync(messagesDir, { recursive: true });
45
+ const messagePath = path.join(messagesDir, `${recipient}.json`);
46
+ let messages = fs.existsSync(messagePath) ? JSON.parse(fs.readFileSync(messagePath, "utf-8")) : [];
47
+ messages.push({ timestamp: new Date().toISOString(), from: "manager", traceId: parsed.data.traceId, content: parsed.data.message, read: false });
48
+ fs.writeFileSync(messagePath, JSON.stringify(messages, null, 2));
49
+ return { content: [{ type: "text", text: `Message sent to @${recipient}.` }] };
50
+ } catch (error) {
51
+ return { content: [{ type: "text", text: "Failed to send message." }] };
52
+ }
53
+ },
54
+ read_agent_messages: async (args: any, projectRoot: string) => {
55
+ const parsed = READ_AGENT_MESSAGES_ARGS_SCHEMA.safeParse(args ?? {});
56
+ if (!parsed.success) return { content: [{ type: "text", text: "Invalid agent name." }] };
57
+ try {
58
+ const frameworkDir = getFrameworkDir(projectRoot);
59
+ const agentName = parsed.data.agent.replace(/^@/, "");
60
+ const messagePath = path.join(projectRoot, frameworkDir, "messages", `${agentName}.json`);
61
+ if (!fs.existsSync(messagePath)) return { content: [{ type: "text", text: "No messages found." }] };
62
+ const messages = JSON.parse(fs.readFileSync(messagePath, "utf-8"));
63
+ const unread = messages.filter((m: any) => !m.read);
64
+ fs.writeFileSync(messagePath, JSON.stringify(messages.map((m: any) => ({ ...m, read: true })), null, 2));
65
+ return { content: [{ type: "text", text: unread.length === 0 ? "No new messages." : `### INBOX: @${agentName}\n\n` + unread.map((m: any) => `- **From:** ${m.from}\n **Message:** ${m.content}`).join("\n\n") }] };
66
+ } catch (error) {
67
+ return { content: [{ type: "text", text: "Failed to read messages." }] };
68
+ }
69
+ },
70
+ };
@@ -0,0 +1,76 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { execSync } from "child_process";
4
+ import { Project } from "ts-morph";
5
+ import {
6
+ resolveSafePath,
7
+ getFrameworkDir
8
+ } from "../utils.js";
9
+ import {
10
+ VALIDATE_REPOSITORY_HEALTH_ARGS_SCHEMA,
11
+ ANALYZE_DOCUMENTATION_DEBT_ARGS_SCHEMA
12
+ } from "../schemas.js";
13
+
14
+ export const repositoryTools = [
15
+ {
16
+ name: "validate_repository_health",
17
+ description: "Runs project health checks (lint, test, build) before critical actions like commits.",
18
+ inputSchema: {
19
+ type: "object",
20
+ properties: {
21
+ scope: { type: "string", enum: ["full", "lint", "test", "build"], default: "full" },
22
+ },
23
+ },
24
+ },
25
+ {
26
+ name: "analyze_documentation_debt",
27
+ description: "Scans the codebase for missing documentation, including JSDoc, READMEs, and API contracts.",
28
+ inputSchema: {
29
+ type: "object",
30
+ properties: {
31
+ path: { type: "string", description: "Path to scan (relative to project root)", default: "." },
32
+ },
33
+ },
34
+ },
35
+ ];
36
+
37
+ export const repositoryHandlers = {
38
+ validate_repository_health: async (args: any, projectRoot: string) => {
39
+ const parsed = VALIDATE_REPOSITORY_HEALTH_ARGS_SCHEMA.safeParse(args ?? {});
40
+ const scope = parsed.success ? parsed.data.scope : "full";
41
+ try {
42
+ const pkgPath = path.join(projectRoot, "package.json");
43
+ if (!fs.existsSync(pkgPath)) return { content: [{ type: "text", text: "package.json not found." }] };
44
+ const scripts = JSON.parse(fs.readFileSync(pkgPath, "utf-8")).scripts || {};
45
+ const runScript = (name: string) => scripts[name] ? { name, status: (execSync(`npm run ${name}`, { stdio: "pipe", cwd: projectRoot }) ? "PASSED" : "FAILED") } : { name, status: "SKIPPED" };
46
+ const results = [];
47
+ if (scope === "full" || scope === "lint") results.push(runScript("lint"));
48
+ if (scope === "full" || scope === "test") results.push(runScript("test"));
49
+ if (scope === "full" || scope === "build") results.push(runScript("build"));
50
+ return { content: [{ type: "text", text: `### REPOSITORY HEALTH REPORT\n\n` + results.map(r => `- **${r.name.toUpperCase()}:** ${r.status}`).join("\n") }] };
51
+ } catch (error) {
52
+ return { content: [{ type: "text", text: "Health validation failed." }] };
53
+ }
54
+ },
55
+ analyze_documentation_debt: async (args: any, projectRoot: string) => {
56
+ const parsed = ANALYZE_DOCUMENTATION_DEBT_ARGS_SCHEMA.safeParse(args ?? {});
57
+ const targetPath = parsed.success ? parsed.data.path : ".";
58
+ try {
59
+ const safeTargetPath = resolveSafePath(projectRoot, targetPath);
60
+ const tsProject = new Project();
61
+ tsProject.addSourceFilesAtPaths(path.join(safeTargetPath, "**/*.{ts,tsx}"));
62
+ const missingJSDoc: string[] = [];
63
+ for (const sourceFile of tsProject.getSourceFiles()) {
64
+ const relativePath = path.relative(projectRoot, sourceFile.getFilePath());
65
+ if (relativePath.includes("node_modules") || relativePath.includes("dist")) continue;
66
+ sourceFile.getExportedDeclarations().forEach((declarations, name) => {
67
+ declarations.forEach(decl => { if (typeof (decl as any).getJsDocs === "function" && (decl as any).getJsDocs().length === 0) missingJSDoc.push(`${relativePath} -> ${name}`); });
68
+ });
69
+ }
70
+ const majorDirs = ["apps/backend", "apps/web", "packages/shared-types", "packages/framework-mcp"], missingREADME = majorDirs.filter(dir => fs.existsSync(path.join(projectRoot, dir)) && !fs.existsSync(path.join(projectRoot, dir, "README.md")));
71
+ return { content: [{ type: "text", text: `### DOCUMENTATION DEBT REPORT\n\n**Missing JSDoc:**\n${missingJSDoc.slice(0, 10).join("\n")}\n\n**Missing README.md:**\n${missingREADME.join("\n")}` }] };
72
+ } catch (error) {
73
+ return { content: [{ type: "text", text: "Documentation debt scan failed." }] };
74
+ }
75
+ },
76
+ };