agent-enderun 0.8.8 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/.enderun/BRAIN_DASHBOARD.md +5 -5
  2. package/{ENDERUN.md → .enderun/ENDERUN.md} +4 -2
  3. package/.enderun/PROJECT_MEMORY.md +32 -43
  4. package/.enderun/STATUS.md +25 -28
  5. package/.enderun/agents/agent_army_schema.json +29 -7
  6. package/.enderun/agents/backend.md +48 -39
  7. package/.enderun/agents/database.md +55 -0
  8. package/.enderun/agents/devops.md +30 -17
  9. package/.enderun/agents/explorer.md +5 -5
  10. package/.enderun/agents/frontend.md +97 -76
  11. package/.enderun/agents/git.md +4 -4
  12. package/.enderun/agents/manager.md +68 -59
  13. package/.enderun/agents/mobile.md +4 -4
  14. package/.enderun/agents/native.md +106 -0
  15. package/.enderun/agents/quality.md +122 -0
  16. package/.enderun/agents/schema/agent-lifecycle-schema.json +29 -8
  17. package/.enderun/cli-commands.json +7 -3
  18. package/.enderun/config.json +8 -2
  19. package/.enderun/knowledge/SHIM_TEMPLATE.md +25 -0
  20. package/.enderun/knowledge/context_boundary_rules.md +57 -0
  21. package/.enderun/knowledge/corporate-governance/high-risk-action-approval-flow.md +29 -0
  22. package/.enderun/knowledge/documentation_ownership.md +53 -0
  23. package/.enderun/knowledge/eslint-standards.md +63 -0
  24. package/.enderun/knowledge/frontend_professionalization_guidelines.md +7 -7
  25. package/.enderun/knowledge/hermes_live_test_guidelines.md +5 -5
  26. package/.enderun/knowledge/hermes_protocol.md +1 -1
  27. package/.enderun/knowledge/lessons-learned.md +14 -0
  28. package/.enderun/knowledge/manager_authority_audit_enforcement.md +10 -10
  29. package/.enderun/knowledge/project_scaffold_guidelines.md +4 -4
  30. package/.enderun/knowledge/reference_application_guidelines.md +6 -6
  31. package/.enderun/mcp_config.json +9 -0
  32. package/.enderun/memory-graph/agent-contexts/backend.json +1 -1
  33. package/.enderun/memory-graph/agent-contexts/devops.json +1 -1
  34. package/.enderun/memory-graph/agent-contexts/explorer.json +1 -1
  35. package/.enderun/memory-graph/agent-contexts/frontend.json +1 -1
  36. package/.enderun/memory-graph/agent-contexts/git.json +1 -1
  37. package/.enderun/memory-graph/agent-contexts/manager.json +1 -1
  38. package/.enderun/memory-graph/agent-contexts/mobile.json +1 -1
  39. package/.enderun/memory-graph/agent-contexts/native.json +1 -0
  40. package/.enderun/memory-graph/agent-contexts/quality.json +1 -0
  41. package/.enderun/memory-graph/graph.json +123 -15
  42. package/.enderun/memory-graph/shared-facts.json +26 -31
  43. package/.enderun/queue/README.md +13 -13
  44. package/README.md +171 -393
  45. package/agent.md +25 -0
  46. package/bin/cli.js +10 -10
  47. package/bin/init-check.js +69 -0
  48. package/bin/update-contract.js +57 -38
  49. package/bin/validate-agent-army.js +38 -13
  50. package/claude.md +14 -8
  51. package/docs/README.md +24 -16
  52. package/docs/architecture/README.md +9 -0
  53. package/docs/architecture/standards/data-fetching-patterns.md +13 -0
  54. package/docs/architecture/standards/design-system.md +31 -0
  55. package/docs/architecture/standards/logging.md +7 -0
  56. package/docs/architecture/standards/tech-stack.md +9 -0
  57. package/docs/getting-started.md +9 -259
  58. package/docs/user/README.md +35 -0
  59. package/docs/user/action-plan-2026.md +9 -0
  60. package/docs/user/getting-started.md +13 -0
  61. package/docs/user/roadmap.md +13 -0
  62. package/eslint.config.js +68 -0
  63. package/framework-mcp/dist/index.js +178 -24
  64. package/framework-mcp/package.json +6 -33
  65. package/framework-mcp/tsconfig.json +3 -6
  66. package/gemini.md +12 -6
  67. package/grok.md +25 -0
  68. package/package.json +13 -5
  69. package/panda.config.ts +86 -86
  70. package/src/cli/adapters.ts +208 -0
  71. package/src/cli/commands/app.ts +38 -0
  72. package/src/cli/commands/check.ts +87 -0
  73. package/src/cli/commands/compliance.ts +55 -0
  74. package/src/cli/commands/contract.ts +45 -0
  75. package/src/cli/commands/explorer.ts +45 -0
  76. package/src/cli/commands/git.ts +39 -0
  77. package/src/cli/commands/init.ts +272 -0
  78. package/src/cli/commands/knowledge.ts +44 -0
  79. package/src/cli/commands/lint.ts +25 -0
  80. package/src/cli/commands/log.ts +37 -0
  81. package/src/cli/commands/memory.ts +78 -0
  82. package/src/cli/commands/orchestrate.ts +111 -0
  83. package/src/cli/commands/script.ts +20 -0
  84. package/src/cli/commands/security.ts +38 -0
  85. package/src/cli/commands/status.ts +59 -0
  86. package/src/cli/commands/trace.ts +46 -0
  87. package/src/cli/index.ts +74 -2155
  88. package/src/cli/utils/app.ts +764 -0
  89. package/src/cli/utils/claude.ts +56 -0
  90. package/src/cli/utils/fs.ts +139 -0
  91. package/src/cli/utils/memory.ts +141 -0
  92. package/src/cli/utils/pkg.ts +215 -0
  93. package/src/cli/utils/string.ts +48 -0
  94. package/src/cli/utils/time.ts +27 -0
  95. package/tsconfig.json +9 -0
  96. package/.enderun/agents/analyst.md +0 -238
  97. package/.enderun/agents/orchestrator.md +0 -172
  98. package/.enderun/agents/qa.md +0 -124
  99. package/.enderun/agents/security.md +0 -202
  100. package/.enderun/knowledge/api_design_rules.md +0 -6
  101. package/.enderun/knowledge/branded_types_pattern.md +0 -8
  102. package/.enderun/knowledge/code_review_checklist.md +0 -7
  103. package/.enderun/knowledge/contract_versioning.md +0 -7
  104. package/.enderun/knowledge/database_migration.md +0 -6
  105. package/.enderun/knowledge/deployment_checklist.md +0 -7
  106. package/.enderun/knowledge/git_commit_strategy.md +0 -10
  107. package/.enderun/knowledge/monitoring_setup.md +0 -5
  108. package/.enderun/knowledge/performance_guidelines.md +0 -11
  109. package/.enderun/knowledge/repository_patterns.md +0 -9
  110. package/.enderun/knowledge/security_scanning.md +0 -6
  111. package/.enderun/knowledge/testing_standards.md +0 -7
  112. package/.enderun/knowledge/troubleshooting_guide.md +0 -5
  113. package/.enderun/logs/analyst.json +0 -1
  114. package/.enderun/logs/backend.json +0 -1
  115. package/.enderun/logs/devops.json +0 -1
  116. package/.enderun/logs/explorer.json +0 -1
  117. package/.enderun/logs/frontend.json +0 -1
  118. package/.enderun/logs/git.json +0 -1
  119. package/.enderun/logs/manager.json +0 -1
  120. package/.enderun/logs/mobile.json +0 -1
  121. package/.enderun/logs/native.json +0 -1
  122. package/.enderun/logs/orchestrator.json +0 -1
  123. package/.enderun/logs/qa.json +0 -1
  124. package/.enderun/logs/security.json +0 -1
  125. package/.enderun/memory-graph/agent-contexts/analyst.json +0 -1
  126. package/.enderun/memory-graph/agent-contexts/orchestrator.json +0 -1
  127. package/.enderun/memory-graph/agent-contexts/qa.json +0 -1
  128. package/.enderun/memory-graph/agent-contexts/security.json +0 -1
  129. package/.env.example +0 -10
  130. package/cursor.md +0 -19
  131. package/docs/action-plan-2026.md +0 -119
  132. package/docs/roadmap.md +0 -142
  133. package/framework-mcp/README.md +0 -55
  134. package/framework-mcp/dist/schemas.js +0 -84
  135. package/framework-mcp/dist/tools/codebase.js +0 -294
  136. package/framework-mcp/dist/tools/contract.js +0 -197
  137. package/framework-mcp/dist/tools/framework.js +0 -225
  138. package/framework-mcp/dist/tools/git.js +0 -51
  139. package/framework-mcp/dist/tools/governance.js +0 -696
  140. package/framework-mcp/dist/tools/index.js +0 -47
  141. package/framework-mcp/dist/tools/knowledge.js +0 -133
  142. package/framework-mcp/dist/tools/memory.js +0 -217
  143. package/framework-mcp/dist/tools/messages.js +0 -193
  144. package/framework-mcp/dist/tools/monitoring.js +0 -294
  145. package/framework-mcp/dist/tools/orchestration.js +0 -369
  146. package/framework-mcp/dist/tools/pipeline.js +0 -267
  147. package/framework-mcp/dist/tools/security.js +0 -137
  148. package/framework-mcp/dist/utils.js +0 -121
  149. package/framework-mcp/src/index.ts +0 -49
  150. package/framework-mcp/src/schemas.ts +0 -106
  151. package/framework-mcp/src/tools/codebase.ts +0 -284
  152. package/framework-mcp/src/tools/contract.ts +0 -221
  153. package/framework-mcp/src/tools/framework.ts +0 -223
  154. package/framework-mcp/src/tools/git.ts +0 -42
  155. package/framework-mcp/src/tools/governance.ts +0 -891
  156. package/framework-mcp/src/tools/index.ts +0 -50
  157. package/framework-mcp/src/tools/knowledge.ts +0 -141
  158. package/framework-mcp/src/tools/memory.ts +0 -207
  159. package/framework-mcp/src/tools/messages.ts +0 -213
  160. package/framework-mcp/src/tools/monitoring.ts +0 -351
  161. package/framework-mcp/src/tools/orchestration.ts +0 -440
  162. package/framework-mcp/src/tools/pipeline.ts +0 -353
  163. package/framework-mcp/src/tools/security.ts +0 -143
  164. package/framework-mcp/src/utils.ts +0 -130
