agent-enderun 0.8.6 → 0.8.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/.enderun/BRAIN_DASHBOARD.md +4 -4
  2. package/.enderun/PROJECT_MEMORY.md +7 -7
  3. package/.enderun/STATUS.md +3 -5
  4. package/.enderun/agents/agent_army_schema.json +1 -1
  5. package/.enderun/agents/analyst.md +11 -18
  6. package/.enderun/agents/backend.md +42 -31
  7. package/.enderun/agents/devops.md +9 -18
  8. package/.enderun/agents/explorer.md +7 -16
  9. package/.enderun/agents/frontend.md +75 -91
  10. package/.enderun/agents/git.md +7 -16
  11. package/.enderun/agents/manager.md +38 -25
  12. package/.enderun/agents/mobile.md +7 -16
  13. package/.enderun/agents/orchestrator.md +16 -25
  14. package/.enderun/agents/qa.md +16 -22
  15. package/.enderun/agents/security.md +9 -18
  16. package/.enderun/cli-commands.json +0 -4
  17. package/.enderun/config.json +2 -2
  18. package/.enderun/knowledge/manager_authority_audit_enforcement.md +2 -2
  19. package/.enderun/knowledge/token_economy_and_memory.md +129 -0
  20. package/.enderun/memory-graph/agent-contexts/manager.json +1 -1
  21. package/.enderun/memory-graph/graph.json +1 -2
  22. package/.enderun/memory-graph/shared-facts.json +1 -2
  23. package/ENDERUN.md +1 -1
  24. package/README.md +379 -244
  25. package/docs/roadmap.md +1 -1
  26. package/framework-mcp/dist/schemas.js +0 -34
  27. package/framework-mcp/dist/tools/framework.js +40 -11
  28. package/framework-mcp/dist/tools/governance.js +1 -1
  29. package/framework-mcp/dist/tools/memory.js +24 -7
  30. package/framework-mcp/dist/tools/monitoring.js +6 -6
  31. package/framework-mcp/dist/tools/orchestration.js +9 -8
  32. package/framework-mcp/dist/utils.js +2 -2
  33. package/framework-mcp/package.json +1 -1
  34. package/framework-mcp/src/tools/framework.ts +39 -8
  35. package/framework-mcp/src/tools/memory.ts +20 -5
  36. package/framework-mcp/src/tools/monitoring.ts +6 -6
  37. package/framework-mcp/src/tools/orchestration.ts +9 -8
  38. package/framework-mcp/src/utils.ts +2 -2
  39. package/mcp.json +2 -2
  40. package/package.json +1 -1
  41. package/src/cli/index.ts +30 -2
  42. package/.enderun/agents/native.md +0 -115
  43. package/.enderun/claude.md +0 -19
  44. package/.enderun/cursor.md +0 -19
  45. package/.enderun/gemini.md +0 -19
  46. package/.enderun/memory-graph/agent-contexts/native.json +0 -1
package/docs/roadmap.md CHANGED
@@ -42,7 +42,7 @@ This program systematically audits current agent maturity levels and runs struct
42
42
  | **@explorer** | 6.5 | 8.5 | High / Stable. Highly capable in dependency analysis and codebase intelligence. |
43
43
  | **@analyst** | 6.5 | 8.5 | Medium / Stable. Handles system health metrics and compliance audits. |
44
44
  | **@git** | 6.0 | 8.0 | Medium / Stable. Coordinates PRs, semantic commits, and version compliance. |
45
- | **@mobile & @native** | 2.0 | 7.5 | Low / Dormant. Foundational blueprints and Hermes safety rules added. |
45
+ | **@mobile** | 2.0 | 7.5 | Low / Dormant. Foundational blueprints and Hermes safety rules added. |
46
46
 
47
47
  > **Note:** `@frontend` is currently the highest risk to enterprise onboarding and is the top priority.
48
48
 
@@ -46,12 +46,6 @@ export const UPDATE_KNOWLEDGE_BASE_ARGS_SCHEMA = z.object({
46
46
  content: z.string().min(1),
47
47
  tags: z.array(z.string().min(1)).default([]),
48
48
  });
49
- export const ANALYZE_DATABASE_SCHEMA_ARGS_SCHEMA = z.object({
50
- path: z.string().default("apps/backend"),
51
- });
52
- export const GENERATE_STRATEGIC_BRIEFING_ARGS_SCHEMA = z.object({
53
- focusArea: z.string().optional(),
54
- });
55
49
  export const ANALYZE_CONSTITUTION_COMPLIANCE_ARGS_SCHEMA = z.object({
56
50
  path: z.string().min(1),
57
51
  });
@@ -59,15 +53,6 @@ export const GENERATE_DEPENDENCY_GRAPH_ARGS_SCHEMA = z.object({
59
53
  path: z.string().default("src"),
60
54
  format: z.enum(["mermaid", "json"]).default("mermaid"),
61
55
  });
