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.
Files changed (75) hide show
  1. package/.enderun/PROJECT_MEMORY.md +117 -3
  2. package/.enderun/STATUS.md +4 -4
  3. package/.enderun/agents/analyst.md +98 -12
  4. package/.enderun/agents/backend.md +258 -14
  5. package/.enderun/agents/explorer.md +67 -2
  6. package/.enderun/agents/frontend.md +404 -66
  7. package/.enderun/agents/git.md +19 -2
  8. package/.enderun/agents/manager.md +412 -45
  9. package/.enderun/agents/mobile.md +63 -19
  10. package/.enderun/agents/native.md +61 -11
  11. package/.enderun/blueprints/backend/errors/blueprint.json +11 -0
  12. package/.enderun/blueprints/backend/errors/domain-error.ts +34 -55
  13. package/.enderun/docs/tech-stack.md +7 -8
  14. package/.enderun/knowledge/contract_versioning.md +1 -1
  15. package/.enderun/knowledge/framework_vs_user_project_boundary.md +52 -0
  16. package/.enderun/knowledge/frontend_professionalization_guidelines.md +108 -0
  17. package/.enderun/knowledge/hermes_live_test_guidelines.md +90 -0
  18. package/.enderun/knowledge/manager_authority_audit_enforcement.md +104 -0
  19. package/.enderun/knowledge/project_scaffold_guidelines.md +99 -0
  20. package/.enderun/knowledge/reference_application_guidelines.md +90 -0
  21. package/.enderun/messages/2026-05-23-backend-to-manager-auth-errors-reply.json +23 -0
  22. package/.enderun/messages/2026-05-23-manager-to-backend-auth-errors.json +26 -0
  23. package/ENDERUN.md +12 -4
  24. package/README.md +185 -626
  25. package/antigravity.md +15 -0
  26. package/bin/cli.js +105 -30
  27. package/bin/update-contract.js +2 -2
  28. package/docs/README.md +33 -3
  29. package/docs/getting-started.md +497 -0
  30. package/docs/roadmap.md +200 -0
  31. package/framework-mcp/README.md +1 -1
  32. package/framework-mcp/dist/index.js +0 -0
  33. package/framework-mcp/dist/tools/academy.js +6 -8
  34. package/framework-mcp/dist/tools/codebase.js +6 -6
  35. package/framework-mcp/dist/tools/contract.js +94 -4
  36. package/framework-mcp/dist/tools/database.js +1 -1
  37. package/framework-mcp/dist/tools/framework.js +8 -12
  38. package/framework-mcp/dist/tools/git.js +2 -2
  39. package/framework-mcp/dist/tools/knowledge.js +4 -4
  40. package/framework-mcp/dist/tools/memory.js +5 -7
  41. package/framework-mcp/dist/tools/messages.js +6 -6
  42. package/framework-mcp/dist/tools/repository.js +4 -4
  43. package/framework-mcp/dist/tools/scaffold.js +6 -3
  44. package/framework-mcp/dist/tools/security.js +4 -4
  45. package/framework-mcp/dist/utils.js +2 -2
  46. package/framework-mcp/package.json +1 -1
  47. package/framework-mcp/src/schemas.ts +7 -0
  48. package/framework-mcp/src/tools/academy.ts +5 -9
  49. package/framework-mcp/src/tools/codebase.ts +6 -6
  50. package/framework-mcp/src/tools/contract.ts +114 -4
  51. package/framework-mcp/src/tools/database.ts +1 -1
  52. package/framework-mcp/src/tools/framework.ts +9 -12
  53. package/framework-mcp/src/tools/git.ts +2 -2
  54. package/framework-mcp/src/tools/knowledge.ts +5 -6
  55. package/framework-mcp/src/tools/memory.ts +5 -6
  56. package/framework-mcp/src/tools/messages.ts +94 -28
  57. package/framework-mcp/src/tools/repository.ts +5 -6
  58. package/framework-mcp/src/tools/scaffold.ts +9 -6
  59. package/framework-mcp/src/tools/security.ts +4 -4
  60. package/framework-mcp/src/utils.ts +2 -2
  61. package/gemini.md +4 -4
  62. package/package.json +11 -7
  63. package/panda.config.ts +3 -1
  64. package/.enderun/benchmarks/.gitkeep +0 -0
  65. package/.enderun/blueprints/backend/middleware/error-handler.ts +0 -24
  66. package/.enderun/blueprints/backend/types/api.ts +0 -20
  67. package/.enderun/blueprints/backend/types/brands.ts +0 -12
  68. package/.enderun/blueprints/backend/types/constants.ts +0 -34
  69. package/.enderun/blueprints/backend/types/index.ts +0 -49
  70. package/.enderun/blueprints/backend/types/logs.ts +0 -16
  71. package/.enderun/blueprints/backend/types/models.ts +0 -65
  72. package/.enderun/blueprints/frontend/ui/Button.tsx +0 -60
  73. package/.enderun/blueprints/frontend/ui/Input.tsx +0 -43
  74. package/.enderun/monitoring/.gitkeep +0 -0
  75. 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 (error) {
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 (error) {
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 (error) {
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 (error) {
251
- return { content: [{ type: "text", text: "Continuity analysis failed." }] };
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 (error) {
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 (error) {
60
- return { content: [{ type: "text", text: "Failed to verify contract." }] };
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 (error) {
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 (error) {
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 (error) {
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
- import { VERIFY_FRAMEWORK_HEALTH_ARGS_SCHEMA } from "../schemas.js";
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 (error) {
83
- return { content: [{ type: "text", text: "Failed to bootstrap legacy memory." }] };
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 (error) {
98
- return { content: [{ type: "text", text: "Framework active. Memory unreadable." }] };
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 (error) {
151
- return { content: [{ type: "text", text: "Failed to gather memory insights." }] };
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 (error) {
164
- return { content: [{ type: "text", text: "Context discovery failed." }] };
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 (error) {
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 (error) {
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 (error) {
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 (error) {
139
- return { content: [{ type: "text", text: "Failed to generate knowledge graph." }] };
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 (e) {
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 (error) {
76
- if (fs.existsSync(lockPath) && fs.readFileSync(lockPath, "utf-8").includes(lockOwner)) fs.unlinkSync(lockPath);
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 (error) {
87
- return { content: [{ type: "text", text: "ERROR: Failed to read PROJECT_MEMORY.md" }] };
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
- inputSchema: {
15
- type: "object",
16
- properties: {
17
- to: { type: "string", description: "Recipient agent name" },
18
- message: { type: "string", description: "The message content" },
19
- traceId: { type: "string", description: "The active Trace ID" },
20
- category: { type: "string", enum: ["ACTION", "DELEGATION", "INFO", "ALERT"], default: "INFO" },
21
- priority: { type: "string", enum: ["LOW", "MEDIUM", "HIGH", "URGENT"], default: "MEDIUM" },
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
- read: boolean;
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: "manager",
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
- read: false
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 (error) {
90
- return { content: [{ type: "text", text: "Failed to send message via Hermes." }] };
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 unread = messages.filter((m) => !m.read && (!parsed.data.traceId || m.traceId === parsed.data.traceId));
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
- fs.writeFileSync(messagePath, JSON.stringify(messages.map((m) => (
106
- !m.read && (!parsed.data.traceId || m.traceId === parsed.data.traceId) ? { ...m, read: true } : m
107
- )), null, 2));
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 (unread.length === 0) return { content: [{ type: "text", text: "No new messages." }] };
136
+ if (pending.length === 0) return { content: [{ type: "text", text: "No new messages." }] };
110
137
 
111
- const formatted = unread.map((m) =>
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 (error) {
117
- return { content: [{ type: "text", text: "Failed to read messages." }] };
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 unread = messages.filter(m => !m.read);
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- Unread: ${unread.length}\n- Priority Distribution: ${JSON.stringify(priorityDist)}`
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 (error) {
144
- return { content: [{ type: "text", text: "Failed to get inbox stats." }] };
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 (error) {
73
- return { content: [{ type: "text", text: "Health validation failed." }] };
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 (error) {
99
- return { content: [{ type: "text", text: "Documentation debt scan failed." }] };
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: any) {
75
- return { content: [{ type: "text", text: `Scaffold failed: ${error.message}` }] };
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: any) {
101
- return { content: [{ type: "text", text: `Failed to save blueprint: ${error.message}` }] };
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: any) {
144
- return { content: [{ type: "text", text: `Listing blueprints failed: ${error.message}` }] };
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 (error) {
86
- return { content: [{ type: "text", text: "Security scan failed." }] };
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 (error) {
140
- return { content: [{ type: "text", text: "Compliance analysis failed." }] };
139
+ } catch {
140
+ return { content: [{ type: "text", text: "Constitution compliance check failed." }] };
141
141
  }
142
142
  },
143
143
  };