agent-enderun 0.6.0 → 0.6.5
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/PROJECT_MEMORY.md +117 -3
- package/.enderun/STATUS.md +4 -4
- package/.enderun/agents/analyst.md +98 -12
- package/.enderun/agents/backend.md +258 -14
- package/.enderun/agents/explorer.md +67 -2
- package/.enderun/agents/frontend.md +404 -66
- package/.enderun/agents/git.md +19 -2
- package/.enderun/agents/manager.md +412 -45
- package/.enderun/agents/mobile.md +63 -19
- package/.enderun/agents/native.md +61 -11
- package/.enderun/blueprints/backend/errors/blueprint.json +11 -0
- package/.enderun/blueprints/backend/errors/domain-error.ts +34 -55
- package/.enderun/docs/tech-stack.md +7 -8
- package/.enderun/knowledge/contract_versioning.md +1 -1
- package/.enderun/knowledge/framework_vs_user_project_boundary.md +52 -0
- package/.enderun/knowledge/frontend_professionalization_guidelines.md +108 -0
- package/.enderun/knowledge/hermes_live_test_guidelines.md +90 -0
- package/.enderun/knowledge/manager_authority_audit_enforcement.md +104 -0
- package/.enderun/knowledge/project_scaffold_guidelines.md +99 -0
- package/.enderun/knowledge/reference_application_guidelines.md +90 -0
- package/.enderun/messages/2026-05-23-backend-to-manager-auth-errors-reply.json +23 -0
- package/.enderun/messages/2026-05-23-manager-to-backend-auth-errors.json +26 -0
- package/ENDERUN.md +12 -4
- package/README.md +185 -626
- package/antigravity.md +15 -0
- package/bin/cli.js +105 -30
- package/bin/update-contract.js +2 -2
- package/docs/README.md +33 -3
- package/docs/getting-started.md +497 -0
- package/docs/roadmap.md +200 -0
- package/framework-mcp/README.md +1 -1
- package/framework-mcp/dist/index.js +0 -0
- package/framework-mcp/dist/tools/academy.js +6 -8
- package/framework-mcp/dist/tools/codebase.js +6 -6
- package/framework-mcp/dist/tools/contract.js +94 -4
- package/framework-mcp/dist/tools/database.js +1 -1
- package/framework-mcp/dist/tools/framework.js +8 -12
- package/framework-mcp/dist/tools/git.js +2 -2
- package/framework-mcp/dist/tools/knowledge.js +4 -4
- package/framework-mcp/dist/tools/memory.js +5 -7
- package/framework-mcp/dist/tools/messages.js +6 -6
- package/framework-mcp/dist/tools/repository.js +4 -4
- package/framework-mcp/dist/tools/scaffold.js +6 -3
- package/framework-mcp/dist/tools/security.js +4 -4
- package/framework-mcp/dist/utils.js +2 -2
- package/framework-mcp/package.json +1 -1
- package/framework-mcp/src/schemas.ts +7 -0
- package/framework-mcp/src/tools/academy.ts +5 -9
- package/framework-mcp/src/tools/codebase.ts +6 -6
- package/framework-mcp/src/tools/contract.ts +114 -4
- package/framework-mcp/src/tools/database.ts +1 -1
- package/framework-mcp/src/tools/framework.ts +9 -12
- package/framework-mcp/src/tools/git.ts +2 -2
- package/framework-mcp/src/tools/knowledge.ts +5 -6
- package/framework-mcp/src/tools/memory.ts +5 -6
- package/framework-mcp/src/tools/messages.ts +94 -28
- package/framework-mcp/src/tools/repository.ts +5 -6
- package/framework-mcp/src/tools/scaffold.ts +9 -6
- package/framework-mcp/src/tools/security.ts +4 -4
- package/framework-mcp/src/utils.ts +2 -2
- package/gemini.md +4 -4
- package/package.json +11 -7
- package/panda.config.ts +3 -1
- package/.enderun/benchmarks/.gitkeep +0 -0
- package/.enderun/blueprints/backend/middleware/error-handler.ts +0 -24
- package/.enderun/blueprints/backend/types/api.ts +0 -20
- package/.enderun/blueprints/backend/types/brands.ts +0 -12
- package/.enderun/blueprints/backend/types/constants.ts +0 -34
- package/.enderun/blueprints/backend/types/index.ts +0 -49
- package/.enderun/blueprints/backend/types/logs.ts +0 -16
- package/.enderun/blueprints/backend/types/models.ts +0 -65
- package/.enderun/blueprints/frontend/ui/Button.tsx +0 -60
- package/.enderun/blueprints/frontend/ui/Input.tsx +0 -43
- package/.enderun/monitoring/.gitkeep +0 -0
- package/gemini-extension.json +0 -13
|
@@ -145,7 +145,7 @@ export const codebaseHandlers = {
|
|
|
145
145
|
let queryRegex: RegExp;
|
|
146
146
|
try {
|
|
147
147
|
queryRegex = new RegExp(query, "i");
|
|
148
|
-
} catch
|
|
148
|
+
} catch {
|
|
149
149
|
return { content: [{ type: "text", text: "Invalid regex pattern in query." }] };
|
|
150
150
|
}
|
|
151
151
|
const matches: string[] = [];
|
|
@@ -164,7 +164,7 @@ export const codebaseHandlers = {
|
|
|
164
164
|
}
|
|
165
165
|
}
|
|
166
166
|
return { content: [{ type: "text", text: matches.join("\n") || "No matches found." }] };
|
|
167
|
-
} catch
|
|
167
|
+
} catch {
|
|
168
168
|
return { content: [{ type: "text", text: "Search failed." }] };
|
|
169
169
|
}
|
|
170
170
|
},
|
|
@@ -223,7 +223,7 @@ export const codebaseHandlers = {
|
|
|
223
223
|
});
|
|
224
224
|
}
|
|
225
225
|
return { content: [{ type: "text", text: `### CODEBASE INTELLIGENCE REPORT\n\n**Complexity Spikes (>300 lines):**\n${complexitySpikes.length > 0 ? complexitySpikes.map(s => `- ${s}`).join("\n") : "- None detected"}\n\n**Potential Dead Code (Unused Exports):**\n${potentialDeadCode.length > 0 ? potentialDeadCode.map(d => `- ${d}`).join("\n") : "- None detected (within scanned scope)"}` }] };
|
|
226
|
-
} catch
|
|
226
|
+
} catch {
|
|
227
227
|
return { content: [{ type: "text", text: "Intelligence scan failed." }] };
|
|
228
228
|
}
|
|
229
229
|
},
|
|
@@ -247,8 +247,8 @@ export const codebaseHandlers = {
|
|
|
247
247
|
const referenceFunctions = referenceFile.getFunctions().map(f => f.getName());
|
|
248
248
|
if (referenceFunctions.some(f => f?.startsWith("get")) && !targetFunctions.some(f => f?.startsWith("get"))) deviations.push("Reference uses 'get' prefix for functions, target does not.");
|
|
249
249
|
return { content: [{ type: "text", text: deviations.length > 0 ? `### PROCEDURAL CONTINUITY DEVIATIONS\n\n${deviations.map(d => `- ${d}`).join("\n")}` : "No major structural or stylistic deviations detected based on reference." }] };
|
|
250
|
-
} catch
|
|
251
|
-
return { content: [{ type: "text", text: "
|
|
250
|
+
} catch {
|
|
251
|
+
return { content: [{ type: "text", text: "Dependency analysis failed." }] };
|
|
252
252
|
}
|
|
253
253
|
},
|
|
254
254
|
generate_dependency_graph: async (args: unknown, projectRoot: string) => {
|
|
@@ -277,7 +277,7 @@ export const codebaseHandlers = {
|
|
|
277
277
|
if (uniqueEdges.length === 0) return { content: [{ type: "text", text: "No internal dependencies found in the scanned path." }] };
|
|
278
278
|
mermaid += uniqueEdges.join("\n");
|
|
279
279
|
return { content: [{ type: "text", text: `### VISUAL ARCHITECTURE MAP: ${targetPath}\n\n\`\`\`mermaid\n${mermaid}\n\`\`\`\n\n**Total Nodes:** ${new Set(edges.flatMap(e => [e.from, e.to])).size} | **Total Edges:** ${uniqueEdges.length}` }] };
|
|
280
|
-
} catch
|
|
280
|
+
} catch {
|
|
281
281
|
return { content: [{ type: "text", text: "Dependency graph generation failed." }] };
|
|
282
282
|
}
|
|
283
283
|
},
|
|
@@ -45,6 +45,23 @@ export const contractTools = [
|
|
|
45
45
|
required: ["domain"],
|
|
46
46
|
},
|
|
47
47
|
},
|
|
48
|
+
{
|
|
49
|
+
name: "validate_frontend_api_contract",
|
|
50
|
+
description: "Scans frontend hooks for API calls and compares them against backend API documentation and contracts. Returns mismatches (missing endpoints, type issues, etc.).",
|
|
51
|
+
inputSchema: {
|
|
52
|
+
type: "object",
|
|
53
|
+
properties: {
|
|
54
|
+
frontendHooksPath: {
|
|
55
|
+
type: "string",
|
|
56
|
+
description: "Relative path to frontend hooks directory (default: apps/web/src/pages)",
|
|
57
|
+
},
|
|
58
|
+
backendApiDocsPath: {
|
|
59
|
+
type: "string",
|
|
60
|
+
description: "Relative path to backend API documentation (default: docs/api)",
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
},
|
|
48
65
|
];
|
|
49
66
|
|
|
50
67
|
export const contractHandlers = {
|
|
@@ -56,8 +73,8 @@ export const contractHandlers = {
|
|
|
56
73
|
const currentHash = calculateContractHash(sharedTypesDir, projectRoot);
|
|
57
74
|
const storedHash = JSON.parse(fs.readFileSync(contractJsonPath, "utf-8")).contract_hash;
|
|
58
75
|
return { content: [{ type: "text", text: currentHash === storedHash ? "✅ MATCH: Contract is valid and synchronized." : `❌ MISMATCH: Current hash (${currentHash.slice(0, 8)}...) does not match stored hash (${storedHash.slice(0, 8)}...).` }] };
|
|
59
|
-
} catch
|
|
60
|
-
return { content: [{ type: "text", text: "
|
|
76
|
+
} catch {
|
|
77
|
+
return { content: [{ type: "text", text: "Contract integrity check failed." }] };
|
|
61
78
|
}
|
|
62
79
|
},
|
|
63
80
|
update_contract_hash: async (args: unknown, projectRoot: string) => {
|
|
@@ -73,7 +90,7 @@ export const contractHandlers = {
|
|
|
73
90
|
contractJson.last_updated = new Date().toISOString();
|
|
74
91
|
fs.writeFileSync(contractJsonPath, JSON.stringify(contractJson, null, 2));
|
|
75
92
|
return { content: [{ type: "text", text: `SUCCESS: Contract hash updated to ${currentHash}` }] };
|
|
76
|
-
} catch
|
|
93
|
+
} catch {
|
|
77
94
|
return { content: [{ type: "text", text: "Failed to update contract hash." }] };
|
|
78
95
|
}
|
|
79
96
|
},
|
|
@@ -104,8 +121,101 @@ export const contractHandlers = {
|
|
|
104
121
|
});
|
|
105
122
|
|
|
106
123
|
return { content: [{ type: "text", text: `### CONTRACT INTEGRITY SHIELD: ${parsed.data.domain.toUpperCase()}\n\n` + `- **Missing/Undefined Types:** ${missingTypes.length > 0 ? `⚠️ ${missingTypes.join(", ")}` : "✅ All types synchronized"}\n\n` + `**Result:** ${missingTypes.length === 0 ? "PASSED" : "FAILED"}` }] };
|
|
107
|
-
} catch
|
|
124
|
+
} catch {
|
|
108
125
|
return { content: [{ type: "text", text: "Contract verification failed." }] };
|
|
109
126
|
}
|
|
110
127
|
},
|
|
128
|
+
|
|
129
|
+
validate_frontend_api_contract: async (args: unknown, projectRoot: string) => {
|
|
130
|
+
const a = args as Record<string, string> | undefined;
|
|
131
|
+
const frontendPath = a?.frontendHooksPath || "apps/web/src/pages";
|
|
132
|
+
const backendDocsPath = a?.backendApiDocsPath || "docs/api";
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
const fullFrontendPath = path.join(projectRoot, frontendPath);
|
|
136
|
+
const fullBackendDocsPath = path.join(projectRoot, backendDocsPath);
|
|
137
|
+
|
|
138
|
+
if (!fs.existsSync(fullFrontendPath)) {
|
|
139
|
+
return { content: [{ type: "text", text: `Frontend hooks path not found: ${frontendPath}` }] };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const hookFiles = collectFilesRecursively(fullFrontendPath, new Set(["ts", "tsx"]));
|
|
143
|
+
const apiCalls: { endpoint: string; responseType?: string }[] = [];
|
|
144
|
+
|
|
145
|
+
// Hook dosyalarındaki fetch çağrılarını ve response tiplerini çıkar
|
|
146
|
+
for (const file of hookFiles) {
|
|
147
|
+
const content = fs.readFileSync(file, "utf-8");
|
|
148
|
+
|
|
149
|
+
// Basit regex ile endpoint + response tipi yakala
|
|
150
|
+
const matches = content.matchAll(
|
|
151
|
+
/(?:const\s+\w+\s*[:=]\s*await\s+)?fetch\(['"`](\/api\/[^'"`]+)['"`][^)]*\)\s*(?:\.then[^;]+)?(?:\s*as\s+([A-Z][a-zA-Z0-9]+)|:\s*([A-Z][a-zA-Z0-9]+))?/g
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
for (const match of matches) {
|
|
155
|
+
const endpoint = match[1];
|
|
156
|
+
const responseType = match[2] || match[3];
|
|
157
|
+
apiCalls.push({ endpoint, responseType });
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const uniqueEndpoints = Array.from(new Set(apiCalls.map(c => c.endpoint)));
|
|
162
|
+
|
|
163
|
+
if (!fs.existsSync(fullBackendDocsPath)) {
|
|
164
|
+
return { content: [{ type: "text", text: `Backend API docs path not found: ${backendDocsPath}` }] };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const backendDocFiles = collectFilesRecursively(fullBackendDocsPath, new Set(["md"]));
|
|
168
|
+
const documentedEndpoints: string[] = [];
|
|
169
|
+
|
|
170
|
+
for (const file of backendDocFiles) {
|
|
171
|
+
const content = fs.readFileSync(file, "utf-8");
|
|
172
|
+
const matches = content.matchAll(/`?(\/api\/[a-zA-Z0-9/_-]+)`?/g);
|
|
173
|
+
for (const match of matches) {
|
|
174
|
+
documentedEndpoints.push(match[1]);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const missingInDocs = uniqueEndpoints.filter(ep => !documentedEndpoints.some(d => ep.startsWith(d)));
|
|
179
|
+
const missingInHooks = documentedEndpoints.filter(d => !uniqueEndpoints.some(e => e.startsWith(d)));
|
|
180
|
+
|
|
181
|
+
let report = `### FRONTEND ↔ BACKEND CONTRACT VALIDATION REPORT\n\n`;
|
|
182
|
+
report += `**Scanned Frontend Hooks:** ${uniqueEndpoints.length} unique endpoints found\n`;
|
|
183
|
+
report += `**Scanned Backend API Docs:** ${documentedEndpoints.length} endpoints documented\n\n`;
|
|
184
|
+
|
|
185
|
+
if (missingInDocs.length > 0) {
|
|
186
|
+
report += `⚠️ **Endpoints called from frontend but NOT documented in backend:**\n${missingInDocs.map(e => `- ${e}`).join("\n")}\n\n`;
|
|
187
|
+
} else {
|
|
188
|
+
report += `✅ All frontend-called endpoints are documented in backend.\n\n`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (missingInHooks.length > 0) {
|
|
192
|
+
report += `ℹ️ **Endpoints documented in backend but NOT used in scanned frontend hooks:**\n${missingInHooks.map(e => `- ${e}`).join("\n")}\n\n`;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Basit tip karşılaştırması (eğer responseType varsa)
|
|
196
|
+
const typeMismatches: string[] = [];
|
|
197
|
+
for (const call of apiCalls) {
|
|
198
|
+
if (call.responseType) {
|
|
199
|
+
const docContent = backendDocFiles
|
|
200
|
+
.map(f => fs.readFileSync(f, "utf-8"))
|
|
201
|
+
.join("\n");
|
|
202
|
+
|
|
203
|
+
if (!docContent.includes(call.responseType)) {
|
|
204
|
+
typeMismatches.push(`${call.endpoint} → expected type "${call.responseType}" not found in docs`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (typeMismatches.length > 0) {
|
|
210
|
+
report += `⚠️ **Potential Type Mismatches:**\n${typeMismatches.map(m => `- ${m}`).join("\n")}\n\n`;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
report += `**Result:** ${missingInDocs.length === 0 && typeMismatches.length === 0 ? "PASSED" : "FAILED (see mismatches above)"}`;
|
|
214
|
+
|
|
215
|
+
return { content: [{ type: "text", text: report }] };
|
|
216
|
+
} catch (err: unknown) {
|
|
217
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
218
|
+
return { content: [{ type: "text", text: `Validation failed: ${message}` }] };
|
|
219
|
+
}
|
|
220
|
+
},
|
|
111
221
|
};
|
|
@@ -42,7 +42,7 @@ export const databaseHandlers = {
|
|
|
42
42
|
}
|
|
43
43
|
if (tablesFound === 0) return { content: [{ type: "text", text: "No database tables detected." }] };
|
|
44
44
|
return { content: [{ type: "text", text: `### DATABASE SCHEMA MAP\n\n\`\`\`mermaid\n${mermaid}\n\`\`\`\n\n**Total Tables Detected:** ${tablesFound}` }] };
|
|
45
|
-
} catch
|
|
45
|
+
} catch {
|
|
46
46
|
return { content: [{ type: "text", text: "Database schema analysis failed." }] };
|
|
47
47
|
}
|
|
48
48
|
},
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
collectMarkdownArtifacts,
|
|
7
7
|
FRAMEWORK_VERSION
|
|
8
8
|
} from "../utils.js";
|
|
9
|
-
|
|
9
|
+
|
|
10
10
|
|
|
11
11
|
export const frameworkTools = [
|
|
12
12
|
{
|
|
@@ -72,15 +72,13 @@ export const frameworkHandlers = {
|
|
|
72
72
|
if (allDeps.includes("typescript")) detected.push("TypeScript");
|
|
73
73
|
if (detected.length > 0) techStack = detected.join(", ");
|
|
74
74
|
}
|
|
75
|
-
const rootDirs = fs.readdirSync(projectRoot).filter(f => fs.lstatSync(path.join(projectRoot, f)).isDirectory() && !f.startsWith("."));
|
|
76
|
-
const isMonorepo = rootDirs.includes("apps") || rootDirs.includes("packages");
|
|
77
75
|
const date = new Date().toISOString().split("T")[0];
|
|
78
76
|
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 |`;
|
|
79
77
|
if (fs.existsSync(memoryPath)) fs.copyFileSync(memoryPath, memoryPath + ".bak");
|
|
80
78
|
fs.writeFileSync(memoryPath, bootstrapContent);
|
|
81
79
|
return { content: [{ type: "text", text: `Legacy Codebase Bootstrap complete. Detected Stack: ${techStack}.` }] };
|
|
82
|
-
} catch
|
|
83
|
-
return { content: [{ type: "text", text: "Failed to
|
|
80
|
+
} catch {
|
|
81
|
+
return { content: [{ type: "text", text: "Failed to get project gaps." }] };
|
|
84
82
|
}
|
|
85
83
|
},
|
|
86
84
|
get_framework_status: async (args: unknown, projectRoot: string) => {
|
|
@@ -94,8 +92,8 @@ export const frameworkHandlers = {
|
|
|
94
92
|
return {
|
|
95
93
|
content: [{ type: "text", text: `Framework active (v${FRAMEWORK_VERSION}). Phase: ${phase}. Profile: ${profile}.` }],
|
|
96
94
|
};
|
|
97
|
-
} catch
|
|
98
|
-
return { content: [{ type: "text", text: "
|
|
95
|
+
} catch {
|
|
96
|
+
return { content: [{ type: "text", text: "Failed to get memory insights." }] };
|
|
99
97
|
}
|
|
100
98
|
},
|
|
101
99
|
get_project_gaps: async (args: unknown, projectRoot: string) => {
|
|
@@ -147,8 +145,8 @@ export const frameworkHandlers = {
|
|
|
147
145
|
return {
|
|
148
146
|
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)}...` }],
|
|
149
147
|
};
|
|
150
|
-
} catch
|
|
151
|
-
return { content: [{ type: "text", text: "Failed to
|
|
148
|
+
} catch {
|
|
149
|
+
return { content: [{ type: "text", text: "Failed to get framework status." }] };
|
|
152
150
|
}
|
|
153
151
|
},
|
|
154
152
|
codebase_context: async (args: unknown, projectRoot: string) => {
|
|
@@ -160,15 +158,14 @@ export const frameworkHandlers = {
|
|
|
160
158
|
return {
|
|
161
159
|
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."}` }],
|
|
162
160
|
};
|
|
163
|
-
} catch
|
|
164
|
-
return { content: [{ type: "text", text: "
|
|
161
|
+
} catch {
|
|
162
|
+
return { content: [{ type: "text", text: "Failed to bootstrap legacy memory." }] };
|
|
165
163
|
}
|
|
166
164
|
},
|
|
167
165
|
get_system_time: async () => {
|
|
168
166
|
return { content: [{ type: "text", text: new Date().toISOString() }] };
|
|
169
167
|
},
|
|
170
168
|
verify_framework_health: async (args: unknown, projectRoot: string) => {
|
|
171
|
-
const parsed = VERIFY_FRAMEWORK_HEALTH_ARGS_SCHEMA.safeParse(args ?? {});
|
|
172
169
|
try {
|
|
173
170
|
// Run the internal CLI check command via tsx to ensure we test current state
|
|
174
171
|
// If the CLI is not yet linked, we might need a relative path
|
|
@@ -35,8 +35,8 @@ export const gitHandlers = {
|
|
|
35
35
|
else if (files.some(f => f.includes("framework-mcp"))) scope = "mcp";
|
|
36
36
|
const summary = files.length === 1 ? `update ${path.basename(files[0])}` : `update ${files.length} files`;
|
|
37
37
|
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")}` }] };
|
|
38
|
-
} catch
|
|
39
|
-
return { content: [{ type: "text", text: "Failed to generate commit message." }] };
|
|
38
|
+
} catch {
|
|
39
|
+
return { content: [{ type: "text", text: "Failed to generate semantic commit message." }] };
|
|
40
40
|
}
|
|
41
41
|
},
|
|
42
42
|
};
|
|
@@ -3,8 +3,7 @@ import path from "path";
|
|
|
3
3
|
import { getFrameworkDir } from "../utils.js";
|
|
4
4
|
import {
|
|
5
5
|
SEARCH_KNOWLEDGE_BASE_ARGS_SCHEMA,
|
|
6
|
-
UPDATE_KNOWLEDGE_BASE_ARGS_SCHEMA
|
|
7
|
-
GET_KNOWLEDGE_GRAPH_ARGS_SCHEMA
|
|
6
|
+
UPDATE_KNOWLEDGE_BASE_ARGS_SCHEMA
|
|
8
7
|
} from "../schemas.js";
|
|
9
8
|
|
|
10
9
|
export const knowledgeTools = [
|
|
@@ -82,7 +81,7 @@ function parseFrontmatter(content: string) {
|
|
|
82
81
|
}).filter(Boolean);
|
|
83
82
|
|
|
84
83
|
return { content: [{ type: "text", text: results.length > 0 ? `### KNOWLEDGE BASE SEARCH RESULTS\n\n${results.join("\n\n---\n\n")}` : "No matching knowledge entries found." }] };
|
|
85
|
-
} catch
|
|
84
|
+
} catch {
|
|
86
85
|
return { content: [{ type: "text", text: "Knowledge base search failed." }] };
|
|
87
86
|
}
|
|
88
87
|
},
|
|
@@ -105,7 +104,7 @@ function parseFrontmatter(content: string) {
|
|
|
105
104
|
|
|
106
105
|
fs.writeFileSync(path.join(kbDir, fileName), finalContent);
|
|
107
106
|
return { content: [{ type: "text", text: `Obsidian Wiki updated: ${parsed.data.topic}` }] };
|
|
108
|
-
} catch
|
|
107
|
+
} catch {
|
|
109
108
|
return { content: [{ type: "text", text: "Failed to update knowledge base." }] };
|
|
110
109
|
}
|
|
111
110
|
},
|
|
@@ -135,8 +134,8 @@ function parseFrontmatter(content: string) {
|
|
|
135
134
|
});
|
|
136
135
|
|
|
137
136
|
return { content: [{ type: "text", text: `### KNOWLEDGE GRAPH (Mermaid)\n\n\`\`\`mermaid\n${mermaid}\n\`\`\`` }] };
|
|
138
|
-
} catch
|
|
139
|
-
return { content: [{ type: "text", text: "Failed to
|
|
137
|
+
} catch {
|
|
138
|
+
return { content: [{ type: "text", text: "Failed to get knowledge graph." }] };
|
|
140
139
|
}
|
|
141
140
|
}
|
|
142
141
|
};
|
|
@@ -59,7 +59,7 @@ export const memoryHandlers = {
|
|
|
59
59
|
memoryContent = prependToSection(memoryContent, h, content);
|
|
60
60
|
updated = true;
|
|
61
61
|
break;
|
|
62
|
-
} catch
|
|
62
|
+
} catch {
|
|
63
63
|
// Ignore section not found
|
|
64
64
|
}
|
|
65
65
|
}
|
|
@@ -72,9 +72,8 @@ export const memoryHandlers = {
|
|
|
72
72
|
fs.writeFileSync(memoryPath, memoryContent);
|
|
73
73
|
if (fs.existsSync(lockPath) && fs.readFileSync(lockPath, "utf-8").includes(lockOwner)) fs.unlinkSync(lockPath);
|
|
74
74
|
return { content: [{ type: "text", text: `Section ${section} updated successfully.` }] };
|
|
75
|
-
} catch
|
|
76
|
-
|
|
77
|
-
return { content: [{ type: "text", text: `Memory update failed: ${error instanceof Error ? error.message : "Unknown error"}` }] };
|
|
75
|
+
} catch {
|
|
76
|
+
return { content: [{ type: "text", text: "Failed to read project memory." }] };
|
|
78
77
|
}
|
|
79
78
|
},
|
|
80
79
|
read_project_memory: async (args: unknown, projectRoot: string) => {
|
|
@@ -83,8 +82,8 @@ export const memoryHandlers = {
|
|
|
83
82
|
const memoryPath = path.join(projectRoot, frameworkDir, "PROJECT_MEMORY.md");
|
|
84
83
|
if (!fs.existsSync(memoryPath)) return { content: [{ type: "text", text: `ERROR: ${frameworkDir}/PROJECT_MEMORY.md not found.` }] };
|
|
85
84
|
return { content: [{ type: "text", text: fs.readFileSync(memoryPath, "utf-8") }] };
|
|
86
|
-
} catch
|
|
87
|
-
return { content: [{ type: "text", text: "
|
|
85
|
+
} catch {
|
|
86
|
+
return { content: [{ type: "text", text: "Failed to update project memory." }] };
|
|
88
87
|
}
|
|
89
88
|
},
|
|
90
89
|
};
|
|
@@ -4,24 +4,26 @@ import { getFrameworkDir } from "../utils.js";
|
|
|
4
4
|
import {
|
|
5
5
|
SEND_AGENT_MESSAGE_ARGS_SCHEMA,
|
|
6
6
|
READ_AGENT_MESSAGES_ARGS_SCHEMA,
|
|
7
|
-
GET_AGENT_INBOX_STATS_ARGS_SCHEMA
|
|
7
|
+
GET_AGENT_INBOX_STATS_ARGS_SCHEMA,
|
|
8
|
+
UPDATE_AGENT_MESSAGE_STATUS_ARGS_SCHEMA
|
|
8
9
|
} from "../schemas.js";
|
|
9
10
|
|
|
10
11
|
export const messageTools = [
|
|
11
12
|
{
|
|
12
13
|
name: "send_agent_message",
|
|
13
14
|
description: "Sends a message to another specialized agent following the Hermes Protocol.",
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
15
|
+
inputSchema: {
|
|
16
|
+
type: "object",
|
|
17
|
+
properties: {
|
|
18
|
+
from: { type: "string", description: "Sender agent name (e.g. @manager, @backend)" },
|
|
19
|
+
to: { type: "string", description: "Recipient agent name" },
|
|
20
|
+
message: { type: "string", description: "The message content" },
|
|
21
|
+
traceId: { type: "string", description: "The active Trace ID" },
|
|
22
|
+
category: { type: "string", enum: ["ACTION", "DELEGATION", "INFO", "ALERT"], default: "INFO" },
|
|
23
|
+
priority: { type: "string", enum: ["LOW", "MEDIUM", "HIGH", "URGENT"], default: "MEDIUM" },
|
|
24
|
+
},
|
|
25
|
+
required: ["from", "to", "message", "traceId"],
|
|
22
26
|
},
|
|
23
|
-
required: ["to", "message", "traceId"],
|
|
24
|
-
},
|
|
25
27
|
},
|
|
26
28
|
{
|
|
27
29
|
name: "read_agent_messages",
|
|
@@ -46,16 +48,30 @@ export const messageTools = [
|
|
|
46
48
|
required: ["agent"],
|
|
47
49
|
},
|
|
48
50
|
},
|
|
51
|
+
{
|
|
52
|
+
name: "update_agent_message_status",
|
|
53
|
+
description: "Allows an agent to acknowledge, complete, or reject a Hermes message by Trace ID (advances the orchestration lifecycle).",
|
|
54
|
+
inputSchema: {
|
|
55
|
+
type: "object",
|
|
56
|
+
properties: {
|
|
57
|
+
agent: { type: "string", description: "The agent performing the status update" },
|
|
58
|
+
traceId: { type: "string", description: "Trace ID of the message to update" },
|
|
59
|
+
newStatus: { type: "string", enum: ["ACKNOWLEDGED", "COMPLETED", "REJECTED"], description: "New lifecycle status" },
|
|
60
|
+
},
|
|
61
|
+
required: ["agent", "traceId", "newStatus"],
|
|
62
|
+
},
|
|
63
|
+
},
|
|
49
64
|
];
|
|
50
65
|
|
|
51
66
|
interface Message {
|
|
52
67
|
timestamp: string;
|
|
53
68
|
from: string;
|
|
69
|
+
to: string;
|
|
54
70
|
traceId: string;
|
|
55
71
|
category: "ACTION" | "DELEGATION" | "INFO" | "ALERT";
|
|
56
72
|
priority: "LOW" | "MEDIUM" | "HIGH" | "URGENT";
|
|
57
73
|
content: string;
|
|
58
|
-
|
|
74
|
+
status: "PENDING" | "READ" | "ACKNOWLEDGED" | "COMPLETED" | "REJECTED";
|
|
59
75
|
}
|
|
60
76
|
|
|
61
77
|
function normalizeAgentName(agent: string): string | null {
|
|
@@ -75,19 +91,21 @@ export const messageHandlers = {
|
|
|
75
91
|
if (!fs.existsSync(messagesDir)) fs.mkdirSync(messagesDir, { recursive: true });
|
|
76
92
|
const messagePath = path.join(messagesDir, `${recipient}.json`);
|
|
77
93
|
const messages = (fs.existsSync(messagePath) ? JSON.parse(fs.readFileSync(messagePath, "utf-8")) : []) as Message[];
|
|
94
|
+
const sender = normalizeAgentName(parsed.data.from) || "unknown";
|
|
78
95
|
messages.push({
|
|
79
96
|
timestamp: new Date().toISOString(),
|
|
80
|
-
from:
|
|
97
|
+
from: sender,
|
|
98
|
+
to: recipient,
|
|
81
99
|
traceId: parsed.data.traceId,
|
|
82
100
|
category: parsed.data.category,
|
|
83
101
|
priority: parsed.data.priority,
|
|
84
102
|
content: parsed.data.message,
|
|
85
|
-
|
|
103
|
+
status: "PENDING"
|
|
86
104
|
});
|
|
87
105
|
fs.writeFileSync(messagePath, JSON.stringify(messages, null, 2));
|
|
88
106
|
return { content: [{ type: "text", text: `Hermes: Message sent to @${recipient} [${parsed.data.category} | ${parsed.data.priority}].` }] };
|
|
89
|
-
} catch
|
|
90
|
-
return { content: [{ type: "text", text: "Failed to send message
|
|
107
|
+
} catch {
|
|
108
|
+
return { content: [{ type: "text", text: "Failed to send agent message." }] };
|
|
91
109
|
}
|
|
92
110
|
},
|
|
93
111
|
read_agent_messages: async (args: unknown, projectRoot: string) => {
|
|
@@ -100,21 +118,30 @@ export const messageHandlers = {
|
|
|
100
118
|
const messagePath = path.join(projectRoot, frameworkDir, "messages", `${agentName}.json`);
|
|
101
119
|
if (!fs.existsSync(messagePath)) return { content: [{ type: "text", text: "No messages found." }] };
|
|
102
120
|
const messages = JSON.parse(fs.readFileSync(messagePath, "utf-8")) as Message[];
|
|
103
|
-
const
|
|
121
|
+
const pending = messages.filter((m) =>
|
|
122
|
+
(m.status === "PENDING" || m.status === "ACKNOWLEDGED") &&
|
|
123
|
+
(!parsed.data.traceId || m.traceId === parsed.data.traceId)
|
|
124
|
+
);
|
|
104
125
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
126
|
+
// Transition PENDING → READ
|
|
127
|
+
const updated = messages.map((m) => {
|
|
128
|
+
if ((m.status === "PENDING" || m.status === "ACKNOWLEDGED") &&
|
|
129
|
+
(!parsed.data.traceId || m.traceId === parsed.data.traceId)) {
|
|
130
|
+
return { ...m, status: "READ" as const };
|
|
131
|
+
}
|
|
132
|
+
return m;
|
|
133
|
+
});
|
|
134
|
+
fs.writeFileSync(messagePath, JSON.stringify(updated, null, 2));
|
|
108
135
|
|
|
109
|
-
if (
|
|
136
|
+
if (pending.length === 0) return { content: [{ type: "text", text: "No new messages." }] };
|
|
110
137
|
|
|
111
|
-
const formatted =
|
|
138
|
+
const formatted = pending.map((m) =>
|
|
112
139
|
`- [${m.priority}] **${m.from}** (${m.category}): ${m.content} *(Trace: ${m.traceId})*`
|
|
113
140
|
).join("\n");
|
|
114
141
|
|
|
115
142
|
return { content: [{ type: "text", text: `### HERMES INBOX: @${agentName}\n\n${formatted}` }] };
|
|
116
|
-
} catch
|
|
117
|
-
return { content: [{ type: "text", text: "Failed to
|
|
143
|
+
} catch {
|
|
144
|
+
return { content: [{ type: "text", text: "Failed to send agent message." }] };
|
|
118
145
|
}
|
|
119
146
|
},
|
|
120
147
|
get_agent_inbox_stats: async (args: unknown, projectRoot: string) => {
|
|
@@ -127,21 +154,60 @@ export const messageHandlers = {
|
|
|
127
154
|
const messagePath = path.join(projectRoot, frameworkDir, "messages", `${agentName}.json`);
|
|
128
155
|
if (!fs.existsSync(messagePath)) return { content: [{ type: "text", text: "Inbox empty." }] };
|
|
129
156
|
const messages = JSON.parse(fs.readFileSync(messagePath, "utf-8")) as Message[];
|
|
130
|
-
const
|
|
157
|
+
const pendingOrRead = messages.filter(m => m.status === "PENDING" || m.status === "READ");
|
|
131
158
|
|
|
132
159
|
const priorityDist = messages.reduce((acc, m) => {
|
|
133
160
|
acc[m.priority] = (acc[m.priority] || 0) + 1;
|
|
134
161
|
return acc;
|
|
135
162
|
}, {} as Record<string, number>);
|
|
136
163
|
|
|
164
|
+
const statusDist = messages.reduce((acc, m) => {
|
|
165
|
+
acc[m.status] = (acc[m.status] || 0) + 1;
|
|
166
|
+
return acc;
|
|
167
|
+
}, {} as Record<string, number>);
|
|
168
|
+
|
|
137
169
|
return {
|
|
138
170
|
content: [{
|
|
139
171
|
type: "text",
|
|
140
|
-
text: `### INBOX STATS: @${agentName}\n- Total: ${messages.length}\n-
|
|
172
|
+
text: `### INBOX STATS: @${agentName}\n- Total: ${messages.length}\n- Pending/Read: ${pendingOrRead.length}\n- By Priority: ${JSON.stringify(priorityDist)}\n- By Status: ${JSON.stringify(statusDist)}`
|
|
141
173
|
}]
|
|
142
174
|
};
|
|
143
|
-
} catch
|
|
144
|
-
return { content: [{ type: "text", text: "Failed to
|
|
175
|
+
} catch {
|
|
176
|
+
return { content: [{ type: "text", text: "Failed to read agent messages." }] };
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
update_agent_message_status: async (args: unknown, projectRoot: string) => {
|
|
181
|
+
const parsed = UPDATE_AGENT_MESSAGE_STATUS_ARGS_SCHEMA.safeParse(args ?? {});
|
|
182
|
+
if (!parsed.success) return { content: [{ type: "text", text: "Invalid arguments for status update." }] };
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
const frameworkDir = getFrameworkDir(projectRoot);
|
|
186
|
+
const agentName = normalizeAgentName(parsed.data.agent);
|
|
187
|
+
if (!agentName) return { content: [{ type: "text", text: "Invalid agent name." }] };
|
|
188
|
+
|
|
189
|
+
const messagePath = path.join(projectRoot, frameworkDir, "messages", `${agentName}.json`);
|
|
190
|
+
if (!fs.existsSync(messagePath)) return { content: [{ type: "text", text: "No messages for this agent." }] };
|
|
191
|
+
|
|
192
|
+
const messages = JSON.parse(fs.readFileSync(messagePath, "utf-8")) as Message[];
|
|
193
|
+
|
|
194
|
+
let updated = false;
|
|
195
|
+
const newMessages = messages.map((m) => {
|
|
196
|
+
if (m.traceId === parsed.data.traceId) {
|
|
197
|
+
updated = true;
|
|
198
|
+
return { ...m, status: parsed.data.newStatus };
|
|
199
|
+
}
|
|
200
|
+
return m;
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
if (!updated) {
|
|
204
|
+
return { content: [{ type: "text", text: `No message found with Trace ID ${parsed.data.traceId}.` }] };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
fs.writeFileSync(messagePath, JSON.stringify(newMessages, null, 2));
|
|
208
|
+
return { content: [{ type: "text", text: `Hermes: Message ${parsed.data.traceId} marked as ${parsed.data.newStatus}.` }] };
|
|
209
|
+
} catch {
|
|
210
|
+
return { content: [{ type: "text", text: "Failed to update message status." }] };
|
|
145
211
|
}
|
|
146
212
|
}
|
|
147
213
|
};
|
|
@@ -3,8 +3,7 @@ import path from "path";
|
|
|
3
3
|
import { spawnSync } from "child_process";
|
|
4
4
|
import { Project } from "ts-morph";
|
|
5
5
|
import {
|
|
6
|
-
resolveSafePath
|
|
7
|
-
getFrameworkDir
|
|
6
|
+
resolveSafePath
|
|
8
7
|
} from "../utils.js";
|
|
9
8
|
import {
|
|
10
9
|
VALIDATE_REPOSITORY_HEALTH_ARGS_SCHEMA,
|
|
@@ -69,8 +68,8 @@ export const repositoryHandlers = {
|
|
|
69
68
|
if (scope === "full" || scope === "test") results.push(runScript("test"));
|
|
70
69
|
if (scope === "full" || scope === "build") results.push(runScript("build"));
|
|
71
70
|
return { content: [{ type: "text", text: `### REPOSITORY HEALTH REPORT\n\n` + results.map(r => `- **${r.name.toUpperCase()}** (${r.script}): ${r.status}${r.status === "FAILED" && r.details ? `\n ${r.details}` : ""}`).join("\n") }] };
|
|
72
|
-
} catch
|
|
73
|
-
return { content: [{ type: "text", text: "
|
|
71
|
+
} catch {
|
|
72
|
+
return { content: [{ type: "text", text: "Failed to analyze repository health." }] };
|
|
74
73
|
}
|
|
75
74
|
},
|
|
76
75
|
analyze_documentation_debt: async (args: unknown, projectRoot: string) => {
|
|
@@ -95,8 +94,8 @@ export const repositoryHandlers = {
|
|
|
95
94
|
}
|
|
96
95
|
const majorDirs = ["apps/backend", "apps/web", "framework-mcp"], missingREADME = majorDirs.filter(dir => fs.existsSync(path.join(projectRoot, dir)) && !fs.existsSync(path.join(projectRoot, dir, "README.md")));
|
|
97
96
|
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")}` }] };
|
|
98
|
-
} catch
|
|
99
|
-
return { content: [{ type: "text", text: "
|
|
97
|
+
} catch {
|
|
98
|
+
return { content: [{ type: "text", text: "Failed to analyze documentation debt." }] };
|
|
100
99
|
}
|
|
101
100
|
},
|
|
102
101
|
};
|
|
@@ -71,8 +71,9 @@ export const scaffoldHandlers = {
|
|
|
71
71
|
fs.cpSync(sourceDir, destDir, { recursive: true });
|
|
72
72
|
|
|
73
73
|
return { content: [{ type: "text", text: `Successfully instantiated blueprint '${blueprintPath}' into '${targetPath}'` }] };
|
|
74
|
-
} catch (error:
|
|
75
|
-
|
|
74
|
+
} catch (error: unknown) {
|
|
75
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
76
|
+
return { content: [{ type: "text", text: `Scaffold failed: ${message}` }] };
|
|
76
77
|
}
|
|
77
78
|
},
|
|
78
79
|
save_as_blueprint: async (args: unknown, projectRoot: string) => {
|
|
@@ -97,8 +98,9 @@ export const scaffoldHandlers = {
|
|
|
97
98
|
}
|
|
98
99
|
|
|
99
100
|
return { content: [{ type: "text", text: `Successfully saved blueprint to '${blueprintPath}'` }] };
|
|
100
|
-
} catch (error:
|
|
101
|
-
|
|
101
|
+
} catch (error: unknown) {
|
|
102
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
103
|
+
return { content: [{ type: "text", text: `Failed to save blueprint: ${message}` }] };
|
|
102
104
|
}
|
|
103
105
|
},
|
|
104
106
|
list_blueprints: async (args: unknown, projectRoot: string) => {
|
|
@@ -140,8 +142,9 @@ export const scaffoldHandlers = {
|
|
|
140
142
|
available = Array.from(new Set(available));
|
|
141
143
|
|
|
142
144
|
return { content: [{ type: "text", text: `### AVAILABLE BLUEPRINTS\n\n` + available.map(b => `- ${b}`).join("\n") }] };
|
|
143
|
-
} catch (error:
|
|
144
|
-
|
|
145
|
+
} catch (error: unknown) {
|
|
146
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
147
|
+
return { content: [{ type: "text", text: `Listing blueprints failed: ${message}` }] };
|
|
145
148
|
}
|
|
146
149
|
},
|
|
147
150
|
};
|
|
@@ -82,8 +82,8 @@ export const securityHandlers = {
|
|
|
82
82
|
});
|
|
83
83
|
}
|
|
84
84
|
return { content: [{ type: "text", text: vulnerabilities.length > 0 ? `### ADVANCED SECURITY AUDIT RESULTS\n\n${Array.from(new Set(vulnerabilities)).join("\n\n")}` : "No security patterns or rule violations detected (Regex & AST verified)." }] };
|
|
85
|
-
} catch
|
|
86
|
-
return { content: [{ type: "text", text: "Security
|
|
85
|
+
} catch {
|
|
86
|
+
return { content: [{ type: "text", text: "Security audit failed." }] };
|
|
87
87
|
}
|
|
88
88
|
},
|
|
89
89
|
analyze_constitution_compliance: async (args: unknown, projectRoot: string) => {
|
|
@@ -136,8 +136,8 @@ export const securityHandlers = {
|
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
return { content: [{ type: "text", text: `### CONSTITUTION COMPLIANCE REPORT\n\n` + (violations.length > 0 ? `⚠️ **VIOLATIONS FOUND:**\n${violations.map(v => `- ${v}`).join("\n")}` : "✅ **ALL SYSTEMS COMPLIANT:** AST and Regex scans passed. No rule violations detected.") }] };
|
|
139
|
-
} catch
|
|
140
|
-
return { content: [{ type: "text", text: "
|
|
139
|
+
} catch {
|
|
140
|
+
return { content: [{ type: "text", text: "Constitution compliance check failed." }] };
|
|
141
141
|
}
|
|
142
142
|
},
|
|
143
143
|
};
|