62
- export const GENERATE_ACADEMY_PROGRESS_REPORT_ARGS_SCHEMA = z.object({
63
- days: z.number().default(7),
64
- });
65
- export const ANALYZE_DOCUMENTATION_DEBT_ARGS_SCHEMA = z.object({
66
- path: z.string().default("."),
67
- });
68
- export const VALIDATE_REPOSITORY_HEALTH_ARGS_SCHEMA = z.object({
69
- scope: z.enum(["full", "lint", "test", "build"]).default("full"),
70
- });
71
56
  export const READ_AGENT_MESSAGES_ARGS_SCHEMA = z.object({
72
57
  agent: z.string().min(1),
73
58
  traceId: z.string().min(1).optional(),
@@ -81,17 +66,10 @@ export const GENERATE_SEMANTIC_COMMIT_MESSAGE_ARGS_SCHEMA = z.object({
81
66
  export const ANALYZE_CODEBASE_INTELLIGENCE_ARGS_SCHEMA = z.object({
82
67
  path: z.string().default("."),
83
68
  });
84
- export const GET_ACADEMY_PERFORMANCE_ARGS_SCHEMA = z.object({
85
- periodDays: z.number().default(30),
86
- });
87
69
  export const ANALYZE_PROCEDURAL_CONTINUITY_ARGS_SCHEMA = z.object({
88
70
  targetPath: z.string().min(1),
89
71
  referencePath: z.string().min(1),
90
72
  });
91
- export const GET_AGENT_AUDIT_REPORT_ARGS_SCHEMA = z.object({
92
- agent: z.string().min(1),
93
- days: z.number().default(7),
94
- });
95
73
  export const GET_AGENT_INBOX_STATS_ARGS_SCHEMA = z.object({
96
74
  agent: z.string().min(1),
97
75
  });
@@ -104,15 +82,3 @@ export const SYNC_CONTRACT_HASH_ARGS_SCHEMA = z.object({
104
82
  export const VERIFY_FRAMEWORK_HEALTH_ARGS_SCHEMA = z.object({
105
83
  detailed: z.boolean().default(false),
106
84
  });
107
- export const INSTANTIATE_BLUEPRINT_ARGS_SCHEMA = z.object({
108
- blueprintPath: z.string().min(1),
109
- targetPath: z.string().min(1),
110
- });
111
- export const LIST_BLUEPRINTS_ARGS_SCHEMA = z.object({
112
- category: z.enum(["backend", "frontend"]).optional(),
113
- });
114
- export const SAVE_AS_BLUEPRINT_ARGS_SCHEMA = z.object({
115
- blueprintPath: z.string().min(1),
116
- content: z.string().min(1),
117
- description: z.string().optional(),
118
- });
@@ -1,6 +1,5 @@
1
1
  import fs from "fs";
2
2
  import path from "path";
3
- import { execSync } from "child_process";
4
3
  import { getFrameworkDir, collectMarkdownArtifacts, FRAMEWORK_VERSION } from "../utils.js";
5
4
  export const frameworkTools = [
6
5
  {
@@ -119,10 +118,9 @@ export const frameworkHandlers = {
119
118
  missing.push(`[MISSING FOLDER] ${frameworkDir}/agents`);
120
119
  }
121
120
  else {
122
- const agents = fs.readdirSync(agentsDir).filter((f) => {
123
- const fullAgentDir = path.join(agentsDir, f);
124
- return fs.statSync(fullAgentDir).isDirectory() && fs.existsSync(path.join(fullAgentDir, "agent.json"));
125
- });
121
+ const agents = fs.readdirSync(agentsDir)
122
+ .filter((f) => f.endsWith(".md") && f !== "README.md")
123
+ .map((f) => f.replace(".md", ""));
126
124
  const logsDir = path.join(projectRoot, frameworkDir, "logs");
127
125
  for (const agentName of agents) {
128
126
  const logFile = path.join(logsDir, `${agentName}.json`);
@@ -182,15 +180,46 @@ export const frameworkHandlers = {
182
180
  },
183
181
  verify_framework_health: async (args, projectRoot) => {
184
182
  try {
185
- // Run the internal CLI check command via tsx to ensure we test current state
186
- // If the CLI is not yet linked, we might need a relative path
187
- const cliPath = path.join(projectRoot, "bin/cli.js");
188
- const output = execSync(`node ${cliPath} check`, { cwd: projectRoot, encoding: "utf-8", stdio: "pipe" });
189
- return { content: [{ type: "text", text: `### FRAMEWORK HEALTH CHECK\n\n${output}` }] };
183
+ const frameworkDir = getFrameworkDir(projectRoot);
184
+ const requiredFiles = [
185
+ ["Constitution", fs.existsSync(path.join(projectRoot, "ENDERUN.md")) ? "ENDERUN.md" : path.join(frameworkDir, "ENDERUN.md")],
186
+ ["Memory", path.join(frameworkDir, "PROJECT_MEMORY.md")],
187
+ ["Command Map", path.join(frameworkDir, "cli-commands.json")],
188
+ ["Framework Config", path.join(frameworkDir, "config.json")],
189
+ ["Agent Status", path.join(frameworkDir, "STATUS.md")],
190
+ ["MCP Config", "mcp.json"],
191
+ ["Backend Contract", "apps/backend/contract.version.json"],
192
+ ["MCP Server", "framework-mcp/package.json"],
193
+ ["Panda CSS Config", "panda.config.ts"],
194
+ ["Brain Dashboard", path.join(frameworkDir, "BRAIN_DASHBOARD.md")],
195
+ ["Docs Portal", "docs/README.md"],
196
+ ["Getting Started", "docs/getting-started.md"],
197
+ ];
198
+ const lines = [];
199
+ let issues = 0;
200
+ for (const [name, relativePath] of requiredFiles) {
201
+ if (fs.existsSync(path.join(projectRoot, relativePath))) {
202
+ lines.push(`OK ${name}: ${relativePath}`);
203
+ }
204
+ else {
205
+ issues += 1;
206
+ lines.push(`MISSING ${name}: ${relativePath}`);
207
+ }
208
+ }
209
+ const buildPath = path.join(projectRoot, "framework-mcp/dist/index.js");
210
+ if (fs.existsSync(buildPath)) {
211
+ lines.push("OK MCP Build: framework-mcp/dist/index.js");
212
+ }
213
+ else {
214
+ issues += 1;
215
+ lines.push("MISSING MCP Build: framework-mcp/dist/index.js");
216
+ }
217
+ const status = issues === 0 ? "PASSED" : "FAILED";
218
+ return { content: [{ type: "text", text: `### FRAMEWORK HEALTH CHECK (${status})\n\n${lines.join("\n")}` }] };
190
219
  }
191
220
  catch (error) {
192
221
  const err = error;
193
- return { content: [{ type: "text", text: `### FRAMEWORK HEALTH CHECK (FAILED)\n\n${err.stdout || err.message}` }] };
222
+ return { content: [{ type: "text", text: `### FRAMEWORK HEALTH CHECK (FAILED)\n\n${err.message ?? "Unknown error"}` }] };
194
223
  }
195
224
  }
196
225
  };
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Governance Tools - MCP Tools for enforcing corporate governance standards
3
3
  *
4
- * These tools bridge the .gemini/knowledge/corporate-governance/ knowledge base
4
+ * These tools bridge the .enderun/knowledge/corporate-governance/ knowledge base
5
5
  * with actual code-level compliance verification.
6
6
  *
7
7
  * Philosophy: We do not just document standards — we actively verify them.
@@ -78,14 +78,31 @@ export const memoryHandlers = {
78
78
  const lockPath = memoryPath + ".lock";
79
79
  const lockOwner = `lock-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
80
80
  try {
81
- if (fs.existsSync(lockPath)) {
82
- const stats = fs.statSync(lockPath);
83
- if (Date.now() - stats.mtimeMs > 120000)
84
- fs.unlinkSync(lockPath);
85
- else
86
- return { content: [{ type: "text", text: "Memory is locked. Try again later." }] };
81
+ try {
82
+ // Atomic write using 'wx' flag to prevent TOCTOU race conditions
83
+ fs.writeFileSync(lockPath, JSON.stringify({ owner: lockOwner, createdAt: new Date().toISOString() }), { flag: "wx" });
84
+ }
85
+ catch (error) {
86
+ if (error.code === "EEXIST") {
87
+ const stats = fs.statSync(lockPath);
88
+ if (Date.now() - stats.mtimeMs > 120000) {
89
+ try {
90
+ fs.unlinkSync(lockPath);
91
+ // Retry atomic write once after unlinking stale lock
92
+ fs.writeFileSync(lockPath, JSON.stringify({ owner: lockOwner, createdAt: new Date().toISOString() }), { flag: "wx" });
93
+ }
94
+ catch {
95
+ return { content: [{ type: "text", text: "Memory is locked. Try again later." }] };
96
+ }
97
+ }
98
+ else {
99
+ return { content: [{ type: "text", text: "Memory is locked. Try again later." }] };
100
+ }
101
+ }
102
+ else {
103
+ throw error;
104
+ }
87
105
  }
88
- fs.writeFileSync(lockPath, JSON.stringify({ owner: lockOwner, createdAt: new Date().toISOString() }));
89
106
  let memoryContent = fs.readFileSync(memoryPath, "utf-8");
90
107
  if (section === "HISTORY") {
91
108
  let updated = false;
@@ -93,7 +93,7 @@ export const monitoringHandlers = {
93
93
  results.agents = [{ error: "STATUS.md not found or unreadable" }];
94
94
  }
95
95
  // Read queue depths
96
- const queueBase = path.join(projectRoot, ".enderun", "queue");
96
+ const queueBase = path.join(projectRoot, getFrameworkDir(projectRoot), "queue");
97
97
  for (const subdir of ["pending", "processing", "completed", "failed"]) {
98
98
  try {
99
99
  const files = await fs.readdir(path.join(queueBase, subdir));
@@ -104,7 +104,7 @@ export const monitoringHandlers = {
104
104
  }
105
105
  }
106
106
  // Read orchestrator log for recent blockages and broadcasts
107
- const logPath = path.join(projectRoot, ".enderun", "logs", "orchestrator.json");
107
+ const logPath = path.join(projectRoot, getFrameworkDir(projectRoot), "logs", "orchestrator.json");
108
108
  try {
109
109
  const logContent = await fs.readFile(logPath, "utf-8");
110
110
  const log = JSON.parse(logContent);
@@ -128,7 +128,7 @@ export const monitoringHandlers = {
128
128
  const { agentName, lastN } = args;
129
129
  const n = lastN ?? 20;
130
130
  const logName = agentName.replace("@", "") + ".json";
131
- const logPath = path.join(projectRoot, ".enderun", "logs", logName);
131
+ const logPath = path.join(projectRoot, getFrameworkDir(projectRoot), "logs", logName);
132
132
  try {
133
133
  const content = await fs.readFile(logPath, "utf-8");
134
134
  const log = JSON.parse(content);
@@ -172,7 +172,7 @@ export const monitoringHandlers = {
172
172
  async detect_bottleneck_agents(args, projectRoot) {
173
173
  const { thresholdMinutes } = args;
174
174
  const threshold = thresholdMinutes ?? 30;
175
- const statusPath = path.join(projectRoot, ".enderun", "STATUS.md");
175
+ const statusPath = path.join(projectRoot, getFrameworkDir(projectRoot), "STATUS.md");
176
176
  const bottlenecks = [];
177
177
  try {
178
178
  const content = await fs.readFile(statusPath, "utf-8");
@@ -256,7 +256,7 @@ export const monitoringHandlers = {
256
256
  report.agentStatuses = [];
257
257
  }
258
258
  // Queue snapshot
259
- const queueBase = path.join(projectRoot, ".enderun", "queue");
259
+ const queueBase = path.join(projectRoot, getFrameworkDir(projectRoot), "queue");
260
260
  const snapshot = {};
261
261
  for (const dir of ["pending", "processing", "completed", "failed"]) {
262
262
  try {
@@ -269,7 +269,7 @@ export const monitoringHandlers = {
269
269
  }
270
270
  report.queueSnapshot = snapshot;
271
271
  // Recent blockages
272
- const logPath = path.join(projectRoot, ".enderun", "logs", "orchestrator.json");
272
+ const logPath = path.join(projectRoot, getFrameworkDir(projectRoot), "logs", "orchestrator.json");
273
273
  try {
274
274
  const logContent = await fs.readFile(logPath, "utf-8");
275
275
  const log = JSON.parse(logContent);
@@ -1,5 +1,6 @@
1
1
  import { promises as fs } from "fs";
2
2
  import path from "path";
3
+ import { getFrameworkDir } from "../utils.js";
3
4
  // ─── Tool Definitions ─────────────────────────────────────────────────────────
4
5
  export const orchestrationTools = [
5
6
  {
@@ -178,7 +179,7 @@ export const orchestrationHandlers = {
178
179
  parallelOpportunities: executionWaves.filter((w) => w.length > 1).length,
179
180
  };
180
181
  // Persist to queue
181
- const queueDir = path.join(projectRoot, ".enderun", "queue", "pending");
182
+ const queueDir = path.join(projectRoot, getFrameworkDir(projectRoot), "queue", "pending");
182
183
  await fs.mkdir(queueDir, { recursive: true });
183
184
  await fs.writeFile(path.join(queueDir, `${traceId}-dag.json`), JSON.stringify(dag, null, 2), "utf-8");
184
185
  return { content: [{ type: "text", text: JSON.stringify(dag, null, 2) }] };
@@ -188,7 +189,7 @@ export const orchestrationHandlers = {
188
189
  // Try to load capability roster from shared-facts.json (dynamic — fixes KRITIK-3)
189
190
  let agentCapabilityRoster = [];
190
191
  try {
191
- const sharedFactsPath = path.join(projectRoot, ".enderun", "memory-graph", "shared-facts.json");
192
+ const sharedFactsPath = path.join(projectRoot, getFrameworkDir(projectRoot), "memory-graph", "shared-facts.json");
192
193
  const content = await fs.readFile(sharedFactsPath, "utf-8");
193
194
  const facts = JSON.parse(content);
194
195
  agentCapabilityRoster = facts.agentCapabilityRoster ?? [];
@@ -249,7 +250,7 @@ export const orchestrationHandlers = {
249
250
  sourceMap: Object.keys(domainMap).length > 0 ? "shared-facts" : "fallback",
250
251
  };
251
252
  // Write assignment log
252
- const logPath = path.join(projectRoot, ".enderun", "logs", "orchestrator.json");
253
+ const logPath = path.join(projectRoot, getFrameworkDir(projectRoot), "logs", "orchestrator.json");
253
254
  let log = { assignments: [] };
254
255
  try {
255
256
  const existing = await fs.readFile(logPath, "utf-8");
@@ -264,7 +265,7 @@ export const orchestrationHandlers = {
264
265
  return { content: [{ type: "text", text: JSON.stringify(assignment, null, 2) }] };
265
266
  },
266
267
  async get_agent_workload(_args, projectRoot) {
267
- const statusPath = path.join(projectRoot, ".enderun", "STATUS.md");
268
+ const statusPath = path.join(projectRoot, getFrameworkDir(projectRoot), "STATUS.md");
268
269
  let statusContent;
269
270
  try {
270
271
  statusContent = await fs.readFile(statusPath, "utf-8");
@@ -301,7 +302,7 @@ export const orchestrationHandlers = {
301
302
  status: "ESCALATED_TO_MANAGER",
302
303
  };
303
304
  // Log blockage
304
- const logPath = path.join(projectRoot, ".enderun", "logs", "orchestrator.json");
305
+ const logPath = path.join(projectRoot, getFrameworkDir(projectRoot), "logs", "orchestrator.json");
305
306
  let log = { assignments: [], blockages: [] };
306
307
  try {
307
308
  const existing = await fs.readFile(logPath, "utf-8");
@@ -316,7 +317,7 @@ export const orchestrationHandlers = {
316
317
  await fs.mkdir(path.dirname(logPath), { recursive: true });
317
318
  await fs.writeFile(logPath, JSON.stringify(log, null, 2), "utf-8");
318
319
  // Write a message to @manager's inbox via messages folder
319
- const messagesDir = path.join(projectRoot, ".enderun", "messages");
320
+ const messagesDir = path.join(projectRoot, getFrameworkDir(projectRoot), "messages");
320
321
  await fs.mkdir(messagesDir, { recursive: true });
321
322
  const messageFile = path.join(messagesDir, `BLOCKAGE-${traceId}-${Date.now()}.json`);
322
323
  await fs.writeFile(messageFile, JSON.stringify({
@@ -342,11 +343,11 @@ export const orchestrationHandlers = {
342
343
  timestamp: new Date().toISOString(),
343
344
  };
344
345
  // Write to messages folder for all agents
345
- const messagesDir = path.join(projectRoot, ".enderun", "messages");
346
+ const messagesDir = path.join(projectRoot, getFrameworkDir(projectRoot), "messages");
346
347
  await fs.mkdir(messagesDir, { recursive: true });
347
348
  await fs.writeFile(path.join(messagesDir, `${broadcast.id}.json`), JSON.stringify(broadcast, null, 2), "utf-8");
348
349
  // Log broadcast
349
- const logPath = path.join(projectRoot, ".enderun", "logs", "orchestrator.json");
350
+ const logPath = path.join(projectRoot, getFrameworkDir(projectRoot), "logs", "orchestrator.json");
350
351
  let log = {
351
352
  assignments: [],
352
353
  blockages: [],
@@ -8,13 +8,13 @@ const getPackageVersion = () => {
8
8
  const pkgPath = path.join(__dirname, "../package.json");
9
9
  if (fs.existsSync(pkgPath)) {
10
10
  const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
11
- return pkg.version || "0.7.5";
11
+ return pkg.version || "0.8.8";
12
12
  }
13
13
  }
14
14
  catch {
15
15
  // fallback
16
16
  }
17
- return "0.6.6";
17
+ return "0.8.8";
18
18
  };
19
19
  export const FRAMEWORK_VERSION = getPackageVersion();
20
20
  /**
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-enderun-mcp",
3
- "version": "0.8.6",
3
+ "version": "0.8.8",
4
4
  "description": "Enterprise-grade MCP Server for AI Agent Framework",
5
5
  "author": "Yusuf BEKAR",
6
6
  "license": "MIT",
@@ -1,6 +1,5 @@
1
1
  import fs from "fs";
2
2
  import path from "path";
3
- import { execSync } from "child_process";
4
3
  import {
5
4
  getFrameworkDir,
6
5
  collectMarkdownArtifacts,
@@ -179,14 +178,46 @@ export const frameworkHandlers = {
179
178
  },
180
179
  verify_framework_health: async (args: unknown, projectRoot: string) => {
181
180
  try {
182
- // Run the internal CLI check command via tsx to ensure we test current state
183
- // If the CLI is not yet linked, we might need a relative path
184
- const cliPath = path.join(projectRoot, "bin/cli.js");
185
- const output = execSync(`node ${cliPath} check`, { cwd: projectRoot, encoding: "utf-8", stdio: "pipe" });
186
- return { content: [{ type: "text", text: `### FRAMEWORK HEALTH CHECK\n\n${output}` }] };
181
+ const frameworkDir = getFrameworkDir(projectRoot);
182
+ const requiredFiles = [
183
+ ["Constitution", fs.existsSync(path.join(projectRoot, "ENDERUN.md")) ? "ENDERUN.md" : path.join(frameworkDir, "ENDERUN.md")],
184
+ ["Memory", path.join(frameworkDir, "PROJECT_MEMORY.md")],
185
+ ["Command Map", path.join(frameworkDir, "cli-commands.json")],
186
+ ["Framework Config", path.join(frameworkDir, "config.json")],
187
+ ["Agent Status", path.join(frameworkDir, "STATUS.md")],
188
+ ["MCP Config", "mcp.json"],
189
+ ["Backend Contract", "apps/backend/contract.version.json"],
190
+ ["MCP Server", "framework-mcp/package.json"],
191
+ ["Panda CSS Config", "panda.config.ts"],
192
+ ["Brain Dashboard", path.join(frameworkDir, "BRAIN_DASHBOARD.md")],
193
+ ["Docs Portal", "docs/README.md"],
194
+ ["Getting Started", "docs/getting-started.md"],
195
+ ] as const;
196
+
197
+ const lines: string[] = [];
198
+ let issues = 0;
199
+ for (const [name, relativePath] of requiredFiles) {
200
+ if (fs.existsSync(path.join(projectRoot, relativePath))) {
201
+ lines.push(`OK ${name}: ${relativePath}`);
202
+ } else {
203
+ issues += 1;
204
+ lines.push(`MISSING ${name}: ${relativePath}`);
205
+ }
206
+ }
207
+
208
+ const buildPath = path.join(projectRoot, "framework-mcp/dist/index.js");
209
+ if (fs.existsSync(buildPath)) {
210
+ lines.push("OK MCP Build: framework-mcp/dist/index.js");
211
+ } else {
212
+ issues += 1;
213
+ lines.push("MISSING MCP Build: framework-mcp/dist/index.js");
214
+ }
215
+
216
+ const status = issues === 0 ? "PASSED" : "FAILED";
217
+ return { content: [{ type: "text", text: `### FRAMEWORK HEALTH CHECK (${status})\n\n${lines.join("\n")}` }] };
187
218
  } catch (error: unknown) {
188
- const err = error as { stdout?: string; message?: string };
189
- return { content: [{ type: "text", text: `### FRAMEWORK HEALTH CHECK (FAILED)\n\n${err.stdout || err.message}` }] };
219
+ const err = error as { message?: string };
220
+ return { content: [{ type: "text", text: `### FRAMEWORK HEALTH CHECK (FAILED)\n\n${err.message ?? "Unknown error"}` }] };
190
221
  }
191
222
  }
192
223
  };
@@ -83,12 +83,27 @@ export const memoryHandlers = {
83
83
  const lockPath = memoryPath + ".lock";
84
84
  const lockOwner = `lock-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
85
85
  try {
86
- if (fs.existsSync(lockPath)) {
87
- const stats = fs.statSync(lockPath);
88
- if (Date.now() - stats.mtimeMs > 120000) fs.unlinkSync(lockPath);
89
- else return { content: [{ type: "text", text: "Memory is locked. Try again later." }] };
86
+ try {
87
+ // Atomic write using 'wx' flag to prevent TOCTOU race conditions
88
+ fs.writeFileSync(lockPath, JSON.stringify({ owner: lockOwner, createdAt: new Date().toISOString() }), { flag: "wx" });
89
+ } catch (error: any) {
90
+ if (error.code === "EEXIST") {
91
+ const stats = fs.statSync(lockPath);
92
+ if (Date.now() - stats.mtimeMs > 120000) {
93
+ try {
94
+ fs.unlinkSync(lockPath);
95
+ // Retry atomic write once after unlinking stale lock
96
+ fs.writeFileSync(lockPath, JSON.stringify({ owner: lockOwner, createdAt: new Date().toISOString() }), { flag: "wx" });
97
+ } catch {
98
+ return { content: [{ type: "text", text: "Memory is locked. Try again later." }] };
99
+ }
100
+ } else {
101
+ return { content: [{ type: "text", text: "Memory is locked. Try again later." }] };
102
+ }
103
+ } else {
104
+ throw error;
105
+ }
90
106
  }
91
- fs.writeFileSync(lockPath, JSON.stringify({ owner: lockOwner, createdAt: new Date().toISOString() }));
92
107
  let memoryContent = fs.readFileSync(memoryPath, "utf-8");
93
108
  if (section === "HISTORY") {
94
109
  let updated = false;
@@ -113,7 +113,7 @@ export const monitoringHandlers: Record<
113
113
  }
114
114
 
115
115
  // Read queue depths
116
- const queueBase = path.join(projectRoot, ".enderun", "queue");
116
+ const queueBase = path.join(projectRoot, getFrameworkDir(projectRoot), "queue");
117
117
  for (const subdir of ["pending", "processing", "completed", "failed"] as const) {
118
118
  try {
119
119
  const files = await fs.readdir(path.join(queueBase, subdir));
@@ -124,7 +124,7 @@ export const monitoringHandlers: Record<
124
124
  }
125
125
 
126
126
  // Read orchestrator log for recent blockages and broadcasts
127
- const logPath = path.join(projectRoot, ".enderun", "logs", "orchestrator.json");
127
+ const logPath = path.join(projectRoot, getFrameworkDir(projectRoot), "logs", "orchestrator.json");
128
128
  try {
129
129
  const logContent = await fs.readFile(logPath, "utf-8");
130
130
  const log = JSON.parse(logContent) as {
@@ -152,7 +152,7 @@ export const monitoringHandlers: Record<
152
152
  const { agentName, lastN } = args as { agentName: string; lastN?: number };
153
153
  const n = lastN ?? 20;
154
154
  const logName = agentName.replace("@", "") + ".json";
155
- const logPath = path.join(projectRoot, ".enderun", "logs", logName);
155
+ const logPath = path.join(projectRoot, getFrameworkDir(projectRoot), "logs", logName);
156
156
 
157
157
  try {
158
158
  const content = await fs.readFile(logPath, "utf-8");
@@ -206,7 +206,7 @@ export const monitoringHandlers: Record<
206
206
  const { thresholdMinutes } = args as { thresholdMinutes?: number };
207
207
  const threshold = thresholdMinutes ?? 30;
208
208
 
209
- const statusPath = path.join(projectRoot, ".enderun", "STATUS.md");
209
+ const statusPath = path.join(projectRoot, getFrameworkDir(projectRoot), "STATUS.md");
210
210
  const bottlenecks: Array<{ agent: string; reason: string; state: string; severity: string }> = [];
211
211
 
212
212
  try {
@@ -308,7 +308,7 @@ export const monitoringHandlers: Record<
308
308
  }
309
309
 
310
310
  // Queue snapshot
311
- const queueBase = path.join(projectRoot, ".enderun", "queue");
311
+ const queueBase = path.join(projectRoot, getFrameworkDir(projectRoot), "queue");
312
312
  const snapshot: Record<string, number> = {};
313
313
  for (const dir of ["pending", "processing", "completed", "failed"]) {
314
314
  try {
@@ -321,7 +321,7 @@ export const monitoringHandlers: Record<
321
321
  report.queueSnapshot = snapshot;
322
322
 
323
323
  // Recent blockages
324
- const logPath = path.join(projectRoot, ".enderun", "logs", "orchestrator.json");
324
+ const logPath = path.join(projectRoot, getFrameworkDir(projectRoot), "logs", "orchestrator.json");
325
325
  try {
326
326
  const logContent = await fs.readFile(logPath, "utf-8");
327
327
  const log = JSON.parse(logContent) as { blockages?: unknown[] };
@@ -1,6 +1,7 @@
1
1
  import { Tool } from "@modelcontextprotocol/sdk/types.js";
2
2
  import { promises as fs } from "fs";
3
3
  import path from "path";
4
+ import { getFrameworkDir } from "../utils.js";
4
5
 
5
6
  // ─── Tool Definitions ─────────────────────────────────────────────────────────
6
7
 
@@ -208,7 +209,7 @@ export const orchestrationHandlers: Record<
208
209
  };
209
210
 
210
211
  // Persist to queue
211
- const queueDir = path.join(projectRoot, ".enderun", "queue", "pending");
212
+ const queueDir = path.join(projectRoot, getFrameworkDir(projectRoot), "queue", "pending");
212
213
  await fs.mkdir(queueDir, { recursive: true });
213
214
  await fs.writeFile(
214
215
  path.join(queueDir, `${traceId}-dag.json`),
@@ -230,7 +231,7 @@ export const orchestrationHandlers: Record<
230
231
  // Try to load capability roster from shared-facts.json (dynamic — fixes KRITIK-3)
231
232
  let agentCapabilityRoster: Array<{ agent: string; primaryDomain: string; capability: number }> = [];
232
233
  try {
233
- const sharedFactsPath = path.join(projectRoot, ".enderun", "memory-graph", "shared-facts.json");
234
+ const sharedFactsPath = path.join(projectRoot, getFrameworkDir(projectRoot), "memory-graph", "shared-facts.json");
234
235
  const content = await fs.readFile(sharedFactsPath, "utf-8");
235
236
  const facts = JSON.parse(content) as { agentCapabilityRoster?: typeof agentCapabilityRoster };
236
237
  agentCapabilityRoster = facts.agentCapabilityRoster ?? [];
@@ -294,7 +295,7 @@ export const orchestrationHandlers: Record<
294
295
  };
295
296
 
296
297
  // Write assignment log
297
- const logPath = path.join(projectRoot, ".enderun", "logs", "orchestrator.json");
298
+ const logPath = path.join(projectRoot, getFrameworkDir(projectRoot), "logs", "orchestrator.json");
298
299
  let log: { assignments: unknown[] } = { assignments: [] };
299
300
  try {
300
301
  const existing = await fs.readFile(logPath, "utf-8");
@@ -310,7 +311,7 @@ export const orchestrationHandlers: Record<
310
311
  },
311
312
 
312
313
  async get_agent_workload(_args, projectRoot) {
313
- const statusPath = path.join(projectRoot, ".enderun", "STATUS.md");
314
+ const statusPath = path.join(projectRoot, getFrameworkDir(projectRoot), "STATUS.md");
314
315
  let statusContent: string;
315
316
  try {
316
317
  statusContent = await fs.readFile(statusPath, "utf-8");
@@ -356,7 +357,7 @@ export const orchestrationHandlers: Record<
356
357
  };
357
358
 
358
359
  // Log blockage
359
- const logPath = path.join(projectRoot, ".enderun", "logs", "orchestrator.json");
360
+ const logPath = path.join(projectRoot, getFrameworkDir(projectRoot), "logs", "orchestrator.json");
360
361
  let log: { assignments: unknown[]; blockages: unknown[] } = { assignments: [], blockages: [] };
361
362
  try {
362
363
  const existing = await fs.readFile(logPath, "utf-8");
@@ -370,7 +371,7 @@ export const orchestrationHandlers: Record<
370
371
  await fs.writeFile(logPath, JSON.stringify(log, null, 2), "utf-8");
371
372
 
372
373
  // Write a message to @manager's inbox via messages folder
373
- const messagesDir = path.join(projectRoot, ".enderun", "messages");
374
+ const messagesDir = path.join(projectRoot, getFrameworkDir(projectRoot), "messages");
374
375
  await fs.mkdir(messagesDir, { recursive: true });
375
376
  const messageFile = path.join(messagesDir, `BLOCKAGE-${traceId}-${Date.now()}.json`);
376
377
  await fs.writeFile(
@@ -409,7 +410,7 @@ export const orchestrationHandlers: Record<
409
410
  };
410
411
 
411
412
  // Write to messages folder for all agents
412
- const messagesDir = path.join(projectRoot, ".enderun", "messages");
413
+ const messagesDir = path.join(projectRoot, getFrameworkDir(projectRoot), "messages");
413
414
  await fs.mkdir(messagesDir, { recursive: true });
414
415
  await fs.writeFile(
415
416
  path.join(messagesDir, `${broadcast.id}.json`),
@@ -418,7 +419,7 @@ export const orchestrationHandlers: Record<
418
419
  );
419
420
 
420
421
  // Log broadcast
421
- const logPath = path.join(projectRoot, ".enderun", "logs", "orchestrator.json");
422
+ const logPath = path.join(projectRoot, getFrameworkDir(projectRoot), "logs", "orchestrator.json");
422
423
  let log: { assignments: unknown[]; blockages: unknown[]; broadcasts: unknown[] } = {
423
424
  assignments: [],
424
425
  blockages: [],
@@ -10,12 +10,12 @@ const getPackageVersion = (): string => {
10
10
  const pkgPath = path.join(__dirname, "../package.json");
11
11
  if (fs.existsSync(pkgPath)) {
12
12
  const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
13
- return pkg.version || "0.8.5";
13
+ return pkg.version || "0.8.8";
14
14
  }
15
15
  } catch {
16
16
  // fallback
17
17
  }
18
- return "0.8.5";
18
+ return "0.8.8";
19
19
  };
20
20
 
21
21
  export const FRAMEWORK_VERSION = getPackageVersion();
package/mcp.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "mcpServers": {
3
3
  "agent-enderun": {
4
- "command": "npx",
5
- "args": ["agent-enderun", "mcp"],
4
+ "command": "node",
5
+ "args": ["framework-mcp/dist/index.js"],
6
6
  "env": {}
7
7
  }
8
8
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-enderun",
3
- "version": "0.8.6",
3
+ "version": "0.8.8",
4
4
  "description": "The Supreme AI Governance & Orchestration Framework for Enterprise Development",
5
5
  "author": "Yusuf BEKAR",
6
6
  "license": "MIT",