agent-enderun 0.7.2 → 0.7.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 (177) hide show
  1. package/.enderun/BRAIN_DASHBOARD.md +47 -0
  2. package/.enderun/PROJECT_MEMORY.md +139 -0
  3. package/.enderun/STATUS.md +62 -0
  4. package/.enderun/agents/agent_army_schema.json +85 -0
  5. package/.enderun/agents/analyst/agent.json +23 -0
  6. package/.enderun/agents/analyst/prompt.md +225 -0
  7. package/.enderun/agents/backend/agent.json +24 -0
  8. package/.enderun/agents/backend/prompt.md +331 -0
  9. package/.enderun/agents/devops/agent.json +61 -0
  10. package/.enderun/agents/devops/prompt.md +127 -0
  11. package/.enderun/agents/explorer/agent.json +22 -0
  12. package/.enderun/agents/explorer/prompt.md +115 -0
  13. package/.enderun/agents/frontend/agent.json +24 -0
  14. package/.enderun/agents/frontend/prompt.md +313 -0
  15. package/.enderun/agents/git/agent.json +22 -0
  16. package/.enderun/agents/git/prompt.md +99 -0
  17. package/.enderun/agents/manager/agent.json +48 -0
  18. package/.enderun/agents/manager/prompt.md +231 -0
  19. package/.enderun/agents/mobile/agent.json +21 -0
  20. package/.enderun/agents/mobile/prompt.md +91 -0
  21. package/.enderun/agents/native/agent.json +21 -0
  22. package/.enderun/agents/native/prompt.md +96 -0
  23. package/.enderun/agents/orchestrator/agent.json +53 -0
  24. package/.enderun/agents/orchestrator/prompt.md +124 -0
  25. package/.enderun/agents/qa/agent.json +57 -0
  26. package/.enderun/agents/qa/prompt.md +69 -0
  27. package/.enderun/agents/schema/agent-lifecycle-schema.json +65 -0
  28. package/.enderun/agents/security/agent.json +54 -0
  29. package/.enderun/agents/security/prompt.md +154 -0
  30. package/.enderun/gemini.md +19 -0
  31. package/.enderun/knowledge/corporate-governance/analyst-contract-integrity-guide.md +92 -0
  32. package/.enderun/knowledge/corporate-governance/audit-logging-standard.md +59 -0
  33. package/.enderun/knowledge/corporate-governance/backend-high-risk-endpoint-guide.md +125 -0
  34. package/.enderun/knowledge/corporate-governance/branded-types-law.md +77 -0
  35. package/.enderun/knowledge/corporate-governance/contract-drift-detection-simulation.md +92 -0
  36. package/.enderun/knowledge/corporate-governance/corporate-governance-playbook.md +89 -0
  37. package/.enderun/knowledge/corporate-governance/corporate-project-kickoff-checklist.md +56 -0
  38. package/.enderun/knowledge/corporate-governance/corporate-project-kickoff-simulation.md +144 -0
  39. package/.enderun/knowledge/corporate-governance/corporate-project-scaffolding-sop.md +103 -0
  40. package/.enderun/knowledge/corporate-governance/domain-error-handling-standard.md +75 -0
  41. package/.enderun/knowledge/corporate-governance/frontend-high-risk-modal-guide.md +211 -0
  42. package/.enderun/knowledge/corporate-governance/governance-baseline-briefing-template.md +54 -0
  43. package/.enderun/knowledge/corporate-governance/high-risk-action-approval-flow-simulation.md +167 -0
  44. package/.enderun/knowledge/corporate-governance/high-risk-actions-managerapproval.md +76 -0
  45. package/.enderun/knowledge/corporate-governance/high-risk-security-modal-hermes-flow.md +49 -0
  46. package/.enderun/knowledge/corporate-governance/manager-first-30-days-guide.md +112 -0
  47. package/.enderun/knowledge/corporate-governance/manager-first-three-briefings.md +79 -0
  48. package/.enderun/knowledge/deployment_checklist.md +7 -0
  49. package/{.antigravitycli → .enderun}/knowledge/framework_vs_user_project_boundary.md +3 -3
  50. package/{.antigravitycli → .enderun}/knowledge/frontend_professionalization_guidelines.md +17 -20
  51. package/{.antigravitycli → .enderun}/knowledge/hermes_live_test_guidelines.md +9 -9
  52. package/{.antigravitycli → .enderun}/knowledge/hermes_protocol.md +2 -2
  53. package/.enderun/knowledge/kentim_constitution.md +34 -0
  54. package/{.antigravitycli → .enderun}/knowledge/manager_authority_audit_enforcement.md +11 -11
  55. package/{.antigravitycli → .enderun}/knowledge/project_scaffold_guidelines.md +8 -8
  56. package/{.antigravitycli → .enderun}/knowledge/reference_application_guidelines.md +7 -7
  57. package/.enderun/logs/analyst.json +1 -0
  58. package/.enderun/logs/backend.json +1 -0
  59. package/.enderun/logs/devops.json +1 -0
  60. package/.enderun/logs/explorer.json +1 -0
  61. package/.enderun/logs/frontend.json +1 -0
  62. package/.enderun/logs/git.json +1 -0
  63. package/.enderun/logs/manager.json +20 -0
  64. package/.enderun/logs/mobile.json +1 -0
  65. package/.enderun/logs/native.json +1 -0
  66. package/.enderun/logs/orchestrator.json +1 -0
  67. package/.enderun/logs/qa.json +1 -0
  68. package/.enderun/logs/security.json +1 -0
  69. package/.enderun/memory-graph/agent-contexts/analyst.json +1 -0
  70. package/.enderun/memory-graph/agent-contexts/backend.json +1 -0
  71. package/.enderun/memory-graph/agent-contexts/devops.json +1 -0
  72. package/.enderun/memory-graph/agent-contexts/explorer.json +1 -0
  73. package/.enderun/memory-graph/agent-contexts/frontend.json +1 -0
  74. package/.enderun/memory-graph/agent-contexts/git.json +1 -0
  75. package/.enderun/memory-graph/agent-contexts/manager.json +1 -0
  76. package/.enderun/memory-graph/agent-contexts/mobile.json +1 -0
  77. package/.enderun/memory-graph/agent-contexts/native.json +1 -0
  78. package/.enderun/memory-graph/agent-contexts/orchestrator.json +1 -0
  79. package/.enderun/memory-graph/agent-contexts/qa.json +1 -0
  80. package/.enderun/memory-graph/agent-contexts/security.json +1 -0
  81. package/.enderun/memory-graph/graph.json +26 -0
  82. package/.enderun/memory-graph/shared-facts.json +52 -0
  83. package/.enderun/monitoring/.gitkeep +0 -0
  84. package/.enderun/queue/README.md +36 -0
  85. package/ENDERUN.md +4 -4
  86. package/README.md +151 -89
  87. package/bin/cli.js +13 -2138
  88. package/bin/hermes-sandbox.js +0 -0
  89. package/bin/validate-agent-army.js +3 -3
  90. package/docs/architecture/approval-flows.md +2 -37
  91. package/docs/backend/error-handling.md +2 -51
  92. package/docs/frontend/component-patterns.md +2 -47
  93. package/docs/getting-started.md +7 -15
  94. package/docs/roadmap.md +2 -2
  95. package/framework-mcp/dist/index.js +0 -0
  96. package/framework-mcp/dist/schemas.js +3 -3
  97. package/framework-mcp/dist/tools/framework.js +17 -10
  98. package/framework-mcp/dist/tools/governance.js +696 -0
  99. package/framework-mcp/dist/tools/index.js +14 -12
  100. package/framework-mcp/dist/tools/memory.js +106 -0
  101. package/framework-mcp/dist/tools/monitoring.js +294 -0
  102. package/framework-mcp/dist/tools/orchestration.js +368 -0
  103. package/framework-mcp/dist/tools/pipeline.js +267 -0
  104. package/framework-mcp/dist/utils.js +25 -3
  105. package/framework-mcp/package.json +3 -3
  106. package/framework-mcp/src/schemas.ts +3 -3
  107. package/framework-mcp/src/tools/framework.ts +18 -10
  108. package/framework-mcp/src/tools/governance.ts +891 -0
  109. package/framework-mcp/src/tools/index.ts +14 -12
  110. package/framework-mcp/src/tools/memory.ts +103 -0
  111. package/framework-mcp/src/tools/monitoring.ts +351 -0
  112. package/framework-mcp/src/tools/orchestration.ts +439 -0
  113. package/framework-mcp/src/tools/pipeline.ts +353 -0
  114. package/framework-mcp/src/utils.ts +24 -3
  115. package/framework-mcp/tsconfig.json +2 -1
  116. package/gemini.md +19 -0
  117. package/package.json +14 -20
  118. package/.antigravitycli/BRAIN_DASHBOARD.md +0 -43
  119. package/.antigravitycli/ENDERUN.md +0 -253
  120. package/.antigravitycli/PROJECT_MEMORY.md +0 -78
  121. package/.antigravitycli/STATUS.md +0 -21
  122. package/.antigravitycli/agents/agent_army_schema.json +0 -79
  123. package/.antigravitycli/agents/analyst/agent.json +0 -55
  124. package/.antigravitycli/agents/backend/agent.json +0 -58
  125. package/.antigravitycli/agents/explorer/agent.json +0 -50
  126. package/.antigravitycli/agents/frontend/agent.json +0 -56
  127. package/.antigravitycli/agents/git/agent.json +0 -53
  128. package/.antigravitycli/agents/manager/agent.json +0 -82
  129. package/.antigravitycli/agents/mobile/agent.json +0 -52
  130. package/.antigravitycli/agents/native/agent.json +0 -56
  131. package/.antigravitycli/assets/manager.png +0 -0
  132. package/.antigravitycli/blueprints/README.md +0 -82
  133. package/.antigravitycli/blueprints/backend/errors/config/meta.json +0 -16
  134. package/.antigravitycli/blueprints/backend/errors/domain-error.ts +0 -64
  135. package/.antigravitycli/knowledge/README.md +0 -22
  136. package/.antigravitycli/knowledge/database_governance_guidelines.md +0 -118
  137. package/.antigravitycli/knowledge/deployment_checklist.md +0 -133
  138. package/.antigravitycli/knowledge/documentation_ownership.md +0 -122
  139. package/.antigravitycli/knowledge/documentation_ownership_status.md +0 -122
  140. package/.antigravitycli/knowledge/enterprise_capabilities_reference.md +0 -149
  141. package/.antigravitycli/knowledge/enterprise_frontend_adaptation.md +0 -232
  142. package/.antigravitycli/knowledge/enterprise_project_adaptation.md +0 -168
  143. package/.antigravitycli/knowledge/frontend_real_battle_test_protocol.md +0 -162
  144. package/antigravity.md +0 -15
  145. package/docs/architecture/decisions/README.md +0 -31
  146. package/docs/architecture/notification-strategy.md +0 -38
  147. package/docs/backend/audit-logging.md +0 -34
  148. package/docs/frontend/forms.md +0 -40
  149. package/docs/frontend/notifications.md +0 -37
  150. package/framework-mcp/dist/tools/academy.js +0 -182
  151. package/framework-mcp/dist/tools/database.js +0 -57
  152. package/framework-mcp/dist/tools/repository.js +0 -101
  153. package/framework-mcp/dist/tools/scaffold.js +0 -132
  154. package/framework-mcp/src/tools/academy.ts +0 -180
  155. package/framework-mcp/src/tools/database.ts +0 -54
  156. package/framework-mcp/src/tools/repository.ts +0 -101
  157. package/framework-mcp/src/tools/scaffold.ts +0 -150
  158. /package/{.antigravitycli/messages → .enderun/benchmarks}/.gitkeep +0 -0
  159. /package/{.antigravitycli → .enderun}/cli-commands.json +0 -0
  160. /package/{.antigravitycli → .enderun}/config.json +0 -0
  161. /package/{.antigravitycli → .enderun}/knowledge/api_design_rules.md +0 -0
  162. /package/{.antigravitycli → .enderun}/knowledge/async_error_handling.md +0 -0
  163. /package/{.antigravitycli → .enderun}/knowledge/blueprint_driven_construction.md +0 -0
  164. /package/{.antigravitycli → .enderun}/knowledge/branded_types_pattern.md +0 -0
  165. /package/{.antigravitycli → .enderun}/knowledge/code_review_checklist.md +0 -0
  166. /package/{.antigravitycli → .enderun}/knowledge/contract_versioning.md +0 -0
  167. /package/{.antigravitycli → .enderun}/knowledge/database_migration.md +0 -0
  168. /package/{.antigravitycli → .enderun}/knowledge/git_commit_strategy.md +0 -0
  169. /package/{.antigravitycli → .enderun}/knowledge/legacy_onboarding.md +0 -0
  170. /package/{.antigravitycli → .enderun}/knowledge/monitoring_setup.md +0 -0
  171. /package/{.antigravitycli → .enderun}/knowledge/performance_guidelines.md +0 -0
  172. /package/{.antigravitycli → .enderun}/knowledge/repository_patterns.md +0 -0
  173. /package/{.antigravitycli → .enderun}/knowledge/responsive_design_standards.md +0 -0
  174. /package/{.antigravitycli → .enderun}/knowledge/security_scanning.md +0 -0
  175. /package/{.antigravitycli → .enderun}/knowledge/testing_standards.md +0 -0
  176. /package/{.antigravitycli → .enderun}/knowledge/troubleshooting_guide.md +0 -0
  177. /package/{.antigravitycli → .enderun}/knowledge/zero_ui_library_policy.md +0 -0
