swarm-orchestrator 7.0.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +199 -0
  3. package/agents/reviewer.agent.md +50 -0
  4. package/agents/worker.agent.md +41 -0
  5. package/config/default-agents.yaml +56 -0
  6. package/config/pm-agent.yaml +36 -0
  7. package/config/quality-gates.yaml +88 -0
  8. package/config/repair-agent.yaml +34 -0
  9. package/config/semgrep-rules/complexity-mismatch.yaml +6 -0
  10. package/config/semgrep-rules/exception-swallowing.yaml +13 -0
  11. package/config/semgrep-rules/hardcoded-answer.yaml +6 -0
  12. package/config/semgrep-rules/mock-mutation.yaml +6 -0
  13. package/config/semgrep-rules/test-modification.yaml +6 -0
  14. package/config/user-agents.yaml +23 -0
  15. package/dist/src/adapters/adapter-factory.js +29 -0
  16. package/dist/src/adapters/agent-adapter.js +32 -0
  17. package/dist/src/adapters/claude-code-adapter.js +126 -0
  18. package/dist/src/adapters/claude-code-teams.js +119 -0
  19. package/dist/src/adapters/codex-adapter.js +118 -0
  20. package/dist/src/adapters/copilot-adapter.js +224 -0
  21. package/dist/src/adapters/fatal-error-classifier.js +88 -0
  22. package/dist/src/adapters/index.js +44 -0
  23. package/dist/src/adapters/persistent-session.js +204 -0
  24. package/dist/src/adapters/process-supervisor.js +199 -0
  25. package/dist/src/agents-exporter.js +398 -0
  26. package/dist/src/analytics-log.js +161 -0
  27. package/dist/src/baseline-scanner.js +65 -0
  28. package/dist/src/bootstrap-evidence.js +88 -0
  29. package/dist/src/bootstrap-orchestrator.js +205 -0
  30. package/dist/src/bootstrap-types.js +5 -0
  31. package/dist/src/branch-merger.js +427 -0
  32. package/dist/src/cli/attest-handlers.js +44 -0
  33. package/dist/src/cli/cost-prompt.js +51 -0
  34. package/dist/src/cli/demo-handlers.js +274 -0
  35. package/dist/src/cli/flags.js +152 -0
  36. package/dist/src/cli/index.js +57 -0
  37. package/dist/src/cli/live-status.js +186 -0
  38. package/dist/src/cli/misc-handlers.js +242 -0
  39. package/dist/src/cli/plan-handlers.js +350 -0
  40. package/dist/src/cli/share-handlers.js +163 -0
  41. package/dist/src/cli/status-handlers.js +457 -0
  42. package/dist/src/cli/swarm-handlers.js +638 -0
  43. package/dist/src/cli/usage.js +72 -0
  44. package/dist/src/cli.js +217 -0
  45. package/dist/src/commit-pattern-detector.js +143 -0
  46. package/dist/src/commit-quality-analyzer.js +38 -0
  47. package/dist/src/config-loader.js +339 -0
  48. package/dist/src/context-broker.js +358 -0
  49. package/dist/src/copilot-cli-wrapper.js +258 -0
  50. package/dist/src/cost-estimator.js +241 -0
  51. package/dist/src/defaults.js +18 -0
  52. package/dist/src/demo-mode.js +180 -0
  53. package/dist/src/deployment-handler.js +78 -0
  54. package/dist/src/deployment-manager.js +228 -0
  55. package/dist/src/execution-queue.js +196 -0
  56. package/dist/src/external-tool-manager.js +184 -0
  57. package/dist/src/gate-prompt-builder.js +117 -0
  58. package/dist/src/gate-remediation.js +173 -0
  59. package/dist/src/github-issues-ingester.js +152 -0
  60. package/dist/src/github-mcp-integrator.js +164 -0
  61. package/dist/src/hook-generator.js +229 -0
  62. package/dist/src/index.js +48 -0
  63. package/dist/src/knowledge-base.js +300 -0
  64. package/dist/src/logger.js +111 -0
  65. package/dist/src/meta-analyzer.js +208 -0
  66. package/dist/src/metrics-collector.js +209 -0
  67. package/dist/src/metrics-types.js +5 -0
  68. package/dist/src/multi-repo-coordinator.js +179 -0
  69. package/dist/src/orchestrator/async-meta-analysis.js +84 -0
  70. package/dist/src/orchestrator/fatal-run-error.js +43 -0
  71. package/dist/src/orchestrator/final-gates-remediation.js +258 -0
  72. package/dist/src/orchestrator/git-state-utils.js +119 -0
  73. package/dist/src/orchestrator/pause-controller.js +55 -0
  74. package/dist/src/orchestrator/replan-runner.js +258 -0
  75. package/dist/src/orchestrator/step-executor.js +391 -0
  76. package/dist/src/orchestrator/wave-scheduler-loop.js +204 -0
  77. package/dist/src/owasp-mapper.js +153 -0
  78. package/dist/src/owasp-report-renderer.js +39 -0
  79. package/dist/src/plan-files.js +142 -0
  80. package/dist/src/plan-generator.js +1207 -0
  81. package/dist/src/pm-agent.js +296 -0
  82. package/dist/src/post-run-reporter.js +203 -0
  83. package/dist/src/pr-automation.js +103 -0
  84. package/dist/src/pr-manager.js +318 -0
  85. package/dist/src/presenter/index.js +209 -0
  86. package/dist/src/prompt-builder.js +173 -0
  87. package/dist/src/quality-gates/config-loader.js +196 -0
  88. package/dist/src/quality-gates/default-config.js +243 -0
  89. package/dist/src/quality-gates/file-utils.js +152 -0
  90. package/dist/src/quality-gates/gate-runner.js +171 -0
  91. package/dist/src/quality-gates/gates/accessibility.js +355 -0
  92. package/dist/src/quality-gates/gates/duplicate-blocks.js +144 -0
  93. package/dist/src/quality-gates/gates/hardcoded-config.js +52 -0
  94. package/dist/src/quality-gates/gates/readme-claims.js +62 -0
  95. package/dist/src/quality-gates/gates/runtime-checks.js +265 -0
  96. package/dist/src/quality-gates/gates/scaffold-defaults.js +108 -0
  97. package/dist/src/quality-gates/gates/test-coverage.js +240 -0
  98. package/dist/src/quality-gates/gates/test-file-protection.js +85 -0
  99. package/dist/src/quality-gates/gates/test-isolation.js +74 -0
  100. package/dist/src/quality-gates/index.js +17 -0
  101. package/dist/src/quality-gates/registry.js +205 -0
  102. package/dist/src/quality-gates/types.js +2 -0
  103. package/dist/src/quick-fix-mode.js +302 -0
  104. package/dist/src/recipe-loader.js +122 -0
  105. package/dist/src/repair-agent.js +396 -0
  106. package/dist/src/repo-analyzer.js +342 -0
  107. package/dist/src/report-generator.js +146 -0
  108. package/dist/src/report-renderer.js +90 -0
  109. package/dist/src/requirement-filter.js +52 -0
  110. package/dist/src/sarif-formatter.js +140 -0
  111. package/dist/src/scheduling/dependency-analyzer.js +178 -0
  112. package/dist/src/scheduling/work-stealing-queue.js +123 -0
  113. package/dist/src/secret-redactor.js +90 -0
  114. package/dist/src/session-executor.js +587 -0
  115. package/dist/src/session-manager.js +221 -0
  116. package/dist/src/share/transcript-verification.js +128 -0
  117. package/dist/src/share-parser.js +477 -0
  118. package/dist/src/spinner.js +146 -0
  119. package/dist/src/step-runner.js +365 -0
  120. package/dist/src/swarm-orchestrator.js +686 -0
  121. package/dist/src/task-classifier.js +134 -0
  122. package/dist/src/test-command-discovery.js +123 -0
  123. package/dist/src/text-similarity.js +24 -0
  124. package/dist/src/tier-maps.js +158 -0
  125. package/dist/src/types.js +5 -0
  126. package/dist/src/url-shortener.js +135 -0
  127. package/dist/src/verification/attestation.js +166 -0
  128. package/dist/src/verification/cheat-detector.js +240 -0
  129. package/dist/src/verification/command-runner.js +70 -0
  130. package/dist/src/verification/composite-score.js +157 -0
  131. package/dist/src/verification/cosign-attestation.js +137 -0
  132. package/dist/src/verification/diff-analysis.js +89 -0
  133. package/dist/src/verification/differential-gate.js +173 -0
  134. package/dist/src/verification/index.js +41 -0
  135. package/dist/src/verification/mutation-gate.js +242 -0
  136. package/dist/src/verification/property-gate.js +201 -0
  137. package/dist/src/verification/test-synthesizer.js +243 -0
  138. package/dist/src/verifier/outcome-checks.js +396 -0
  139. package/dist/src/verifier/transcript-checks.js +207 -0
  140. package/dist/src/verifier/verification-reporters.js +148 -0
  141. package/dist/src/verifier-engine.js +491 -0
  142. package/dist/src/wave-resizer.js +131 -0
  143. package/dist/src/wave-scheduler.js +44 -0
  144. package/dist/src/worktree-manager.js +368 -0
  145. package/dist/src/worktree-reserved-paths.js +97 -0
  146. package/package.json +80 -0
  147. package/plugin/agents/.gitkeep +0 -0
  148. package/plugin/agents/backend_master.agent.md +72 -0
  149. package/plugin/agents/devops_pro.agent.md +45 -0
  150. package/plugin/agents/frontend_expert.agent.md +59 -0
  151. package/plugin/agents/integrator_finalizer.agent.md +44 -0
  152. package/plugin/agents/meta_reviewer.agent.md +44 -0
  153. package/plugin/agents/security_auditor.agent.md +43 -0
  154. package/plugin/agents/tester_elite.agent.md +43 -0
  155. package/plugin/hooks/hooks.json +19 -0
  156. package/plugin/hooks/post-tool-use.json +9 -0
  157. package/plugin/hooks/pre-tool-use.json +9 -0
  158. package/plugin/plugin.json +47 -0
  159. package/plugin/skills/swarm-gates/SKILL.md +53 -0
  160. package/plugin/skills/swarm-orchestrate/SKILL.md +34 -0
  161. package/plugin/skills/swarm-verify/SKILL.md +44 -0
  162. package/templates/README.md +35 -0
  163. package/templates/cli-tool.json +31 -0
  164. package/templates/fullstack.json +52 -0
  165. package/templates/library.json +38 -0
  166. package/templates/react-app.json +45 -0
  167. package/templates/recipes/add-api-docs.json +35 -0
  168. package/templates/recipes/add-auth.json +47 -0
  169. package/templates/recipes/add-ci.json +39 -0
  170. package/templates/recipes/add-tests.json +39 -0
  171. package/templates/recipes/migrate-to-ts.json +42 -0
  172. package/templates/recipes/refactor-modularize.json +44 -0
  173. package/templates/recipes/security-audit.json +35 -0
  174. package/templates/rest-api.json +38 -0
