agent-enderun 0.5.8 → 0.6.0
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 +16 -151
- package/.enderun/STATUS.md +19 -10
- package/.enderun/agents/analyst.md +19 -6
- package/.enderun/agents/backend.md +66 -48
- package/.enderun/agents/explorer.md +14 -2
- package/.enderun/agents/frontend.md +59 -14
- package/.enderun/agents/git.md +1 -1
- package/.enderun/agents/manager.md +10 -2
- package/.enderun/agents/mobile.md +1 -1
- package/.enderun/agents/native.md +1 -1
- package/.enderun/blueprints/backend/errors/domain-error.ts +84 -0
- package/.enderun/blueprints/backend/middleware/error-handler.ts +24 -0
- package/.enderun/blueprints/backend/types/api.ts +20 -0
- package/.enderun/blueprints/backend/types/brands.ts +12 -0
- package/.enderun/blueprints/backend/types/constants.ts +34 -0
- package/.enderun/blueprints/backend/types/index.ts +49 -0
- package/.enderun/blueprints/backend/types/logs.ts +16 -0
- package/.enderun/blueprints/backend/types/models.ts +65 -0
- package/.enderun/blueprints/frontend/ui/Button.tsx +60 -0
- package/.enderun/blueprints/frontend/ui/Input.tsx +43 -0
- package/.enderun/docs/api/README.md +7 -12
- package/.enderun/docs/project-docs.md +17 -7
- package/.enderun/knowledge/blueprint_driven_construction.md +22 -0
- package/.enderun/knowledge/code_review_checklist.md +1 -1
- package/.enderun/knowledge/contract_versioning.md +5 -5
- package/ENDERUN.md +29 -6
- package/README.md +45 -23
- package/bin/cli.js +205 -27
- package/bin/update-contract.js +3 -0
- package/{packages/framework-mcp → framework-mcp}/dist/index.js +0 -0
- package/{packages/framework-mcp → framework-mcp}/dist/schemas.js +12 -0
- package/{packages/framework-mcp → framework-mcp}/dist/tools/contract.js +16 -10
- package/{packages/framework-mcp → framework-mcp}/dist/tools/framework.js +5 -3
- package/{packages/framework-mcp → framework-mcp}/dist/tools/git.js +2 -4
- package/{packages/framework-mcp → framework-mcp}/dist/tools/index.js +3 -0
- package/{packages/framework-mcp → framework-mcp}/dist/tools/knowledge.js +1 -1
- package/{packages/framework-mcp → framework-mcp}/dist/tools/repository.js +1 -1
- package/framework-mcp/dist/tools/scaffold.js +129 -0
- package/{packages/framework-mcp → framework-mcp}/dist/utils.js +1 -1
- package/{packages/framework-mcp → framework-mcp}/package.json +4 -1
- package/{packages/framework-mcp → framework-mcp}/src/schemas.ts +15 -0
- package/{packages/framework-mcp → framework-mcp}/src/tools/contract.ts +22 -9
- package/{packages/framework-mcp → framework-mcp}/src/tools/framework.ts +6 -4
- package/{packages/framework-mcp → framework-mcp}/src/tools/git.ts +2 -3
- package/{packages/framework-mcp → framework-mcp}/src/tools/index.ts +3 -0
- package/{packages/framework-mcp → framework-mcp}/src/tools/knowledge.ts +6 -6
- package/{packages/framework-mcp → framework-mcp}/src/tools/repository.ts +1 -1
- package/framework-mcp/src/tools/scaffold.ts +147 -0
- package/{packages/framework-mcp → framework-mcp}/src/utils.ts +1 -1
- package/package.json +8 -12
- package/panda.config.ts +79 -5
- package/.enderun/logs/manager.json +0 -18
- package/packages/shared-types/README.md +0 -61
- package/packages/shared-types/contract.version.json +0 -12
- package/packages/shared-types/dist/index.d.ts +0 -92
- package/packages/shared-types/dist/index.js +0 -8
- package/packages/shared-types/dist/index.js.map +0 -1
- package/packages/shared-types/package.json +0 -33
- package/packages/shared-types/src/index.ts +0 -102
- package/packages/shared-types/tsconfig.json +0 -9
- /package/{packages/framework-mcp → framework-mcp}/README.md +0 -0
- /package/{packages/framework-mcp → framework-mcp}/dist/tools/academy.js +0 -0
- /package/{packages/framework-mcp → framework-mcp}/dist/tools/codebase.js +0 -0
- /package/{packages/framework-mcp → framework-mcp}/dist/tools/database.js +0 -0
- /package/{packages/framework-mcp → framework-mcp}/dist/tools/memory.js +0 -0
- /package/{packages/framework-mcp → framework-mcp}/dist/tools/messages.js +0 -0
- /package/{packages/framework-mcp → framework-mcp}/dist/tools/security.js +0 -0
- /package/{packages/framework-mcp → framework-mcp}/src/index.ts +0 -0
- /package/{packages/framework-mcp → framework-mcp}/src/tools/academy.ts +0 -0
- /package/{packages/framework-mcp → framework-mcp}/src/tools/codebase.ts +0 -0
- /package/{packages/framework-mcp → framework-mcp}/src/tools/database.ts +0 -0
- /package/{packages/framework-mcp → framework-mcp}/src/tools/memory.ts +0 -0
- /package/{packages/framework-mcp → framework-mcp}/src/tools/messages.ts +0 -0
- /package/{packages/framework-mcp → framework-mcp}/src/tools/security.ts +0 -0
- /package/{packages/framework-mcp → framework-mcp}/tsconfig.json +0 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { resolveSafePath, getFrameworkDir } from "../utils.js";
|
|
4
|
+
import { INSTANTIATE_BLUEPRINT_ARGS_SCHEMA, LIST_BLUEPRINTS_ARGS_SCHEMA, SAVE_AS_BLUEPRINT_ARGS_SCHEMA } from "../schemas.js";
|
|
5
|
+
export const scaffoldTools = [
|
|
6
|
+
{
|
|
7
|
+
name: "instantiate_blueprint",
|
|
8
|
+
description: "Clones a Gold Standard capability from .enderun/blueprints into the application source.",
|
|
9
|
+
inputSchema: {
|
|
10
|
+
type: "object",
|
|
11
|
+
properties: {
|
|
12
|
+
blueprintPath: { type: "string", description: "Relative path within blueprints/ (e.g., backend/errors)" },
|
|
13
|
+
targetPath: { type: "string", description: "Target directory in the app (e.g., apps/backend/src/errors)" },
|
|
14
|
+
},
|
|
15
|
+
required: ["blueprintPath", "targetPath"],
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
name: "save_as_blueprint",
|
|
20
|
+
description: "Saves or updates a high-quality code pattern as a Gold Standard blueprint for reuse.",
|
|
21
|
+
inputSchema: {
|
|
22
|
+
type: "object",
|
|
23
|
+
properties: {
|
|
24
|
+
blueprintPath: { type: "string", description: "Target relative path in blueprints/ (e.g., frontend/ui/Button.tsx)" },
|
|
25
|
+
content: { type: "string", description: "The full source code of the blueprint" },
|
|
26
|
+
description: { type: "string", description: "Brief description of what this blueprint provides" },
|
|
27
|
+
},
|
|
28
|
+
required: ["blueprintPath", "content"],
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: "list_blueprints",
|
|
33
|
+
description: "Lists all available Gold Standard blueprints in the framework.",
|
|
34
|
+
inputSchema: {
|
|
35
|
+
type: "object",
|
|
36
|
+
properties: {
|
|
37
|
+
category: { type: "string", enum: ["backend", "frontend"], description: "Optional category to filter blueprints" },
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
];
|
|
42
|
+
export const scaffoldHandlers = {
|
|
43
|
+
instantiate_blueprint: async (args, projectRoot) => {
|
|
44
|
+
const parsed = INSTANTIATE_BLUEPRINT_ARGS_SCHEMA.safeParse(args);
|
|
45
|
+
if (!parsed.success)
|
|
46
|
+
return { content: [{ type: "text", text: `Invalid arguments: ${parsed.error.message}` }] };
|
|
47
|
+
const { blueprintPath, targetPath } = parsed.data;
|
|
48
|
+
const frameworkDir = getFrameworkDir(projectRoot);
|
|
49
|
+
const sourceDir = resolveSafePath(projectRoot, path.join(frameworkDir, "blueprints", blueprintPath));
|
|
50
|
+
const destDir = resolveSafePath(projectRoot, targetPath);
|
|
51
|
+
try {
|
|
52
|
+
if (!fs.existsSync(sourceDir)) {
|
|
53
|
+
return { content: [{ type: "text", text: `Blueprint not found: ${blueprintPath}` }] };
|
|
54
|
+
}
|
|
55
|
+
// Create target directory if it doesn't exist
|
|
56
|
+
fs.mkdirSync(path.dirname(destDir), { recursive: true });
|
|
57
|
+
// Copy recursively
|
|
58
|
+
// Using cpSync which is available in Node.js 16.7.0+
|
|
59
|
+
fs.cpSync(sourceDir, destDir, { recursive: true });
|
|
60
|
+
return { content: [{ type: "text", text: `Successfully instantiated blueprint '${blueprintPath}' into '${targetPath}'` }] };
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
return { content: [{ type: "text", text: `Scaffold failed: ${error.message}` }] };
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
save_as_blueprint: async (args, projectRoot) => {
|
|
67
|
+
const parsed = SAVE_AS_BLUEPRINT_ARGS_SCHEMA.safeParse(args);
|
|
68
|
+
if (!parsed.success)
|
|
69
|
+
return { content: [{ type: "text", text: `Invalid arguments: ${parsed.error.message}` }] };
|
|
70
|
+
const { blueprintPath, content, description } = parsed.data;
|
|
71
|
+
const frameworkDir = getFrameworkDir(projectRoot);
|
|
72
|
+
const targetFile = resolveSafePath(projectRoot, path.join(frameworkDir, "blueprints", blueprintPath));
|
|
73
|
+
try {
|
|
74
|
+
// Ensure the blueprints subdirectory exists
|
|
75
|
+
fs.mkdirSync(path.dirname(targetFile), { recursive: true });
|
|
76
|
+
// Write the blueprint content
|
|
77
|
+
fs.writeFileSync(targetFile, content, "utf-8");
|
|
78
|
+
// Optionally write a .metadata file if description provided
|
|
79
|
+
if (description) {
|
|
80
|
+
const metaFile = `${targetFile}.metadata.json`;
|
|
81
|
+
fs.writeFileSync(metaFile, JSON.stringify({ description, savedAt: new Date().toISOString() }, null, 2), "utf-8");
|
|
82
|
+
}
|
|
83
|
+
return { content: [{ type: "text", text: `Successfully saved blueprint to '${blueprintPath}'` }] };
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
return { content: [{ type: "text", text: `Failed to save blueprint: ${error.message}` }] };
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
list_blueprints: async (args, projectRoot) => {
|
|
90
|
+
const parsed = LIST_BLUEPRINTS_ARGS_SCHEMA.safeParse(args ?? {});
|
|
91
|
+
const category = parsed.success ? parsed.data.category : undefined;
|
|
92
|
+
const frameworkDir = getFrameworkDir(projectRoot);
|
|
93
|
+
const blueprintsRoot = path.join(projectRoot, frameworkDir, "blueprints");
|
|
94
|
+
try {
|
|
95
|
+
if (!fs.existsSync(blueprintsRoot)) {
|
|
96
|
+
return { content: [{ type: "text", text: "Blueprints directory not found." }] };
|
|
97
|
+
}
|
|
98
|
+
const walk = (dir) => {
|
|
99
|
+
const results = [];
|
|
100
|
+
if (!fs.existsSync(dir))
|
|
101
|
+
return results;
|
|
102
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
103
|
+
for (const entry of entries) {
|
|
104
|
+
const res = path.join(dir, entry.name);
|
|
105
|
+
if (entry.isDirectory()) {
|
|
106
|
+
// If it's a leaf directory containing files, or we want to show all dirs
|
|
107
|
+
const subEntries = fs.readdirSync(res);
|
|
108
|
+
const hasFiles = subEntries.some(e => !fs.lstatSync(path.join(res, e)).isDirectory());
|
|
109
|
+
if (hasFiles) {
|
|
110
|
+
results.push(path.relative(blueprintsRoot, res));
|
|
111
|
+
}
|
|
112
|
+
results.push(...walk(res));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return results;
|
|
116
|
+
};
|
|
117
|
+
let available = walk(blueprintsRoot);
|
|
118
|
+
if (category) {
|
|
119
|
+
available = available.filter(b => b.startsWith(category));
|
|
120
|
+
}
|
|
121
|
+
// Remove duplicates
|
|
122
|
+
available = Array.from(new Set(available));
|
|
123
|
+
return { content: [{ type: "text", text: `### AVAILABLE BLUEPRINTS\n\n` + available.map(b => `- ${b}`).join("\n") }] };
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
return { content: [{ type: "text", text: `Listing blueprints failed: ${error.message}` }] };
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from "path";
|
|
2
2
|
import fs from "fs";
|
|
3
|
-
export const FRAMEWORK_VERSION = "0.
|
|
3
|
+
export const FRAMEWORK_VERSION = "0.6.0";
|
|
4
4
|
export function getFrameworkDir(projectRoot) {
|
|
5
5
|
const adapters = [".gemini", ".claude", ".cursor", ".enderun", ".codex"];
|
|
6
6
|
for (const adp of adapters) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-enderun-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Enterprise-grade MCP Server for AI Agent Framework",
|
|
5
5
|
"author": "Yusuf BEKAR",
|
|
6
6
|
"license": "MIT",
|
|
@@ -25,6 +25,9 @@
|
|
|
25
25
|
"dist",
|
|
26
26
|
"README.md"
|
|
27
27
|
],
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
28
31
|
"scripts": {
|
|
29
32
|
"build": "tsc -p tsconfig.json",
|
|
30
33
|
"start": "node dist/index.js",
|
|
@@ -124,3 +124,18 @@ export const SYNC_CONTRACT_HASH_ARGS_SCHEMA = z.object({
|
|
|
124
124
|
export const VERIFY_FRAMEWORK_HEALTH_ARGS_SCHEMA = z.object({
|
|
125
125
|
detailed: z.boolean().default(false),
|
|
126
126
|
});
|
|
127
|
+
|
|
128
|
+
export const INSTANTIATE_BLUEPRINT_ARGS_SCHEMA = z.object({
|
|
129
|
+
blueprintPath: z.string().min(1),
|
|
130
|
+
targetPath: z.string().min(1),
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
export const LIST_BLUEPRINTS_ARGS_SCHEMA = z.object({
|
|
134
|
+
category: z.enum(["backend", "frontend"]).optional(),
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
export const SAVE_AS_BLUEPRINT_ARGS_SCHEMA = z.object({
|
|
138
|
+
blueprintPath: z.string().min(1),
|
|
139
|
+
content: z.string().min(1),
|
|
140
|
+
description: z.string().optional(),
|
|
141
|
+
});
|
|
@@ -50,8 +50,8 @@ export const contractTools = [
|
|
|
50
50
|
export const contractHandlers = {
|
|
51
51
|
verify_api_contract: async (args: unknown, projectRoot: string) => {
|
|
52
52
|
try {
|
|
53
|
-
const sharedTypesDir = path.join(projectRoot, "
|
|
54
|
-
const contractJsonPath = path.join(projectRoot, "
|
|
53
|
+
const sharedTypesDir = path.join(projectRoot, "apps/backend/src/types");
|
|
54
|
+
const contractJsonPath = path.join(projectRoot, "apps/backend/contract.version.json");
|
|
55
55
|
if (!fs.existsSync(sharedTypesDir) || !fs.existsSync(contractJsonPath)) return { content: [{ type: "text", text: "Missing shared-types directory or contract.version.json" }] };
|
|
56
56
|
const currentHash = calculateContractHash(sharedTypesDir, projectRoot);
|
|
57
57
|
const storedHash = JSON.parse(fs.readFileSync(contractJsonPath, "utf-8")).contract_hash;
|
|
@@ -62,8 +62,8 @@ export const contractHandlers = {
|
|
|
62
62
|
},
|
|
63
63
|
update_contract_hash: async (args: unknown, projectRoot: string) => {
|
|
64
64
|
try {
|
|
65
|
-
const sharedTypesDir = path.join(projectRoot, "
|
|
66
|
-
const contractJsonPath = path.join(projectRoot, "
|
|
65
|
+
const sharedTypesDir = path.join(projectRoot, "apps/backend/src/types");
|
|
66
|
+
const contractJsonPath = path.join(projectRoot, "apps/backend/contract.version.json");
|
|
67
67
|
if (!fs.existsSync(sharedTypesDir)) return { content: [{ type: "text", text: "Missing shared-types directory" }] };
|
|
68
68
|
const files = collectFilesRecursively(sharedTypesDir, new Set(["ts"])).sort();
|
|
69
69
|
if (files.length === 0) return { content: [{ type: "text", text: "⚠️ WARNING: No TypeScript files found in shared-types/src. Hash not updated." }] };
|
|
@@ -83,13 +83,26 @@ export const contractHandlers = {
|
|
|
83
83
|
try {
|
|
84
84
|
const frameworkDir = getFrameworkDir(projectRoot);
|
|
85
85
|
const apiDocPath = path.join(projectRoot, frameworkDir, "docs/api", `${parsed.data.domain}.md`);
|
|
86
|
-
const
|
|
86
|
+
const sharedTypesDir = path.join(projectRoot, "apps/backend/src/types");
|
|
87
|
+
|
|
87
88
|
if (!fs.existsSync(apiDocPath)) return { content: [{ type: "text", text: `API documentation not found for domain: ${parsed.data.domain}` }] };
|
|
88
|
-
if (!fs.existsSync(
|
|
89
|
-
|
|
90
|
-
const
|
|
89
|
+
if (!fs.existsSync(sharedTypesDir)) return { content: [{ type: "text", text: "Shared types directory not found." }] };
|
|
90
|
+
|
|
91
|
+
const project = new Project();
|
|
92
|
+
project.addSourceFilesAtPaths(path.join(sharedTypesDir, "**/*.ts"));
|
|
93
|
+
|
|
94
|
+
const mentionedTypes = Array.from(fs.readFileSync(apiDocPath, "utf-8").matchAll(/`([^`]+)`/g))
|
|
95
|
+
.map(m => m[1])
|
|
96
|
+
.filter(t => /^[A-Z][a-zA-Z0-9]+(DTO|Response|Request|Status|Type)?$/.test(t));
|
|
97
|
+
|
|
91
98
|
const uniqueTypes = Array.from(new Set(mentionedTypes));
|
|
92
|
-
const missingTypes = uniqueTypes.filter(t =>
|
|
99
|
+
const missingTypes = uniqueTypes.filter(t => {
|
|
100
|
+
const found = project.getSourceFiles().some(sf =>
|
|
101
|
+
sf.getInterface(t) || sf.getTypeAlias(t) || sf.getEnum(t) || sf.getClass(t)
|
|
102
|
+
);
|
|
103
|
+
return !found;
|
|
104
|
+
});
|
|
105
|
+
|
|
93
106
|
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"}` }] };
|
|
94
107
|
} catch (error) {
|
|
95
108
|
return { content: [{ type: "text", text: "Contract verification failed." }] };
|
|
@@ -102,8 +102,9 @@ export const frameworkHandlers = {
|
|
|
102
102
|
const missing: string[] = [];
|
|
103
103
|
const frameworkDir = getFrameworkDir(projectRoot);
|
|
104
104
|
const checkPaths = [
|
|
105
|
-
{ path: "apps", type: "folder"
|
|
106
|
-
{ path: "
|
|
105
|
+
{ path: "apps/backend/src/types", type: "folder" },
|
|
106
|
+
{ path: "apps/web/src/types", type: "folder" },
|
|
107
|
+
{ path: "framework-mcp/src", type: "folder" },
|
|
107
108
|
{ path: path.join(frameworkDir, "docs/api"), type: "folder", optional: true },
|
|
108
109
|
{ path: ".env", type: "file", optional: true },
|
|
109
110
|
{ path: ".env.example", type: "file" },
|
|
@@ -174,8 +175,9 @@ export const frameworkHandlers = {
|
|
|
174
175
|
const cliPath = path.join(projectRoot, "bin/cli.js");
|
|
175
176
|
const output = execSync(`node ${cliPath} check`, { cwd: projectRoot, encoding: "utf-8", stdio: "pipe" });
|
|
176
177
|
return { content: [{ type: "text", text: `### FRAMEWORK HEALTH CHECK\n\n${output}` }] };
|
|
177
|
-
} catch (error:
|
|
178
|
-
|
|
178
|
+
} catch (error: unknown) {
|
|
179
|
+
const err = error as { stdout?: string; message?: string };
|
|
180
|
+
return { content: [{ type: "text", text: `### FRAMEWORK HEALTH CHECK (FAILED)\n\n${err.stdout || err.message}` }] };
|
|
179
181
|
}
|
|
180
182
|
}
|
|
181
183
|
};
|
|
@@ -26,14 +26,13 @@ export const gitHandlers = {
|
|
|
26
26
|
const files = execSync("git diff --staged --name-only", { encoding: "utf-8", cwd: projectRoot }).split("\n").filter(Boolean);
|
|
27
27
|
let type = "feat", scope = "code";
|
|
28
28
|
if (files.some(f => f.includes(".md"))) type = "docs";
|
|
29
|
-
else if (files.some(f => f.includes("
|
|
29
|
+
else if (files.some(f => f.includes("apps/backend/src/types"))) type = "arch";
|
|
30
30
|
else if (files.some(f => f.includes("framework-mcp"))) type = "chore";
|
|
31
31
|
else if (files.some(f => f.includes("bin/cli.js"))) type = "fix";
|
|
32
32
|
else if (files.some(f => f.includes("test"))) type = "test";
|
|
33
33
|
if (files.some(f => f.includes("apps/web"))) scope = "web";
|
|
34
34
|
else if (files.some(f => f.includes("apps/backend"))) scope = "backend";
|
|
35
|
-
else if (files.some(f => f.includes("
|
|
36
|
-
else if (files.some(f => f.includes("packages/framework-mcp"))) scope = "mcp";
|
|
35
|
+
else if (files.some(f => f.includes("framework-mcp"))) scope = "mcp";
|
|
37
36
|
const summary = files.length === 1 ? `update ${path.basename(files[0])}` : `update ${files.length} files`;
|
|
38
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")}` }] };
|
|
39
38
|
} catch (error) {
|
|
@@ -9,6 +9,7 @@ import { gitTools, gitHandlers } from "./git.js";
|
|
|
9
9
|
import { databaseTools, databaseHandlers } from "./database.js";
|
|
10
10
|
import { knowledgeTools, knowledgeHandlers } from "./knowledge.js";
|
|
11
11
|
import { repositoryTools, repositoryHandlers } from "./repository.js";
|
|
12
|
+
import { scaffoldTools, scaffoldHandlers } from "./scaffold.js";
|
|
12
13
|
|
|
13
14
|
export const allTools = [
|
|
14
15
|
...codebaseTools,
|
|
@@ -22,6 +23,7 @@ export const allTools = [
|
|
|
22
23
|
...databaseTools,
|
|
23
24
|
...knowledgeTools,
|
|
24
25
|
...repositoryTools,
|
|
26
|
+
...scaffoldTools,
|
|
25
27
|
];
|
|
26
28
|
|
|
27
29
|
export const allHandlers: Record<string, (args: unknown, projectRoot: string) => Promise<unknown>> = {
|
|
@@ -36,6 +38,7 @@ export const allHandlers: Record<string, (args: unknown, projectRoot: string) =>
|
|
|
36
38
|
...databaseHandlers,
|
|
37
39
|
...knowledgeHandlers,
|
|
38
40
|
...repositoryHandlers,
|
|
41
|
+
...scaffoldHandlers,
|
|
39
42
|
};
|
|
40
43
|
|
|
41
44
|
// Add compatibility aliases
|
|
@@ -48,15 +48,15 @@ function parseFrontmatter(content: string) {
|
|
|
48
48
|
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
49
49
|
if (!match) return { metadata: {}, body: content };
|
|
50
50
|
const yaml = match[1];
|
|
51
|
-
const metadata: Record<string,
|
|
51
|
+
const metadata: Record<string, string | undefined> = {};
|
|
52
52
|
yaml.split("\n").forEach(line => {
|
|
53
53
|
const [key, ...val] = line.split(":");
|
|
54
54
|
if (key && val.length > 0) metadata[key.trim()] = val.join(":").trim();
|
|
55
55
|
});
|
|
56
56
|
return { metadata, body: content.replace(match[0], "").trim() };
|
|
57
|
-
}
|
|
57
|
+
}
|
|
58
58
|
|
|
59
|
-
export const knowledgeHandlers = {
|
|
59
|
+
export const knowledgeHandlers = {
|
|
60
60
|
search_knowledge_base: async (args: unknown, projectRoot: string) => {
|
|
61
61
|
const parsed = SEARCH_KNOWLEDGE_BASE_ARGS_SCHEMA.safeParse(args ?? {});
|
|
62
62
|
if (!parsed.success) return { content: [{ type: "text", text: "Invalid search query." }] };
|
|
@@ -64,12 +64,12 @@ export const knowledgeHandlers = {
|
|
|
64
64
|
const frameworkDir = getFrameworkDir(projectRoot);
|
|
65
65
|
const kbDir = path.join(projectRoot, frameworkDir, "knowledge");
|
|
66
66
|
if (!fs.existsSync(kbDir)) return { content: [{ type: "text", text: "Knowledge base is empty." }] };
|
|
67
|
-
|
|
67
|
+
|
|
68
68
|
const query = parsed.data.query.toLowerCase();
|
|
69
69
|
const results = fs.readdirSync(kbDir).filter(f => f.endsWith(".md")).map(file => {
|
|
70
70
|
const content = fs.readFileSync(path.join(kbDir, file), "utf-8");
|
|
71
71
|
const { metadata, body } = parseFrontmatter(content);
|
|
72
|
-
|
|
72
|
+
|
|
73
73
|
const matchesQuery = body.toLowerCase().includes(query) ||
|
|
74
74
|
file.toLowerCase().includes(query) ||
|
|
75
75
|
(metadata.tags && metadata.tags.toLowerCase().includes(query)) ||
|
|
@@ -127,7 +127,7 @@ export const knowledgeHandlers = {
|
|
|
127
127
|
mermaid += ` ${id}["${label}"]\n`;
|
|
128
128
|
|
|
129
129
|
if (metadata.related) {
|
|
130
|
-
const related = metadata.related.replace(/[
|
|
130
|
+
const related = metadata.related.replace(/[[\]]/g, "").split(",");
|
|
131
131
|
related.forEach((r: string) => {
|
|
132
132
|
mermaid += ` ${id} --> ${r.trim().replace(".md", "")}\n`;
|
|
133
133
|
});
|
|
@@ -93,7 +93,7 @@ export const repositoryHandlers = {
|
|
|
93
93
|
});
|
|
94
94
|
});
|
|
95
95
|
}
|
|
96
|
-
const majorDirs = ["apps/backend", "apps/web", "
|
|
96
|
+
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
97
|
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
98
|
} catch (error) {
|
|
99
99
|
return { content: [{ type: "text", text: "Documentation debt scan failed." }] };
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import {
|
|
4
|
+
resolveSafePath,
|
|
5
|
+
getFrameworkDir
|
|
6
|
+
} from "../utils.js";
|
|
7
|
+
import {
|
|
8
|
+
INSTANTIATE_BLUEPRINT_ARGS_SCHEMA,
|
|
9
|
+
LIST_BLUEPRINTS_ARGS_SCHEMA,
|
|
10
|
+
SAVE_AS_BLUEPRINT_ARGS_SCHEMA
|
|
11
|
+
} from "../schemas.js";
|
|
12
|
+
|
|
13
|
+
export const scaffoldTools = [
|
|
14
|
+
{
|
|
15
|
+
name: "instantiate_blueprint",
|
|
16
|
+
description: "Clones a Gold Standard capability from .enderun/blueprints into the application source.",
|
|
17
|
+
inputSchema: {
|
|
18
|
+
type: "object",
|
|
19
|
+
properties: {
|
|
20
|
+
blueprintPath: { type: "string", description: "Relative path within blueprints/ (e.g., backend/errors)" },
|
|
21
|
+
targetPath: { type: "string", description: "Target directory in the app (e.g., apps/backend/src/errors)" },
|
|
22
|
+
},
|
|
23
|
+
required: ["blueprintPath", "targetPath"],
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: "save_as_blueprint",
|
|
28
|
+
description: "Saves or updates a high-quality code pattern as a Gold Standard blueprint for reuse.",
|
|
29
|
+
inputSchema: {
|
|
30
|
+
type: "object",
|
|
31
|
+
properties: {
|
|
32
|
+
blueprintPath: { type: "string", description: "Target relative path in blueprints/ (e.g., frontend/ui/Button.tsx)" },
|
|
33
|
+
content: { type: "string", description: "The full source code of the blueprint" },
|
|
34
|
+
description: { type: "string", description: "Brief description of what this blueprint provides" },
|
|
35
|
+
},
|
|
36
|
+
required: ["blueprintPath", "content"],
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: "list_blueprints",
|
|
41
|
+
description: "Lists all available Gold Standard blueprints in the framework.",
|
|
42
|
+
inputSchema: {
|
|
43
|
+
type: "object",
|
|
44
|
+
properties: {
|
|
45
|
+
category: { type: "string", enum: ["backend", "frontend"], description: "Optional category to filter blueprints" },
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
export const scaffoldHandlers = {
|
|
52
|
+
instantiate_blueprint: async (args: unknown, projectRoot: string) => {
|
|
53
|
+
const parsed = INSTANTIATE_BLUEPRINT_ARGS_SCHEMA.safeParse(args);
|
|
54
|
+
if (!parsed.success) return { content: [{ type: "text", text: `Invalid arguments: ${parsed.error.message}` }] };
|
|
55
|
+
|
|
56
|
+
const { blueprintPath, targetPath } = parsed.data;
|
|
57
|
+
const frameworkDir = getFrameworkDir(projectRoot);
|
|
58
|
+
const sourceDir = resolveSafePath(projectRoot, path.join(frameworkDir, "blueprints", blueprintPath));
|
|
59
|
+
const destDir = resolveSafePath(projectRoot, targetPath);
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
if (!fs.existsSync(sourceDir)) {
|
|
63
|
+
return { content: [{ type: "text", text: `Blueprint not found: ${blueprintPath}` }] };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Create target directory if it doesn't exist
|
|
67
|
+
fs.mkdirSync(path.dirname(destDir), { recursive: true });
|
|
68
|
+
|
|
69
|
+
// Copy recursively
|
|
70
|
+
// Using cpSync which is available in Node.js 16.7.0+
|
|
71
|
+
fs.cpSync(sourceDir, destDir, { recursive: true });
|
|
72
|
+
|
|
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}` }] };
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
save_as_blueprint: async (args: unknown, projectRoot: string) => {
|
|
79
|
+
const parsed = SAVE_AS_BLUEPRINT_ARGS_SCHEMA.safeParse(args);
|
|
80
|
+
if (!parsed.success) return { content: [{ type: "text", text: `Invalid arguments: ${parsed.error.message}` }] };
|
|
81
|
+
|
|
82
|
+
const { blueprintPath, content, description } = parsed.data;
|
|
83
|
+
const frameworkDir = getFrameworkDir(projectRoot);
|
|
84
|
+
const targetFile = resolveSafePath(projectRoot, path.join(frameworkDir, "blueprints", blueprintPath));
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
// Ensure the blueprints subdirectory exists
|
|
88
|
+
fs.mkdirSync(path.dirname(targetFile), { recursive: true });
|
|
89
|
+
|
|
90
|
+
// Write the blueprint content
|
|
91
|
+
fs.writeFileSync(targetFile, content, "utf-8");
|
|
92
|
+
|
|
93
|
+
// Optionally write a .metadata file if description provided
|
|
94
|
+
if (description) {
|
|
95
|
+
const metaFile = `${targetFile}.metadata.json`;
|
|
96
|
+
fs.writeFileSync(metaFile, JSON.stringify({ description, savedAt: new Date().toISOString() }, null, 2), "utf-8");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
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}` }] };
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
list_blueprints: async (args: unknown, projectRoot: string) => {
|
|
105
|
+
const parsed = LIST_BLUEPRINTS_ARGS_SCHEMA.safeParse(args ?? {});
|
|
106
|
+
const category = parsed.success ? parsed.data.category : undefined;
|
|
107
|
+
const frameworkDir = getFrameworkDir(projectRoot);
|
|
108
|
+
const blueprintsRoot = path.join(projectRoot, frameworkDir, "blueprints");
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
if (!fs.existsSync(blueprintsRoot)) {
|
|
112
|
+
return { content: [{ type: "text", text: "Blueprints directory not found." }] };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const walk = (dir: string): string[] => {
|
|
116
|
+
const results: string[] = [];
|
|
117
|
+
if (!fs.existsSync(dir)) return results;
|
|
118
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
119
|
+
for (const entry of entries) {
|
|
120
|
+
const res = path.join(dir, entry.name);
|
|
121
|
+
if (entry.isDirectory()) {
|
|
122
|
+
// If it's a leaf directory containing files, or we want to show all dirs
|
|
123
|
+
const subEntries = fs.readdirSync(res);
|
|
124
|
+
const hasFiles = subEntries.some(e => !fs.lstatSync(path.join(res, e)).isDirectory());
|
|
125
|
+
if (hasFiles) {
|
|
126
|
+
results.push(path.relative(blueprintsRoot, res));
|
|
127
|
+
}
|
|
128
|
+
results.push(...walk(res));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return results;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
let available = walk(blueprintsRoot);
|
|
135
|
+
if (category) {
|
|
136
|
+
available = available.filter(b => b.startsWith(category));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Remove duplicates
|
|
140
|
+
available = Array.from(new Set(available));
|
|
141
|
+
|
|
142
|
+
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
|
+
}
|
|
146
|
+
},
|
|
147
|
+
};
|
|
@@ -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.6.0";
|
|
5
5
|
|
|
6
6
|
export function getFrameworkDir(projectRoot: string): string {
|
|
7
7
|
const adapters = [".gemini", ".claude", ".cursor", ".enderun", ".codex"];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-enderun",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "The Supreme AI Governance & Orchestration Framework for Enterprise Development",
|
|
5
5
|
"author": "Yusuf BEKAR",
|
|
6
6
|
"license": "MIT",
|
|
@@ -24,21 +24,17 @@
|
|
|
24
24
|
"framework"
|
|
25
25
|
],
|
|
26
26
|
"workspaces": [
|
|
27
|
-
"
|
|
28
|
-
"
|
|
27
|
+
"apps/*",
|
|
28
|
+
"framework-mcp"
|
|
29
29
|
],
|
|
30
30
|
"bin": {
|
|
31
31
|
"agent-enderun": "bin/cli.js"
|
|
32
32
|
},
|
|
33
33
|
"files": [
|
|
34
34
|
"bin",
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"packages/shared-types/contract.version.json",
|
|
39
|
-
"packages/framework-mcp/dist",
|
|
40
|
-
"packages/framework-mcp/src",
|
|
41
|
-
"packages/framework-mcp/package.json",
|
|
35
|
+
"framework-mcp/dist",
|
|
36
|
+
"framework-mcp/src",
|
|
37
|
+
"framework-mcp/package.json",
|
|
42
38
|
".enderun",
|
|
43
39
|
"ENDERUN.md",
|
|
44
40
|
"README.md",
|
|
@@ -55,7 +51,7 @@
|
|
|
55
51
|
"docs"
|
|
56
52
|
],
|
|
57
53
|
"scripts": {
|
|
58
|
-
"enderun:build": "npm run build --prefix
|
|
54
|
+
"enderun:build": "npm run build --prefix framework-mcp",
|
|
59
55
|
"enderun:test": "vitest run",
|
|
60
56
|
"enderun:test:watch": "vitest",
|
|
61
57
|
"enderun:status": "agent-enderun status",
|
|
@@ -80,7 +76,7 @@
|
|
|
80
76
|
"@pandacss/dev": "^0.53.0"
|
|
81
77
|
},
|
|
82
78
|
"enderun": {
|
|
83
|
-
"version": "0.5.
|
|
79
|
+
"version": "0.5.9",
|
|
84
80
|
"initializedAt": "2026-05-19T14:23:38.760Z"
|
|
85
81
|
},
|
|
86
82
|
"dependencies": {}
|