agent-enderun 1.11.3 → 1.11.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/README.md +84 -277
  2. package/bin/validate-agent-army.js +0 -1
  3. package/dist/framework-mcp/src/index.js +11 -1
  4. package/dist/framework-mcp/src/index.js.map +1 -1
  5. package/dist/framework-mcp/src/tools/control_plane/locking.js +17 -3
  6. package/dist/framework-mcp/src/tools/control_plane/locking.js.map +1 -1
  7. package/dist/framework-mcp/src/tools/definitions.js +0 -10
  8. package/dist/framework-mcp/src/tools/definitions.js.map +1 -1
  9. package/dist/framework-mcp/src/tools/index.js +0 -2
  10. package/dist/framework-mcp/src/tools/index.js.map +1 -1
  11. package/dist/framework-mcp/src/tools/memory/get_insights.js +4 -4
  12. package/dist/framework-mcp/src/tools/memory/get_insights.js.map +1 -1
  13. package/dist/framework-mcp/src/tools/messaging/send_message.js +10 -5
  14. package/dist/framework-mcp/src/tools/messaging/send_message.js.map +1 -1
  15. package/dist/framework-mcp/src/tools/search/grep_search.js +2 -3
  16. package/dist/framework-mcp/src/tools/search/grep_search.js.map +1 -1
  17. package/dist/framework-mcp/src/utils/compliance.d.ts +1 -1
  18. package/dist/framework-mcp/src/utils/compliance.js +68 -19
  19. package/dist/framework-mcp/src/utils/compliance.js.map +1 -1
  20. package/dist/framework-mcp/tests/tools/messaging/send_message.test.js +7 -7
  21. package/dist/framework-mcp/tests/tools/messaging/send_message.test.js.map +1 -1
  22. package/dist/src/cli/adapters/scaffold.d.ts +1 -1
  23. package/dist/src/cli/adapters/scaffold.js +8 -8
  24. package/dist/src/cli/adapters/scaffold.js.map +1 -1
  25. package/dist/src/cli/commands/check.js +25 -40
  26. package/dist/src/cli/commands/check.js.map +1 -1
  27. package/dist/src/cli/commands/contract.d.ts +3 -1
  28. package/dist/src/cli/commands/contract.js +8 -2
  29. package/dist/src/cli/commands/contract.js.map +1 -1
  30. package/dist/src/cli/commands/init/scaffold-core.d.ts +1 -0
  31. package/dist/src/cli/commands/init/scaffold-core.js +14 -1
  32. package/dist/src/cli/commands/init/scaffold-core.js.map +1 -1
  33. package/dist/src/cli/commands/init/scaffold-ops.js +0 -2
  34. package/dist/src/cli/commands/init/scaffold-ops.js.map +1 -1
  35. package/dist/src/cli/commands/init.d.ts +1 -0
  36. package/dist/src/cli/commands/init.js +16 -7
  37. package/dist/src/cli/commands/init.js.map +1 -1
  38. package/dist/src/cli/commands/orchestrate.js +31 -3
  39. package/dist/src/cli/commands/orchestrate.js.map +1 -1
  40. package/dist/src/cli/commands/plan.js +1 -2
  41. package/dist/src/cli/commands/plan.js.map +1 -1
  42. package/dist/src/cli/commands/status.js.map +1 -1
  43. package/dist/src/cli/index.js +18 -21
  44. package/dist/src/cli/index.js.map +1 -1
  45. package/dist/src/cli/shims.js +39 -26
  46. package/dist/src/cli/shims.js.map +1 -1
  47. package/dist/src/cli/utils/compliance.d.ts +5 -1
  48. package/dist/src/cli/utils/compliance.js +90 -42
  49. package/dist/src/cli/utils/compliance.js.map +1 -1
  50. package/dist/src/cli/utils/ui.js +11 -11
  51. package/dist/src/cli/utils/ui.js.map +1 -1
  52. package/dist/src/modules/adapters/definitions.js +130 -113
  53. package/dist/src/modules/adapters/definitions.js.map +1 -1
  54. package/dist/src/modules/adapters/registry.d.ts +12 -0
  55. package/dist/src/modules/adapters/registry.js +19 -0
  56. package/dist/src/modules/adapters/registry.js.map +1 -0
  57. package/dist/src/modules/adapters/shared.js +5 -4
  58. package/dist/src/modules/adapters/shared.js.map +1 -1
  59. package/dist/src/modules/adapters/types.d.ts +1 -1
  60. package/dist/src/modules/adapters/types.js +1 -1
  61. package/dist/src/modules/adapters/types.js.map +1 -1
  62. package/dist/src/modules/agents/definitions.d.ts +6 -6
  63. package/dist/src/modules/agents/definitions.js +35 -18
  64. package/dist/src/modules/agents/definitions.js.map +1 -1
  65. package/dist/src/modules/agents/registry/analyst.js +1 -1
  66. package/dist/src/modules/agents/registry/analyst.js.map +1 -1
  67. package/dist/src/modules/agents/registry/architect.js +1 -1
  68. package/dist/src/modules/agents/registry/architect.js.map +1 -1
  69. package/dist/src/modules/agents/registry/backend.js +1 -1
  70. package/dist/src/modules/agents/registry/backend.js.map +1 -1
  71. package/dist/src/modules/agents/registry/database.js +1 -1
  72. package/dist/src/modules/agents/registry/database.js.map +1 -1
  73. package/dist/src/modules/agents/registry/devops.js +1 -1
  74. package/dist/src/modules/agents/registry/devops.js.map +1 -1
  75. package/dist/src/modules/agents/registry/explorer.js +1 -1
  76. package/dist/src/modules/agents/registry/explorer.js.map +1 -1
  77. package/dist/src/modules/agents/registry/frontend.js +1 -1
  78. package/dist/src/modules/agents/registry/frontend.js.map +1 -1
  79. package/dist/src/modules/agents/registry/git.js +1 -1
  80. package/dist/src/modules/agents/registry/git.js.map +1 -1
  81. package/dist/src/modules/agents/registry/manager.js +1 -2
  82. package/dist/src/modules/agents/registry/manager.js.map +1 -1
  83. package/dist/src/modules/agents/registry/mobile.js +1 -1
  84. package/dist/src/modules/agents/registry/mobile.js.map +1 -1
  85. package/dist/src/modules/agents/registry/quality.js +1 -1
  86. package/dist/src/modules/agents/registry/quality.js.map +1 -1
  87. package/dist/src/modules/agents/registry/security.js +1 -1
  88. package/dist/src/modules/agents/registry/security.js.map +1 -1
  89. package/dist/src/shared/constants.d.ts +5 -2
  90. package/dist/src/shared/constants.js +5 -0
  91. package/dist/src/shared/constants.js.map +1 -1
  92. package/dist/src/shared/fs.js +4 -3
  93. package/dist/src/shared/fs.js.map +1 -1
  94. package/dist/tests/agents-definitions.test.js +2 -1
  95. package/dist/tests/agents-definitions.test.js.map +1 -1
  96. package/dist/tests/approve.test.js +3 -1
  97. package/dist/tests/approve.test.js.map +1 -1
  98. package/dist/tests/integration/agent_flow.test.js +2 -0
  99. package/dist/tests/integration/agent_flow.test.js.map +1 -1
  100. package/dist/tests/integration/hermes_locking.test.d.ts +1 -0
  101. package/dist/tests/integration/hermes_locking.test.js +64 -0
  102. package/dist/tests/integration/hermes_locking.test.js.map +1 -0
  103. package/dist/tests/memory-utils.test.js +2 -0
  104. package/dist/tests/memory-utils.test.js.map +1 -1
  105. package/dist/tests/orchestrate.test.js +11 -6
  106. package/dist/tests/orchestrate.test.js.map +1 -1
  107. package/dist/tests/status.test.js +2 -0
  108. package/dist/tests/status.test.js.map +1 -1
  109. package/framework-mcp/dist/index.js +11 -1
  110. package/framework-mcp/dist/tools/control_plane/locking.js +17 -3
  111. package/framework-mcp/dist/tools/definitions.js +0 -10
  112. package/framework-mcp/dist/tools/index.js +0 -2
  113. package/framework-mcp/dist/tools/memory/get_insights.js +4 -4
  114. package/framework-mcp/dist/tools/messaging/send_message.js +10 -5
  115. package/framework-mcp/dist/tools/search/grep_search.js +2 -3
  116. package/framework-mcp/dist/utils/compliance.js +68 -19
  117. package/framework-mcp/node_modules/.package-lock.json +1179 -0
  118. package/framework-mcp/package-lock.json +1191 -0
  119. package/framework-mcp/package.json +2 -2
  120. package/framework-mcp/src/index.ts +13 -1
  121. package/framework-mcp/src/tools/control_plane/locking.ts +18 -4
  122. package/framework-mcp/src/tools/definitions.ts +0 -10
  123. package/framework-mcp/src/tools/index.ts +0 -2
  124. package/framework-mcp/src/tools/memory/get_insights.ts +4 -4
  125. package/framework-mcp/src/tools/messaging/send_message.ts +11 -5
  126. package/framework-mcp/src/tools/search/grep_search.ts +2 -3
  127. package/framework-mcp/src/utils/compliance.ts +83 -25
  128. package/framework-mcp/tests/tools/messaging/send_message.test.ts +7 -7
  129. package/package.json +5 -5
  130. package/src/cli/adapters/scaffold.ts +9 -8
  131. package/src/cli/commands/check.ts +26 -48
  132. package/src/cli/commands/contract.ts +5 -2
  133. package/src/cli/commands/init/scaffold-core.ts +18 -2
  134. package/src/cli/commands/init/scaffold-ops.ts +0 -3
  135. package/src/cli/commands/init.ts +19 -8
  136. package/src/cli/commands/orchestrate.ts +36 -3
  137. package/src/cli/commands/plan.ts +1 -2
  138. package/src/cli/commands/status.ts +0 -1
  139. package/src/cli/index.ts +18 -22
  140. package/src/cli/shims.ts +39 -26
  141. package/src/cli/utils/compliance.ts +101 -47
  142. package/src/cli/utils/ui.ts +11 -11
  143. package/src/modules/adapters/definitions.ts +102 -57
  144. package/src/modules/adapters/registry.ts +27 -0
  145. package/src/modules/adapters/shared.ts +5 -4
  146. package/src/modules/adapters/types.ts +1 -1
  147. package/src/modules/agents/definitions.ts +35 -17
  148. package/src/modules/agents/registry/analyst.ts +1 -1
  149. package/src/modules/agents/registry/architect.ts +1 -1
  150. package/src/modules/agents/registry/backend.ts +1 -1
  151. package/src/modules/agents/registry/database.ts +1 -1
  152. package/src/modules/agents/registry/devops.ts +1 -1
  153. package/src/modules/agents/registry/explorer.ts +1 -1
  154. package/src/modules/agents/registry/frontend.ts +1 -1
  155. package/src/modules/agents/registry/git.ts +1 -1
  156. package/src/modules/agents/registry/manager.ts +1 -2
  157. package/src/modules/agents/registry/mobile.ts +1 -1
  158. package/src/modules/agents/registry/quality.ts +1 -1
  159. package/src/modules/agents/registry/security.ts +1 -1
  160. package/src/shared/constants.ts +5 -0
  161. package/src/shared/fs.ts +4 -3
  162. package/templates/standards/frontend-standards.md +7 -2
  163. package/templates/standards/governance-standards.md +27 -1
  164. package/templates/standards/quality-standards.md +2 -1
  165. package/bin/enderun-dashboard.js +0 -25
  166. package/framework-mcp/src/tools/dashboard/start_dashboard.ts +0 -33
  167. package/src/cli/commands/dashboard/data-service.ts +0 -167
  168. package/src/cli/commands/dashboard/html-template.ts +0 -696
  169. package/src/cli/commands/dashboard/server.ts +0 -43
  170. package/src/cli/commands/dashboard.ts +0 -11
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-enderun/mcp",
3
- "version": "1.10.7",
3
+ "version": "1.11.5",
4
4
  "description": "Agent Enderun Model Context Protocol (MCP) Server",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -14,6 +14,6 @@