@@ -0,0 +1,258 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.executeReplan = executeReplan;
40
+ const fs = __importStar(require("fs"));
41
+ const path = __importStar(require("path"));
42
+ const plan_generator_1 = require("../plan-generator");
43
+ const repair_agent_1 = __importDefault(require("../repair-agent"));
44
+ const logger_1 = require("../logger");
45
+ const logger = (0, logger_1.getLogger)('orchestrator');
46
+ /**
47
+ * WARNING: This function mutates context.plan by assignment.
48
+ *
49
+ * After executeReplan returns, context.plan points to a different
50
+ * ExecutionPlan object than it did before the call. Any code that
51
+ * cached context.plan or context.plan.steps before the call is
52
+ * now holding a stale reference.
53
+ *
54
+ * The wave scheduler (wave-scheduler-loop.ts) handles this by
55
+ * re-reading context.plan.steps on every loop iteration. Do not
56
+ * change this function's mutation behavior without updating the
57
+ * scheduler's invariant comment and test.
58
+ *
59
+ * Behavior: retries failed steps on new branches with a "-retryN"
60
+ * suffix (preserving completed work), then appends and runs any
61
+ * `replanPayload.addSteps`, finally merging their successful
62
+ * branches to main so subsequent quality-gate re-checks see the
63
+ * remediation changes.
64
+ *
65
+ * @param host - orchestrator surface providing workingDir, resolveAgent,
66
+ * switchBranch, createAgentBranch, executeStepInSwarm, mergeWaveBranches
67
+ * @param context - mutable replan context; `context.plan` may be reassigned
68
+ * @param replanPayload - retries and add-steps to execute
69
+ * @param agents - available agent map
70
+ * @param options - orchestrator options (model, onProgress)
71
+ */
72
+ async function executeReplan(host, context, replanPayload, agents, options) {
73
+ logger.info('\n🔄 Executing replan...');
74
+ const maxRetries = options?.maxRetries ?? 3;
75
+ // initialize replan state
76
+ context.replanState = {
77
+ triggeredAt: new Date().toISOString(),
78
+ payload: replanPayload,
79
+ retryBranches: new Map()
80
+ };
81
+ // update knowledge base with replan event
82
+ context.knowledgeBase?.addOrUpdatePattern({
83
+ category: 'failure_mode',
84
+ insight: `replan triggered for steps: ${replanPayload.retrySteps.join(', ')}`,
85
+ confidence: 'high',
86
+ evidence: [`replan at ${context.replanState.triggeredAt}`],
87
+ impact: 'medium'
88
+ });
89
+ // execute retries for each failed step using the repair agent
90
+ for (const stepNumber of replanPayload.retrySteps) {
91
+ const step = context.plan.steps.find(s => s.stepNumber === stepNumber);
92
+ if (!step) {
93
+ logger.warn(` replan: step ${stepNumber} not found, skipping`);
94
+ continue;
95
+ }
96
+ const agent = host.resolveAgent(agents, step.agentName);
97
+ if (!agent) {
98
+ logger.warn(` replan: agent ${step.agentName} not found, skipping`);
99
+ continue;
100
+ }
101
+ // get current retry count
102
+ const resultIndex = context.results.findIndex(r => r.stepNumber === stepNumber);
103
+ const result = context.results[resultIndex];
104
+ const retryCount = (result?.retryCount || 0) + 1;
105
+ if (retryCount > maxRetries) {
106
+ logger.error(` step ${stepNumber} exceeded max retries (${maxRetries}), skipping`);
107
+ continue;
108
+ }
109
+ logger.info(` 🔧 Spawning repair agent for step ${stepNumber} (${agent.name}) - attempt ${retryCount}`);
110
+ // create retry branch with suffix
111
+ const retryBranchName = `swarm/${context.executionId}/step-${stepNumber}-${agent.name.toLowerCase()}-retry${retryCount}`;
112
+ // track retry branch
113
+ if (!context.replanState.retryBranches.has(stepNumber)) {
114
+ context.replanState.retryBranches.set(stepNumber, []);
115
+ }
116
+ context.replanState.retryBranches.get(stepNumber).push(retryBranchName);
117
+ // reset result status
118
+ if (result) {
119
+ result.status = 'pending';
120
+ result.retryCount = retryCount;
121
+ result.branchName = retryBranchName;
122
+ delete result.error;
123
+ delete result.sessionResult;
124
+ delete result.verificationResult;
125
+ }
126
+ try {
127
+ // switch to main branch before creating retry branch
128
+ await host.switchBranch(context.mainBranch);
129
+ await host.createAgentBranch(retryBranchName, context.mainBranch);
130
+ // Build repair context from the failed step's results
131
+ const stepDir = path.join(context.runDir, 'steps', `step-${stepNumber}`);
132
+ const transcriptPath = path.join(stepDir, 'share.md');
133
+ const verificationReportPath = path.join(context.runDir, 'verification', `step-${stepNumber}-verification.md`);
134
+ const failedChecks = [];
135
+ let rootCause = 'Verification checks failed';
136
+ if (result?.verificationResult) {
137
+ const repairAgentHelper = new repair_agent_1.default(host.workingDir, maxRetries);
138
+ const extracted = repairAgentHelper.extractFailedChecks(result.verificationResult);
139
+ failedChecks.push(...extracted);
140
+ // Derive root cause from check types (includes outcome-based checks)
141
+ const hasTestFailure = result.verificationResult.checks.some(c => !c.passed && (c.type === 'test' || c.type === 'test_exec'));
142
+ const hasBuildFailure = result.verificationResult.checks.some(c => !c.passed && (c.type === 'build' || c.type === 'build_exec'));
143
+ const hasCommitFailure = result.verificationResult.checks.some(c => !c.passed && c.type === 'commit');
144
+ const hasNoDiff = result.verificationResult.checks.some(c => !c.passed && c.type === 'git_diff');
145
+ const hasMissingFiles = result.verificationResult.checks.some(c => !c.passed && c.type === 'file_existence');
146
+ if (hasMissingFiles)
147
+ rootCause = 'Expected files were not created';
148
+ else if (hasTestFailure)
149
+ rootCause = 'Tests not executed or failed';
150
+ else if (hasBuildFailure)
151
+ rootCause = 'Build not executed or failed';
152
+ else if (hasNoDiff)
153
+ rootCause = 'Agent made no code changes';
154
+ else if (hasCommitFailure)
155
+ rootCause = 'No commits made';
156
+ }
157
+ const repairContext = {
158
+ stepNumber,
159
+ agentName: agent.name,
160
+ originalTask: step.task,
161
+ transcriptPath,
162
+ verificationReportPath,
163
+ branchName: retryBranchName,
164
+ failedChecks,
165
+ rootCause,
166
+ retryCount,
167
+ failureContext: result.verificationResult?.failureContext,
168
+ };
169
+ const repairAgent = new repair_agent_1.default(host.workingDir, maxRetries);
170
+ const sessionOpts = {
171
+ allowAllTools: true,
172
+ ...(options?.model && { model: options.model })
173
+ };
174
+ const repairResult = await repairAgent.attemptRepair(repairContext, sessionOpts, {
175
+ requireTests: /\b(test suite|unit test|integration test|e2e test|write tests)\b/i.test(step.task),
176
+ requireBuild: /\b(npm build|run build|compile|bundle|webpack)\b/i.test(step.task),
177
+ requireCommits: false
178
+ });
179
+ // Log repair cost
180
+ logger.info(` 📊 Repair cost: ~${repairResult.estimatedTokenCost} tokens, ${repairResult.attempts} attempt(s), ${Math.round(repairResult.totalDurationMs / 1000)}s`);
181
+ // Save repair result to run directory
182
+ const repairResultPath = path.join(context.runDir, `repair-step-${stepNumber}.json`);
183
+ fs.writeFileSync(repairResultPath, JSON.stringify(repairResult, null, 2), 'utf8');
184
+ if (repairResult.success) {
185
+ if (result) {
186
+ result.status = 'completed';
187
+ result.endTime = new Date().toISOString();
188
+ }
189
+ logger.info(` ✅ Repair succeeded for step ${stepNumber} after ${repairResult.attempts} attempt(s)`);
190
+ }
191
+ else {
192
+ // Repair failed - fall back to standard re-execution as last resort
193
+ logger.warn(` ⚠️ Repair agent failed; falling back to full re-execution for step ${stepNumber}`);
194
+ const retryStep = { ...step, task: `[RETRY ${retryCount}] ${step.task}` };
195
+ await host.executeStepInSwarm(retryStep, agent, context, options);
196
+ logger.info(` ✅ Fallback retry succeeded for step ${stepNumber}`);
197
+ }
198
+ }
199
+ catch (error) {
200
+ const err = error;
201
+ logger.error(` ❌ Retry ${retryCount} failed for step ${stepNumber}: ${err.message}`);
202
+ }
203
+ }
204
+ // append and execute any new steps
205
+ if (replanPayload.addSteps && replanPayload.addSteps.length > 0) {
206
+ const generator = new plan_generator_1.PlanGenerator(Array.from(agents.values()));
207
+ const completed = context.results.filter(r => r.status === 'completed').map(r => r.stepNumber);
208
+ const revised = generator.revisePlan(context.plan, replanPayload, completed);
209
+ const oldMax = Math.max(...context.plan.steps.map(s => s.stepNumber));
210
+ const newSteps = revised.steps.filter((s) => s.stepNumber > oldMax);
211
+ context.plan = revised;
212
+ for (const s of newSteps) {
213
+ context.results.push({
214
+ stepNumber: s.stepNumber,
215
+ agentName: s.agentName,
216
+ status: 'pending'
217
+ });
218
+ }
219
+ logger.info(` ➕ Replan added ${newSteps.length} new step(s)`);
220
+ // Notify listeners immediately so totalSteps and progress state update
221
+ // before the replan steps start executing
222
+ options?.onProgress?.(context, `replan-added:${newSteps.length}`);
223
+ // Execute added steps in parallel when they have no mutual dependencies
224
+ const replanPromises = newSteps.map(async (added) => {
225
+ const agent = host.resolveAgent(agents, added.agentName);
226
+ if (!agent) {
227
+ logger.warn(` replan: agent ${added.agentName} not found for step ${added.stepNumber}, skipping`);
228
+ return;
229
+ }
230
+ logger.info(` 🧩 Executing added step ${added.stepNumber} (${agent.name})`);
231
+ try {
232
+ await host.executeStepInSwarm(added, agent, context, options);
233
+ }
234
+ catch (err) {
235
+ const msg = err instanceof Error ? err.message : String(err);
236
+ logger.error(` ❌ Replan step ${added.stepNumber} failed: ${msg}`);
237
+ }
238
+ });
239
+ await Promise.allSettled(replanPromises);
240
+ // Merge completed replan branches to main so quality gate re-checks
241
+ // see the remediation changes. Without this, replan work stays on
242
+ // unmerged branches and gate re-runs read stale code.
243
+ const completedReplan = newSteps
244
+ .map(s => context.results.find(r => r.stepNumber === s.stepNumber))
245
+ .filter((r) => !!r && r.status === 'completed' && !!r.branchName);
246
+ if (completedReplan.length > 0) {
247
+ context.contextBroker.forceReleaseStaleLocks();
248
+ await host.mergeWaveBranches(completedReplan, context, options);
249
+ }
250
+ }
251
+ // save replan state to run directory
252
+ const replanPath = path.join(context.runDir, 'replan-state.json');
253
+ fs.writeFileSync(replanPath, JSON.stringify({
254
+ ...context.replanState,
255
+ retryBranches: Object.fromEntries(context.replanState.retryBranches)
256
+ }, null, 2), 'utf8');
257
+ logger.info(' 📝 Replan state saved');
258
+ }
@@ -0,0 +1,391 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.executeStepInSwarm = executeStepInSwarm;
40
+ const child_process_1 = require("child_process");
41
+ const fs = __importStar(require("fs"));
42
+ const path = __importStar(require("path"));
43
+ const adapters_1 = require("../adapters");
44
+ const commit_quality_analyzer_1 = require("../commit-quality-analyzer");
45
+ const deployment_handler_1 = require("../deployment-handler");
46
+ const hook_generator_1 = require("../hook-generator");
47
+ const prompt_builder_1 = require("../prompt-builder");
48
+ const session_executor_1 = __importDefault(require("../session-executor"));
49
+ const spinner_1 = require("../spinner");
50
+ const defaults_1 = require("../defaults");
51
+ const worktree_reserved_paths_1 = require("../worktree-reserved-paths");
52
+ const live_status_1 = require("../cli/live-status");
53
+ const fatal_run_error_1 = require("./fatal-run-error");
54
+ /**
55
+ * Human-readable label for a CLI agent tool, used in fallback transcript headers.
56
+ */
57
+ function transcriptToolLabel(tool) {
58
+ switch (tool) {
59
+ case 'codex': return 'Codex';
60
+ case 'claude-code': return 'Claude Code';
61
+ case 'claude-code-teams': return 'Claude Code Teams';
62
+ case 'copilot': return 'Copilot';
63
+ default: return tool.charAt(0).toUpperCase() + tool.slice(1);
64
+ }
65
+ }
66
+ /**
67
+ * Execute a single step end-to-end: wait for deps, create worktree,
68
+ * build prompt, run the agent session, commit
69
+ * uncommitted work, verify, record context / cost / metrics, optional
70
+ * deployment on success, rollback on failure.
71
+ *
72
+ * Mutates `context.results[<stepNumber>]` in place for status,
73
+ * branchName, timestamps, sessionResult, verificationResult, and error.
74
+ * Pushes to `context.stepCostRecords` when cost tracking is active.
75
+ *
76
+ * Throws on verification failure so the scheduler can mark the step
77
+ * failed; a ContextEntry is still added to the broker on failure so
78
+ * downstream waiters don't hang.
79
+ *
80
+ * @param host - orchestrator surface (workingDir, shareParser,
81
+ * verifier, createAgentWorktree)
82
+ * @param step - the plan step to execute
83
+ * @param agent - the agent assigned to this step
84
+ * @param context - mutable execution context
85
+ * @param options - execution options (model, cliAgent, hooks, etc.)
86
+ */
87
+ async function executeStepInSwarm(host, step, agent, context, options) {
88
+ const resultIndex = context.results.findIndex(r => r.stepNumber === step.stepNumber);
89
+ const result = context.results[resultIndex];
90
+ if (!result) {
91
+ throw new Error(`Result for step ${step.stepNumber} not found`);
92
+ }
93
+ try {
94
+ // wait for dependencies with spinner feedback
95
+ if (step.dependencies.length > 0) {
96
+ const depSpinner = new spinner_1.Spinner(`Step ${step.stepNumber} — Waiting for dependencies (${step.dependencies.join(', ')})...`, { style: 'pulse', prefix: ' ' });
97
+ depSpinner.start();
98
+ try {
99
+ const satisfied = await context.contextBroker.waitForDependencies(step.dependencies, defaults_1.DEFAULT_DEPENDENCY_WAIT_MS);
100
+ if (!satisfied) {
101
+ depSpinner.fail(`Step ${step.stepNumber} — Dependencies timeout`);
102
+ throw new Error('Dependencies timeout after 10 minutes');
103
+ }
104
+ depSpinner.succeed(`Step ${step.stepNumber} — Dependencies ready`);
105
+ }
106
+ catch (depError) {
107
+ depSpinner.fail(`Step ${step.stepNumber} — Dependency failed`);
108
+ throw depError;
109
+ }
110
+ }
111
+ // Track step execution
112
+ context.metricsCollector?.trackStep(step.stepNumber, agent.name);
113
+ // create per-agent branch and worktree for true parallel isolation
114
+ const branchName = `swarm/${context.executionId}/step-${step.stepNumber}-${agent.name.toLowerCase()}`;
115
+ result.branchName = branchName;
116
+ result.status = 'running';
117
+ result.startTime = new Date().toISOString();
118
+ // Notify progress: step is now running
119
+ options?.onProgress?.(context, `step-running:${step.stepNumber}`);
120
+ // Use git worktree so each agent has its own isolated working directory
121
+ const stepRepoDir = step.repo || host.workingDir;
122
+ const worktreePath = await host.createAgentWorktree(branchName, context.mainBranch, context.runDir, step.stepNumber, stepRepoDir);
123
+ const liveStatus = (0, live_status_1.getLiveStatus)();
124
+ const liveStepId = `step-${step.stepNumber}`;
125
+ const liveLabel = `step-${step.stepNumber} ${agent.name}`;
126
+ // Capture baseline SHA before agent execution for outcome-based verification
127
+ const baseSha = (0, child_process_1.execSync)('git rev-parse HEAD', { cwd: worktreePath, encoding: 'utf8' }).trim();
128
+ // build enhanced prompt with dependency context
129
+ const strictIsolation = options?.strictIsolation ?? false;
130
+ const dependencyContext = context.contextBroker.getDependencyContext(step.dependencies, strictIsolation);
131
+ const enhancedPrompt = (0, prompt_builder_1.buildSwarmPrompt)(step, agent, { ...context, targetProjectRoot: host.workingDir }, dependencyContext);
132
+ // Inner fleet toggle: prefix prompt with /fleet for parallel sub-agent dispatch
133
+ const finalPrompt = options?.useInnerFleet
134
+ ? `/fleet ${enhancedPrompt}`
135
+ : enhancedPrompt;
136
+ // execute session on agent branch - IN THE WORKTREE DIRECTORY
137
+ const stepDir = path.join(context.runDir, 'steps', `step-${step.stepNumber}`);
138
+ const transcriptPath = path.join(stepDir, 'share.md');
139
+ // Ensure step directory exists before session runs
140
+ if (!fs.existsSync(stepDir)) {
141
+ fs.mkdirSync(stepDir, { recursive: true });
142
+ }
143
+ // Create a session executor for this worktree
144
+ // Per-step cliAgent takes priority; falls back to run-level option; defaults to copilot
145
+ const adapterName = step.cliAgent || options?.cliAgent || 'copilot';
146
+ const stepAdapter = (0, adapters_1.resolveAdapter)(adapterName);
147
+ const worktreeExecutor = new session_executor_1.default(worktreePath, stepAdapter);
148
+ // Route agent stdout/stderr lines through LiveStatus.print so they appear
149
+ // above the live block, prefixed with `step-N ›` (dim). When LiveStatus is
150
+ // inactive (CI / non-TTY where it was never started, or piped output), the
151
+ // print falls through to a plain stdout write, so behavior degrades.
152
+ const dimOpen = process.stdout.isTTY && !process.env.NO_COLOR ? '\x1b[2m' : '';
153
+ const dimClose = process.stdout.isTTY && !process.env.NO_COLOR ? '\x1b[22m' : '';
154
+ const linePrefix = `${dimOpen}${liveStepId} ›${dimClose}`;
155
+ const actionRe = /^\s*●\s+(.+?)\s*$/;
156
+ const sessionOptions = {
157
+ allowAllTools: true,
158
+ shareToFile: transcriptPath,
159
+ executionMode: 'auto',
160
+ persistentSessionId: `${context.executionId}:${adapterName}:${worktreePath}`,
161
+ onAgentLine: (line) => {
162
+ // Always update the live action from `●` markers so the user sees what
163
+ // the agent is currently doing. Only print the full narration line when
164
+ // --stream-agent is set; otherwise the firehose stays in the run
165
+ // transcript at runs/<id>/steps/step-N/share.md.
166
+ const match = actionRe.exec(line);
167
+ if (match) {
168
+ liveStatus.setAction(liveStepId, match[1]);
169
+ }
170
+ if (options?.streamAgent) {
171
+ liveStatus.print(`${linePrefix} ${line}`);
172
+ }
173
+ },
174
+ ...(options?.model && { model: options.model }),
175
+ };
176
+ // Generate per-step hooks for scope enforcement and evidence capture
177
+ // Hooks default to on unless explicitly disabled via --no-hooks
178
+ let generatedHooks;
179
+ if (options?.hooksEnabled !== false) {
180
+ const hookGen = new hook_generator_1.HookGenerator();
181
+ generatedHooks = hookGen.generateStepHooks({
182
+ step,
183
+ agent,
184
+ executionId: context.executionId,
185
+ runDir: context.runDir,
186
+ stepBranch: branchName,
187
+ workingDir: worktreePath,
188
+ existingTestFiles: context.baselineSnapshot?.testFiles || [],
189
+ });
190
+ // Hooks are auto-loaded by Copilot CLI from <gitRoot>/.github/hooks/
191
+ }
192
+ // only call the agent if we don't already have a session result
193
+ if (!result.sessionResult) {
194
+ liveStatus.addStep(liveStepId, liveLabel);
195
+ const sessionResult = await worktreeExecutor.executeSession(finalPrompt, sessionOptions);
196
+ result.sessionResult = sessionResult;
197
+ // Account-level fatal error: usage-limit, auth, or extended rate-limit.
198
+ // No replan or repair can recover, so abort the run before the
199
+ // verification + replan loop wastes the per-instance budget. The
200
+ // catch block at the bottom of this function records the failure
201
+ // and signals the broker so any waiting dependents unblock.
202
+ if (sessionResult.fatalError) {
203
+ throw new fatal_run_error_1.FatalRunError(sessionResult.fatalError, {
204
+ stepNumber: step.stepNumber,
205
+ agentName: agent.name,
206
+ });
207
+ }
208
+ // Non-zero exit code does not mean the agent failed its task.
209
+ // Claude Code often exits non-zero after completing file changes
210
+ // (e.g., cleanup command fails, permission prompt at exit). The
211
+ // verification pipeline below judges whether the work is acceptable;
212
+ // surface the exit code as a non-fatal note via the live block action.
213
+ if (!sessionResult.success) {
214
+ liveStatus.setAction(liveStepId, `non-zero exit ${sessionResult.exitCode}; checking work`);
215
+ }
216
+ }
217
+ // Clean up hook files after session completes (evidence log in runDir persists)
218
+ if (generatedHooks) {
219
+ const hookGen = new hook_generator_1.HookGenerator();
220
+ hookGen.cleanupHooks(generatedHooks.hooksFilePath);
221
+ }
222
+ // Check if transcript was created, create fallback if not
223
+ if (!fs.existsSync(transcriptPath)) {
224
+ const toolLabel = transcriptToolLabel(options?.cliAgent || 'copilot');
225
+ const fallbackContent = `# ${toolLabel} Session Transcript\n\nSession output:\n\`\`\`\n${result.sessionResult?.output || 'No output captured'}\n\`\`\`\n`;
226
+ fs.writeFileSync(transcriptPath, fallbackContent, 'utf8');
227
+ }
228
+ // parse transcript for context
229
+ const transcriptContent = fs.readFileSync(transcriptPath, 'utf8');
230
+ const shareIndex = host.shareParser.parse(transcriptContent);
231
+ // Track commits from this step
232
+ if (shareIndex.gitCommits) {
233
+ shareIndex.gitCommits.forEach(() => {
234
+ context.metricsCollector?.trackCommit(agent.name);
235
+ });
236
+ // Analyze commit quality for anti-patterns
237
+ await (0, commit_quality_analyzer_1.analyzeCommitQuality)(shareIndex.gitCommits, step.stepNumber, agent.name);
238
+ }
239
+ // Commit any uncommitted work the agent left behind.
240
+ // Some adapters (e.g. Codex) modify files without committing;
241
+ // the git_diff verification check only sees committed changes.
242
+ try {
243
+ const status = (0, child_process_1.execSync)('git status --porcelain', { cwd: worktreePath, encoding: 'utf8' }).trim();
244
+ if (status) {
245
+ // Exclude orchestrator-reserved and build-artifact paths from per-step commits.
246
+ // Bare `git add -A` captures orchestrator state written to runs/, plans/,
247
+ // .quickfix/, node_modules/, __pycache__, etc., bloating commit diffs with
248
+ // noise unrelated to the agent's actual work (smoke5c failure mode, issue #27).
249
+ // gitPathspecExcludes() mirrors the Python-side capture exclusion from
250
+ // worktree_reserved_paths.py so both sides stay in sync. See PR 2.
251
+ const excludes = (0, worktree_reserved_paths_1.gitPathspecExcludes)();
252
+ (0, child_process_1.execSync)(`git add -A -- . ${excludes.map(e => `'${e}'`).join(' ')}`, { cwd: worktreePath, stdio: 'pipe' });
253
+ (0, child_process_1.execSync)(`git commit -m "auto-commit uncommitted work from step ${step.stepNumber} (${agent.name})"`, { cwd: worktreePath, stdio: 'pipe', env: { ...process.env, GIT_TERMINAL_PROMPT: '0' } });
254
+ }
255
+ }
256
+ catch {
257
+ // Commit may fail if working tree is truly clean or in detached HEAD; non-fatal
258
+ }
259
+ // Verification phase: surface as a sub-action on the step's live line.
260
+ // When LiveStatus is inactive (non-TTY where it never started, tests),
261
+ // fall back to the legacy spinner so progress is still visible.
262
+ liveStatus.setAction(liveStepId, 'verifying work');
263
+ const verifySpinner = liveStatus.isTTY()
264
+ ? null
265
+ : new spinner_1.Spinner(`Step ${step.stepNumber} — Verifying work...`, { style: 'dots', prefix: ' ' });
266
+ verifySpinner?.start();
267
+ const verificationResult = await host.verifier.verifyStep(step.stepNumber, agent.name, transcriptPath, {
268
+ // only require tests if task explicitly mentions testing/test suite
269
+ requireTests: /\b(test suite|unit test|integration test|e2e test|write tests)\b/i.test(step.task),
270
+ // only require build if task explicitly mentions build process (not "build an app")
271
+ requireBuild: /\b(npm build|run build|compile|bundle|webpack)\b/i.test(step.task),
272
+ // commits are desired but not blocking for demo
273
+ requireCommits: false
274
+ }, shareIndex, generatedHooks?.evidenceLogPath, { workdir: worktreePath, baseSha });
275
+ result.verificationResult = verificationResult;
276
+ // Track verification result
277
+ context.metricsCollector?.trackVerification(verificationResult.passed);
278
+ // generate and commit verification report
279
+ const reportPath = path.join(context.runDir, 'verification', `step-${step.stepNumber}-verification.md`);
280
+ await host.verifier.generateVerificationReport(verificationResult, reportPath);
281
+ if (verificationResult.passed) {
282
+ verifySpinner?.stop();
283
+ liveStatus.finishStep(liveStepId, 'done', 'verified');
284
+ // add to shared context BEFORE advisory gates so replan steps can depend on this step
285
+ context.contextBroker.addStepContext({
286
+ stepNumber: step.stepNumber,
287
+ agentName: agent.name,
288
+ timestamp: new Date().toISOString(),
289
+ data: {
290
+ filesChanged: shareIndex.changedFiles,
291
+ outputsSummary: step.expectedOutputs.join(', '),
292
+ branchName,
293
+ commitShas: shareIndex.gitCommits.map(c => c.sha || 'unknown'),
294
+ verificationPassed: verificationResult.passed,
295
+ transcript: transcriptPath
296
+ }
297
+ });
298
+ await host.verifier.commitVerificationReport(reportPath, step.stepNumber, agent.name, true);
299
+ // Quality gates run only in the final pass (after all branches merged) to avoid
300
+ // spawning extra Copilot sessions mid-execution. The final gates block handles
301
+ // auto-remediation for every gate type, making per-step checks redundant.
302
+ // optional deployment for deployment-focused steps when --confirm-deploy is set
303
+ const isDeploymentStep = /\b(deploy|deployment|vercel|netlify)\b/i.test(step.task);
304
+ if (isDeploymentStep && options?.confirmDeploy) {
305
+ await (0, deployment_handler_1.executeOptionalDeployment)(host.workingDir, step, agent, {
306
+ runDir: context.runDir,
307
+ executionId: context.executionId,
308
+ results: context.results,
309
+ deployments: context.deployments,
310
+ }, {
311
+ ...(options.confirmDeploy !== undefined && { confirmDeploy: options.confirmDeploy }),
312
+ ...(options.enableExternal !== undefined && { enableExternal: options.enableExternal }),
313
+ ...(options.dryRun !== undefined && { dryRun: options.dryRun }),
314
+ });
315
+ }
316
+ }
317
+ else {
318
+ // verification failed - attempt rollback
319
+ verifySpinner?.stop();
320
+ liveStatus.setAction(liveStepId, 'verification failed; rolling back');
321
+ const rollbackResult = await host.verifier.rollback(step.stepNumber, branchName, shareIndex.changedFiles, context.mainBranch);
322
+ const rollbackNote = rollbackResult.success
323
+ ? `verification failed · rolled back ${rollbackResult.filesRestored.length} file${rollbackResult.filesRestored.length === 1 ? '' : 's'}`
324
+ : 'verification failed';
325
+ liveStatus.finishStep(liveStepId, 'failed', rollbackNote);
326
+ throw new Error('Step failed verification - see verification report');
327
+ }
328
+ // context was already added before advisory gates block
329
+ result.status = 'completed';
330
+ result.endTime = new Date().toISOString();
331
+ // Record actual cost for this step
332
+ if (context.costEstimator && context.stepCostRecords) {
333
+ const stepEstimate = context.costEstimate?.perStep.find(s => s.stepNumber === step.stepNumber);
334
+ const durationMs = result.startTime && result.endTime
335
+ ? new Date(result.endTime).getTime() - new Date(result.startTime).getTime()
336
+ : 0;
337
+ // D5: Use instrumented request count from adapter when available.
338
+ // The adapter parses the actual CLI output for request markers
339
+ // (e.g. Copilot's "Requests N Premium" stderr summary).
340
+ // Fall back to 1 only when the adapter could not determine the count.
341
+ const instrumentedRequests = result.sessionResult?.premiumRequestsConsumed;
342
+ const actualRequests = typeof instrumentedRequests === 'number' ? instrumentedRequests : 1;
343
+ context.costEstimator.recordActual(step.stepNumber, stepEstimate?.estimatedPremiumRequests ?? 1, actualRequests, 0);
344
+ context.stepCostRecords.push({
345
+ stepNumber: step.stepNumber,
346
+ agentName: agent.name,
347
+ estimatedPremiumRequests: stepEstimate?.estimatedPremiumRequests ?? 1,
348
+ actualPremiumRequests: actualRequests,
349
+ retryCount: 0,
350
+ promptTokens: stepEstimate?.estimatedPromptTokens ?? 0,
351
+ fleetMode: !!options?.useInnerFleet,
352
+ durationMs,
353
+ });
354
+ }
355
+ // Notify progress: step completed
356
+ options?.onProgress?.(context, `step-done:${step.stepNumber}`);
357
+ // The per-branch merge confirmation ("✅ Merged swarm/...") is
358
+ // already emitted by BranchMerger. No need to announce the step's
359
+ // completion a third time after the verification spinner.
360
+ }
361
+ catch (error) {
362
+ const err = error;
363
+ result.status = 'failed';
364
+ result.error = err.message;
365
+ result.endTime = new Date().toISOString();
366
+ // Signal completion even for failed steps so dependent replan steps
367
+ // don't hang forever in waitForDependencies. The replan step can
368
+ // check context.results to see the step failed and adapt.
369
+ context.contextBroker.addStepContext({
370
+ stepNumber: step.stepNumber,
371
+ agentName: agent.name,
372
+ timestamp: new Date().toISOString(),
373
+ data: {
374
+ filesChanged: [],
375
+ outputsSummary: 'step failed verification',
376
+ branchName: result.branchName || '',
377
+ commitShas: [],
378
+ verificationPassed: false,
379
+ transcript: path.join(context.runDir, 'steps', `step-${step.stepNumber}`, 'share.md')
380
+ }
381
+ });
382
+ // Notify progress: step failed
383
+ options?.onProgress?.(context, `step-failed:${step.stepNumber}`);
384
+ // Move the step out of the live block with a failure marker. finishStep
385
+ // is idempotent on an already-finished step (it returns silently when the
386
+ // id is gone), so calling it here covers both fail-before-verify and
387
+ // verify-already-finished paths.
388
+ (0, live_status_1.getLiveStatus)().finishStep(`step-${step.stepNumber}`, 'failed', err.message);
389
+ throw error;
390
+ }
391
+ }