@@ -0,0 +1,368 @@
1
+ import { promises as fs } from "fs";
2
+ import path from "path";
3
+ // ─── Tool Definitions ─────────────────────────────────────────────────────────
4
+ export const orchestrationTools = [
5
+ {
6
+ name: "create_task_dag",
7
+ 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.",
8
+ inputSchema: {
9
+ type: "object",
10
+ properties: {
11
+ traceId: {
12
+ type: "string",
13
+ description: "Trace ID inherited from @manager briefing. Required.",
14
+ },
15
+ tasks: {
16
+ type: "array",
17
+ description: "Array of task definitions to build the DAG from.",
18
+ items: {
19
+ type: "object",
20
+ required: ["id", "agent", "briefing"],
21
+ properties: {
22
+ id: { type: "string", description: "Unique task ID (e.g. T001)" },
23
+ agent: { type: "string", description: "Assigned agent (e.g. @backend)" },
24
+ briefing: { type: "string", description: "Task description" },
25
+ dependsOn: {
26
+ type: "array",
27
+ items: { type: "string" },
28
+ description: "Task IDs that must complete before this task starts",
29
+ },
30
+ priority: {
31
+ type: "string",
32
+ enum: ["CRITICAL", "HIGH", "MEDIUM", "LOW"],
33
+ default: "MEDIUM",
34
+ },
35
+ },
36
+ },
37
+ },
38
+ },
39
+ required: ["traceId", "tasks"],
40
+ },
41
+ },
42
+ {
43
+ name: "assign_agent_to_task",
44
+ description: "Assigns the most capable available agent to a task based on domain and capability score. Checks STATUS.md for agent availability before assigning.",
45
+ inputSchema: {
46
+ type: "object",
47
+ properties: {
48
+ traceId: { type: "string", description: "Active Trace ID" },
49
+ taskId: { type: "string", description: "Task ID to assign" },
50
+ domain: {
51
+ type: "string",
52
+ enum: ["backend", "frontend", "security", "qa", "devops", "mobile", "native", "analysis", "git", "exploration"],
53
+ description: "Domain of the task — used to filter eligible agents",
54
+ },
55
+ preferredAgent: {
56
+ type: "string",
57
+ description: "Optional preferred agent. If not available, falls back to capability-based selection.",
58
+ },
59
+ },
60
+ required: ["traceId", "taskId", "domain"],
61
+ },
62
+ },
63
+ {
64
+ name: "get_agent_workload",
65
+ 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.",
66
+ inputSchema: {
67
+ type: "object",
68
+ properties: {
69
+ projectRoot: { type: "string", description: "Root directory of the project." },
70
+ },
71
+ required: [],
72
+ },
73
+ },
74
+ {
75
+ name: "resolve_agent_blockage",
76
+ description: "Handles a BLOCKED agent by documenting the blockage, escalating to @manager, and optionally reassigning the task to another eligible agent.",
77
+ inputSchema: {
78
+ type: "object",
79
+ properties: {
80
+ blockedAgent: { type: "string", description: "The agent identifier that is BLOCKED, e.g. @frontend" },
81
+ traceId: { type: "string", description: "Active Trace ID" },
82
+ blockReason: { type: "string", description: "Mandatory description of why the agent is blocked" },
83
+ reassign: {
84
+ type: "boolean",
85
+ description: "If true, attempt to find and assign an alternative agent",
86
+ default: false,
87
+ },
88
+ },
89
+ required: ["blockedAgent", "traceId", "blockReason"],
90
+ },
91
+ },
92
+ {
93
+ name: "broadcast_army_directive",
94
+ 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.",
95
+ inputSchema: {
96
+ type: "object",
97
+ properties: {
98
+ traceId: { type: "string", description: "Active Trace ID" },
99
+ directiveType: {
100
+ type: "string",
101
+ enum: [
102
+ "WAR_ROOM_ACTIVATE",
103
+ "WAR_ROOM_DEACTIVATE",
104
+ "CONTRACT_CHANGED",
105
+ "PHASE_ROLLBACK",
106
+ "POLICY_UPDATE",
107
+ "ARMY_PAUSE",
108
+ "ARMY_RESUME",
109
+ ],
110
+ description: "Type of directive being broadcast",
111
+ },
112
+ message: { type: "string", description: "Human-readable directive message" },
113
+ urgency: {
114
+ type: "string",
115
+ enum: ["IMMEDIATE", "HIGH", "NORMAL"],
116
+ default: "NORMAL",
117
+ },
118
+ },
119
+ required: ["traceId", "directiveType", "message"],
120
+ },
121
+ },
122
+ ];
123
+ // ─── Handlers ─────────────────────────────────────────────────────────────────
124
+ export const orchestrationHandlers = {
125
+ async create_task_dag(args, projectRoot) {
126
+ const { traceId, tasks } = args;
127
+ // Build adjacency structure
128
+ const dagNodes = tasks.map((t) => ({
129
+ id: t.id,
130
+ agent: t.agent,
131
+ briefing: t.briefing,
132
+ status: "PENDING",
133
+ priority: t.priority ?? "MEDIUM",
134
+ dependsOn: t.dependsOn ?? [],
135
+ }));
136
+ // Topological sort to find execution order
137
+ const executionWaves = [];
138
+ const resolved = new Set();
139
+ const remaining = [...dagNodes];
140
+ while (remaining.length > 0) {
141
+ const wave = remaining.filter((n) => n.dependsOn.every((dep) => resolved.has(dep)));
142
+ if (wave.length === 0) {
143
+ return {
144
+ content: [
145
+ {
146
+ type: "text",
147
+ text: JSON.stringify({
148
+ error: "CIRCULAR_DEPENDENCY",
149
+ message: "Circular dependency detected in task DAG. Check dependsOn fields.",
150
+ }),
151
+ },
152
+ ],
153
+ };
154
+ }
155
+ executionWaves.push(wave.map((n) => n.id));
156
+ wave.forEach((n) => {
157
+ resolved.add(n.id);
158
+ remaining.splice(remaining.indexOf(n), 1);
159
+ });
160
+ }
161
+ // Generate Mermaid diagram
162
+ const mermaidLines = ["graph TD"];
163
+ for (const node of dagNodes) {
164
+ const label = `${node.id}["${node.agent}: ${node.briefing.slice(0, 40)}..."]`;
165
+ mermaidLines.push(` ${node.id}${label}`);
166
+ }
167
+ for (const node of dagNodes) {
168
+ for (const dep of node.dependsOn) {
169
+ mermaidLines.push(` ${dep} --> ${node.id}`);
170
+ }
171
+ }
172
+ const dag = {
173
+ traceId,
174
+ createdAt: new Date().toISOString(),
175
+ nodes: dagNodes,
176
+ executionWaves,
177
+ mermaid: mermaidLines.join("\n"),
178
+ parallelOpportunities: executionWaves.filter((w) => w.length > 1).length,
179
+ };
180
+ // Persist to queue
181
+ const queueDir = path.join(projectRoot, ".enderun", "queue", "pending");
182
+ await fs.mkdir(queueDir, { recursive: true });
183
+ await fs.writeFile(path.join(queueDir, `${traceId}-dag.json`), JSON.stringify(dag, null, 2), "utf-8");
184
+ return { content: [{ type: "text", text: JSON.stringify(dag, null, 2) }] };
185
+ },
186
+ async assign_agent_to_task(args, projectRoot) {
187
+ const { traceId, taskId, domain, preferredAgent } = args;
188
+ // Try to load capability roster from shared-facts.json (dynamic — fixes KRITIK-3)
189
+ let agentCapabilityRoster = [];
190
+ try {
191
+ const sharedFactsPath = path.join(projectRoot, ".enderun", "memory-graph", "shared-facts.json");
192
+ const content = await fs.readFile(sharedFactsPath, "utf-8");
193
+ const facts = JSON.parse(content);
194
+ agentCapabilityRoster = facts.agentCapabilityRoster ?? [];
195
+ }
196
+ catch {
197
+ // Fallback to hardcoded map if shared-facts not available
198
+ }
199
+ // Build domain → agents map from shared-facts roster
200
+ // Exclude @manager — @manager does NOT execute specialist tasks
201
+ const domainMap = {};
202
+ for (const entry of agentCapabilityRoster) {
203
+ if (entry.agent === "@manager" || entry.agent === "@orchestrator")
204
+ continue;
205
+ const d = entry.primaryDomain;
206
+ if (!domainMap[d])
207
+ domainMap[d] = [];
208
+ domainMap[d].push({ agent: entry.agent, capability: entry.capability });
209
+ }
210
+ // Hardcoded fallback (used only if shared-facts unavailable)
211
+ const fallbackDomainMap = {
212
+ backend: [{ agent: "@backend", capability: 9.2 }],
213
+ frontend: [{ agent: "@frontend", capability: 9.0 }],
214
+ security: [{ agent: "@security", capability: 9.3 }],
215
+ qa: [{ agent: "@qa", capability: 9.1 }],
216
+ devops: [{ agent: "@devops", capability: 9.0 }],
217
+ mobile: [{ agent: "@mobile", capability: 8.8 }],
218
+ native: [{ agent: "@native", capability: 8.9 }],
219
+ analysis: [{ agent: "@analyst", capability: 9.6 }],
220
+ git: [{ agent: "@git", capability: 9.1 }],
221
+ exploration: [{ agent: "@explorer", capability: 9.4 }],
222
+ };
223
+ const effectiveMap = Object.keys(domainMap).length > 0 ? domainMap : fallbackDomainMap;
224
+ const candidates = effectiveMap[domain] ?? [];
225
+ let assigned = [...candidates].sort((a, b) => b.capability - a.capability)[0];
226
+ if (preferredAgent && preferredAgent !== "@manager" && preferredAgent !== "@orchestrator") {
227
+ const preferred = candidates.find((c) => c.agent === preferredAgent);
228
+ if (preferred)
229
+ assigned = preferred;
230
+ }
231
+ if (!assigned) {
232
+ return {
233
+ content: [
234
+ {
235
+ type: "text",
236
+ text: JSON.stringify({ error: "NO_ELIGIBLE_AGENT", domain, taskId,
237
+ note: "@manager and @orchestrator are excluded from task assignment" }),
238
+ },
239
+ ],
240
+ };
241
+ }
242
+ const assignment = {
243
+ traceId,
244
+ taskId,
245
+ assignedAgent: assigned.agent,
246
+ capabilityScore: assigned.capability,
247
+ assignedAt: new Date().toISOString(),
248
+ domain,
249
+ sourceMap: Object.keys(domainMap).length > 0 ? "shared-facts" : "fallback",
250
+ };
251
+ // Write assignment log
252
+ const logPath = path.join(projectRoot, ".enderun", "logs", "orchestrator.json");
253
+ let log = { assignments: [] };
254
+ try {
255
+ const existing = await fs.readFile(logPath, "utf-8");
256
+ log = JSON.parse(existing);
257
+ }
258
+ catch {
259
+ // File doesn't exist yet — start fresh
260
+ }
261
+ log.assignments.push(assignment);
262
+ await fs.mkdir(path.dirname(logPath), { recursive: true });
263
+ await fs.writeFile(logPath, JSON.stringify(log, null, 2), "utf-8");
264
+ return { content: [{ type: "text", text: JSON.stringify(assignment, null, 2) }] };
265
+ },
266
+ async get_agent_workload(_args, projectRoot) {
267
+ const statusPath = path.join(projectRoot, ".enderun", "STATUS.md");
268
+ let statusContent;
269
+ try {
270
+ statusContent = await fs.readFile(statusPath, "utf-8");
271
+ }
272
+ catch {
273
+ statusContent = "";
274
+ }
275
+ // Parse the markdown table
276
+ const lines = statusContent.length > 0
277
+ ? statusContent.split("\n").filter((l) => l.includes("|") && !l.includes("---"))
278
+ : [];
279
+ const agents = lines.length > 1
280
+ ? lines
281
+ .slice(1) // skip header
282
+ .map((line) => {
283
+ const cells = line.split("|").map((c) => c.trim()).filter(Boolean);
284
+ return cells.length >= 4
285
+ ? { agent: cells[0], state: cells[1], currentTask: cells[2], healthScore: cells[4] }
286
+ : null;
287
+ })
288
+ .filter(Boolean)
289
+ : [];
290
+ return { content: [{ type: "text", text: JSON.stringify({ agents, generatedAt: new Date().toISOString() }, null, 2) }] };
291
+ },
292
+ async resolve_agent_blockage(args, projectRoot) {
293
+ const { blockedAgent, traceId, blockReason, reassign } = args;
294
+ const resolution = {
295
+ traceId,
296
+ blockedAgent,
297
+ blockReason,
298
+ detectedAt: new Date().toISOString(),
299
+ escalatedToManager: true,
300
+ reassignAttempted: reassign ?? false,
301
+ status: "ESCALATED_TO_MANAGER",
302
+ };
303
+ // Log blockage
304
+ const logPath = path.join(projectRoot, ".enderun", "logs", "orchestrator.json");
305
+ let log = { assignments: [], blockages: [] };
306
+ try {
307
+ const existing = await fs.readFile(logPath, "utf-8");
308
+ log = JSON.parse(existing);
309
+ if (!log.blockages)
310
+ log.blockages = [];
311
+ }
312
+ catch {
313
+ // Start fresh
314
+ }
315
+ log.blockages.push(resolution);
316
+ await fs.mkdir(path.dirname(logPath), { recursive: true });
317
+ await fs.writeFile(logPath, JSON.stringify(log, null, 2), "utf-8");
318
+ // Write a message to @manager's inbox via messages folder
319
+ const messagesDir = path.join(projectRoot, ".enderun", "messages");
320
+ await fs.mkdir(messagesDir, { recursive: true });
321
+ const messageFile = path.join(messagesDir, `BLOCKAGE-${traceId}-${Date.now()}.json`);
322
+ await fs.writeFile(messageFile, JSON.stringify({
323
+ from: "@orchestrator",
324
+ to: "@manager",
325
+ type: "AGENT_BLOCKED",
326
+ traceId,
327
+ payload: { blockedAgent, blockReason, resolution },
328
+ timestamp: new Date().toISOString(),
329
+ }), "utf-8");
330
+ return { content: [{ type: "text", text: JSON.stringify(resolution, null, 2) }] };
331
+ },
332
+ async broadcast_army_directive(args, projectRoot) {
333
+ const { traceId, directiveType, message, urgency } = args;
334
+ const broadcast = {
335
+ id: `BROADCAST-${traceId}-${Date.now()}`,
336
+ from: "@orchestrator",
337
+ to: "*",
338
+ traceId,
339
+ directiveType,
340
+ message,
341
+ urgency: urgency ?? "NORMAL",
342
+ timestamp: new Date().toISOString(),
343
+ };
344
+ // Write to messages folder for all agents
345
+ const messagesDir = path.join(projectRoot, ".enderun", "messages");
346
+ await fs.mkdir(messagesDir, { recursive: true });
347
+ await fs.writeFile(path.join(messagesDir, `${broadcast.id}.json`), JSON.stringify(broadcast, null, 2), "utf-8");
348
+ // Log broadcast
349
+ const logPath = path.join(projectRoot, ".enderun", "logs", "orchestrator.json");
350
+ let log = {
351
+ assignments: [],
352
+ blockages: [],
353
+ broadcasts: [],
354
+ };
355
+ try {
356
+ const existing = await fs.readFile(logPath, "utf-8");
357
+ log = JSON.parse(existing);
358
+ if (!log.broadcasts)
359
+ log.broadcasts = [];
360
+ }
361
+ catch {
362
+ // Start fresh
363
+ }
364
+ log.broadcasts.push(broadcast);
365
+ await fs.writeFile(logPath, JSON.stringify(log, null, 2), "utf-8");
366
+ return { content: [{ type: "text", text: JSON.stringify(broadcast, null, 2) }] };
367
+ },
368
+ };
@@ -0,0 +1,267 @@
1
+ import { promises as fs } from "fs";
2
+ import path from "path";
3
+ // ─── Tool Definitions ─────────────────────────────────────────────────────────
4
+ export const pipelineTools = [
5
+ {
6
+ name: "create_execution_pipeline",
7
+ description: "Creates a multi-stage execution pipeline for complex workflows (e.g., build → test → security audit → deploy). Each stage is assigned to a specific agent and has defined dependencies. The pipeline is persisted and can be advanced or rolled back.",
8
+ inputSchema: {
9
+ type: "object",
10
+ properties: {
11
+ traceId: { type: "string", description: "Active Trace ID from @manager briefing" },
12
+ name: { type: "string", description: "Pipeline name (e.g. 'Production Deploy v1.2.0')" },
13
+ description: { type: "string", description: "What this pipeline accomplishes" },
14
+ stages: {
15
+ type: "array",
16
+ description: "Ordered pipeline stages",
17
+ items: {
18
+ type: "object",
19
+ required: ["id", "name", "agent"],
20
+ properties: {
21
+ id: { type: "string" },
22
+ name: { type: "string" },
23
+ agent: { type: "string" },
24
+ dependsOn: { type: "array", items: { type: "string" } },
25
+ },
26
+ },
27
+ },
28
+ rollbackStages: {
29
+ type: "array",
30
+ description: "Optional rollback stages executed in reverse order on pipeline failure",
31
+ items: {
32
+ type: "object",
33
+ required: ["id", "name", "agent"],
34
+ properties: {
35
+ id: { type: "string" },
36
+ name: { type: "string" },
37
+ agent: { type: "string" },
38
+ dependsOn: { type: "array", items: { type: "string" } },
39
+ },
40
+ },
41
+ },
42
+ },
43
+ required: ["traceId", "name", "stages"],
44
+ },
45
+ },
46
+ {
47
+ name: "advance_pipeline_stage",
48
+ description: "Advances a pipeline to the next eligible stage. Marks the current stage as COMPLETED and activates all dependent stages that now have their dependencies satisfied. Returns the new pipeline state.",
49
+ inputSchema: {
50
+ type: "object",
51
+ properties: {
52
+ pipelineId: { type: "string", description: "Pipeline ID" },
53
+ completedStageId: { type: "string", description: "The stage that just completed successfully" },
54
+ stageOutput: { type: "string", description: "Optional output/result from the completed stage" },
55
+ },
56
+ required: ["pipelineId", "completedStageId"],
57
+ },
58
+ },
59
+ {
60
+ name: "rollback_pipeline",
61
+ description: "Rolls back a pipeline to the last stable state by executing the defined rollback stages in reverse order. Updates the pipeline status to ROLLED_BACK.",
62
+ inputSchema: {
63
+ type: "object",
64
+ properties: {
65
+ pipelineId: { type: "string", description: "Pipeline ID to roll back" },
66
+ reason: { type: "string", description: "Reason for the rollback (mandatory for audit)" },
67
+ },
68
+ required: ["pipelineId", "reason"],
69
+ },
70
+ },
71
+ {
72
+ name: "get_pipeline_status",
73
+ description: "Returns the full current status of a pipeline including all stage states, progress percentage, blocking stages, and estimated completion.",
74
+ inputSchema: {
75
+ type: "object",
76
+ properties: {
77
+ pipelineId: { type: "string", description: "Pipeline ID" },
78
+ },
79
+ required: ["pipelineId"],
80
+ },
81
+ },
82
+ ];
83
+ // ─── Handlers ─────────────────────────────────────────────────────────────────
84
+ export const pipelineHandlers = {
85
+ async create_execution_pipeline(args, projectRoot) {
86
+ const { traceId, name, description, stages, rollbackStages } = args;
87
+ const pipelineId = `PIPE-${traceId}-${Date.now()}`;
88
+ const now = new Date().toISOString();
89
+ const pipeline = {
90
+ id: pipelineId,
91
+ traceId,
92
+ name,
93
+ description: description ?? "",
94
+ createdAt: now,
95
+ updatedAt: now,
96
+ status: "PENDING",
97
+ stages: stages.map((s) => ({
98
+ ...s,
99
+ dependsOn: s.dependsOn ?? [],
100
+ status: "PENDING",
101
+ })),
102
+ rollbackStages: rollbackStages?.map((s) => ({
103
+ ...s,
104
+ dependsOn: s.dependsOn ?? [],
105
+ status: "PENDING",
106
+ })),
107
+ };
108
+ // Activate stages with no dependencies
109
+ for (const stage of pipeline.stages) {
110
+ if (stage.dependsOn.length === 0) {
111
+ stage.status = "RUNNING";
112
+ stage.startedAt = now;
113
+ }
114
+ }
115
+ pipeline.status = "RUNNING";
116
+ // Persist pipeline
117
+ const pipelineDir = path.join(projectRoot, ".enderun", "queue", "pipelines");
118
+ await fs.mkdir(pipelineDir, { recursive: true });
119
+ await fs.writeFile(path.join(pipelineDir, `${pipelineId}.json`), JSON.stringify(pipeline, null, 2), "utf-8");
120
+ return { content: [{ type: "text", text: JSON.stringify(pipeline, null, 2) }] };
121
+ },
122
+ async advance_pipeline_stage(args, projectRoot) {
123
+ const { pipelineId, completedStageId, stageOutput } = args;
124
+ const pipelineDir = path.join(projectRoot, ".enderun", "queue", "pipelines");
125
+ const pipelinePath = path.join(pipelineDir, `${pipelineId}.json`);
126
+ let pipeline;
127
+ try {
128
+ const content = await fs.readFile(pipelinePath, "utf-8");
129
+ pipeline = JSON.parse(content);
130
+ }
131
+ catch {
132
+ return {
133
+ content: [
134
+ { type: "text", text: JSON.stringify({ error: "PIPELINE_NOT_FOUND", pipelineId }) },
135
+ ],
136
+ };
137
+ }
138
+ const now = new Date().toISOString();
139
+ // Mark completed stage
140
+ const completedStage = pipeline.stages.find((s) => s.id === completedStageId);
141
+ if (!completedStage) {
142
+ return {
143
+ content: [
144
+ { type: "text", text: JSON.stringify({ error: "STAGE_NOT_FOUND", completedStageId }) },
145
+ ],
146
+ };
147
+ }
148
+ completedStage.status = "COMPLETED";
149
+ completedStage.completedAt = now;
150
+ if (stageOutput)
151
+ completedStage.output = stageOutput;
152
+ // Activate newly unblocked stages
153
+ const completedIds = new Set(pipeline.stages.filter((s) => s.status === "COMPLETED").map((s) => s.id));
154
+ const newlyActivated = [];
155
+ for (const stage of pipeline.stages) {
156
+ if (stage.status === "PENDING" && stage.dependsOn.every((dep) => completedIds.has(dep))) {
157
+ stage.status = "RUNNING";
158
+ stage.startedAt = now;
159
+ newlyActivated.push(stage.id);
160
+ }
161
+ }
162
+ // Check if pipeline is complete
163
+ const allDone = pipeline.stages.every((s) => s.status === "COMPLETED");
164
+ if (allDone) {
165
+ pipeline.status = "COMPLETED";
166
+ }
167
+ pipeline.updatedAt = now;
168
+ await fs.writeFile(pipelinePath, JSON.stringify(pipeline, null, 2), "utf-8");
169
+ return {
170
+ content: [
171
+ {
172
+ type: "text",
173
+ text: JSON.stringify({ pipeline, newlyActivatedStages: newlyActivated }, null, 2),
174
+ },
175
+ ],
176
+ };
177
+ },
178
+ async rollback_pipeline(args, projectRoot) {
179
+ const { pipelineId, reason } = args;
180
+ const pipelineDir = path.join(projectRoot, ".enderun", "queue", "pipelines");
181
+ const pipelinePath = path.join(pipelineDir, `${pipelineId}.json`);
182
+ let pipeline;
183
+ try {
184
+ const content = await fs.readFile(pipelinePath, "utf-8");
185
+ pipeline = JSON.parse(content);
186
+ }
187
+ catch {
188
+ return {
189
+ content: [
190
+ { type: "text", text: JSON.stringify({ error: "PIPELINE_NOT_FOUND", pipelineId }) },
191
+ ],
192
+ };
193
+ }
194
+ const now = new Date().toISOString();
195
+ pipeline.status = "ROLLED_BACK";
196
+ pipeline.updatedAt = now;
197
+ // Mark all non-completed stages as SKIPPED
198
+ for (const stage of pipeline.stages) {
199
+ if (stage.status === "RUNNING" || stage.status === "PENDING") {
200
+ stage.status = "SKIPPED";
201
+ }
202
+ }
203
+ // Activate rollback stages if defined
204
+ if (pipeline.rollbackStages) {
205
+ for (const stage of pipeline.rollbackStages) {
206
+ stage.status = "RUNNING";
207
+ stage.startedAt = now;
208
+ }
209
+ }
210
+ await fs.writeFile(pipelinePath, JSON.stringify(pipeline, null, 2), "utf-8");
211
+ // Write rollback event to devops log
212
+ const devopsLogPath = path.join(projectRoot, ".enderun", "logs", "devops.json");
213
+ let devopsLog = { rollbacks: [] };
214
+ try {
215
+ const existing = await fs.readFile(devopsLogPath, "utf-8");
216
+ devopsLog = JSON.parse(existing);
217
+ if (!devopsLog.rollbacks)
218
+ devopsLog.rollbacks = [];
219
+ }
220
+ catch {
221
+ // Start fresh
222
+ }
223
+ devopsLog.rollbacks.push({ pipelineId, reason, timestamp: now });
224
+ await fs.mkdir(path.dirname(devopsLogPath), { recursive: true });
225
+ await fs.writeFile(devopsLogPath, JSON.stringify(devopsLog, null, 2), "utf-8");
226
+ return { content: [{ type: "text", text: JSON.stringify({ pipeline, rollbackReason: reason }, null, 2) }] };
227
+ },
228
+ async get_pipeline_status(args, projectRoot) {
229
+ const { pipelineId } = args;
230
+ const pipelinePath = path.join(projectRoot, ".enderun", "queue", "pipelines", `${pipelineId}.json`);
231
+ try {
232
+ const content = await fs.readFile(pipelinePath, "utf-8");
233
+ const pipeline = JSON.parse(content);
234
+ const total = pipeline.stages.length;
235
+ const completed = pipeline.stages.filter((s) => s.status === "COMPLETED").length;
236
+ const running = pipeline.stages.filter((s) => s.status === "RUNNING").map((s) => s.id);
237
+ const blocked = pipeline.stages.filter((s) => s.status === "PENDING" && s.dependsOn.some((dep) => {
238
+ const depStage = pipeline.stages.find((st) => st.id === dep);
239
+ return depStage && depStage.status !== "COMPLETED";
240
+ })).map((s) => s.id);
241
+ return {
242
+ content: [
243
+ {
244
+ type: "text",
245
+ text: JSON.stringify({
246
+ pipelineId,
247
+ name: pipeline.name,
248
+ status: pipeline.status,
249
+ progress: `${completed}/${total} stages (${Math.round((completed / total) * 100)}%)`,
250
+ currentlyRunning: running,
251
+ waitingOn: blocked,
252
+ createdAt: pipeline.createdAt,
253
+ updatedAt: pipeline.updatedAt,
254
+ }, null, 2),
255
+ },
256
+ ],
257
+ };
258
+ }
259
+ catch {
260
+ return {
261
+ content: [
262
+ { type: "text", text: JSON.stringify({ error: "PIPELINE_NOT_FOUND", pipelineId }) },
263
+ ],
264
+ };
265
+ }
266
+ },
267
+ };
@@ -8,7 +8,7 @@ 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.1";
11
+ return pkg.version || "0.7.5";
12
12
  }