@@ -1,294 +0,0 @@
1
- import { promises as fs } from "fs";
2
- import path from "path";
3
- import { getFrameworkDir } from "../utils.js";
4
- // ─── Tool Definitions ─────────────────────────────────────────────────────────
5
- export const monitoringTools = [
6
- {
7
- name: "get_army_health_dashboard",
8
- description: "Returns a comprehensive health dashboard for the entire Agent Enderun Army. Includes agent states, active tasks, health scores, queue depth, and recent blockages. Single call for full army situational awareness.",
9
- inputSchema: {
10
- type: "object",
11
- properties: {
12
- projectRoot: { type: "string", description: "Root directory of the project" },
13
- },
14
- required: [],
15
- },
16
- },
17
- {
18
- name: "calculate_agent_performance_score",
19
- description: "Calculates a dynamic performance score for a specific agent based on their last N actions from the audit log. Returns success rate, average execution time, and blockage frequency.",
20
- inputSchema: {
21
- type: "object",
22
- properties: {
23
- agentName: { type: "string", description: "Agent identifier (e.g. @backend)" },
24
- lastN: { type: "number", description: "Number of recent actions to analyze", default: 20 },
25
- },
26
- required: ["agentName"],
27
- },
28
- },
29
- {
30
- name: "detect_bottleneck_agents",
31
- description: "Scans all agent logs and STATUS.md to identify agents that are causing delays — either by being BLOCKED, exceeding execution timeout, or having a high blockage frequency. Returns a prioritized list of bottlenecks.",
32
- inputSchema: {
33
- type: "object",
34
- properties: {
35
- thresholdMinutes: {
36
- type: "number",
37
- description: "Minutes after which an EXECUTING agent is considered a bottleneck",
38
- default: 30,
39
- },
40
- },
41
- required: [],
42
- },
43
- },
44
- {
45
- name: "generate_war_room_report",
46
- description: "Generates a crisis-mode summary report for @manager. Includes: all BLOCKED/ERROR agents, unresolved dependencies, critical path analysis, and recommended immediate actions. Designed for high-urgency situations.",
47
- inputSchema: {
48
- type: "object",
49
- properties: {
50
- traceId: { type: "string", description: "Active Trace ID for the crisis" },
51
- crisisDescription: { type: "string", description: "Brief description of the crisis or incident" },
52
- },
53
- required: ["traceId", "crisisDescription"],
54
- },
55
- },
56
- ];
57
- // ─── Handlers ─────────────────────────────────────────────────────────────────
58
- export const monitoringHandlers = {
59
- async get_army_health_dashboard(_args, projectRoot) {
60
- const results = {
61
- generatedAt: new Date().toISOString(),
62
- agents: [],
63
- queueDepth: { pending: 0, processing: 0, completed: 0, failed: 0 },
64
- recentBlockages: [],
65
- recentBroadcasts: [],
66
- overallHealth: "GREEN",
67
- };
68
- // Read STATUS.md
69
- const frameworkDir = getFrameworkDir(projectRoot);
70
- const statusPath = path.join(projectRoot, frameworkDir, "STATUS.md");
71
- try {
72
- const statusContent = await fs.readFile(statusPath, "utf-8");
73
- const lines = statusContent
74
- .split("\n")
75
- .filter((l) => l.includes("|") && !l.includes("---") && !l.includes("Agent"));
76
- results.agents = lines
77
- .map((line) => {
78
- const cells = line.split("|").map((c) => c.trim()).filter(Boolean);
79
- return cells.length >= 4
80
- ? {
81
- agent: cells[0],
82
- state: cells[1],
83
- currentTask: cells[2],
84
- blockedBy: cells[3],
85
- healthScore: cells[4] ?? "—",
86
- lastAction: cells[5] ?? "—",
87
- }
88
- : null;
89
- })
90
- .filter(Boolean);
91
- }
92
- catch {
93
- results.agents = [{ error: "STATUS.md not found or unreadable" }];
94
- }
95
- // Read queue depths
96
- const queueBase = path.join(projectRoot, getFrameworkDir(projectRoot), "queue");
97
- for (const subdir of ["pending", "processing", "completed", "failed"]) {
98
- try {
99
- const files = await fs.readdir(path.join(queueBase, subdir));
100
- results.queueDepth[subdir] = files.filter((f) => f.endsWith(".json")).length;
101
- }
102
- catch {
103
- results.queueDepth[subdir] = 0;
104
- }
105
- }
106
- // Read orchestrator log for recent blockages and broadcasts
107
- const logPath = path.join(projectRoot, getFrameworkDir(projectRoot), "logs", "orchestrator.json");
108
- try {
109
- const logContent = await fs.readFile(logPath, "utf-8");
110
- const log = JSON.parse(logContent);
111
- results.recentBlockages = (log.blockages ?? []).slice(-5);
112
- results.recentBroadcasts = (log.broadcasts ?? []).slice(-3);
113
- }
114
- catch {
115
- // No log yet
116
- }
117
- // Calculate overall health
118
- const blockedCount = results.agents.filter((a) => a?.state?.includes("BLOCKED") || a?.state?.includes("ERROR")).length;
119
- if (blockedCount >= 3)
120
- results.overallHealth = "RED";
121
- else if (blockedCount >= 1)
122
- results.overallHealth = "YELLOW";
123
- else
124
- results.overallHealth = "GREEN";
125
- return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
126
- },
127
- async calculate_agent_performance_score(args, projectRoot) {
128
- const { agentName, lastN } = args;
129
- const n = lastN ?? 20;
130
- const logName = agentName.replace("@", "") + ".json";
131
- const logPath = path.join(projectRoot, getFrameworkDir(projectRoot), "logs", logName);
132
- try {
133
- const content = await fs.readFile(logPath, "utf-8");
134
- const log = JSON.parse(content);
135
- const actions = (log.actions ?? []).slice(-n);
136
- const total = actions.length;
137
- const successful = actions.filter((a) => a.status === "SUCCESS" || a.status === "DONE").length;
138
- const successRate = total > 0 ? ((successful / total) * 100).toFixed(1) + "%" : "N/A";
139
- const avgDuration = total > 0
140
- ? (actions.reduce((sum, a) => sum + (a.durationMs ?? 0), 0) / total).toFixed(0) + "ms"
141
- : "N/A";
142
- return {
143
- content: [
144
- {
145
- type: "text",
146
- text: JSON.stringify({
147
- agent: agentName,
148
- analyzedActions: total,
149
- successRate,
150
- averageExecutionTime: avgDuration,
151
- generatedAt: new Date().toISOString(),
152
- }, null, 2),
153
- },
154
- ],
155
- };
156
- }
157
- catch {
158
- return {
159
- content: [
160
- {
161
- type: "text",
162
- text: JSON.stringify({
163
- agent: agentName,
164
- error: "No log found or log is empty",
165
- analyzedActions: 0,
166
- }),
167
- },
168
- ],
169
- };
170
- }
171
- },
172
- async detect_bottleneck_agents(args, projectRoot) {
173
- const { thresholdMinutes } = args;
174
- const threshold = thresholdMinutes ?? 30;
175
- const statusPath = path.join(projectRoot, getFrameworkDir(projectRoot), "STATUS.md");
176
- const bottlenecks = [];
177
- try {
178
- const content = await fs.readFile(statusPath, "utf-8");
179
- const lines = content
180
- .split("\n")
181
- .filter((l) => l.includes("|") && !l.includes("---") && !l.includes("Agent"));
182
- for (const line of lines) {
183
- const cells = line.split("|").map((c) => c.trim()).filter(Boolean);
184
- if (cells.length < 2)
185
- continue;
186
- const [agent, state] = cells;
187
- if (state.includes("BLOCKED")) {
188
- bottlenecks.push({ agent, state, reason: "Agent is in BLOCKED state", severity: "CRITICAL" });
189
- }
190
- else if (state.includes("ERROR")) {
191
- bottlenecks.push({ agent, state, reason: "Agent is in ERROR state", severity: "CRITICAL" });
192
- }
193
- else if (state.includes("EXECUTING")) {
194
- bottlenecks.push({
195
- agent,
196
- state,
197
- reason: `Agent has been EXECUTING for more than ${threshold} minutes (verify manually)`,
198
- severity: "HIGH",
199
- });
200
- }
201
- }
202
- }
203
- catch {
204
- return {
205
- content: [
206
- {
207
- type: "text",
208
- text: JSON.stringify({ error: "Could not read STATUS.md" }),
209
- },
210
- ],
211
- };
212
- }
213
- return {
214
- content: [
215
- {
216
- type: "text",
217
- text: JSON.stringify({
218
- bottlenecks,
219
- total: bottlenecks.length,
220
- analysisThresholdMinutes: threshold,
221
- generatedAt: new Date().toISOString(),
222
- }, null, 2),
223
- },
224
- ],
225
- };
226
- },
227
- async generate_war_room_report(args, projectRoot) {
228
- const { traceId, crisisDescription } = args;
229
- // Gather all data for the war room report
230
- const report = {
231
- warRoomReport: true,
232
- traceId,
233
- crisisDescription,
234
- generatedAt: new Date().toISOString(),
235
- agentStatuses: [],
236
- queueSnapshot: {},
237
- recentBlockages: [],
238
- immediateActions: [],
239
- };
240
- // Agent statuses
241
- const frameworkDir = getFrameworkDir(projectRoot);
242
- const statusPath = path.join(projectRoot, frameworkDir, "STATUS.md");
243
- try {
244
- const content = await fs.readFile(statusPath, "utf-8");
245
- const lines = content
246
- .split("\n")
247
- .filter((l) => l.includes("|") && !l.includes("---") && !l.includes("Agent"));
248
- report.agentStatuses = lines
249
- .map((l) => {
250
- const cells = l.split("|").map((c) => c.trim()).filter(Boolean);
251
- return cells.length >= 2 ? { agent: cells[0], state: cells[1] } : null;
252
- })
253
- .filter(Boolean);
254
- }
255
- catch {
256
- report.agentStatuses = [];
257
- }
258
- // Queue snapshot
259
- const queueBase = path.join(projectRoot, getFrameworkDir(projectRoot), "queue");
260
- const snapshot = {};
261
- for (const dir of ["pending", "processing", "completed", "failed"]) {
262
- try {
263
- const files = await fs.readdir(path.join(queueBase, dir));
264
- snapshot[dir] = files.length;
265
- }
266
- catch {
267
- snapshot[dir] = 0;
268
- }
269
- }
270
- report.queueSnapshot = snapshot;
271
- // Recent blockages
272
- const logPath = path.join(projectRoot, getFrameworkDir(projectRoot), "logs", "orchestrator.json");
273
- try {
274
- const logContent = await fs.readFile(logPath, "utf-8");
275
- const log = JSON.parse(logContent);
276
- report.recentBlockages = (log.blockages ?? []).slice(-10);
277
- }
278
- catch {
279
- report.recentBlockages = [];
280
- }
281
- // Generate recommended immediate actions
282
- const blockedAgents = report.agentStatuses.filter((a) => a.state.includes("BLOCKED") || a.state.includes("ERROR"));
283
- if (blockedAgents.length > 0) {
284
- report.immediateActions.push(`IMMEDIATE: Resolve ${blockedAgents.map((a) => a.agent).join(", ")} blockages — these are on the critical path`);
285
- }
286
- if ((snapshot["pending"] ?? 0) > 5) {
287
- report.immediateActions.push("HIGH: Queue has excessive pending tasks — consider deploying additional agents");
288
- }
289
- if (report.immediateActions.length === 0) {
290
- report.immediateActions.push("No critical immediate actions required — monitor agent states");
291
- }
292
- return { content: [{ type: "text", text: JSON.stringify(report, null, 2) }] };
293
- },
294
- };
@@ -1,369 +0,0 @@
1
- import { promises as fs } from "fs";
2
- import path from "path";
3
- import { getFrameworkDir } from "../utils.js";
4
- // ─── Tool Definitions ─────────────────────────────────────────────────────────
5
- export const orchestrationTools = [
6
- {
7
- name: "create_task_dag",
8
- description: "Creates a Directed Acyclic Graph (DAG) of tasks with dependencies. Used by @orchestrator to plan parallel execution. Returns both a JSON structure and a Mermaid diagram for visualization.",
9
- inputSchema: {
10
- type: "object",
11
- properties: {
12
- traceId: {
13
- type: "string",
14
- description: "Trace ID inherited from @manager briefing. Required.",
15
- },
16
- tasks: {
17
- type: "array",
18
- description: "Array of task definitions to build the DAG from.",
19
- items: {
20
- type: "object",
21
- required: ["id", "agent", "briefing"],
22
- properties: {
23
- id: { type: "string", description: "Unique task ID (e.g. T001)" },
24
- agent: { type: "string", description: "Assigned agent (e.g. @backend)" },
25
- briefing: { type: "string", description: "Task description" },
26
- dependsOn: {
27
- type: "array",
28
- items: { type: "string" },
29
- description: "Task IDs that must complete before this task starts",
30
- },
31
- priority: {
32
- type: "string",
33
- enum: ["CRITICAL", "HIGH", "MEDIUM", "LOW"],
34
- default: "MEDIUM",
35
- },
36
- },
37
- },
38
- },
39
- },
40
- required: ["traceId", "tasks"],
41
- },
42
- },
43
- {
44
- name: "assign_agent_to_task",
45
- description: "Assigns the most capable available agent to a task based on domain and capability score. Checks STATUS.md for agent availability before assigning.",
46
- inputSchema: {
47
- type: "object",
48
- properties: {
49
- traceId: { type: "string", description: "Active Trace ID" },
50
- taskId: { type: "string", description: "Task ID to assign" },
51
- domain: {
52
- type: "string",
53
- enum: ["backend", "frontend", "security", "qa", "devops", "mobile", "native", "analysis", "git", "exploration"],
54
- description: "Domain of the task — used to filter eligible agents",
55
- },
56
- preferredAgent: {
57
- type: "string",
58
- description: "Optional preferred agent. If not available, falls back to capability-based selection.",
59
- },
60
- },
61
- required: ["traceId", "taskId", "domain"],
62
- },
63
- },
64
- {
65
- name: "get_agent_workload",
66
- description: "Returns the current workload for all agents: active task count, state, current task description, and health score. Used by @orchestrator to make load-aware assignment decisions.",
67
- inputSchema: {
68
- type: "object",
69
- properties: {
70
- projectRoot: { type: "string", description: "Root directory of the project." },
71
- },
72
- required: [],
73
- },
74
- },
75
- {
76
- name: "resolve_agent_blockage",
77
- description: "Handles a BLOCKED agent by documenting the blockage, escalating to @manager, and optionally reassigning the task to another eligible agent.",
78
- inputSchema: {
79
- type: "object",
80
- properties: {
81
- blockedAgent: { type: "string", description: "The agent identifier that is BLOCKED, e.g. @frontend" },
82
- traceId: { type: "string", description: "Active Trace ID" },
83
- blockReason: { type: "string", description: "Mandatory description of why the agent is blocked" },
84
- reassign: {
85
- type: "boolean",
86
- description: "If true, attempt to find and assign an alternative agent",
87
- default: false,
88
- },
89
- },
90
- required: ["blockedAgent", "traceId", "blockReason"],
91
- },
92
- },
93
- {
94
- name: "broadcast_army_directive",
95
- description: "Broadcasts a directive message to all agents simultaneously via the Hermes messaging system. Used for war-room mode, policy changes, or global state transitions.",
96
- inputSchema: {
97
- type: "object",
98
- properties: {
99
- traceId: { type: "string", description: "Active Trace ID" },
100
- directiveType: {
101
- type: "string",
102
- enum: [
103
- "WAR_ROOM_ACTIVATE",
104
- "WAR_ROOM_DEACTIVATE",
105
- "CONTRACT_CHANGED",
106
- "PHASE_ROLLBACK",
107
- "POLICY_UPDATE",
108
- "ARMY_PAUSE",
109
- "ARMY_RESUME",
110
- ],
111
- description: "Type of directive being broadcast",
112
- },
113
- message: { type: "string", description: "Human-readable directive message" },
114
- urgency: {
115
- type: "string",
116
- enum: ["IMMEDIATE", "HIGH", "NORMAL"],
117
- default: "NORMAL",
118
- },
119
- },
120
- required: ["traceId", "directiveType", "message"],
121
- },
122
- },
123
- ];
124
- // ─── Handlers ─────────────────────────────────────────────────────────────────
125
- export const orchestrationHandlers = {
126
- async create_task_dag(args, projectRoot) {
127
- const { traceId, tasks } = args;
128
- // Build adjacency structure
129
- const dagNodes = tasks.map((t) => ({
130
- id: t.id,
131
- agent: t.agent,
132
- briefing: t.briefing,
133
- status: "PENDING",
134
- priority: t.priority ?? "MEDIUM",
135
- dependsOn: t.dependsOn ?? [],
136
- }));
137
- // Topological sort to find execution order
138
- const executionWaves = [];
139
- const resolved = new Set();
140
- const remaining = [...dagNodes];
141
- while (remaining.length > 0) {
142
- const wave = remaining.filter((n) => n.dependsOn.every((dep) => resolved.has(dep)));
143
- if (wave.length === 0) {
144
- return {
145
- content: [
146
- {
147
- type: "text",
148
- text: JSON.stringify({
149
- error: "CIRCULAR_DEPENDENCY",
150
- message: "Circular dependency detected in task DAG. Check dependsOn fields.",
151
- }),
152
- },
153
- ],
154
- };
155
- }
156
- executionWaves.push(wave.map((n) => n.id));
157
- wave.forEach((n) => {
158
- resolved.add(n.id);
159
- remaining.splice(remaining.indexOf(n), 1);
160
- });
161
- }
162
- // Generate Mermaid diagram
163
- const mermaidLines = ["graph TD"];
164
- for (const node of dagNodes) {
165
- const label = `${node.id}["${node.agent}: ${node.briefing.slice(0, 40)}..."]`;
166
- mermaidLines.push(` ${node.id}${label}`);
167
- }
168
- for (const node of dagNodes) {
169
- for (const dep of node.dependsOn) {
170
- mermaidLines.push(` ${dep} --> ${node.id}`);
171
- }
172
- }
173
- const dag = {
174
- traceId,
175
- createdAt: new Date().toISOString(),
176
- nodes: dagNodes,
177
- executionWaves,
178
- mermaid: mermaidLines.join("\n"),
179
- parallelOpportunities: executionWaves.filter((w) => w.length > 1).length,
180
- };
181
- // Persist to queue
182
- const queueDir = path.join(projectRoot, getFrameworkDir(projectRoot), "queue", "pending");
183
- await fs.mkdir(queueDir, { recursive: true });
184
- await fs.writeFile(path.join(queueDir, `${traceId}-dag.json`), JSON.stringify(dag, null, 2), "utf-8");
185
- return { content: [{ type: "text", text: JSON.stringify(dag, null, 2) }] };
186
- },
187
- async assign_agent_to_task(args, projectRoot) {
188
- const { traceId, taskId, domain, preferredAgent } = args;
189
- // Try to load capability roster from shared-facts.json (dynamic — fixes KRITIK-3)
190
- let agentCapabilityRoster = [];
191
- try {
192
- const sharedFactsPath = path.join(projectRoot, getFrameworkDir(projectRoot), "memory-graph", "shared-facts.json");
193
- const content = await fs.readFile(sharedFactsPath, "utf-8");
194
- const facts = JSON.parse(content);
195
- agentCapabilityRoster = facts.agentCapabilityRoster ?? [];
196
- }
197
- catch {
198
- // Fallback to hardcoded map if shared-facts not available
199
- }
200
- // Build domain → agents map from shared-facts roster
201
- // Exclude @manager — @manager does NOT execute specialist tasks
202
- const domainMap = {};
203
- for (const entry of agentCapabilityRoster) {
204
- if (entry.agent === "@manager" || entry.agent === "@orchestrator")
205
- continue;
206
- const d = entry.primaryDomain;
207
- if (!domainMap[d])
208
- domainMap[d] = [];
209
- domainMap[d].push({ agent: entry.agent, capability: entry.capability });
210
- }
211
- // Hardcoded fallback (used only if shared-facts unavailable)
212
- const fallbackDomainMap = {
213
- backend: [{ agent: "@backend", capability: 9.2 }],
214
- frontend: [{ agent: "@frontend", capability: 9.0 }],
215
- security: [{ agent: "@security", capability: 9.3 }],
216
- qa: [{ agent: "@qa", capability: 9.1 }],
217
- devops: [{ agent: "@devops", capability: 9.0 }],
218
- mobile: [{ agent: "@mobile", capability: 8.8 }],
219
- native: [{ agent: "@native", capability: 8.9 }],
220
- analysis: [{ agent: "@analyst", capability: 9.6 }],
221
- git: [{ agent: "@git", capability: 9.1 }],
222
- exploration: [{ agent: "@explorer", capability: 9.4 }],
223
- };
224
- const effectiveMap = Object.keys(domainMap).length > 0 ? domainMap : fallbackDomainMap;
225
- const candidates = effectiveMap[domain] ?? [];
226
- let assigned = [...candidates].sort((a, b) => b.capability - a.capability)[0];
227
- if (preferredAgent && preferredAgent !== "@manager" && preferredAgent !== "@orchestrator") {
228
- const preferred = candidates.find((c) => c.agent === preferredAgent);
229
- if (preferred)
230
- assigned = preferred;
231
- }
232
- if (!assigned) {
233
- return {
234
- content: [
235
- {
236
- type: "text",
237
- text: JSON.stringify({ error: "NO_ELIGIBLE_AGENT", domain, taskId,
238
- note: "@manager and @orchestrator are excluded from task assignment" }),
239
- },
240
- ],
241
- };
242
- }
243
- const assignment = {
244
- traceId,
245
- taskId,
246
- assignedAgent: assigned.agent,
247
- capabilityScore: assigned.capability,
248
- assignedAt: new Date().toISOString(),
249
- domain,
250
- sourceMap: Object.keys(domainMap).length > 0 ? "shared-facts" : "fallback",
251
- };
252
- // Write assignment log
253
- const logPath = path.join(projectRoot, getFrameworkDir(projectRoot), "logs", "orchestrator.json");
254
- let log = { assignments: [] };
255
- try {
256
- const existing = await fs.readFile(logPath, "utf-8");
257
- log = JSON.parse(existing);
258
- }
259
- catch {
260
- // File doesn't exist yet — start fresh
261
- }
262
- log.assignments.push(assignment);
263
- await fs.mkdir(path.dirname(logPath), { recursive: true });
264
- await fs.writeFile(logPath, JSON.stringify(log, null, 2), "utf-8");
265
- return { content: [{ type: "text", text: JSON.stringify(assignment, null, 2) }] };
266
- },
267
- async get_agent_workload(_args, projectRoot) {
268
- const statusPath = path.join(projectRoot, getFrameworkDir(projectRoot), "STATUS.md");
269
- let statusContent;
270
- try {
271
- statusContent = await fs.readFile(statusPath, "utf-8");
272
- }
273
- catch {
274
- statusContent = "";
275
- }
276
- // Parse the markdown table
277
- const lines = statusContent.length > 0
278
- ? statusContent.split("\n").filter((l) => l.includes("|") && !l.includes("---"))
279
- : [];
280
- const agents = lines.length > 1
281
- ? lines
282
- .slice(1) // skip header
283
- .map((line) => {
284
- const cells = line.split("|").map((c) => c.trim()).filter(Boolean);
285
- return cells.length >= 4
286
- ? { agent: cells[0], state: cells[1], currentTask: cells[2], healthScore: cells[4] }
287
- : null;
288
- })
289
- .filter(Boolean)
290
- : [];
291
- return { content: [{ type: "text", text: JSON.stringify({ agents, generatedAt: new Date().toISOString() }, null, 2) }] };
292
- },
293
- async resolve_agent_blockage(args, projectRoot) {
294
- const { blockedAgent, traceId, blockReason, reassign } = args;
295
- const resolution = {
296
- traceId,
297
- blockedAgent,
298
- blockReason,
299
- detectedAt: new Date().toISOString(),
300
- escalatedToManager: true,
301
- reassignAttempted: reassign ?? false,
302
- status: "ESCALATED_TO_MANAGER",
303
- };
304
- // Log blockage
305
- const logPath = path.join(projectRoot, getFrameworkDir(projectRoot), "logs", "orchestrator.json");
306
- let log = { assignments: [], blockages: [] };
307
- try {
308
- const existing = await fs.readFile(logPath, "utf-8");
309
- log = JSON.parse(existing);
310
- if (!log.blockages)
311
- log.blockages = [];
312
- }
313
- catch {
314
- // Start fresh
315
- }
316
- log.blockages.push(resolution);
317
- await fs.mkdir(path.dirname(logPath), { recursive: true });
318
- await fs.writeFile(logPath, JSON.stringify(log, null, 2), "utf-8");
319
- // Write a message to @manager's inbox via messages folder
320
- const messagesDir = path.join(projectRoot, getFrameworkDir(projectRoot), "messages");
321
- await fs.mkdir(messagesDir, { recursive: true });
322
- const messageFile = path.join(messagesDir, `BLOCKAGE-${traceId}-${Date.now()}.json`);
323
- await fs.writeFile(messageFile, JSON.stringify({
324
- from: "@orchestrator",
325
- to: "@manager",
326
- type: "AGENT_BLOCKED",
327
- traceId,
328
- payload: { blockedAgent, blockReason, resolution },
329
- timestamp: new Date().toISOString(),
330
- }), "utf-8");
331
- return { content: [{ type: "text", text: JSON.stringify(resolution, null, 2) }] };
332
- },
333
- async broadcast_army_directive(args, projectRoot) {
334
- const { traceId, directiveType, message, urgency } = args;
335
- const broadcast = {
336
- id: `BROADCAST-${traceId}-${Date.now()}`,
337
- from: "@orchestrator",
338
- to: "*",
339
- traceId,
340
- directiveType,
341
- message,
342
- urgency: urgency ?? "NORMAL",
343
- timestamp: new Date().toISOString(),
344
- };
345
- // Write to messages folder for all agents
346
- const messagesDir = path.join(projectRoot, getFrameworkDir(projectRoot), "messages");
347
- await fs.mkdir(messagesDir, { recursive: true });
348
- await fs.writeFile(path.join(messagesDir, `${broadcast.id}.json`), JSON.stringify(broadcast, null, 2), "utf-8");
349
- // Log broadcast
350
- const logPath = path.join(projectRoot, getFrameworkDir(projectRoot), "logs", "orchestrator.json");
351
- let log = {
352
- assignments: [],
353
- blockages: [],
354
- broadcasts: [],
355
- };
356
- try {
357
- const existing = await fs.readFile(logPath, "utf-8");
358
- log = JSON.parse(existing);
359
- if (!log.broadcasts)
360
- log.broadcasts = [];
361
- }
362
- catch {
363
- // Start fresh
364
- }
365
- log.broadcasts.push(broadcast);
366
- await fs.writeFile(logPath, JSON.stringify(log, null, 2), "utf-8");
367
- return { content: [{ type: "text", text: JSON.stringify(broadcast, null, 2) }] };
368
- },
369
- };