agent-enderun 0.4.7 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.enderun/PROJECT_MEMORY.md +15 -1
- package/.enderun/docs/tech-stack.md +38 -10
- package/.enderun/knowledge/branded_types_pattern.md +7 -0
- package/.enderun/knowledge/hermes_protocol.md +59 -0
- package/.enderun/knowledge/zero_ui_library_policy.md +8 -4
- package/ENDERUN.md +11 -6
- package/README.md +62 -51
- package/bin/cli.js +8 -10
- package/bin/update-contract.js +44 -0
- package/claude.md +4 -1
- package/codex.md +4 -1
- package/cursor.md +4 -1
- package/gemini.md +4 -1
- package/package.json +10 -8
- package/packages/framework-mcp/dist/index.js +2 -1
- package/packages/framework-mcp/dist/schemas.js +14 -0
- package/packages/framework-mcp/dist/tools/framework.js +25 -0
- package/packages/framework-mcp/dist/tools/knowledge.js +75 -11
- package/packages/framework-mcp/dist/tools/memory.js +3 -1
- package/packages/framework-mcp/dist/tools/messages.js +61 -8
- package/packages/framework-mcp/dist/tools/repository.js +6 -2
- package/packages/framework-mcp/dist/utils.js +3 -3
- package/packages/framework-mcp/package.json +1 -1
- package/packages/framework-mcp/src/index.ts +2 -1
- package/packages/framework-mcp/src/schemas.ts +18 -0
- package/packages/framework-mcp/src/tools/academy.ts +17 -11
- package/packages/framework-mcp/src/tools/codebase.ts +5 -5
- package/packages/framework-mcp/src/tools/contract.ts +3 -3
- package/packages/framework-mcp/src/tools/database.ts +1 -1
- package/packages/framework-mcp/src/tools/framework.ts +29 -5
- package/packages/framework-mcp/src/tools/git.ts +1 -1
- package/packages/framework-mcp/src/tools/index.ts +1 -1
- package/packages/framework-mcp/src/tools/knowledge.ts +88 -14
- package/packages/framework-mcp/src/tools/memory.ts +5 -3
- package/packages/framework-mcp/src/tools/messages.ts +81 -13
- package/packages/framework-mcp/src/tools/repository.ts +8 -3
- package/packages/framework-mcp/src/tools/security.ts +2 -2
- package/packages/framework-mcp/src/utils.ts +3 -3
- package/packages/shared-types/contract.version.json +7 -4
- package/packages/shared-types/package.json +1 -1
- package/.enderun/ENDERUN.md +0 -205
|
@@ -36,7 +36,7 @@ export const contractTools = [
|
|
|
36
36
|
];
|
|
37
37
|
|
|
38
38
|
export const contractHandlers = {
|
|
39
|
-
verify_api_contract: async (args:
|
|
39
|
+
verify_api_contract: async (args: unknown, projectRoot: string) => {
|
|
40
40
|
try {
|
|
41
41
|
const sharedTypesDir = path.join(projectRoot, "packages/shared-types/src");
|
|
42
42
|
const contractJsonPath = path.join(projectRoot, "packages/shared-types/contract.version.json");
|
|
@@ -51,7 +51,7 @@ export const contractHandlers = {
|
|
|
51
51
|
return { content: [{ type: "text", text: "Failed to verify contract." }] };
|
|
52
52
|
}
|
|
53
53
|
},
|
|
54
|
-
update_contract_hash: async (args:
|
|
54
|
+
update_contract_hash: async (args: unknown, projectRoot: string) => {
|
|
55
55
|
try {
|
|
56
56
|
const sharedTypesDir = path.join(projectRoot, "packages/shared-types/src");
|
|
57
57
|
const contractJsonPath = path.join(projectRoot, "packages/shared-types/contract.version.json");
|
|
@@ -70,7 +70,7 @@ export const contractHandlers = {
|
|
|
70
70
|
return { content: [{ type: "text", text: "Failed to update contract hash." }] };
|
|
71
71
|
}
|
|
72
72
|
},
|
|
73
|
-
verify_contract_integrity: async (args:
|
|
73
|
+
verify_contract_integrity: async (args: unknown, projectRoot: string) => {
|
|
74
74
|
const parsed = VERIFY_CONTRACT_INTEGRITY_ARGS_SCHEMA.safeParse(args ?? {});
|
|
75
75
|
if (!parsed.success) return { content: [{ type: "text", text: "Invalid domain argument." }] };
|
|
76
76
|
try {
|
|
@@ -18,7 +18,7 @@ export const databaseTools = [
|
|
|
18
18
|
];
|
|
19
19
|
|
|
20
20
|
export const databaseHandlers = {
|
|
21
|
-
analyze_database_schema: async (args:
|
|
21
|
+
analyze_database_schema: async (args: unknown, projectRoot: string) => {
|
|
22
22
|
const parsed = ANALYZE_DATABASE_SCHEMA_ARGS_SCHEMA.safeParse(args ?? {});
|
|
23
23
|
const targetPath = parsed.success ? parsed.data.path : "apps/backend";
|
|
24
24
|
try {
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
|
+
import { execSync } from "child_process";
|
|
3
4
|
import {
|
|
4
5
|
getFrameworkDir,
|
|
5
6
|
collectMarkdownArtifacts,
|
|
6
7
|
FRAMEWORK_VERSION
|
|
7
8
|
} from "../utils.js";
|
|
9
|
+
import { VERIFY_FRAMEWORK_HEALTH_ARGS_SCHEMA } from "../schemas.js";
|
|
8
10
|
|
|
9
11
|
export const frameworkTools = [
|
|
10
12
|
{
|
|
@@ -42,10 +44,20 @@ export const frameworkTools = [
|
|
|
42
44
|
description: "Analyzes a pre-existing codebase to automatically generate the initial project memory, learning its tech stack and architecture.",
|
|
43
45
|
inputSchema: { type: "object", properties: {} },
|
|
44
46
|
},
|
|
47
|
+
{
|
|
48
|
+
name: "verify_framework_health",
|
|
49
|
+
description: "Runs the framework's internal 'check' command and reports system health status via MCP.",
|
|
50
|
+
inputSchema: {
|
|
51
|
+
type: "object",
|
|
52
|
+
properties: {
|
|
53
|
+
detailed: { type: "boolean", default: false, description: "Include detailed issues if any." },
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
},
|
|
45
57
|
];
|
|
46
58
|
|
|
47
59
|
export const frameworkHandlers = {
|
|
48
|
-
bootstrap_legacy_memory: async (args:
|
|
60
|
+
bootstrap_legacy_memory: async (args: unknown, projectRoot: string) => {
|
|
49
61
|
try {
|
|
50
62
|
const frameworkDir = getFrameworkDir(projectRoot);
|
|
51
63
|
const memoryPath = path.join(projectRoot, frameworkDir, "PROJECT_MEMORY.md");
|
|
@@ -71,7 +83,7 @@ export const frameworkHandlers = {
|
|
|
71
83
|
return { content: [{ type: "text", text: "Failed to bootstrap legacy memory." }] };
|
|
72
84
|
}
|
|
73
85
|
},
|
|
74
|
-
get_framework_status: async (args:
|
|
86
|
+
get_framework_status: async (args: unknown, projectRoot: string) => {
|
|
75
87
|
try {
|
|
76
88
|
const frameworkDir = getFrameworkDir(projectRoot);
|
|
77
89
|
const memoryPath = path.join(projectRoot, frameworkDir, "PROJECT_MEMORY.md");
|
|
@@ -86,7 +98,7 @@ export const frameworkHandlers = {
|
|
|
86
98
|
return { content: [{ type: "text", text: "Framework active. Memory unreadable." }] };
|
|
87
99
|
}
|
|
88
100
|
},
|
|
89
|
-
get_project_gaps: async (args:
|
|
101
|
+
get_project_gaps: async (args: unknown, projectRoot: string) => {
|
|
90
102
|
const missing: string[] = [];
|
|
91
103
|
const frameworkDir = getFrameworkDir(projectRoot);
|
|
92
104
|
const checkPaths = [
|
|
@@ -121,7 +133,7 @@ export const frameworkHandlers = {
|
|
|
121
133
|
}
|
|
122
134
|
return { content: [{ type: "text", text: missing.length > 0 ? `Detected Gaps:\n${missing.join("\n")}` : "No structural gaps detected based on core standards." }] };
|
|
123
135
|
},
|
|
124
|
-
get_memory_insights: async (args:
|
|
136
|
+
get_memory_insights: async (args: unknown, projectRoot: string) => {
|
|
125
137
|
try {
|
|
126
138
|
const frameworkDir = getFrameworkDir(projectRoot);
|
|
127
139
|
const memoryPath = path.join(projectRoot, frameworkDir, "PROJECT_MEMORY.md");
|
|
@@ -138,7 +150,7 @@ export const frameworkHandlers = {
|
|
|
138
150
|
return { content: [{ type: "text", text: "Failed to gather memory insights." }] };
|
|
139
151
|
}
|
|
140
152
|
},
|
|
141
|
-
codebase_context: async (args:
|
|
153
|
+
codebase_context: async (args: unknown, projectRoot: string) => {
|
|
142
154
|
try {
|
|
143
155
|
const artifacts = collectMarkdownArtifacts(projectRoot);
|
|
144
156
|
const frameworkDir = getFrameworkDir(projectRoot);
|
|
@@ -154,4 +166,16 @@ export const frameworkHandlers = {
|
|
|
154
166
|
get_system_time: async () => {
|
|
155
167
|
return { content: [{ type: "text", text: new Date().toISOString() }] };
|
|
156
168
|
},
|
|
169
|
+
verify_framework_health: async (args: unknown, projectRoot: string) => {
|
|
170
|
+
const parsed = VERIFY_FRAMEWORK_HEALTH_ARGS_SCHEMA.safeParse(args ?? {});
|
|
171
|
+
try {
|
|
172
|
+
// Run the internal CLI check command via tsx to ensure we test current state
|
|
173
|
+
// If the CLI is not yet linked, we might need a relative path
|
|
174
|
+
const cliPath = path.join(projectRoot, "bin/cli.js");
|
|
175
|
+
const output = execSync(`node ${cliPath} check`, { cwd: projectRoot, encoding: "utf-8", stdio: "pipe" });
|
|
176
|
+
return { content: [{ type: "text", text: `### FRAMEWORK HEALTH CHECK\n\n${output}` }] };
|
|
177
|
+
} catch (error: any) {
|
|
178
|
+
return { content: [{ type: "text", text: `### FRAMEWORK HEALTH CHECK (FAILED)\n\n${error.stdout || error.message}` }] };
|
|
179
|
+
}
|
|
180
|
+
}
|
|
157
181
|
};
|
|
@@ -17,7 +17,7 @@ export const gitTools = [
|
|
|
17
17
|
];
|
|
18
18
|
|
|
19
19
|
export const gitHandlers = {
|
|
20
|
-
generate_semantic_commit_message: async (args:
|
|
20
|
+
generate_semantic_commit_message: async (args: unknown, projectRoot: string) => {
|
|
21
21
|
const parsed = GENERATE_SEMANTIC_COMMIT_MESSAGE_ARGS_SCHEMA.safeParse(args ?? {});
|
|
22
22
|
if (!parsed.success) return { content: [{ type: "text", text: "Invalid traceId argument." }] };
|
|
23
23
|
try {
|
|
@@ -24,7 +24,7 @@ export const allTools = [
|
|
|
24
24
|
...repositoryTools,
|
|
25
25
|
];
|
|
26
26
|
|
|
27
|
-
export const allHandlers: Record<string, (args:
|
|
27
|
+
export const allHandlers: Record<string, (args: unknown, projectRoot: string) => Promise<unknown>> = {
|
|
28
28
|
...codebaseHandlers,
|
|
29
29
|
...frameworkHandlers,
|
|
30
30
|
...securityHandlers,
|
|
@@ -3,66 +3,140 @@ 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
|
|
6
|
+
UPDATE_KNOWLEDGE_BASE_ARGS_SCHEMA,
|
|
7
|
+
GET_KNOWLEDGE_GRAPH_ARGS_SCHEMA
|
|
7
8
|
} from "../schemas.js";
|
|
8
9
|
|
|
9
10
|
export const knowledgeTools = [
|
|
10
11
|
{
|
|
11
12
|
name: "search_knowledge_base",
|
|
12
|
-
description: "Searches the Academy's
|
|
13
|
+
description: "Searches the Academy's Obsidian-style knowledge base using keywords or tags.",
|
|
13
14
|
inputSchema: {
|
|
14
15
|
type: "object",
|
|
15
16
|
properties: {
|
|
16
|
-
query: { type: "string", description: "Search query or
|
|
17
|
+
query: { type: "string", description: "Search query, topic, or tag (e.g. #security)" },
|
|
17
18
|
},
|
|
18
19
|
required: ["query"],
|
|
19
20
|
},
|
|
20
21
|
},
|
|
21
22
|
{
|
|
22
23
|
name: "update_knowledge_base",
|
|
23
|
-
description: "Adds or updates an entry
|
|
24
|
+
description: "Adds or updates an entry with mandatory YAML frontmatter for Obsidian compatibility.",
|
|
24
25
|
inputSchema: {
|
|
25
26
|
type: "object",
|
|
26
27
|
properties: {
|
|
27
|
-
topic: { type: "string", description: "The topic or title
|
|
28
|
-
content: { type: "string", description: "
|
|
28
|
+
topic: { type: "string", description: "The topic or title" },
|
|
29
|
+
content: { type: "string", description: "Content (YAML frontmatter will be auto-generated if missing)" },
|
|
30
|
+
tags: { type: "array", items: { type: "string" }, description: "Tags for the entry" },
|
|
29
31
|
},
|
|
30
32
|
required: ["topic", "content"],
|
|
31
33
|
},
|
|
32
34
|
},
|
|
35
|
+
{
|
|
36
|
+
name: "get_knowledge_graph",
|
|
37
|
+
description: "Generates a Mermaid diagram showing relationships between knowledge entries based on 'related' metadata.",
|
|
38
|
+
inputSchema: {
|
|
39
|
+
type: "object",
|
|
40
|
+
properties: {
|
|
41
|
+
tag: { type: "string", description: "Filter by tag" },
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
},
|
|
33
45
|
];
|
|
34
46
|
|
|
47
|
+
function parseFrontmatter(content: string) {
|
|
48
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
49
|
+
if (!match) return { metadata: {}, body: content };
|
|
50
|
+
const yaml = match[1];
|
|
51
|
+
const metadata: Record<string, any> = {};
|
|
52
|
+
yaml.split("\n").forEach(line => {
|
|
53
|
+
const [key, ...val] = line.split(":");
|
|
54
|
+
if (key && val.length > 0) metadata[key.trim()] = val.join(":").trim();
|
|
55
|
+
});
|
|
56
|
+
return { metadata, body: content.replace(match[0], "").trim() };
|
|
57
|
+
}
|
|
58
|
+
|
|
35
59
|
export const knowledgeHandlers = {
|
|
36
|
-
search_knowledge_base: async (args:
|
|
60
|
+
search_knowledge_base: async (args: unknown, projectRoot: string) => {
|
|
37
61
|
const parsed = SEARCH_KNOWLEDGE_BASE_ARGS_SCHEMA.safeParse(args ?? {});
|
|
38
62
|
if (!parsed.success) return { content: [{ type: "text", text: "Invalid search query." }] };
|
|
39
63
|
try {
|
|
40
64
|
const frameworkDir = getFrameworkDir(projectRoot);
|
|
41
65
|
const kbDir = path.join(projectRoot, frameworkDir, "knowledge");
|
|
42
66
|
if (!fs.existsSync(kbDir)) return { content: [{ type: "text", text: "Knowledge base is empty." }] };
|
|
67
|
+
|
|
68
|
+
const query = parsed.data.query.toLowerCase();
|
|
43
69
|
const results = fs.readdirSync(kbDir).filter(f => f.endsWith(".md")).map(file => {
|
|
44
70
|
const content = fs.readFileSync(path.join(kbDir, file), "utf-8");
|
|
45
|
-
|
|
46
|
-
|
|
71
|
+
const { metadata, body } = parseFrontmatter(content);
|
|
72
|
+
|
|
73
|
+
const matchesQuery = body.toLowerCase().includes(query) ||
|
|
74
|
+
file.toLowerCase().includes(query) ||
|
|
75
|
+
(metadata.tags && metadata.tags.toLowerCase().includes(query)) ||
|
|
76
|
+
(metadata.title && metadata.title.toLowerCase().includes(query));
|
|
77
|
+
|
|
78
|
+
if (matchesQuery) {
|
|
79
|
+
return `### ${metadata.title || file.replace(".md", "")}\n**Tags:** ${metadata.tags || "None"}\n\n${body.slice(0, 300)}...`;
|
|
47
80
|
}
|
|
48
81
|
return null;
|
|
49
82
|
}).filter(Boolean);
|
|
50
|
-
|
|
83
|
+
|
|
84
|
+
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." }] };
|
|
51
85
|
} catch (error) {
|
|
52
86
|
return { content: [{ type: "text", text: "Knowledge base search failed." }] };
|
|
53
87
|
}
|
|
54
88
|
},
|
|
55
|
-
update_knowledge_base: async (args:
|
|
89
|
+
update_knowledge_base: async (args: unknown, projectRoot: string) => {
|
|
56
90
|
const parsed = UPDATE_KNOWLEDGE_BASE_ARGS_SCHEMA.safeParse(args ?? {});
|
|
57
|
-
if (!parsed.success) return { content: [{ type: "text", text: "Invalid
|
|
91
|
+
if (!parsed.success) return { content: [{ type: "text", text: "Invalid arguments." }] };
|
|
58
92
|
try {
|
|
59
93
|
const frameworkDir = getFrameworkDir(projectRoot);
|
|
60
94
|
const kbDir = path.join(projectRoot, frameworkDir, "knowledge");
|
|
61
95
|
if (!fs.existsSync(kbDir)) fs.mkdirSync(kbDir, { recursive: true });
|
|
62
|
-
|
|
63
|
-
|
|
96
|
+
|
|
97
|
+
const fileName = parsed.data.topic.replace(/[^a-z0-9]/gi, "_").toLowerCase() + ".md";
|
|
98
|
+
const tags = (parsed as any).data.tags || [];
|
|
99
|
+
|
|
100
|
+
let finalContent = parsed.data.content;
|
|
101
|
+
if (!finalContent.startsWith("---")) {
|
|
102
|
+
const frontmatter = `---\ntitle: ${parsed.data.topic}\ntags: [${tags.join(", ")}]\nlast_updated: ${new Date().toISOString()}\n---\n\n`;
|
|
103
|
+
finalContent = frontmatter + finalContent;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
fs.writeFileSync(path.join(kbDir, fileName), finalContent);
|
|
107
|
+
return { content: [{ type: "text", text: `Obsidian Wiki updated: ${parsed.data.topic}` }] };
|
|
64
108
|
} catch (error) {
|
|
65
109
|
return { content: [{ type: "text", text: "Failed to update knowledge base." }] };
|
|
66
110
|
}
|
|
67
111
|
},
|
|
112
|
+
get_knowledge_graph: async (args: unknown, projectRoot: string) => {
|
|
113
|
+
try {
|
|
114
|
+
const frameworkDir = getFrameworkDir(projectRoot);
|
|
115
|
+
const kbDir = path.join(projectRoot, frameworkDir, "knowledge");
|
|
116
|
+
if (!fs.existsSync(kbDir)) return { content: [{ type: "text", text: "Knowledge base empty." }] };
|
|
117
|
+
|
|
118
|
+
const files = fs.readdirSync(kbDir).filter(f => f.endsWith(".md"));
|
|
119
|
+
let mermaid = "graph TD\n";
|
|
120
|
+
|
|
121
|
+
files.forEach(file => {
|
|
122
|
+
const content = fs.readFileSync(path.join(kbDir, file), "utf-8");
|
|
123
|
+
const { metadata } = parseFrontmatter(content);
|
|
124
|
+
const id = file.replace(".md", "");
|
|
125
|
+
const label = metadata.title || id;
|
|
126
|
+
|
|
127
|
+
mermaid += ` ${id}["${label}"]\n`;
|
|
128
|
+
|
|
129
|
+
if (metadata.related) {
|
|
130
|
+
const related = metadata.related.replace(/[\[\]]/g, "").split(",");
|
|
131
|
+
related.forEach((r: string) => {
|
|
132
|
+
mermaid += ` ${id} --> ${r.trim().replace(".md", "")}\n`;
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
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." }] };
|
|
140
|
+
}
|
|
141
|
+
}
|
|
68
142
|
};
|
|
@@ -35,7 +35,7 @@ export const memoryTools = [
|
|
|
35
35
|
];
|
|
36
36
|
|
|
37
37
|
export const memoryHandlers = {
|
|
38
|
-
update_project_memory: async (args:
|
|
38
|
+
update_project_memory: async (args: unknown, projectRoot: string) => {
|
|
39
39
|
const parsed = UPDATE_MEMORY_ARGS_SCHEMA.safeParse(args ?? {});
|
|
40
40
|
if (!parsed.success) return { content: [{ type: "text", text: "Invalid section or content." }] };
|
|
41
41
|
const { section, content } = parsed.data;
|
|
@@ -59,7 +59,9 @@ export const memoryHandlers = {
|
|
|
59
59
|
memoryContent = prependToSection(memoryContent, h, content);
|
|
60
60
|
updated = true;
|
|
61
61
|
break;
|
|
62
|
-
} catch (e) {
|
|
62
|
+
} catch (e) {
|
|
63
|
+
// Ignore section not found
|
|
64
|
+
}
|
|
63
65
|
}
|
|
64
66
|
if (!updated) throw new Error("HISTORY section not found.");
|
|
65
67
|
} else if (section === "CURRENT STATUS") {
|
|
@@ -75,7 +77,7 @@ export const memoryHandlers = {
|
|
|
75
77
|
return { content: [{ type: "text", text: `Memory update failed: ${error instanceof Error ? error.message : "Unknown error"}` }] };
|
|
76
78
|
}
|
|
77
79
|
},
|
|
78
|
-
read_project_memory: async (args:
|
|
80
|
+
read_project_memory: async (args: unknown, projectRoot: string) => {
|
|
79
81
|
try {
|
|
80
82
|
const frameworkDir = getFrameworkDir(projectRoot);
|
|
81
83
|
const memoryPath = path.join(projectRoot, frameworkDir, "PROJECT_MEMORY.md");
|
|
@@ -3,38 +3,63 @@ import path from "path";
|
|
|
3
3
|
import { getFrameworkDir } from "../utils.js";
|
|
4
4
|
import {
|
|
5
5
|
SEND_AGENT_MESSAGE_ARGS_SCHEMA,
|
|
6
|
-
READ_AGENT_MESSAGES_ARGS_SCHEMA
|
|
6
|
+
READ_AGENT_MESSAGES_ARGS_SCHEMA,
|
|
7
|
+
GET_AGENT_INBOX_STATS_ARGS_SCHEMA
|
|
7
8
|
} from "../schemas.js";
|
|
8
9
|
|
|
9
10
|
export const messageTools = [
|
|
10
11
|
{
|
|
11
12
|
name: "send_agent_message",
|
|
12
|
-
description: "Sends a message to another specialized agent
|
|
13
|
+
description: "Sends a message to another specialized agent following the Hermes Protocol.",
|
|
13
14
|
inputSchema: {
|
|
14
15
|
type: "object",
|
|
15
16
|
properties: {
|
|
16
17
|
to: { type: "string", description: "Recipient agent name" },
|
|
17
18
|
message: { type: "string", description: "The message content" },
|
|
18
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" },
|
|
19
22
|
},
|
|
20
23
|
required: ["to", "message", "traceId"],
|
|
21
24
|
},
|
|
22
25
|
},
|
|
23
26
|
{
|
|
24
27
|
name: "read_agent_messages",
|
|
25
|
-
description: "Reads messages sent to the current agent.",
|
|
28
|
+
description: "Reads messages sent to the current agent. Filters by Trace ID if provided.",
|
|
26
29
|
inputSchema: {
|
|
27
30
|
type: "object",
|
|
28
31
|
properties: {
|
|
29
32
|
agent: { type: "string", description: "Current agent name reading their messages" },
|
|
33
|
+
traceId: { type: "string", description: "Optional Trace ID to filter messages" },
|
|
34
|
+
},
|
|
35
|
+
required: ["agent"],
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: "get_agent_inbox_stats",
|
|
40
|
+
description: "Returns statistics about the agent's inbox (total, unread, priority distribution).",
|
|
41
|
+
inputSchema: {
|
|
42
|
+
type: "object",
|
|
43
|
+
properties: {
|
|
44
|
+
agent: { type: "string", description: "Agent name" },
|
|
30
45
|
},
|
|
31
46
|
required: ["agent"],
|
|
32
47
|
},
|
|
33
48
|
},
|
|
34
49
|
];
|
|
35
50
|
|
|
51
|
+
interface Message {
|
|
52
|
+
timestamp: string;
|
|
53
|
+
from: string;
|
|
54
|
+
traceId: string;
|
|
55
|
+
category: "ACTION" | "DELEGATION" | "INFO" | "ALERT";
|
|
56
|
+
priority: "LOW" | "MEDIUM" | "HIGH" | "URGENT";
|
|
57
|
+
content: string;
|
|
58
|
+
read: boolean;
|
|
59
|
+
}
|
|
60
|
+
|
|
36
61
|
export const messageHandlers = {
|
|
37
|
-
send_agent_message: async (args:
|
|
62
|
+
send_agent_message: async (args: unknown, projectRoot: string) => {
|
|
38
63
|
const parsed = SEND_AGENT_MESSAGE_ARGS_SCHEMA.safeParse(args ?? {});
|
|
39
64
|
if (!parsed.success) return { content: [{ type: "text", text: "Invalid message arguments." }] };
|
|
40
65
|
try {
|
|
@@ -43,15 +68,23 @@ export const messageHandlers = {
|
|
|
43
68
|
const messagesDir = path.join(projectRoot, frameworkDir, "messages");
|
|
44
69
|
if (!fs.existsSync(messagesDir)) fs.mkdirSync(messagesDir, { recursive: true });
|
|
45
70
|
const messagePath = path.join(messagesDir, `${recipient}.json`);
|
|
46
|
-
|
|
47
|
-
messages.push({
|
|
71
|
+
const messages = (fs.existsSync(messagePath) ? JSON.parse(fs.readFileSync(messagePath, "utf-8")) : []) as Message[];
|
|
72
|
+
messages.push({
|
|
73
|
+
timestamp: new Date().toISOString(),
|
|
74
|
+
from: "manager",
|
|
75
|
+
traceId: parsed.data.traceId,
|
|
76
|
+
category: parsed.data.category as any,
|
|
77
|
+
priority: parsed.data.priority as any,
|
|
78
|
+
content: parsed.data.message,
|
|
79
|
+
read: false
|
|
80
|
+
});
|
|
48
81
|
fs.writeFileSync(messagePath, JSON.stringify(messages, null, 2));
|
|
49
|
-
return { content: [{ type: "text", text: `Message sent to @${recipient}.` }] };
|
|
82
|
+
return { content: [{ type: "text", text: `Hermes: Message sent to @${recipient} [${parsed.data.category} | ${parsed.data.priority}].` }] };
|
|
50
83
|
} catch (error) {
|
|
51
|
-
return { content: [{ type: "text", text: "Failed to send message." }] };
|
|
84
|
+
return { content: [{ type: "text", text: "Failed to send message via Hermes." }] };
|
|
52
85
|
}
|
|
53
86
|
},
|
|
54
|
-
read_agent_messages: async (args:
|
|
87
|
+
read_agent_messages: async (args: unknown, projectRoot: string) => {
|
|
55
88
|
const parsed = READ_AGENT_MESSAGES_ARGS_SCHEMA.safeParse(args ?? {});
|
|
56
89
|
if (!parsed.success) return { content: [{ type: "text", text: "Invalid agent name." }] };
|
|
57
90
|
try {
|
|
@@ -59,12 +92,47 @@ export const messageHandlers = {
|
|
|
59
92
|
const agentName = parsed.data.agent.replace(/^@/, "");
|
|
60
93
|
const messagePath = path.join(projectRoot, frameworkDir, "messages", `${agentName}.json`);
|
|
61
94
|
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
|
|
64
|
-
|
|
65
|
-
|
|
95
|
+
const messages = JSON.parse(fs.readFileSync(messagePath, "utf-8")) as Message[];
|
|
96
|
+
const unread = messages.filter((m) => !m.read);
|
|
97
|
+
|
|
98
|
+
// Mark as read
|
|
99
|
+
fs.writeFileSync(messagePath, JSON.stringify(messages.map((m) => ({ ...m, read: true })), null, 2));
|
|
100
|
+
|
|
101
|
+
if (unread.length === 0) return { content: [{ type: "text", text: "No new messages." }] };
|
|
102
|
+
|
|
103
|
+
const formatted = unread.map((m) =>
|
|
104
|
+
`- [${m.priority}] **${m.from}** (${m.category}): ${m.content} *(Trace: ${m.traceId})*`
|
|
105
|
+
).join("\n");
|
|
106
|
+
|
|
107
|
+
return { content: [{ type: "text", text: `### HERMES INBOX: @${agentName}\n\n${formatted}` }] };
|
|
66
108
|
} catch (error) {
|
|
67
109
|
return { content: [{ type: "text", text: "Failed to read messages." }] };
|
|
68
110
|
}
|
|
69
111
|
},
|
|
112
|
+
get_agent_inbox_stats: async (args: unknown, projectRoot: string) => {
|
|
113
|
+
const parsed = GET_AGENT_INBOX_STATS_ARGS_SCHEMA.safeParse(args ?? {});
|
|
114
|
+
if (!parsed.success) return { content: [{ type: "text", text: "Invalid agent name." }] };
|
|
115
|
+
try {
|
|
116
|
+
const frameworkDir = getFrameworkDir(projectRoot);
|
|
117
|
+
const agentName = parsed.data.agent.replace(/^@/, "");
|
|
118
|
+
const messagePath = path.join(projectRoot, frameworkDir, "messages", `${agentName}.json`);
|
|
119
|
+
if (!fs.existsSync(messagePath)) return { content: [{ type: "text", text: "Inbox empty." }] };
|
|
120
|
+
const messages = JSON.parse(fs.readFileSync(messagePath, "utf-8")) as Message[];
|
|
121
|
+
const unread = messages.filter(m => !m.read);
|
|
122
|
+
|
|
123
|
+
const priorityDist = messages.reduce((acc, m) => {
|
|
124
|
+
acc[m.priority] = (acc[m.priority] || 0) + 1;
|
|
125
|
+
return acc;
|
|
126
|
+
}, {} as Record<string, number>);
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
content: [{
|
|
130
|
+
type: "text",
|
|
131
|
+
text: `### INBOX STATS: @${agentName}\n- Total: ${messages.length}\n- Unread: ${unread.length}\n- Priority Distribution: ${JSON.stringify(priorityDist)}`
|
|
132
|
+
}]
|
|
133
|
+
};
|
|
134
|
+
} catch (error) {
|
|
135
|
+
return { content: [{ type: "text", text: "Failed to get inbox stats." }] };
|
|
136
|
+
}
|
|
137
|
+
}
|
|
70
138
|
};
|
|
@@ -35,7 +35,7 @@ export const repositoryTools = [
|
|
|
35
35
|
];
|
|
36
36
|
|
|
37
37
|
export const repositoryHandlers = {
|
|
38
|
-
validate_repository_health: async (args:
|
|
38
|
+
validate_repository_health: async (args: unknown, projectRoot: string) => {
|
|
39
39
|
const parsed = VALIDATE_REPOSITORY_HEALTH_ARGS_SCHEMA.safeParse(args ?? {});
|
|
40
40
|
const scope = parsed.success ? parsed.data.scope : "full";
|
|
41
41
|
try {
|
|
@@ -52,7 +52,7 @@ export const repositoryHandlers = {
|
|
|
52
52
|
return { content: [{ type: "text", text: "Health validation failed." }] };
|
|
53
53
|
}
|
|
54
54
|
},
|
|
55
|
-
analyze_documentation_debt: async (args:
|
|
55
|
+
analyze_documentation_debt: async (args: unknown, projectRoot: string) => {
|
|
56
56
|
const parsed = ANALYZE_DOCUMENTATION_DEBT_ARGS_SCHEMA.safeParse(args ?? {});
|
|
57
57
|
const targetPath = parsed.success ? parsed.data.path : ".";
|
|
58
58
|
try {
|
|
@@ -64,7 +64,12 @@ export const repositoryHandlers = {
|
|
|
64
64
|
const relativePath = path.relative(projectRoot, sourceFile.getFilePath());
|
|
65
65
|
if (relativePath.includes("node_modules") || relativePath.includes("dist")) continue;
|
|
66
66
|
sourceFile.getExportedDeclarations().forEach((declarations, name) => {
|
|
67
|
-
declarations.forEach(decl => {
|
|
67
|
+
declarations.forEach(decl => {
|
|
68
|
+
const d = decl as unknown as { getJsDocs?: () => unknown[] };
|
|
69
|
+
if (typeof d.getJsDocs === "function" && d.getJsDocs().length === 0) {
|
|
70
|
+
missingJSDoc.push(`${relativePath} -> ${name}`);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
68
73
|
});
|
|
69
74
|
}
|
|
70
75
|
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")));
|
|
@@ -43,7 +43,7 @@ export const securityTools = [
|
|
|
43
43
|
];
|
|
44
44
|
|
|
45
45
|
export const securityHandlers = {
|
|
46
|
-
security_audit_scan: async (args:
|
|
46
|
+
security_audit_scan: async (args: unknown, projectRoot: string) => {
|
|
47
47
|
const parsed = SECURITY_AUDIT_ARGS_SCHEMA.safeParse(args ?? {});
|
|
48
48
|
if (!parsed.success) return { content: [{ type: "text", text: "Invalid path argument." }] };
|
|
49
49
|
const vulnerabilities: string[] = [];
|
|
@@ -86,7 +86,7 @@ export const securityHandlers = {
|
|
|
86
86
|
return { content: [{ type: "text", text: "Security scan failed." }] };
|
|
87
87
|
}
|
|
88
88
|
},
|
|
89
|
-
analyze_constitution_compliance: async (args:
|
|
89
|
+
analyze_constitution_compliance: async (args: unknown, projectRoot: string) => {
|
|
90
90
|
const parsed = ANALYZE_CONSTITUTION_COMPLIANCE_ARGS_SCHEMA.safeParse(args ?? {});
|
|
91
91
|
if (!parsed.success) return { content: [{ type: "text", text: "Invalid path argument." }] };
|
|
92
92
|
try {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from "path";
|
|
2
2
|
import fs from "fs";
|
|
3
3
|
|
|
4
|
-
export const FRAMEWORK_VERSION = "0.
|
|
4
|
+
export const FRAMEWORK_VERSION = "0.5.1";
|
|
5
5
|
|
|
6
6
|
export function getFrameworkDir(projectRoot: string): string {
|
|
7
7
|
const adapters = [".gemini", ".claude", ".cursor", ".codex", ".enderun"];
|
|
@@ -79,7 +79,7 @@ export function collectMarkdownArtifacts(projectRoot: string): string[] {
|
|
|
79
79
|
|
|
80
80
|
export function replaceSectionContent(markdown: string, sectionTitle: string, newBody: string): string {
|
|
81
81
|
const escaped = sectionTitle.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
82
|
-
const sectionRegex = new RegExp(`## ${escaped}[\\s\\S]*?(?=\\n## |$)
|
|
82
|
+
const sectionRegex = new RegExp(`## ${escaped}[\\s\\S]*?(?=\\n## |$)`);
|
|
83
83
|
if (!sectionRegex.test(markdown)) {
|
|
84
84
|
throw new Error(`Section not found: ${sectionTitle}`);
|
|
85
85
|
}
|
|
@@ -88,7 +88,7 @@ export function replaceSectionContent(markdown: string, sectionTitle: string, ne
|
|
|
88
88
|
|
|
89
89
|
export function prependToSection(markdown: string, sectionTitle: string, contentToPrepend: string): string {
|
|
90
90
|
const escaped = sectionTitle.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
91
|
-
const sectionRegex = new RegExp(`(## ${escaped}\\n)([\\s\\S]*?)(?=\\n## |$)
|
|
91
|
+
const sectionRegex = new RegExp(`(## ${escaped}\\n)([\\s\\S]*?)(?=\\n## |$)`);
|
|
92
92
|
const match = markdown.match(sectionRegex);
|
|
93
93
|
if (!match) {
|
|
94
94
|
throw new Error(`Section not found: ${sectionTitle}`);
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": "0.1.11",
|
|
3
|
-
"last_updated": "2026-05-
|
|
4
|
-
"contract_hash": "
|
|
3
|
+
"last_updated": "2026-05-15T21:03:22.437Z",
|
|
4
|
+
"contract_hash": "e9dff509023440c66449ca811b8cbbe3e159ca95c8a328976f01771dee62b7af",
|
|
5
5
|
"breaking_changes": [
|
|
6
|
-
{
|
|
6
|
+
{
|
|
7
|
+
"version": "0.1.11",
|
|
8
|
+
"description": "Extended shared types for Enderun Brain Dashboard"
|
|
9
|
+
}
|
|
7
10
|
],
|
|
8
11
|
"deprecated_versions": []
|
|
9
|
-
}
|
|
12
|
+
}
|