13
13
  }
14
14
  catch {
@@ -17,8 +17,30 @@ const getPackageVersion = () => {
17
17
  return "0.6.6";
18
18
  };
19
19
  export const FRAMEWORK_VERSION = getPackageVersion();
20
- export function getFrameworkDir(_projectRoot) {
21
- return ".agents"; // Antigravity-only runtime folder
20
+ /**
21
+ * Dynamically detects the framework directory for the current project.
22
+ * Checks in priority order: .enderun → .gemini → .cursor → fallback .gemini
23
+ * This resolves the adapter mismatch bug (KRITIK-1 in v0.7.5 analysis).
24
+ */
25
+ export function getFrameworkDir(projectRoot) {
26
+ const candidates = [".enderun", ".gemini", ".cursor"];
27
+ for (const dir of candidates) {
28
+ const dirPath = path.join(projectRoot, dir);
29
+ if (fs.existsSync(dirPath) && fs.statSync(dirPath).isDirectory()) {
30
+ // Verify it looks like a framework dir (has PROJECT_MEMORY.md or agents/ dir)
31
+ const hasMemory = fs.existsSync(path.join(dirPath, "PROJECT_MEMORY.md"));
32
+ const hasAgents = fs.existsSync(path.join(dirPath, "agents"));
33
+ if (hasMemory || hasAgents) {
34
+ return dir;
35
+ }
36
+ }
37
+ }
38
+ // Ultimate fallback — return first existing dir or .gemini
39
+ for (const dir of candidates) {
40
+ if (fs.existsSync(path.join(projectRoot, dir)))
41
+ return dir;
42
+ }
43
+ return ".gemini";
22
44
  }
23
45
  export function resolveSafePath(projectRoot, targetPath) {
24
46
  const resolved = path.resolve(projectRoot, targetPath);