agent-enderun 0.5.0 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.enderun/BRAIN_DASHBOARD.md +0 -0
- package/.enderun/PROJECT_MEMORY.md +40 -1
- package/.enderun/STATUS.md +2 -0
- package/.enderun/agents/analyst.md +8 -8
- package/.enderun/agents/backend.md +11 -11
- package/.enderun/agents/explorer.md +4 -4
- package/.enderun/agents/frontend.md +7 -7
- package/.enderun/agents/git.md +5 -5
- package/.enderun/agents/manager.md +12 -12
- package/.enderun/agents/mobile.md +5 -5
- package/.enderun/agents/native.md +5 -5
- package/.enderun/benchmarks/.gitkeep +0 -0
- package/.enderun/cli-commands.json +0 -0
- package/.enderun/config.json +0 -0
- package/.enderun/docs/api/README.md +0 -0
- package/.enderun/docs/api/auth.md +0 -0
- package/.enderun/docs/api/errors.md +0 -0
- package/.enderun/docs/error-handling.md +0 -0
- package/.enderun/docs/privacy.md +0 -0
- package/.enderun/docs/project-docs.md +0 -0
- package/.enderun/docs/security.md +0 -0
- package/.enderun/docs/tech-stack.md +38 -10
- package/.enderun/docs/troubleshooting.md +0 -0
- package/.enderun/knowledge/api_design_rules.md +0 -0
- package/.enderun/knowledge/async_error_handling.md +0 -0
- package/.enderun/knowledge/branded_types_pattern.md +7 -0
- package/.enderun/knowledge/code_review_checklist.md +0 -0
- package/.enderun/knowledge/contract_versioning.md +0 -0
- package/.enderun/knowledge/database_migration.md +0 -0
- package/.enderun/knowledge/deployment_checklist.md +0 -0
- package/.enderun/knowledge/git_commit_strategy.md +0 -0
- package/.enderun/knowledge/hermes_protocol.md +59 -0
- package/.enderun/knowledge/legacy_onboarding.md +0 -0
- package/.enderun/knowledge/monitoring_setup.md +0 -0
- package/.enderun/knowledge/performance_guidelines.md +0 -0
- package/.enderun/knowledge/repository_patterns.md +0 -0
- package/.enderun/knowledge/responsive_design_standards.md +0 -0
- package/.enderun/knowledge/security_scanning.md +0 -0
- package/.enderun/knowledge/testing_standards.md +1 -1
- package/.enderun/knowledge/troubleshooting_guide.md +0 -0
- package/.enderun/knowledge/zero_ui_library_policy.md +8 -4
- package/.enderun/logs/.gitkeep +0 -0
- package/.enderun/messages/.gitkeep +0 -0
- package/.enderun/monitoring/.gitkeep +0 -0
- package/.env.example +0 -0
- package/ENDERUN.md +10 -5
- package/LICENSE +0 -0
- package/README.md +93 -45
- package/bin/cli.js +633 -3
- package/bin/update-contract.js +63 -0
- package/claude.md +2 -2
- package/codex.md +2 -2
- package/cursor.md +2 -2
- package/docs/README.md +23 -0
- package/gemini-extension.json +8 -2
- package/gemini.md +2 -2
- package/mcp.json +0 -0
- package/package.json +4 -3
- package/packages/framework-mcp/dist/index.js +0 -0
- package/packages/framework-mcp/dist/schemas.js +16 -0
- package/packages/framework-mcp/dist/tools/contract.js +13 -7
- 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/messages.js +73 -11
- package/packages/framework-mcp/dist/tools/repository.js +24 -3
- package/packages/framework-mcp/dist/utils.js +2 -2
- package/packages/framework-mcp/package.json +2 -2
- package/packages/framework-mcp/src/schemas.ts +20 -0
- package/packages/framework-mcp/src/tools/contract.ts +14 -7
- package/packages/framework-mcp/src/tools/framework.ts +24 -0
- package/packages/framework-mcp/src/tools/knowledge.ts +86 -12
- package/packages/framework-mcp/src/tools/messages.ts +80 -11
- package/packages/framework-mcp/src/tools/repository.ts +24 -3
- package/packages/framework-mcp/src/utils.ts +2 -2
- package/packages/shared-types/README.md +0 -0
- package/packages/shared-types/contract.version.json +6 -3
- package/packages/shared-types/package.json +4 -4
- package/packages/shared-types/src/index.ts +0 -0
- package/packages/shared-types/tsconfig.json +0 -0
- package/panda.config.ts +5 -1
- package/tsconfig.json +0 -0
- package/.enderun/ENDERUN.md +0 -205
- package/packages/framework-mcp/dist/index.d.ts +0 -1
- package/packages/shared-types/dist/index.d.ts.map +0 -1
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import crypto from "crypto";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = path.dirname(__filename);
|
|
10
|
+
const projectRoot = path.join(__dirname, "..");
|
|
11
|
+
|
|
12
|
+
const contractPath = path.join(projectRoot, "packages/shared-types/contract.version.json");
|
|
13
|
+
const sharedTypesDir = path.join(projectRoot, "packages/shared-types/src");
|
|
14
|
+
|
|
15
|
+
function collectTypeFiles(dir) {
|
|
16
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
17
|
+
const files = [];
|
|
18
|
+
|
|
19
|
+
for (const entry of entries) {
|
|
20
|
+
const fullPath = path.join(dir, entry.name);
|
|
21
|
+
if (entry.isDirectory()) {
|
|
22
|
+
files.push(...collectTypeFiles(fullPath));
|
|
23
|
+
} else if (entry.name.endsWith(".ts")) {
|
|
24
|
+
files.push(fullPath);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return files;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function calculateHash(dir) {
|
|
32
|
+
const hash = crypto.createHash("sha256");
|
|
33
|
+
const files = collectTypeFiles(dir).sort();
|
|
34
|
+
|
|
35
|
+
files.forEach(file => {
|
|
36
|
+
const content = fs.readFileSync(file);
|
|
37
|
+
hash.update(path.relative(projectRoot, file));
|
|
38
|
+
hash.update("\0");
|
|
39
|
+
hash.update(content);
|
|
40
|
+
hash.update("\0");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
return hash.digest("hex");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
console.log("🔍 Calculating shared-types contract hash...");
|
|
48
|
+
const newHash = calculateHash(sharedTypesDir);
|
|
49
|
+
|
|
50
|
+
const contract = JSON.parse(fs.readFileSync(contractPath, "utf-8"));
|
|
51
|
+
|
|
52
|
+
if (contract.contract_hash === newHash) {
|
|
53
|
+
console.log("✅ Contract hash is already up to date.");
|
|
54
|
+
} else {
|
|
55
|
+
contract.contract_hash = newHash;
|
|
56
|
+
contract.last_updated = new Date().toISOString();
|
|
57
|
+
fs.writeFileSync(contractPath, JSON.stringify(contract, null, 2));
|
|
58
|
+
console.log(`🚀 Contract updated with new hash: ${newHash.slice(0, 10)}...`);
|
|
59
|
+
}
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.error("❌ Failed to update contract hash:", error.message);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
package/claude.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
This file is the entry point for Claude. The project's "Supreme Law" and all instructions are located in the following file:
|
|
4
4
|
|
|
5
|
-
👉 **[
|
|
5
|
+
👉 **[.claude/ENDERUN.md](.claude/ENDERUN.md)**
|
|
6
6
|
|
|
7
7
|
### 🤖 AGENT DIRECTIVE
|
|
8
|
-
You
|
|
8
|
+
You are ALWAYS operating as `@manager` (Team-Lead) by default — on every message, every turn. The user does NOT need to type `@manager`. Typing a specific agent (e.g. `@backend`) explicitly will override this and activate that specialist role directly. You are responsible for analysis and delegation.
|
|
9
9
|
|
|
10
10
|
Please read the Supreme Law thoroughly before taking any action.
|
package/codex.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
This file is the entry point for Codex. The project's "Supreme Law" and all instructions are located in the following file:
|
|
4
4
|
|
|
5
|
-
👉 **[
|
|
5
|
+
👉 **[.enderun/ENDERUN.md](.enderun/ENDERUN.md)**
|
|
6
6
|
|
|
7
7
|
### 🤖 AGENT DIRECTIVE
|
|
8
|
-
You
|
|
8
|
+
You are ALWAYS operating as `@manager` (Team-Lead) by default — on every message, every turn. The user does NOT need to type `@manager`. Typing a specific agent (e.g. `@backend`) explicitly will override this and activate that specialist role directly. You are responsible for analysis and delegation.
|
|
9
9
|
|
|
10
10
|
Please read the Supreme Law thoroughly before taking any action.
|
package/cursor.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
This file is the entry point for Cursor. The project's "Supreme Law" and all instructions are located in the following file:
|
|
4
4
|
|
|
5
|
-
👉 **[
|
|
5
|
+
👉 **[.cursor/ENDERUN.md](.cursor/ENDERUN.md)**
|
|
6
6
|
|
|
7
7
|
### 🤖 AGENT DIRECTIVE
|
|
8
|
-
You
|
|
8
|
+
You are ALWAYS operating as `@manager` (Team-Lead) by default — on every message, every turn. The user does NOT need to type `@manager`. Typing a specific agent (e.g. `@backend`) explicitly will override this and activate that specialist role directly. You are responsible for analysis and delegation.
|
|
9
9
|
|
|
10
10
|
Please read the Supreme Law thoroughly before taking any action.
|
package/docs/README.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Project Documentation
|
|
2
|
+
|
|
3
|
+
This folder is managed by **Agent Enderun agents** and contains documentation
|
|
4
|
+
generated for **your project** — not the framework itself.
|
|
5
|
+
|
|
6
|
+
## What Goes Here
|
|
7
|
+
|
|
8
|
+
| File | Owner Agent | Description |
|
|
9
|
+
| :--- | :--- | :--- |
|
|
10
|
+
| `architecture.md` | @backend | System architecture and data flow diagrams |
|
|
11
|
+
| `api-reference.md` | @backend | Public API endpoints and usage examples |
|
|
12
|
+
| `components.md` | @frontend | UI component catalog and usage guidelines |
|
|
13
|
+
| `deployment.md` | @manager | Deployment steps and environment setup |
|
|
14
|
+
| `decisions.md` | @manager | Architecture Decision Records (ADRs) |
|
|
15
|
+
|
|
16
|
+
## Distinction from `.enderun/docs/`
|
|
17
|
+
|
|
18
|
+
| Path | Purpose |
|
|
19
|
+
| :--- | :--- |
|
|
20
|
+
| `/docs/` | **Your project** documentation — written by agents as features are built |
|
|
21
|
+
| `/.enderun/docs/` | **Framework** configuration — tech-stack, security policy, privacy policy |
|
|
22
|
+
|
|
23
|
+
> Agents will populate this folder automatically during **PHASE_2 (Core Development)**.
|
package/gemini-extension.json
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "Agent Enderun",
|
|
3
3
|
"description": "Enterprise-grade AI orchestration framework for software teams.",
|
|
4
|
-
"instructions": "
|
|
5
|
-
|
|
4
|
+
"instructions": ".gemini/ENDERUN.md",
|
|
5
|
+
"mcpServers": {
|
|
6
|
+
"agent-enderun": {
|
|
7
|
+
"command": "node",
|
|
8
|
+
"args": ["packages/framework-mcp/dist/index.js"]
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
}
|
package/gemini.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
This file is the entry point for Gemini. The project's "Supreme Law" and all instructions are located in the following file:
|
|
4
4
|
|
|
5
|
-
👉 **[
|
|
5
|
+
👉 **[.gemini/ENDERUN.md](.gemini/ENDERUN.md)**
|
|
6
6
|
|
|
7
7
|
### 🤖 AGENT DIRECTIVE
|
|
8
|
-
You
|
|
8
|
+
You are ALWAYS operating as `@manager` (Team-Lead) by default — on every message, every turn. The user does NOT need to type `@manager`. Typing a specific agent (e.g. `@backend`) explicitly will override this and activate that specialist role directly. You are responsible for analysis and delegation.
|
|
9
9
|
|
|
10
10
|
Please read the Supreme Law thoroughly before taking any action.
|
package/mcp.json
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-enderun",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"description": "The Supreme AI Governance & Orchestration Framework for Enterprise Development",
|
|
5
5
|
"author": "Yusuf BEKAR",
|
|
6
6
|
"license": "MIT",
|
|
@@ -51,7 +51,8 @@
|
|
|
51
51
|
"gemini-extension.json",
|
|
52
52
|
"cursor.md",
|
|
53
53
|
"claude.md",
|
|
54
|
-
"codex.md"
|
|
54
|
+
"codex.md",
|
|
55
|
+
"docs"
|
|
55
56
|
],
|
|
56
57
|
"scripts": {
|
|
57
58
|
"enderun:build": "npm run build --workspaces --if-present",
|
|
@@ -78,7 +79,7 @@
|
|
|
78
79
|
"vitest": "^3.0.5"
|
|
79
80
|
},
|
|
80
81
|
"enderun": {
|
|
81
|
-
"version": "0.5.
|
|
82
|
+
"version": "0.5.2",
|
|
82
83
|
"initializedAt": "2026-05-09T13:24:27.472Z"
|
|
83
84
|
}
|
|
84
85
|
}
|
|
File without changes
|
|
@@ -29,6 +29,8 @@ export const SEND_AGENT_MESSAGE_ARGS_SCHEMA = z.object({
|
|
|
29
29
|
to: z.string().min(1),
|
|
30
30
|
message: z.string().min(1),
|
|
31
31
|
traceId: z.string().min(1),
|
|
32
|
+
category: z.enum(["ACTION", "DELEGATION", "INFO", "ALERT"]).default("INFO"),
|
|
33
|
+
priority: z.enum(["LOW", "MEDIUM", "HIGH", "URGENT"]).default("MEDIUM"),
|
|
32
34
|
});
|
|
33
35
|
export const SEARCH_KNOWLEDGE_BASE_ARGS_SCHEMA = z.object({
|
|
34
36
|
query: z.string().min(1),
|
|
@@ -36,6 +38,7 @@ export const SEARCH_KNOWLEDGE_BASE_ARGS_SCHEMA = z.object({
|
|
|
36
38
|
export const UPDATE_KNOWLEDGE_BASE_ARGS_SCHEMA = z.object({
|
|
37
39
|
topic: z.string().min(1),
|
|
38
40
|
content: z.string().min(1),
|
|
41
|
+
tags: z.array(z.string().min(1)).default([]),
|
|
39
42
|
});
|
|
40
43
|
export const ANALYZE_DATABASE_SCHEMA_ARGS_SCHEMA = z.object({
|
|
41
44
|
path: z.string().default("apps/backend"),
|
|
@@ -61,6 +64,7 @@ export const VALIDATE_REPOSITORY_HEALTH_ARGS_SCHEMA = z.object({
|
|
|
61
64
|
});
|
|
62
65
|
export const READ_AGENT_MESSAGES_ARGS_SCHEMA = z.object({
|
|
63
66
|
agent: z.string().min(1),
|
|
67
|
+
traceId: z.string().min(1).optional(),
|
|
64
68
|
});
|
|
65
69
|
export const VERIFY_CONTRACT_INTEGRITY_ARGS_SCHEMA = z.object({
|
|
66
70
|
domain: z.string().min(1),
|
|
@@ -82,3 +86,15 @@ export const GET_AGENT_AUDIT_REPORT_ARGS_SCHEMA = z.object({
|
|
|
82
86
|
agent: z.string().min(1),
|
|
83
87
|
days: z.number().default(7),
|
|
84
88
|
});
|
|
89
|
+
export const GET_AGENT_INBOX_STATS_ARGS_SCHEMA = z.object({
|
|
90
|
+
agent: z.string().min(1),
|
|
91
|
+
});
|
|
92
|
+
export const GET_KNOWLEDGE_GRAPH_ARGS_SCHEMA = z.object({
|
|
93
|
+
tag: z.string().optional(),
|
|
94
|
+
});
|
|
95
|
+
export const SYNC_CONTRACT_HASH_ARGS_SCHEMA = z.object({
|
|
96
|
+
force: z.boolean().default(false),
|
|
97
|
+
});
|
|
98
|
+
export const VERIFY_FRAMEWORK_HEALTH_ARGS_SCHEMA = z.object({
|
|
99
|
+
detailed: z.boolean().default(false),
|
|
100
|
+
});
|
|
@@ -4,6 +4,17 @@ import crypto from "crypto";
|
|
|
4
4
|
import { Project } from "ts-morph";
|
|
5
5
|
import { getFrameworkDir, collectFilesRecursively } from "../utils.js";
|
|
6
6
|
import { VERIFY_CONTRACT_INTEGRITY_ARGS_SCHEMA } from "../schemas.js";
|
|
7
|
+
function calculateContractHash(sharedTypesDir, projectRoot) {
|
|
8
|
+
const files = collectFilesRecursively(sharedTypesDir, new Set(["ts"])).sort();
|
|
9
|
+
const hash = crypto.createHash("sha256");
|
|
10
|
+
files.forEach((filePath) => {
|
|
11
|
+
hash.update(path.relative(projectRoot, filePath));
|
|
12
|
+
hash.update("\0");
|
|
13
|
+
hash.update(fs.readFileSync(filePath));
|
|
14
|
+
hash.update("\0");
|
|
15
|
+
});
|
|
16
|
+
return hash.digest("hex");
|
|
17
|
+
}
|
|
7
18
|
export const contractTools = [
|
|
8
19
|
{
|
|
9
20
|
name: "verify_api_contract",
|
|
@@ -37,10 +48,7 @@ export const contractHandlers = {
|
|
|
37
48
|
const contractJsonPath = path.join(projectRoot, "packages/shared-types/contract.version.json");
|
|
38
49
|
if (!fs.existsSync(sharedTypesDir) || !fs.existsSync(contractJsonPath))
|
|
39
50
|
return { content: [{ type: "text", text: "Missing shared-types directory or contract.version.json" }] };
|
|
40
|
-
const
|
|
41
|
-
const hash = crypto.createHash("sha256");
|
|
42
|
-
files.forEach(f => hash.update(fs.readFileSync(f)));
|
|
43
|
-
const currentHash = hash.digest("hex");
|
|
51
|
+
const currentHash = calculateContractHash(sharedTypesDir, projectRoot);
|
|
44
52
|
const storedHash = JSON.parse(fs.readFileSync(contractJsonPath, "utf-8")).contract_hash;
|
|
45
53
|
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)}...).` }] };
|
|
46
54
|
}
|
|
@@ -57,9 +65,7 @@ export const contractHandlers = {
|
|
|
57
65
|
const files = collectFilesRecursively(sharedTypesDir, new Set(["ts"])).sort();
|
|
58
66
|
if (files.length === 0)
|
|
59
67
|
return { content: [{ type: "text", text: "⚠️ WARNING: No TypeScript files found in shared-types/src. Hash not updated." }] };
|
|
60
|
-
const
|
|
61
|
-
files.forEach(f => hash.update(fs.readFileSync(f)));
|
|
62
|
-
const currentHash = hash.digest("hex");
|
|
68
|
+
const currentHash = calculateContractHash(sharedTypesDir, projectRoot);
|
|
63
69
|
const contractJson = fs.existsSync(contractJsonPath) ? JSON.parse(fs.readFileSync(contractJsonPath, "utf-8")) : {};
|
|
64
70
|
contractJson.contract_hash = currentHash;
|
|
65
71
|
contractJson.last_updated = new Date().toISOString();
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
|
+
import { execSync } from "child_process";
|
|
3
4
|
import { getFrameworkDir, collectMarkdownArtifacts, FRAMEWORK_VERSION } from "../utils.js";
|
|
5
|
+
import { VERIFY_FRAMEWORK_HEALTH_ARGS_SCHEMA } from "../schemas.js";
|
|
4
6
|
export const frameworkTools = [
|
|
5
7
|
{
|
|
6
8
|
name: "get_framework_status",
|
|
@@ -37,6 +39,16 @@ export const frameworkTools = [
|
|
|
37
39
|
description: "Analyzes a pre-existing codebase to automatically generate the initial project memory, learning its tech stack and architecture.",
|
|
38
40
|
inputSchema: { type: "object", properties: {} },
|
|
39
41
|
},
|
|
42
|
+
{
|
|
43
|
+
name: "verify_framework_health",
|
|
44
|
+
description: "Runs the framework's internal 'check' command and reports system health status via MCP.",
|
|
45
|
+
inputSchema: {
|
|
46
|
+
type: "object",
|
|
47
|
+
properties: {
|
|
48
|
+
detailed: { type: "boolean", default: false, description: "Include detailed issues if any." },
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
},
|
|
40
52
|
];
|
|
41
53
|
export const frameworkHandlers = {
|
|
42
54
|
bootstrap_legacy_memory: async (args, projectRoot) => {
|
|
@@ -158,4 +170,17 @@ export const frameworkHandlers = {
|
|
|
158
170
|
get_system_time: async () => {
|
|
159
171
|
return { content: [{ type: "text", text: new Date().toISOString() }] };
|
|
160
172
|
},
|
|
173
|
+
verify_framework_health: async (args, projectRoot) => {
|
|
174
|
+
const parsed = VERIFY_FRAMEWORK_HEALTH_ARGS_SCHEMA.safeParse(args ?? {});
|
|
175
|
+
try {
|
|
176
|
+
// Run the internal CLI check command via tsx to ensure we test current state
|
|
177
|
+
// If the CLI is not yet linked, we might need a relative path
|
|
178
|
+
const cliPath = path.join(projectRoot, "bin/cli.js");
|
|
179
|
+
const output = execSync(`node ${cliPath} check`, { cwd: projectRoot, encoding: "utf-8", stdio: "pipe" });
|
|
180
|
+
return { content: [{ type: "text", text: `### FRAMEWORK HEALTH CHECK\n\n${output}` }] };
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
return { content: [{ type: "text", text: `### FRAMEWORK HEALTH CHECK (FAILED)\n\n${error.stdout || error.message}` }] };
|
|
184
|
+
}
|
|
185
|
+
}
|
|
161
186
|
};
|
|
@@ -5,28 +5,52 @@ import { SEARCH_KNOWLEDGE_BASE_ARGS_SCHEMA, UPDATE_KNOWLEDGE_BASE_ARGS_SCHEMA }
|
|
|
5
5
|
export const knowledgeTools = [
|
|
6
6
|
{
|
|
7
7
|
name: "search_knowledge_base",
|
|
8
|
-
description: "Searches the Academy's
|
|
8
|
+
description: "Searches the Academy's Obsidian-style knowledge base using keywords or tags.",
|
|
9
9
|
inputSchema: {
|
|
10
10
|
type: "object",
|
|
11
11
|
properties: {
|
|
12
|
-
query: { type: "string", description: "Search query or
|
|
12
|
+
query: { type: "string", description: "Search query, topic, or tag (e.g. #security)" },
|
|
13
13
|
},
|
|
14
14
|
required: ["query"],
|
|
15
15
|
},
|
|
16
16
|
},
|
|
17
17
|
{
|
|
18
18
|
name: "update_knowledge_base",
|
|
19
|
-
description: "Adds or updates an entry
|
|
19
|
+
description: "Adds or updates an entry with mandatory YAML frontmatter for Obsidian compatibility.",
|
|
20
20
|
inputSchema: {
|
|
21
21
|
type: "object",
|
|
22
22
|
properties: {
|
|
23
|
-
topic: { type: "string", description: "The topic or title
|
|
24
|
-
content: { type: "string", description: "
|
|
23
|
+
topic: { type: "string", description: "The topic or title" },
|
|
24
|
+
content: { type: "string", description: "Content (YAML frontmatter will be auto-generated if missing)" },
|
|
25
|
+
tags: { type: "array", items: { type: "string" }, description: "Tags for the entry" },
|
|
25
26
|
},
|
|
26
27
|
required: ["topic", "content"],
|
|
27
28
|
},
|
|
28
29
|
},
|
|
30
|
+
{
|
|
31
|
+
name: "get_knowledge_graph",
|
|
32
|
+
description: "Generates a Mermaid diagram showing relationships between knowledge entries based on 'related' metadata.",
|
|
33
|
+
inputSchema: {
|
|
34
|
+
type: "object",
|
|
35
|
+
properties: {
|
|
36
|
+
tag: { type: "string", description: "Filter by tag" },
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
},
|
|
29
40
|
];
|
|
41
|
+
function parseFrontmatter(content) {
|
|
42
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
43
|
+
if (!match)
|
|
44
|
+
return { metadata: {}, body: content };
|
|
45
|
+
const yaml = match[1];
|
|
46
|
+
const metadata = {};
|
|
47
|
+
yaml.split("\n").forEach(line => {
|
|
48
|
+
const [key, ...val] = line.split(":");
|
|
49
|
+
if (key && val.length > 0)
|
|
50
|
+
metadata[key.trim()] = val.join(":").trim();
|
|
51
|
+
});
|
|
52
|
+
return { metadata, body: content.replace(match[0], "").trim() };
|
|
53
|
+
}
|
|
30
54
|
export const knowledgeHandlers = {
|
|
31
55
|
search_knowledge_base: async (args, projectRoot) => {
|
|
32
56
|
const parsed = SEARCH_KNOWLEDGE_BASE_ARGS_SCHEMA.safeParse(args ?? {});
|
|
@@ -37,14 +61,20 @@ export const knowledgeHandlers = {
|
|
|
37
61
|
const kbDir = path.join(projectRoot, frameworkDir, "knowledge");
|
|
38
62
|
if (!fs.existsSync(kbDir))
|
|
39
63
|
return { content: [{ type: "text", text: "Knowledge base is empty." }] };
|
|
64
|
+
const query = parsed.data.query.toLowerCase();
|
|
40
65
|
const results = fs.readdirSync(kbDir).filter(f => f.endsWith(".md")).map(file => {
|
|
41
66
|
const content = fs.readFileSync(path.join(kbDir, file), "utf-8");
|
|
42
|
-
|
|
43
|
-
|
|
67
|
+
const { metadata, body } = parseFrontmatter(content);
|
|
68
|
+
const matchesQuery = body.toLowerCase().includes(query) ||
|
|
69
|
+
file.toLowerCase().includes(query) ||
|
|
70
|
+
(metadata.tags && metadata.tags.toLowerCase().includes(query)) ||
|
|
71
|
+
(metadata.title && metadata.title.toLowerCase().includes(query));
|
|
72
|
+
if (matchesQuery) {
|
|
73
|
+
return `### ${metadata.title || file.replace(".md", "")}\n**Tags:** ${metadata.tags || "None"}\n\n${body.slice(0, 300)}...`;
|
|
44
74
|
}
|
|
45
75
|
return null;
|
|
46
76
|
}).filter(Boolean);
|
|
47
|
-
return { content: [{ type: "text", text: results.length > 0 ? `### KNOWLEDGE BASE SEARCH RESULTS\n\n${results.join("\n\n---\n\n")}` : "No matching knowledge
|
|
77
|
+
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." }] };
|
|
48
78
|
}
|
|
49
79
|
catch (error) {
|
|
50
80
|
return { content: [{ type: "text", text: "Knowledge base search failed." }] };
|
|
@@ -53,17 +83,51 @@ export const knowledgeHandlers = {
|
|
|
53
83
|
update_knowledge_base: async (args, projectRoot) => {
|
|
54
84
|
const parsed = UPDATE_KNOWLEDGE_BASE_ARGS_SCHEMA.safeParse(args ?? {});
|
|
55
85
|
if (!parsed.success)
|
|
56
|
-
return { content: [{ type: "text", text: "Invalid
|
|
86
|
+
return { content: [{ type: "text", text: "Invalid arguments." }] };
|
|
57
87
|
try {
|
|
58
88
|
const frameworkDir = getFrameworkDir(projectRoot);
|
|
59
89
|
const kbDir = path.join(projectRoot, frameworkDir, "knowledge");
|
|
60
90
|
if (!fs.existsSync(kbDir))
|
|
61
91
|
fs.mkdirSync(kbDir, { recursive: true });
|
|
62
|
-
|
|
63
|
-
|
|
92
|
+
const fileName = parsed.data.topic.replace(/[^a-z0-9]/gi, "_").toLowerCase() + ".md";
|
|
93
|
+
const tags = parsed.data.tags;
|
|
94
|
+
let finalContent = parsed.data.content;
|
|
95
|
+
if (!finalContent.startsWith("---")) {
|
|
96
|
+
const frontmatter = `---\ntitle: ${parsed.data.topic}\ntags: [${tags.join(", ")}]\nlast_updated: ${new Date().toISOString()}\n---\n\n`;
|
|
97
|
+
finalContent = frontmatter + finalContent;
|
|
98
|
+
}
|
|
99
|
+
fs.writeFileSync(path.join(kbDir, fileName), finalContent);
|
|
100
|
+
return { content: [{ type: "text", text: `Obsidian Wiki updated: ${parsed.data.topic}` }] };
|
|
64
101
|
}
|
|
65
102
|
catch (error) {
|
|
66
103
|
return { content: [{ type: "text", text: "Failed to update knowledge base." }] };
|
|
67
104
|
}
|
|
68
105
|
},
|
|
106
|
+
get_knowledge_graph: async (args, projectRoot) => {
|
|
107
|
+
try {
|
|
108
|
+
const frameworkDir = getFrameworkDir(projectRoot);
|
|
109
|
+
const kbDir = path.join(projectRoot, frameworkDir, "knowledge");
|
|
110
|
+
if (!fs.existsSync(kbDir))
|
|
111
|
+
return { content: [{ type: "text", text: "Knowledge base empty." }] };
|
|
112
|
+
const files = fs.readdirSync(kbDir).filter(f => f.endsWith(".md"));
|
|
113
|
+
let mermaid = "graph TD\n";
|
|
114
|
+
files.forEach(file => {
|
|
115
|
+
const content = fs.readFileSync(path.join(kbDir, file), "utf-8");
|
|
116
|
+
const { metadata } = parseFrontmatter(content);
|
|
117
|
+
const id = file.replace(".md", "");
|
|
118
|
+
const label = metadata.title || id;
|
|
119
|
+
mermaid += ` ${id}["${label}"]\n`;
|
|
120
|
+
if (metadata.related) {
|
|
121
|
+
const related = metadata.related.replace(/[\[\]]/g, "").split(",");
|
|
122
|
+
related.forEach((r) => {
|
|
123
|
+
mermaid += ` ${id} --> ${r.trim().replace(".md", "")}\n`;
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
return { content: [{ type: "text", text: `### KNOWLEDGE GRAPH (Mermaid)\n\n\`\`\`mermaid\n${mermaid}\n\`\`\`` }] };
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
return { content: [{ type: "text", text: "Failed to generate knowledge graph." }] };
|
|
131
|
+
}
|
|
132
|
+
}
|
|
69
133
|
};
|
|
@@ -1,33 +1,51 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { getFrameworkDir } from "../utils.js";
|
|
4
|
-
import { SEND_AGENT_MESSAGE_ARGS_SCHEMA, READ_AGENT_MESSAGES_ARGS_SCHEMA } from "../schemas.js";
|
|
4
|
+
import { SEND_AGENT_MESSAGE_ARGS_SCHEMA, READ_AGENT_MESSAGES_ARGS_SCHEMA, GET_AGENT_INBOX_STATS_ARGS_SCHEMA } from "../schemas.js";
|
|
5
5
|
export const messageTools = [
|
|
6
6
|
{
|
|
7
7
|
name: "send_agent_message",
|
|
8
|
-
description: "Sends a message to another specialized agent
|
|
8
|
+
description: "Sends a message to another specialized agent following the Hermes Protocol.",
|
|
9
9
|
inputSchema: {
|
|
10
10
|
type: "object",
|
|
11
11
|
properties: {
|
|
12
12
|
to: { type: "string", description: "Recipient agent name" },
|
|
13
13
|
message: { type: "string", description: "The message content" },
|
|
14
14
|
traceId: { type: "string", description: "The active Trace ID" },
|
|
15
|
+
category: { type: "string", enum: ["ACTION", "DELEGATION", "INFO", "ALERT"], default: "INFO" },
|
|
16
|
+
priority: { type: "string", enum: ["LOW", "MEDIUM", "HIGH", "URGENT"], default: "MEDIUM" },
|
|
15
17
|
},
|
|
16
18
|
required: ["to", "message", "traceId"],
|
|
17
19
|
},
|
|
18
20
|
},
|
|
19
21
|
{
|
|
20
22
|
name: "read_agent_messages",
|
|
21
|
-
description: "Reads messages sent to the current agent.",
|
|
23
|
+
description: "Reads messages sent to the current agent. Filters by Trace ID if provided.",
|
|
22
24
|
inputSchema: {
|
|
23
25
|
type: "object",
|
|
24
26
|
properties: {
|
|
25
27
|
agent: { type: "string", description: "Current agent name reading their messages" },
|
|
28
|
+
traceId: { type: "string", description: "Optional Trace ID to filter messages" },
|
|
29
|
+
},
|
|
30
|
+
required: ["agent"],
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: "get_agent_inbox_stats",
|
|
35
|
+
description: "Returns statistics about the agent's inbox (total, unread, priority distribution).",
|
|
36
|
+
inputSchema: {
|
|
37
|
+
type: "object",
|
|
38
|
+
properties: {
|
|
39
|
+
agent: { type: "string", description: "Agent name" },
|
|
26
40
|
},
|
|
27
41
|
required: ["agent"],
|
|
28
42
|
},
|
|
29
43
|
},
|
|
30
44
|
];
|
|
45
|
+
function normalizeAgentName(agent) {
|
|
46
|
+
const normalized = agent.replace(/^@/, "").trim();
|
|
47
|
+
return /^[a-z0-9_-]+$/i.test(normalized) ? normalized : null;
|
|
48
|
+
}
|
|
31
49
|
export const messageHandlers = {
|
|
32
50
|
send_agent_message: async (args, projectRoot) => {
|
|
33
51
|
const parsed = SEND_AGENT_MESSAGE_ARGS_SCHEMA.safeParse(args ?? {});
|
|
@@ -35,18 +53,28 @@ export const messageHandlers = {
|
|
|
35
53
|
return { content: [{ type: "text", text: "Invalid message arguments." }] };
|
|
36
54
|
try {
|
|
37
55
|
const frameworkDir = getFrameworkDir(projectRoot);
|
|
38
|
-
const recipient = parsed.data.to
|
|
56
|
+
const recipient = normalizeAgentName(parsed.data.to);
|
|
57
|
+
if (!recipient)
|
|
58
|
+
return { content: [{ type: "text", text: "Invalid recipient agent name." }] };
|
|
39
59
|
const messagesDir = path.join(projectRoot, frameworkDir, "messages");
|
|
40
60
|
if (!fs.existsSync(messagesDir))
|
|
41
61
|
fs.mkdirSync(messagesDir, { recursive: true });
|
|
42
62
|
const messagePath = path.join(messagesDir, `${recipient}.json`);
|
|
43
63
|
const messages = (fs.existsSync(messagePath) ? JSON.parse(fs.readFileSync(messagePath, "utf-8")) : []);
|
|
44
|
-
messages.push({
|
|
64
|
+
messages.push({
|
|
65
|
+
timestamp: new Date().toISOString(),
|
|
66
|
+
from: "manager",
|
|
67
|
+
traceId: parsed.data.traceId,
|
|
68
|
+
category: parsed.data.category,
|
|
69
|
+
priority: parsed.data.priority,
|
|
70
|
+
content: parsed.data.message,
|
|
71
|
+
read: false
|
|
72
|
+
});
|
|
45
73
|
fs.writeFileSync(messagePath, JSON.stringify(messages, null, 2));
|
|
46
|
-
return { content: [{ type: "text", text: `Message sent to @${recipient}.` }] };
|
|
74
|
+
return { content: [{ type: "text", text: `Hermes: Message sent to @${recipient} [${parsed.data.category} | ${parsed.data.priority}].` }] };
|
|
47
75
|
}
|
|
48
76
|
catch (error) {
|
|
49
|
-
return { content: [{ type: "text", text: "Failed to send message." }] };
|
|
77
|
+
return { content: [{ type: "text", text: "Failed to send message via Hermes." }] };
|
|
50
78
|
}
|
|
51
79
|
},
|
|
52
80
|
read_agent_messages: async (args, projectRoot) => {
|
|
@@ -55,17 +83,51 @@ export const messageHandlers = {
|
|
|
55
83
|
return { content: [{ type: "text", text: "Invalid agent name." }] };
|
|
56
84
|
try {
|
|
57
85
|
const frameworkDir = getFrameworkDir(projectRoot);
|
|
58
|
-
const agentName = parsed.data.agent
|
|
86
|
+
const agentName = normalizeAgentName(parsed.data.agent);
|
|
87
|
+
if (!agentName)
|
|
88
|
+
return { content: [{ type: "text", text: "Invalid agent name." }] };
|
|
59
89
|
const messagePath = path.join(projectRoot, frameworkDir, "messages", `${agentName}.json`);
|
|
60
90
|
if (!fs.existsSync(messagePath))
|
|
61
91
|
return { content: [{ type: "text", text: "No messages found." }] };
|
|
62
92
|
const messages = JSON.parse(fs.readFileSync(messagePath, "utf-8"));
|
|
63
|
-
const unread = messages.filter((m) => !m.read);
|
|
64
|
-
fs.writeFileSync(messagePath, JSON.stringify(messages.map((m) => ({ ...m, read: true })), null, 2));
|
|
65
|
-
|
|
93
|
+
const unread = messages.filter((m) => !m.read && (!parsed.data.traceId || m.traceId === parsed.data.traceId));
|
|
94
|
+
fs.writeFileSync(messagePath, JSON.stringify(messages.map((m) => (!m.read && (!parsed.data.traceId || m.traceId === parsed.data.traceId) ? { ...m, read: true } : m)), null, 2));
|
|
95
|
+
if (unread.length === 0)
|
|
96
|
+
return { content: [{ type: "text", text: "No new messages." }] };
|
|
97
|
+
const formatted = unread.map((m) => `- [${m.priority}] **${m.from}** (${m.category}): ${m.content} *(Trace: ${m.traceId})*`).join("\n");
|
|
98
|
+
return { content: [{ type: "text", text: `### HERMES INBOX: @${agentName}\n\n${formatted}` }] };
|
|
66
99
|
}
|
|
67
100
|
catch (error) {
|
|
68
101
|
return { content: [{ type: "text", text: "Failed to read messages." }] };
|
|
69
102
|
}
|
|
70
103
|
},
|
|
104
|
+
get_agent_inbox_stats: async (args, projectRoot) => {
|
|
105
|
+
const parsed = GET_AGENT_INBOX_STATS_ARGS_SCHEMA.safeParse(args ?? {});
|
|
106
|
+
if (!parsed.success)
|
|
107
|
+
return { content: [{ type: "text", text: "Invalid agent name." }] };
|
|
108
|
+
try {
|
|
109
|
+
const frameworkDir = getFrameworkDir(projectRoot);
|
|
110
|
+
const agentName = normalizeAgentName(parsed.data.agent);
|
|
111
|
+
if (!agentName)
|
|
112
|
+
return { content: [{ type: "text", text: "Invalid agent name." }] };
|
|
113
|
+
const messagePath = path.join(projectRoot, frameworkDir, "messages", `${agentName}.json`);
|
|
114
|
+
if (!fs.existsSync(messagePath))
|
|
115
|
+
return { content: [{ type: "text", text: "Inbox empty." }] };
|
|
116
|
+
const messages = JSON.parse(fs.readFileSync(messagePath, "utf-8"));
|
|
117
|
+
const unread = messages.filter(m => !m.read);
|
|
118
|
+
const priorityDist = messages.reduce((acc, m) => {
|
|
119
|
+
acc[m.priority] = (acc[m.priority] || 0) + 1;
|
|
120
|
+
return acc;
|
|
121
|
+
}, {});
|
|
122
|
+
return {
|
|
123
|
+
content: [{
|
|
124
|
+
type: "text",
|
|
125
|
+
text: `### INBOX STATS: @${agentName}\n- Total: ${messages.length}\n- Unread: ${unread.length}\n- Priority Distribution: ${JSON.stringify(priorityDist)}`
|
|
126
|
+
}]
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
return { content: [{ type: "text", text: "Failed to get inbox stats." }] };
|
|
131
|
+
}
|
|
132
|
+
}
|
|
71
133
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
|
-
import {
|
|
3
|
+
import { spawnSync } from "child_process";
|
|
4
4
|
import { Project } from "ts-morph";
|
|
5
5
|
import { resolveSafePath } from "../utils.js";
|
|
6
6
|
import { VALIDATE_REPOSITORY_HEALTH_ARGS_SCHEMA, ANALYZE_DOCUMENTATION_DEBT_ARGS_SCHEMA } from "../schemas.js";
|
|
@@ -35,7 +35,28 @@ export const repositoryHandlers = {
|
|
|
35
35
|
if (!fs.existsSync(pkgPath))
|
|
36
36
|
return { content: [{ type: "text", text: "package.json not found." }] };
|
|
37
37
|
const scripts = JSON.parse(fs.readFileSync(pkgPath, "utf-8")).scripts || {};
|
|
38
|
-
const
|
|
38
|
+
const scriptCandidates = {
|
|
39
|
+
lint: ["lint", "enderun:lint"],
|
|
40
|
+
test: ["test", "enderun:test"],
|
|
41
|
+
build: ["build", "enderun:build"],
|
|
42
|
+
};
|
|
43
|
+
const runScript = (name) => {
|
|
44
|
+
const scriptName = scriptCandidates[name].find((candidate) => scripts[candidate]);
|
|
45
|
+
if (!scriptName)
|
|
46
|
+
return { name, script: "-", status: "SKIPPED", details: "No matching script found." };
|
|
47
|
+
const result = spawnSync("npm", ["run", scriptName], {
|
|
48
|
+
cwd: projectRoot,
|
|
49
|
+
encoding: "utf-8",
|
|
50
|
+
stdio: "pipe",
|
|
51
|
+
});
|
|
52
|
+
const output = `${result.stdout || ""}${result.stderr || ""}`.trim();
|
|
53
|
+
return {
|
|
54
|
+
name,
|
|
55
|
+
script: scriptName,
|
|
56
|
+
status: result.status === 0 ? "PASSED" : "FAILED",
|
|
57
|
+
details: output.split("\n").slice(-5).join("\n"),
|
|
58
|
+
};
|
|
59
|
+
};
|
|
39
60
|
const results = [];
|
|
40
61
|
if (scope === "full" || scope === "lint")
|
|
41
62
|
results.push(runScript("lint"));
|
|
@@ -43,7 +64,7 @@ export const repositoryHandlers = {
|
|
|
43
64
|
results.push(runScript("test"));
|
|
44
65
|
if (scope === "full" || scope === "build")
|
|
45
66
|
results.push(runScript("build"));
|
|
46
|
-
return { content: [{ type: "text", text: `### REPOSITORY HEALTH REPORT\n\n` + results.map(r => `- **${r.name.toUpperCase()}
|
|
67
|
+
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") }] };
|
|
47
68
|
}
|
|
48
69
|
catch (error) {
|
|
49
70
|
return { content: [{ type: "text", text: "Health validation failed." }] };
|