14
14
  },
15
15
  "devDependencies": {
16
16
  "@types/node": "^25.9.1",
17
- "typescript": "^5.0.0"
17
+ "typescript": "^5.9.3"
18
18
  }
19
19
  }
@@ -57,13 +57,25 @@ function validateArgs(toolName: string, args: Record<string, unknown>): string |
57
57
  return null;
58
58
  }
59
59
 
60
- server.setRequestHandler(ListToolsRequestSchema, async () => {
60
+ server.setRequestHandler(ListToolsRequestSchema, async (request) => {
61
+ // 2026 Stateless Spec: Log client info from metadata if available
62
+ const meta = (request as { _meta?: { client?: { name?: string; version?: string } } })._meta;
63
+ if (meta) {
64
+ process.stderr.write(`[MCP] Stateless ListTools from ${meta.client?.name || "unknown"} v${meta.client?.version || "?.?"}\n`);
65
+ }
61
66
  return { tools: TOOLS };
62
67
  });
63
68
 
64
69
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
65
70
  const req = request as { params: { name: string, arguments?: Record<string, unknown> } };
66
71
  const { name, arguments: args } = req.params;
72
+ const meta = (request as { _meta?: { client?: { name?: string; version?: string } } })._meta;
73
+
74
+ // 2026 Stateless Spec: Prioritize metadata-driven context
75
+ if (meta) {
76
+ process.stderr.write(`[MCP] Stateless CallTool: ${name} (Client: ${meta.client?.name || "unknown"})\n`);
77
+ }
78
+
67
79
  const projectRoot = process.env.ENDERUN_PROJECT_ROOT || process.cwd();
68
80
 
69
81
  try {
@@ -15,6 +15,7 @@ export async function handleAcquireLock(projectRoot: string, args: AcquireLockAr
15
15
  try {
16
16
  if (!fs.existsSync(lockDir)) fs.mkdirSync(lockDir, { recursive: true });
17
17
 
18
+ // Check for stale lock first
18
19
  if (fs.existsSync(lockPath)) {
19
20
  const stat = fs.statSync(lockPath);
20
21
  const now = new Date().getTime();
@@ -26,15 +27,28 @@ export async function handleAcquireLock(projectRoot: string, args: AcquireLockAr
26
27
  content: [{ type: "text", text: `Resource '${resource}' is currently locked by another agent.` }]
27
28
  };
28
29
  }
29
- // Lock expired, safe to override
30
- fs.unlinkSync(lockPath);
30
+ // Lock expired, safe to override by deleting first
31
+ try {
32
+ fs.unlinkSync(lockPath);
33
+ } catch (unlinkErr) {
34
+ // If another agent unlinked it first, ignore
35
+ }
31
36
  }
32
37
 
33
- fs.writeFileSync(lockPath, JSON.stringify({ agent, timestamp: new Date().toISOString() }));
38
+ // Use 'wx' flag for atomic file creation
39
+ const lockData = JSON.stringify({ agent, timestamp: new Date().toISOString() });
40
+ fs.writeFileSync(lockPath, lockData, { flag: "wx" });
41
+
34
42
  return {
35
43
  content: [{ type: "text", text: `āœ… Lock acquired for resource '${resource}' by ${agent}.` }]
36
44
  };
37
- } catch (e) {
45
+ } catch (e: any) {
46
+ if (e.code === "EEXIST") {
47
+ return {
48
+ isError: true,
49
+ content: [{ type: "text", text: `Resource '${resource}' was just acquired by another agent.` }]
50
+ };
51
+ }
38
52
  return {
39
53
  isError: true,
40
54
  content: [{ type: "text", text: `Failed to acquire lock: ${String(e)}` }]
@@ -151,16 +151,6 @@ export const TOOLS: ToolDefinition[] = [
151
151
  }
152
152
  }
153
153
  },
154
- {
155
- name: "start_dashboard",
156
- description: "Start the Enderun Web Dashboard (Command Center) to visualize real-time agent status, logs, and system metrics in the browser.",
157
- inputSchema: {
158
- type: "object",
159
- properties: {
160
- port: { type: "number", description: "Optional: Port to run the dashboard on (default: 5858)" }
161
- }
162
- }
163
- },
164
154
  {
165
155
  name: "get_framework_status",
166
156
  description: "Get the current project phase, active traces, and agent states.",
@@ -14,7 +14,6 @@ import { handleAuditDependencies } from "./framework/audit_deps.js";
14
14
  import { handleRunTests } from "./framework/run_tests.js";
15
15
  import { handleGetSystemHealth } from "./observability/get_health.js";
16
16
  import { handleCheckPorts } from "./observability/check_ports.js";
17
- import { handleStartDashboard } from "./dashboard/start_dashboard.js";
18
17
  import { handleOrchestrateLoop } from "./framework/orchestrate.js";
19
18
  import { handleUpdateContractHash } from "./framework/update_contract_hash.js";
20
19
  import { handleReadProjectMemory } from "./memory/read_memory.js";
@@ -51,7 +50,6 @@ export const toolHandlers: Record<string, ToolHandler> = {
51
50
  run_tests: bind(handleRunTests),
52
51
  get_system_health: bind(handleGetSystemHealth),
53
52
  check_active_ports: bind(handleCheckPorts),
54
- start_dashboard: bind(handleStartDashboard),
55
53
  orchestrate_loop: bind(handleOrchestrateLoop),
56
54
  send_agent_message: bind(handleSendAgentMessage),
57
55
  log_agent_action: bind(handleLogAgentAction),
@@ -19,14 +19,14 @@ export function handleGetMemoryInsights(projectRoot: string, _args: ToolArgs): T
19
19
  const content = fs.readFileSync(memoryPath, "utf8");
20
20
  const lines = content.split("\n");
21
21
 
22
- const activePhase = lines.find(l => l.includes("Active Phase:"))?.split(":")[1]?.trim() || "Unknown";
23
- const activeTrace = lines.find(l => l.includes("Active Trace:"))?.split(":")[1]?.trim() || "None";
22
+ const activePhase = lines.find(l => l.includes("**Phase:**"))?.split("**Phase:**")[1]?.trim() || "Unknown";
23
+ const activeTrace = lines.find(l => l.includes("**Trace ID:**"))?.split("**Trace ID:**")[1]?.trim() || "None";
24
24
 
25
25
  // Find the last 5 history items (heuristic)
26
- const historyStartIndex = lines.findIndex(l => l.includes("## History"));
26
+ const historyStartIndex = lines.findIndex(l => l.toUpperCase().includes("HISTORY"));
27
27
  let recentHistory = "No history found.";
28
28
  if (historyStartIndex !== -1) {
29
- recentHistory = lines.slice(historyStartIndex + 1, historyStartIndex + 15)
29
+ recentHistory = lines.slice(historyStartIndex + 1)
30
30
  .filter(l => l.trim().startsWith("-"))
31
31
  .slice(-5)
32
32
  .join("\n");
@@ -5,7 +5,7 @@ import { SendAgentMessageArgs, ToolResult } from "../types.js";
5
5
  import { Metrics } from "../../utils/metrics.js";
6
6
 
7
7
  export async function handleSendAgentMessage(projectRoot: string, args: SendAgentMessageArgs): Promise<ToolResult> {
8
- const { to, category, content, traceId } = args;
8
+ const { to, category, content, traceId, parentId, requiresApproval } = args;
9
9
  const from = args.from || "@mcp";
10
10
 
11
11
  if (!to || !category || !content || !traceId) {
@@ -20,15 +20,15 @@ export async function handleSendAgentMessage(projectRoot: string, args: SendAgen
20
20
  const messagePath = path.join(messagesDir, `${agentName}.json`);
21
21
  const lockPath = path.join(messagesDir, `${agentName}.lock`);
22
22
 
23
- // Hermes Lock Protocol: Retry 3 times with 500ms delay
24
- let retries = 3;
23
+ // Hermes Lock Protocol: Retry 20 times with 500ms delay
24
+ let retries = 20;
25
25
  let acquired = false;
26
26
  while (retries > 0) {
27
27
  try {
28
28
  if (fs.existsSync(lockPath)) {
29
29
  try {
30
30
  const stats = fs.statSync(lockPath);
31
- if (Date.now() - stats.mtimeMs > 5000) {
31
+ if (Date.now() - stats.mtimeMs > 10000) {
32
32
  fs.unlinkSync(lockPath);
33
33
  }
34
34
  } catch {
@@ -60,15 +60,21 @@ export async function handleSendAgentMessage(projectRoot: string, args: SendAgen
60
60
 
61
61
  try {
62
62
  const defaultPriority = (category === "ALERT" || category === "ACTION") ? "HIGH" : "NORMAL";
63
+ const finalRequiresApproval = requiresApproval !== undefined
64
+ ? requiresApproval
65
+ : category === "ALERT";
66
+
63
67
  const message = {
64
68
  timestamp: new Date().toISOString(),
65
69
  from,
66
70
  to,
67
71
  category,
68
72
  traceId,
73
+ parentId,
69
74
  content,
70
75
  priority: args.priority || defaultPriority,
71
- status: "PENDING"
76
+ status: "PENDING",
77
+ requiresApproval: finalRequiresApproval
72
78
  };
73
79
 
74
80
  fs.appendFileSync(messagePath, JSON.stringify(message) + "\n");
@@ -18,9 +18,8 @@ export function handleGrepSearch(projectRoot: string, args: GrepSearchArgs): Too
18
18
  }
19
19
 
20
20
  const results: string[] = [];
21
- let regex: RegExp;
22
21
  try {
23
- regex = new RegExp(pattern, "g");
22
+ new RegExp(pattern);
24
23
  } catch (e) {
25
24
  const err = `Invalid regex pattern: ${String(e)}`;
26
25
  Metrics.logError(projectRoot, "@mcp", "grep_search", err);
@@ -53,7 +52,7 @@ export function handleGrepSearch(projectRoot: string, args: GrepSearchArgs): Too
53
52
  }
54
53
  }
55
54
  }
56
- } catch (e) {
55
+ } catch {
57
56
  // Ignore directories that cannot be read
58
57
  }
59
58
  };
@@ -1,37 +1,95 @@
1
+ import ts from "typescript";
2
+
1
3
  /**
2
4
  * Enterprise Compliance Guardrail
3
- * Checks content against corporate standards before allowing file mutations.
5
+ * Checks content against corporate standards using AST analysis before allowing file mutations.
4
6
  */
5
7
  export function verifyCorporateCompliance(content: string, filePath: string): void {
6
- // 1. Zero Console Policy
7
- if (content.includes("console.log(") || content.includes("console.warn(") || content.includes("console.error(")) {
8
- if (!filePath.includes("logger.ts") && !filePath.includes("check.ts") && !filePath.includes("cli.ts")) {
9
- throw new Error(
10
- "āŒ Corporate Compliance Breach: 'console.log/warn/error' usage is forbidden. " +
11
- "Use the 'logger' system from '@/shared/logger' instead."
12
- );
13
- }
8
+ // Skip compliance checks for non-source files or specific ignored files
9
+ if (filePath.endsWith(".json") || filePath.endsWith(".md") || filePath.endsWith(".env.example")) {
10
+ return;
14
11
  }
15
12
 
16
- // 2. No Explicit Any Policy
17
- const anyRegex = /[:<]\s*any\b|\bas\s+any\b/g;
18
- if (anyRegex.test(content)) {
19
- if (!filePath.includes("definitions.ts") && !filePath.includes("types.ts")) {
20
- throw new Error(
21
- "āŒ Corporate Compliance Breach: 'any' type is forbidden. " +
22
- "Use 'unknown', 'generics', or proper interfaces for type safety."
23
- );
13
+ const sourceFile = ts.createSourceFile(
14
+ filePath,
15
+ content,
16
+ ts.ScriptTarget.Latest,
17
+ true
18
+ );
19
+
20
+ const errors: string[] = [];
21
+
22
+ /**
23
+ * Recursive AST Visitor
24
+ */
25
+ function visit(node: ts.Node) {
26
+ // 1. Zero Console Policy
27
+ if (ts.isPropertyAccessExpression(node)) {
28
+ const expression = node.expression;
29
+ const name = node.name.text;
30
+ if (ts.isIdentifier(expression) && expression.text === "console") {
31
+ if (["log", "warn", "error"].includes(name)) {
32
+ // Check if file is exempt
33
+ if (!filePath.includes("logger.ts") && !filePath.includes("check.ts") && !filePath.includes("cli.ts")) {
34
+ errors.push(`āŒ Corporate Compliance Breach: 'console.${name}' usage is forbidden at line ${sourceFile.getLineAndCharacterOfPosition(node.getStart()).line + 1}.`);
35
+ }
36
+ }
37
+ }
38
+ }
39
+
40
+ // 2. No Explicit Any Policy
41
+ if (ts.isTypeReferenceNode(node)) {
42
+ if (ts.isIdentifier(node.typeName) && node.typeName.text === "any") {
43
+ if (!filePath.includes("definitions.ts") && !filePath.includes("types.ts")) {
44
+ errors.push(`āŒ Corporate Compliance Breach: 'any' type is forbidden at line ${sourceFile.getLineAndCharacterOfPosition(node.getStart()).line + 1}.`);
45
+ }
46
+ }
24
47
  }
48
+
49
+ // 3. Zero UI Library Policy (No @chakra-ui, mui, @shadcn)
50
+ if (ts.isImportDeclaration(node)) {
51
+ const moduleSpecifier = node.moduleSpecifier;
52
+ if (ts.isStringLiteral(moduleSpecifier)) {
53
+ const forbiddenLibs = ["@chakra-ui", "mui", "@shadcn", "antd", "bootstrap"];
54
+ const lib = forbiddenLibs.find(l => moduleSpecifier.text.includes(l));
55
+ if (lib) {
56
+ errors.push(`āŒ Corporate Compliance Breach: External UI library '${lib}' usage is FORBIDDEN at line ${sourceFile.getLineAndCharacterOfPosition(node.getStart()).line + 1}. Build atomic components manually instead.`);
57
+ }
58
+ }
59
+ }
60
+
61
+ // Handle 'any' as a keyword type (e.g., parameter: any)
62
+ if (node.kind === ts.SyntaxKind.AnyKeyword) {
63
+ if (!filePath.includes("definitions.ts") && !filePath.includes("types.ts")) {
64
+ errors.push(`āŒ Corporate Compliance Breach: 'any' keyword is forbidden at line ${sourceFile.getLineAndCharacterOfPosition(node.getStart()).line + 1}.`);
65
+ }
66
+ }
67
+
68
+ ts.forEachChild(node, visit);
25
69
  }
26
70
 
27
- // 3. Hardcoded Secrets Guard (Simple heuristic)
28
- const secretKeywords = [/API_KEY\s*=\s*['"][^'"]+['"]/i, /SECRET\s*=\s*['"][^'"]+['"]/i, /PASSWORD\s*=\s*['"][^'"]+['"]/i];
29
- for (const regex of secretKeywords) {
30
- if (regex.test(content) && !filePath.endsWith(".env.example")) {
31
- throw new Error(
32
- "āŒ Corporate Compliance Breach: Hardcoded secrets detected. " +
33
- "All sensitive data must be managed via '.env' files."
34
- );
71
+ visit(sourceFile);
72
+
73
+ // 3. Hardcoded Secrets & PII Guard
74
+ const piiKeywords = [
75
+ { regex: /API_KEY\s*=\s*['"][^'"]+['"]/i, msg: "Hardcoded API Key" },
76
+ { regex: /SECRET\s*=\s*['"][^'"]+['"]/i, msg: "Hardcoded Secret" },
77
+ { regex: /PASSWORD\s*=\s*['"][^'"]+['"]/i, msg: "Hardcoded Password" },
78
+ { regex: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/, msg: "PII Detected: Email Address" },
79
+ { regex: /\b\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}\b/, msg: "PII Detected: Credit Card Pattern" }
80
+ ];
81
+
82
+ for (const { regex, msg } of piiKeywords) {
83
+ if (regex.test(content)) {
84
+ // Allow emails in specific files like README or package.json
85
+ if (msg.includes("Email") && (filePath.endsWith("README.md") || filePath.endsWith("package.json") || filePath.includes("CONTRIBUTING"))) {
86
+ continue;
87
+ }
88
+ errors.push(`āŒ Corporate Compliance Breach: ${msg} detected.`);
35
89
  }
36
90
  }
91
+
92
+ if (errors.length > 0) {
93
+ throw new Error(errors.join("\n"));
94
+ }
37
95
  }
@@ -72,16 +72,16 @@ describe("Hermes Lock Protocol & Message sending", () => {
72
72
 
73
73
  expect(result.isError).toBe(true);
74
74
  expect(result.content[0].text).toContain("āŒ Could not send message to @backend: Hermes lock is busy.");
75
- // Retries takes 3 attempts with 500ms delay -> duration should be at least ~1000ms
76
- expect(duration).toBeGreaterThanOrEqual(1000);
77
- });
75
+ // Retries takes 20 attempts with 500ms delay -> duration should be at least ~9000ms
76
+ expect(duration).toBeGreaterThanOrEqual(9000);
77
+ }, 15000);
78
78
 
79
- it("should bypass and acquire lock if existing lock is stale (older than 5s)", async () => {
79
+ it("should bypass and acquire lock if existing lock is stale (older than 10s)", async () => {
80
80
  fs.mkdirSync(MESSAGES_DIR, { recursive: true });
81
- // Set lock file mtime to 10 seconds ago
81
+ // Set lock file mtime to 15 seconds ago
82
82
  fs.writeFileSync(BACKEND_LOCK_FILE, "Locked by @test at " + new Date().toISOString(), "utf8");
83
- const tenSecondsAgo = new Date(Date.now() - 10000);
84
- fs.utimesSync(BACKEND_LOCK_FILE, tenSecondsAgo, tenSecondsAgo);
83
+ const fifteenSecondsAgo = new Date(Date.now() - 15000);
84
+ fs.utimesSync(BACKEND_LOCK_FILE, fifteenSecondsAgo, fifteenSecondsAgo);
85
85
 
86
86
  const args: ToolArgs = {
87
87
  to: "@backend",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-enderun",
3
- "version": "1.11.3",
3
+ "version": "1.11.5",
4
4
  "description": "The Supreme AI Governance & Autonomous Orchestration Framework for Enterprise Development. Acts as a Corporate AI Management Assistant.",
5
5
  "author": "Yusuf BEKAR",
6
6
  "license": "MIT",
@@ -24,7 +24,8 @@
24
24
  "framework"
25
25
  ],
26
26
  "workspaces": [
27
- "framework-mcp"
27
+ "framework-mcp",
28
+ "apps/*"
28
29
  ],
29
30
  "bin": {
30
31
  "agent-enderun": "bin/cli.js"
@@ -48,7 +49,6 @@
48
49
  "enderun:verify": "agent-enderun verify-contract",
49
50
  "enderun:update-contract": "agent-enderun update-contract",
50
51
  "enderun:check": "agent-enderun check",
51
- "enderun:dashboard": "node bin/enderun-dashboard.js",
52
52
  "test:init": "node dist/src/cli/index.js init",
53
53
  "enderun:lint": "eslint .",
54
54
  "lint": "eslint .",
@@ -66,12 +66,12 @@
66
66
  "eslint": "^10.4.0",
67
67
  "globals": "^17.6.0",
68
68
  "tsx": "^4.22.3",
69
- "typescript": "^5.0.0",
69
+ "typescript": "^5.9.3",
70
70
  "typescript-eslint": "^8.0.0",
71
71
  "vitest": "^3.0.0"
72
72
  },
73
73
  "enderun": {
74
- "version": "1.11.3",
74
+ "version": "1.11.5",
75
75
  "initializedAt": "2026-06-04T18:51:28.970Z",
76
76
  "frameworkDir": ".enderun"
77
77
  },
@@ -20,7 +20,8 @@ export function scaffoldAgents(
20
20
  dryRun: boolean,
21
21
  agentsToScaffold?: string[],
22
22
  explicitDestDir?: string,
23
- explicitExt?: string
23
+ explicitExt?: string,
24
+ paths?: Record<string, string>
24
25
  ): void {
25
26
  const adapter = ADAPTERS[adapterId];
26
27
  if (!adapter) return;
@@ -46,27 +47,27 @@ export function scaffoldAgents(
46
47
 
47
48
  switch (adapterId) {
48
49
  case "gemini":
49
- content = toGeminiCliMd(agent, baseKnowledgeDir);
50
+ content = toGeminiCliMd(agent, baseKnowledgeDir, paths);
50
51
  break;
51
52
  case "grok":
52
53
  // Grok uses same Gemini-compatible YAML format
53
- content = toGeminiCliMd(agent, baseKnowledgeDir);
54
+ content = toGeminiCliMd(agent, baseKnowledgeDir, paths);
54
55
  break;
55
56
  case "claude":
56
- content = toClaudeCodeMd(agent, baseKnowledgeDir);
57
+ content = toClaudeCodeMd(agent, baseKnowledgeDir, paths);
57
58
  break;
58
59
  case "cursor":
59
- content = toCursorMdc(agent, baseKnowledgeDir);
60
+ content = toCursorMdc(agent, baseKnowledgeDir, paths);
60
61
  break;
61
62
  case "codex":
62
- content = toCodexMd(agent, baseKnowledgeDir);
63
+ content = toCodexMd(agent, baseKnowledgeDir, paths);
63
64
  break;
64
65
  case "antigravity-cli": {
65
66
  // Antigravity uses nested folders: agents/{name}/agent.json and agents/{name}/agent.md
66
67
  const agentDir = path.join(destAgentsDir, agent.name);
67
68
  if (!dryRun) fs.mkdirSync(agentDir, { recursive: true });
68
69
 
69
- content = toAntigravityJson(agent, baseKnowledgeDir);
70
+ content = toAntigravityJson(agent, baseKnowledgeDir, paths);
70
71
  fileName = path.join(agent.name, "agent.json");
71
72
 
72
73
  secondaryContent = `# šŸŽ–ļø Agent Enderun — @${agent.name}\n\n${agent.instructions.identity}\n\n${agent.instructions.mission}`;
@@ -75,7 +76,7 @@ export function scaffoldAgents(
75
76
  }
76
77
  default:
77
78
  // Fallback to Gemini format
78
- content = toGeminiCliMd(agent, baseKnowledgeDir);
79
+ content = toGeminiCliMd(agent, baseKnowledgeDir, paths);
79
80
  break;
80
81
  }
81
82
 
@@ -2,7 +2,7 @@ import fs from "fs";
2
2
  import path from "path";
3
3
  import { execSync } from "child_process";
4
4
 
5
- import { getFrameworkDir, getMemoryPath, getConfiguredPaths, isFrameworkDevelopmentRepo } from "../utils/memory.js";
5
+ import { getFrameworkDir, getMemoryPath, getConfiguredPaths, isFrameworkDevelopmentRepo, readState } from "../utils/memory.js";
6
6
  import { getPackageVersion, getValidatorPath } from "../utils/pkg.js";
7
7
  import { UI } from "../utils/ui.js";
8
8
  import { logger } from "../../shared/logger.js";
@@ -10,16 +10,41 @@ import { scanProjectCompliance } from "../utils/compliance.js";
10
10
  import { ALL_AGENTS } from "../../modules/agents/definitions.js";
11
11
  import { detectActiveAgentLayouts, findAgentInstruction } from "../adapters/paths.js";
12
12
 
13
+ import { verifyApiContractCommand } from "./contract.js";
14
+
13
15
  export async function checkCommand() {
14
16
  UI.intent("Agent Enderun Health Check", `Checking system health and discipline rules (v${getPackageVersion()})...`);
15
17
  let issues = 0;
16
18
 
19
+ const state = readState();
20
+ if (!state) {
21
+ UI.error("Memory state not found. Run 'init' first.");
22
+ return;
23
+ }
24
+
17
25
  const frameworkDir = getFrameworkDir();
18
26
  const memoryPath = getMemoryPath();
19
27
  const pathsMap = getConfiguredPaths();
20
28
 
21
29
  UI.success(`Using framework dir: ${frameworkDir}`);
22
30
 
31
+ // --- Contract Gate (Phase 2+ Discipline) ---
32
+ if (["PHASE_2", "PHASE_3", "PHASE_4"].includes(state.phase)) {
33
+ console.warn("\nšŸ“ Validating API Contracts (Phase 2+ Required)...");
34
+ try {
35
+ const contractOk = await verifyApiContractCommand({ silent: true });
36
+ if (!contractOk) {
37
+ UI.error("Contract verification FAILED! You cannot be in Phase 2+ with unverified contracts.");
38
+ issues++;
39
+ } else {
40
+ UI.success("API Contracts verified and sealed.");
41
+ }
42
+ } catch {
43
+ UI.error("Error during contract verification.");
44
+ issues++;
45
+ }
46
+ }
47
+
23
48
  let knowledgeDir = "knowledge";
24
49
  if (frameworkDir === ".github") knowledgeDir = "instructions";
25
50
 
@@ -57,7 +82,6 @@ export async function checkCommand() {
57
82
  { name: ".env.example", path: ".env.example" },
58
83
  ...(isFrameworkDevelopment ? [{ name: "MCP Server", path: "framework-mcp/package.json" }] : []),
59
84
  { name: "Panda CSS Config", path: appPandaPath, optional: true },
60
- { name: "Brain Dashboard", path: path.join(frameworkDir, "BRAIN_DASHBOARD.md") },
61
85
  ];
62
86
 
63
87
  for (const check of checks) {
@@ -135,52 +159,6 @@ export async function checkCommand() {
135
159
  logger.debug("npx tsc --noEmit check failed", err);
136
160
  }
137
161
 
138
- interface MetricEntry {
139
- timestamp: string;
140
- agent: string;
141
- action: string;
142
- estimatedTokens: number;
143
- }
144
-
145
- const metricsPath = path.join(frameworkDir, "observability/metrics.json");
146
- if (fs.existsSync(metricsPath)) {
147
- try {
148
- const raw = fs.readFileSync(metricsPath, "utf8");
149
- const metrics: MetricEntry[] = JSON.parse(raw);
150
- if (Array.isArray(metrics) && metrics.length > 0) {
151
- console.warn("\nšŸ“Š Token Consumption Report:");
152
- let totalTokens = 0;
153
- const breakdown: Record<string, { count: number; tokens: number }> = {};
154
-
155
- for (const entry of metrics) {
156
- const tokens = typeof entry.estimatedTokens === "number" ? entry.estimatedTokens : 0;
157
- totalTokens += tokens;
158
-
159
- const actionName = (entry.action || "unknown").split(":")[0].trim();
160
- if (!breakdown[actionName]) {
161
- breakdown[actionName] = { count: 0, tokens: 0 };
162
- }
163
- breakdown[actionName].count += 1;
164
- breakdown[actionName].tokens += tokens;
165
- }
166
-
167
- console.warn(` - Total Operations: ${metrics.length}`);
168
- console.warn(` - Total Estimated Tokens: ${totalTokens}`);
169
- console.warn("\n Breakdown by Action:");
170
- for (const [action, data] of Object.entries(breakdown)) {
171
- console.warn(` - ${action}: ${data.count} ops (${data.tokens} tokens)`);
172
- }
173
- } else {
174
- console.warn("\nšŸ“Š Token Consumption Report: No token usage logged yet.");
175
- }
176
- } catch (err) {
177
- console.warn("\nšŸ“Š Token Consumption Report: Could not read or parse metrics file.");
178
- logger.debug("Failed to read or parse metrics.json in checkCommand", err);
179
- }
180
- } else {
181
- console.warn("\nšŸ“Š Token Consumption Report: No token usage logged yet.");
182
- }
183
-
184
162
  if (issues === 0) {
185
163
  UI.success("\nšŸš€ All systems green! Agent Enderun is ready.");
186
164
  } else {
@@ -7,13 +7,14 @@ import { ValidationError } from "../../shared/errors.js";
7
7
  /**
8
8
  * Verify type safety between backend and frontend contracts.
9
9
  */
10
- export async function verifyApiContractCommand() {
10
+ export async function verifyApiContractCommand(options: { silent?: boolean } = {}): Promise<boolean> {
11
11
  const projectRoot = process.cwd();
12
12
  const pathsMap = getConfiguredPaths();
13
13
  const sharedDir = path.join(projectRoot, pathsMap.backend, "src/types");
14
14
  const contractPath = path.join(projectRoot, pathsMap.backend, "contract.version.json");
15
15
 
16
16
  if (!fs.existsSync(sharedDir) || !fs.existsSync(contractPath)) {
17
+ if (options.silent) return false;
17
18
  throw new ValidationError(
18
19
  "API types or contract version file missing.",
19
20
  null,
@@ -25,8 +26,10 @@ export async function verifyApiContractCommand() {
25
26
  const contract = JSON.parse(fs.readFileSync(contractPath, "utf8"));
26
27
 
27
28
  if (contract.contract_hash === currentHash) {
28
- console.warn("āœ… Contract is valid and synchronized.");
29
+ if (!options.silent) console.warn("āœ… Contract is valid and synchronized.");
30
+ return true;
29
31
  } else {
32
+ if (options.silent) return false;
30
33
  throw new ValidationError(
31
34
  "Contract drift detected!",
32
35
  { stored: contract.contract_hash, actual: currentHash },
@@ -45,7 +45,7 @@ export function scaffoldFrameworkConfigs(
45
45
  adapter: AdapterConfig,
46
46
  dryRun: boolean,
47
47
  selectedPalette: string,
48
- options?: { unified?: boolean; adapters?: string[] }
48
+ options?: { unified?: boolean; adapters?: string[]; profile?: string }
49
49
  ) {
50
50
  if (dryRun) return;
51
51
  const frameworkDir = path.join(targetDir, fDir);
@@ -55,6 +55,7 @@ export function scaffoldFrameworkConfigs(
55
55
  name: FRAMEWORK_NAME,
56
56
  version: getPackageVersion(),
57
57
  unified: options?.unified || false,
58
+ profile: options?.profile || "full",
58
59
  adapters: options?.adapters || [adapter.id],
59
60
  theme: {
60
61
  palette: selectedPalette,
@@ -77,10 +78,25 @@ export function scaffoldFrameworkConfigs(
77
78
  }
78
79
 
79
80
  let statusContent = "# šŸŽ–ļø Status\n\n| Agent | State | Active Task | Last Updated | Notes | Extra | Backup |\n|---|---|---|---|---|---|---|\n";
80
- for (const ag of ALL_AGENTS) {
81
+
82
+ const liteAgents = ["manager", "architect", "frontend"];
83
+ const activeAgents = (options?.profile === "lite")
84
+ ? ALL_AGENTS.filter(ag => liteAgents.includes(ag.name))
85
+ : ALL_AGENTS;
86
+
87
+ const initialStatusJson: Record<string, { state: string; task: string; lastUpdated: string }> = {};
88
+ const now = new Date().toISOString();
89
+
90
+ for (const ag of activeAgents) {
81
91
  statusContent += "| @" + ag.name + " | READY | Idle | - | - | - | - |\n";
92
+ initialStatusJson[ag.name] = { state: "READY", task: "Idle", lastUpdated: now };
82
93
  }
83
94
  writeTextFile(path.join(frameworkDir, "STATUS.md"), statusContent);
95
+
96
+ // Populate initial status.json
97
+ const statusJsonPath = path.join(frameworkDir, "memory", "status.json");
98
+ if (!fs.existsSync(path.join(frameworkDir, "memory"))) fs.mkdirSync(path.join(frameworkDir, "memory"), { recursive: true });
99
+ writeJsonFile(statusJsonPath, initialStatusJson, dryRun);
84
100
  }
85
101
 
86
102
  export function scaffoldShims(
@@ -48,9 +48,6 @@ export function scaffoldOps(frameworkDir: string, dryRun: boolean) {
48
48
  };
49
49
  writeJsonFile(path.join(frameworkDir, "cli-commands.json"), cliCommands, dryRun);
50
50
 
51
- // Scaffold BRAIN_DASHBOARD.md
52
- writeTextFile(path.join(frameworkDir, "BRAIN_DASHBOARD.md"), "# 🧠 Brain Dashboard\n\nActive Trace: T-000\nActive Phase: PHASE_0 (Genesis)\n\n## Agent Statuses\n- @manager: READY\n- @architect: READY\n- @backend: READY\n- @frontend: READY\n- @quality: READY\n");
53
-
54
51
  // Scaffold router/routing_rules.md
55
52
  const routerDir = path.join(frameworkDir, "router");
56
53
  if (!fs.existsSync(routerDir)) fs.mkdirSync(routerDir, { recursive: true });