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,638 @@
1
+ "use strict";
2
+ /**
3
+ * Swarm/execution-related CLI command handlers extracted from cli-handlers.ts.
4
+ * Each handler validates its arguments, performs the work, and returns an
5
+ * exit code (0 = success, 1 = failure). No handler calls process.exit.
6
+ */
7
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
+ if (k2 === undefined) k2 = k;
9
+ var desc = Object.getOwnPropertyDescriptor(m, k);
10
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
11
+ desc = { enumerable: true, get: function() { return m[k]; } };
12
+ }
13
+ Object.defineProperty(o, k2, desc);
14
+ }) : (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ o[k2] = m[k];
17
+ }));
18
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
19
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
20
+ }) : function(o, v) {
21
+ o["default"] = v;
22
+ });
23
+ var __importStar = (this && this.__importStar) || (function () {
24
+ var ownKeys = function(o) {
25
+ ownKeys = Object.getOwnPropertyNames || function (o) {
26
+ var ar = [];
27
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
28
+ return ar;
29
+ };
30
+ return ownKeys(o);
31
+ };
32
+ return function (mod) {
33
+ if (mod && mod.__esModule) return mod;
34
+ var result = {};
35
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
36
+ __setModuleDefault(result, mod);
37
+ return result;
38
+ };
39
+ })();
40
+ var __importDefault = (this && this.__importDefault) || function (mod) {
41
+ return (mod && mod.__esModule) ? mod : { "default": mod };
42
+ };
43
+ Object.defineProperty(exports, "__esModule", { value: true });
44
+ exports.validateAdapterSecrets = validateAdapterSecrets;
45
+ exports.executeSwarm = executeSwarm;
46
+ exports.writeCIOutputs = writeCIOutputs;
47
+ exports.handleBootstrapCommand = handleBootstrapCommand;
48
+ exports.handleSwarmCommand = handleSwarmCommand;
49
+ exports.handleQuickCommand = handleQuickCommand;
50
+ exports.handleRunCommand = handleRunCommand;
51
+ const fs = __importStar(require("fs"));
52
+ const path = __importStar(require("path"));
53
+ const config_loader_1 = require("../config-loader");
54
+ const plan_generator_1 = require("../plan-generator");
55
+ const plan_files_1 = require("../plan-files");
56
+ const quick_fix_mode_1 = __importDefault(require("../quick-fix-mode"));
57
+ const swarm_orchestrator_1 = require("../swarm-orchestrator");
58
+ const adapters_1 = require("../adapters");
59
+ const cost_prompt_1 = require("./cost-prompt");
60
+ const flags_1 = require("./flags");
61
+ const usage_1 = require("./usage");
62
+ const spinner_1 = require("../spinner");
63
+ const logger_1 = require("../logger");
64
+ const logger = (0, logger_1.getLogger)('cli:swarm');
65
+ // ---------------------------------------------------------------------------
66
+ // Internal constants
67
+ // ---------------------------------------------------------------------------
68
+ // Checked before execution so users get clear guidance instead of cryptic
69
+ // auth failures deep in the agent subprocess.
70
+ // Copilot authenticates via `gh auth login` (filesystem credentials),
71
+ // so it has no hard env var requirement. GITHUB_TOKEN is only used
72
+ // in CI where Actions provides it automatically.
73
+ // Claude Code supports both API key auth and subscription auth (via `claude login`).
74
+ // Codex supports both OPENAI_API_KEY env var and ~/.codex/auth.json (via `codex login`).
75
+ const ADAPTER_REQUIRED_KEYS = {
76
+ codex: ['OPENAI_API_KEY'],
77
+ };
78
+ // ---------------------------------------------------------------------------
79
+ // Internal helpers
80
+ // ---------------------------------------------------------------------------
81
+ function validateAdapterSecrets(tool) {
82
+ const required = ADAPTER_REQUIRED_KEYS[tool];
83
+ if (!required)
84
+ return;
85
+ // Codex can auth via ~/.codex/auth.json (written by `codex login`) instead of
86
+ // OPENAI_API_KEY. If the auth file exists, treat the key requirement as satisfied.
87
+ const codexAuthFile = path.join(process.env.HOME || '/tmp', '.codex', 'auth.json');
88
+ const codexLoggedIn = tool === 'codex' && fs.existsSync(codexAuthFile);
89
+ const missing = required.filter(k => !process.env[k] && !codexLoggedIn);
90
+ if (missing.length === 0)
91
+ return;
92
+ const lines = [
93
+ `Missing required secrets for --tool ${tool}: ${missing.join(', ')}`,
94
+ '',
95
+ 'Set them in your environment or GitHub Secrets:',
96
+ ...missing.map(k => ` ${k}=<your-key>`),
97
+ '',
98
+ 'In a GitHub Actions workflow, pass them via the env: block:',
99
+ ...missing.map(k => ` ${k}: \${{ secrets.${k} }}`),
100
+ ];
101
+ throw new Error(lines.join('\n'));
102
+ }
103
+ async function executeSwarm(planFilename, options) {
104
+ const selectedTool = options?.cliAgent || 'copilot';
105
+ validateAdapterSecrets(selectedTool);
106
+ let plan = (0, plan_files_1.loadPlanFile)(planFilename);
107
+ // In pretty mode (demo commands) the caller has already printed its own
108
+ // banner with the goal + step count — don't duplicate.
109
+ if (!(0, logger_1.isPrettyMode)()) {
110
+ logger.info('šŸ Swarm Orchestrator - Parallel Execution\n');
111
+ logger.info(`Goal: ${plan.goal}`);
112
+ logger.info(`Total Steps: ${plan.steps.length}\n`);
113
+ }
114
+ const configLoader = new config_loader_1.ConfigLoader();
115
+ const agents = configLoader.loadAllAgents();
116
+ const agentMap = configLoader.buildAgentMap();
117
+ // PM agent review (optional, activated with --pm)
118
+ if (options?.pm) {
119
+ const { PMAgent } = await Promise.resolve().then(() => __importStar(require('../pm-agent')));
120
+ const pmAgent = new PMAgent(agents);
121
+ logger.info('šŸ“‹ PM Agent: Reviewing plan...');
122
+ const pmResult = pmAgent.reviewPlan(plan);
123
+ if (pmResult.reviewNotes.length > 0) {
124
+ logger.info(' Review notes:');
125
+ pmResult.reviewNotes.forEach((note) => logger.info(` - ${note}`));
126
+ }
127
+ if (pmResult.changesApplied.length > 0) {
128
+ logger.info(' Changes applied:');
129
+ pmResult.changesApplied.forEach((change) => logger.info(` - ${change}`));
130
+ }
131
+ if (pmResult.reviewNotes.length === 0 && pmResult.changesApplied.length === 0) {
132
+ logger.info(' Plan approved with no issues.');
133
+ }
134
+ plan = pmResult.revisedPlan;
135
+ logger.info('');
136
+ }
137
+ // Cost estimation (always runs pre-execution)
138
+ const { CostEstimator } = await Promise.resolve().then(() => __importStar(require('../cost-estimator')));
139
+ const costEstimator = new CostEstimator();
140
+ const modelName = options?.model || (0, adapters_1.defaultModelForAdapter)(options?.cliAgent);
141
+ const costEstimate = costEstimator.estimate(plan, {
142
+ modelName,
143
+ fleetMode: !!options?.useInnerFleet,
144
+ qualityGatesEnabled: !options?.noQualityGates,
145
+ });
146
+ const retryPct = Math.round((costEstimate.perStep[0]?.retryProbability ?? 0.15) * 100);
147
+ logger.info(`\nšŸ’° Cost Estimate: ${costEstimate.lowEstimate}-${costEstimate.totalPremiumRequests} premium requests`);
148
+ logger.info(` ${plan.steps.length} steps | ${modelName} (${costEstimate.modelMultiplier}x) | ${retryPct}% retry buffer`);
149
+ if (costEstimate.remediationBuffer > 0) {
150
+ logger.info(` includes ${costEstimate.remediationBuffer} remediation buffer (quality gates enabled)`);
151
+ }
152
+ if (options?.useInnerFleet) {
153
+ logger.info(` /fleet mode: subagent multiplier applied`);
154
+ }
155
+ logger.info('');
156
+ if (options?.costEstimateOnly) {
157
+ logger.info(`Cost Estimate for: ${plan.goal}`);
158
+ logger.info(`Steps: ${plan.steps.length} | Model: ${modelName} (${costEstimate.modelMultiplier}x multiplier)`);
159
+ logger.info(`Estimated premium requests: ${costEstimate.lowEstimate}-${costEstimate.highEstimate}`);
160
+ logger.info(`Retry buffer (${retryPct}% historical failure rate): +${costEstimate.retryBuffer}`);
161
+ logger.info(`Total estimate: ${costEstimate.lowEstimate}-${costEstimate.totalPremiumRequests} premium requests`);
162
+ if (costEstimate.remainingAllowance !== undefined) {
163
+ logger.info(`At $0.04/overage: $${costEstimate.overageCostUSD.toFixed(2)} if over allowance`);
164
+ }
165
+ logger.info('');
166
+ return 0;
167
+ }
168
+ if (options?.maxPremiumRequests !== undefined && costEstimate.totalPremiumRequests > options.maxPremiumRequests) {
169
+ logger.error(`Aborting: estimated ${costEstimate.totalPremiumRequests} premium requests exceeds budget of ${options.maxPremiumRequests}`);
170
+ return 1;
171
+ }
172
+ // Pre-flight auth check: verify credentials before asking the user to confirm cost.
173
+ // This prevents the confusing UX of confirming a spend and then immediately failing on auth.
174
+ if (selectedTool === 'copilot') {
175
+ try {
176
+ const { execSync } = await Promise.resolve().then(() => __importStar(require('child_process')));
177
+ execSync('gh auth status', { stdio: 'pipe', timeout: 10_000 });
178
+ }
179
+ catch {
180
+ logger.error('āŒ GitHub authentication check failed.');
181
+ logger.error(' Run `gh auth login` or `copilot /login` to re-authenticate.');
182
+ logger.error(' If using a token, ensure GH_TOKEN / GITHUB_TOKEN is set and valid.');
183
+ return 1;
184
+ }
185
+ }
186
+ // Gate: require explicit user confirmation before spending tokens
187
+ const confirmed = await (0, cost_prompt_1.confirmCostPrompt)(costEstimate.lowEstimate, costEstimate.totalPremiumRequests, modelName, !!options?.yes);
188
+ if (!confirmed) {
189
+ logger.info('Cancelled.');
190
+ return 0;
191
+ }
192
+ // Resolve the target repo directory: plan metadata > CLI flag > first step repo > plan file location > cwd
193
+ // When the plan file sits under <project>/plans/, infer <project> as the target.
194
+ let inferredFromPlan;
195
+ if (path.isAbsolute(planFilename)) {
196
+ const planDir = path.dirname(planFilename);
197
+ if (path.basename(planDir) === 'plans') {
198
+ inferredFromPlan = path.dirname(planDir);
199
+ }
200
+ }
201
+ const targetDir = plan.metadata?.targetDir
202
+ || options?.targetDir
203
+ || plan.steps.find(s => s.repo)?.repo
204
+ || inferredFromPlan
205
+ || undefined;
206
+ if (targetDir) {
207
+ logger.info(`šŸ“‚ Target directory: ${targetDir}`);
208
+ }
209
+ // targetMode: structural discriminator for whether this run is against an
210
+ // external repo (bootstrap / swarm against a target dir) vs the
211
+ // orchestrator's own cwd (self-improvement). Drives quality-gate scoping:
212
+ // orchestrator-internal gates (duplicateBlocks, accessibility, etc.) skip
213
+ // in target mode; universal gates (hardcodedConfig, testFileProtection)
214
+ // always fire. See SELF_IMPROVEMENT_GATE_KEYS and #27 Phase-4a smoke4.
215
+ const targetMode = Boolean(targetDir);
216
+ const orchestrator = new swarm_orchestrator_1.SwarmOrchestrator(targetDir, targetMode);
217
+ const runId = `swarm-${new Date().toISOString().replace(/[:.]/g, '-')}`;
218
+ const baseDir = targetDir || process.cwd();
219
+ const runDir = path.join(baseDir, 'runs', runId);
220
+ if ((0, logger_1.isPrettyMode)()) {
221
+ // Demo: only the run dir is useful, and dim so it doesn't compete.
222
+ logger.info(` Run: ${runDir}\n`);
223
+ }
224
+ else {
225
+ logger.info(`Run ID: ${runId}`);
226
+ logger.info(`Run Directory: ${runDir}\n`);
227
+ }
228
+ const swarmOptions = {};
229
+ if (options?.model)
230
+ swarmOptions.model = options.model;
231
+ if (options?.confirmDeploy)
232
+ swarmOptions.confirmDeploy = true;
233
+ if (options?.noQualityGates)
234
+ swarmOptions.qualityGates = false;
235
+ if (options?.strictIsolation)
236
+ swarmOptions.strictIsolation = true;
237
+ if (options?.lean)
238
+ swarmOptions.lean = true;
239
+ if (options?.useInnerFleet)
240
+ swarmOptions.useInnerFleet = true;
241
+ if (options?.prMode)
242
+ swarmOptions.prMode = options.prMode;
243
+ if (options?.hooksEnabled !== undefined)
244
+ swarmOptions.hooksEnabled = options.hooksEnabled;
245
+ if (options?.owaspReport)
246
+ swarmOptions.owaspReport = true;
247
+ if (options?.cliAgent)
248
+ swarmOptions.cliAgent = options.cliAgent;
249
+ const context = await orchestrator.executeSwarm(plan, agentMap, runDir, swarmOptions);
250
+ const completed = context.results.filter(r => r.status === 'completed').length;
251
+ const failed = context.results.filter(r => r.status === 'failed').length;
252
+ const totalSteps = context.results.length;
253
+ const actualPremiumRequests = context.stepCostRecords?.reduce((sum, record) => sum + record.actualPremiumRequests, 0) ?? 0;
254
+ const estimateDeltaRatio = costEstimate.totalPremiumRequests > 0
255
+ ? (actualPremiumRequests - costEstimate.totalPremiumRequests) / costEstimate.totalPremiumRequests
256
+ : 0;
257
+ const gateResults = context.finalGateResults || [];
258
+ const gatesPassed = gateResults.filter(g => g.status === 'pass').length;
259
+ const gatesFailed = gateResults.filter(g => g.status === 'fail').length;
260
+ const remediationSteps = Math.max(0, totalSteps - plan.steps.length);
261
+ logger.info('\n' + '═'.repeat(60));
262
+ logger.info(' SWARM EXECUTION COMPLETE');
263
+ logger.info('═'.repeat(60));
264
+ logger.info(`\n āœ… Completed: ${completed}/${totalSteps}`);
265
+ if (failed > 0) {
266
+ logger.info(` āŒ Failed: ${failed}/${totalSteps}`);
267
+ logger.info('\n Failed steps:');
268
+ context.results
269
+ .filter(r => r.status === 'failed')
270
+ .forEach(r => {
271
+ logger.info(` - Step ${r.stepNumber} (${r.agentName}): ${r.error}`);
272
+ });
273
+ }
274
+ const starts = context.results.filter(r => r.startTime).map(r => new Date(r.startTime).getTime());
275
+ const ends = context.results.filter(r => r.endTime).map(r => new Date(r.endTime).getTime());
276
+ if (starts.length > 0 && ends.length > 0) {
277
+ const totalSec = Math.round((Math.max(...ends) - Math.min(...starts)) / 1000);
278
+ const min = Math.floor(totalSec / 60);
279
+ const sec = totalSec % 60;
280
+ logger.info(` ā± Total time: ${min > 0 ? `${min}m ${sec}s` : `${sec}s`}`);
281
+ }
282
+ logger.info(`\n šŸ’° Actual cost: ${actualPremiumRequests} premium requests`);
283
+ logger.info(` estimate: ${costEstimate.lowEstimate}-${costEstimate.totalPremiumRequests} | ${modelName} (${costEstimate.modelMultiplier}x) | ${totalSteps} steps${remediationSteps > 0 ? ` (${plan.steps.length} planned + ${remediationSteps} remediation)` : ''} | ${retryPct}% retry buffer`);
284
+ if (estimateDeltaRatio > 0.2) {
285
+ logger.info(` ⚠ exceeded estimate by ${Math.round(estimateDeltaRatio * 100)}%`);
286
+ }
287
+ logger.info(`\n šŸ“ Artifacts: ${runDir}`);
288
+ logger.info(` šŸ“Š Reports: ${runDir}/verification/`);
289
+ if (gateResults.length > 0) {
290
+ logger.info(` 🧪 Quality gates: ${gatesPassed} passed, ${gatesFailed} failed`);
291
+ }
292
+ // Show PR URLs if --pr mode was active
293
+ if (context.prUrls && context.prUrls.size > 0) {
294
+ logger.info('\n Pull Requests:');
295
+ for (const [stepNum, url] of context.prUrls) {
296
+ logger.info(` Step ${stepNum}: ${url}`);
297
+ }
298
+ }
299
+ logger.info('═'.repeat(60));
300
+ if (failed > 0) {
301
+ logger.info(`\nInspect failed run: swarm report ${runId}`);
302
+ }
303
+ else if (completed === totalSteps) {
304
+ logger.info('\nšŸŽ‰ All steps completed successfully!');
305
+ logger.info(' Review the git log to see the natural commit history:');
306
+ logger.info(' git log --oneline -20\n');
307
+ }
308
+ // CI output: write structured result when running inside GitHub Actions
309
+ const allPassed = context.results.every(r => r.verificationResult?.passed) && gatesFailed === 0;
310
+ if (process.env.GITHUB_ACTIONS) {
311
+ writeCIOutputs(context, plan, allPassed);
312
+ }
313
+ return allPassed ? 0 : 1;
314
+ }
315
+ /**
316
+ * Write CI-specific output files when running inside GitHub Actions.
317
+ * These feed into action.yml outputs via GITHUB_OUTPUT.
318
+ */
319
+ function writeCIOutputs(context, plan, allPassed) {
320
+ const starts = context.results.filter(r => r.startTime).map(r => new Date(r.startTime).getTime());
321
+ const ends = context.results.filter(r => r.endTime).map(r => new Date(r.endTime).getTime());
322
+ const totalDurationMs = (starts.length > 0 && ends.length > 0)
323
+ ? Math.max(...ends) - Math.min(...starts)
324
+ : 0;
325
+ const resultJson = {
326
+ allPassed,
327
+ totalSteps: context.results.length,
328
+ completed: context.results.filter(r => r.status === 'completed').length,
329
+ failed: context.results.filter(r => r.status === 'failed').length,
330
+ totalDurationMs,
331
+ steps: context.results.map(r => ({
332
+ stepNumber: r.stepNumber,
333
+ agentName: r.agentName,
334
+ status: r.status,
335
+ passed: r.verificationResult?.passed ?? false,
336
+ retryCount: r.retryCount ?? 0,
337
+ })),
338
+ };
339
+ fs.writeFileSync('/tmp/swarm-result.json', JSON.stringify(resultJson), 'utf8');
340
+ fs.writeFileSync('/tmp/swarm-plan.json', JSON.stringify(plan, null, 2), 'utf8');
341
+ // Write first PR URL if present
342
+ if (context.prUrls && context.prUrls.size > 0) {
343
+ const firstUrl = context.prUrls.values().next().value;
344
+ if (firstUrl) {
345
+ fs.writeFileSync('/tmp/swarm-pr-url.txt', firstUrl, 'utf8');
346
+ }
347
+ }
348
+ }
349
+ // ---------------------------------------------------------------------------
350
+ // Exported command handlers
351
+ // ---------------------------------------------------------------------------
352
+ async function handleBootstrapCommand(args) {
353
+ if (args.length < 3) {
354
+ logger.error('Usage: swarm bootstrap <repo-path> [<repo-path2> ...] "Goal description"');
355
+ return 1;
356
+ }
357
+ // Extract positional args, skipping flags and their values
358
+ const flagsWithValues = new Set([
359
+ '--tool', '--model', '--resume', '--pr', '--target', '--dir',
360
+ '--quality-gates-config', '--quality-gates-out', '--max-premium-requests',
361
+ ]);
362
+ const positional = [];
363
+ const raw = args.slice(1);
364
+ for (let i = 0; i < raw.length; i++) {
365
+ if (flagsWithValues.has(raw[i])) {
366
+ i++; // skip the flag's value
367
+ }
368
+ else if (!raw[i].startsWith('--')) {
369
+ positional.push(raw[i]);
370
+ }
371
+ }
372
+ if (positional.length < 2) {
373
+ logger.error('Usage: swarm bootstrap <repo-path> [<repo-path2> ...] "Goal description"');
374
+ return 1;
375
+ }
376
+ const repoPaths = positional.slice(0, -1);
377
+ const goal = positional[positional.length - 1];
378
+ for (const repoPath of repoPaths) {
379
+ if (!fs.existsSync(repoPath)) {
380
+ logger.error(`Repository path does not exist: ${repoPath}`);
381
+ return 1;
382
+ }
383
+ }
384
+ logger.info('╔══════════════════════════════════════════════════════════════════════╗');
385
+ logger.info('ā•‘ BOOTSTRAP MODE - Multi-Repo Analysis & Planning ā•‘');
386
+ logger.info('ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•\n');
387
+ const BootstrapOrchestrator = require('../bootstrap-orchestrator').default;
388
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
389
+ const goalSlug = goal.toLowerCase().replace(/[^a-z0-9]+/g, '-').substring(0, 30);
390
+ const runId = `bootstrap-${timestamp}-${goalSlug}`;
391
+ const runDir = path.join(process.cwd(), 'runs', runId);
392
+ try {
393
+ const orchestrator = new BootstrapOrchestrator();
394
+ const bsSpinner = new spinner_1.Spinner('Analyzing repos...', { style: 'dots', prefix: ' ' });
395
+ bsSpinner.start();
396
+ let bootstrapResult;
397
+ try {
398
+ bootstrapResult = await orchestrator.bootstrap(repoPaths, goal, runDir);
399
+ }
400
+ finally {
401
+ bsSpinner.stop();
402
+ }
403
+ const { evidencePath, plan } = bootstrapResult;
404
+ const planPath = (0, plan_files_1.savePlanFile)(plan, runId);
405
+ logger.info('šŸ“‹ Bootstrap Results:');
406
+ logger.info(` Evidence: ${evidencePath}`);
407
+ logger.info(` Plan: ${planPath}`);
408
+ logger.info(` Run ID: ${runId}`);
409
+ logger.info('');
410
+ logger.info('Next steps:');
411
+ logger.info(` 1. Review the evidence: cat ${evidencePath}`);
412
+ logger.info(` 2. Execute the plan: swarm swarm ${planPath}`);
413
+ return 0;
414
+ }
415
+ catch (error) {
416
+ logger.error('Bootstrap failed:', error instanceof Error ? error.message : String(error));
417
+ return 1;
418
+ }
419
+ }
420
+ async function handleSwarmCommand(args) {
421
+ if (args.includes('--help') || args.includes('-h')) {
422
+ logger.info(`
423
+ Usage: swarm swarm <planfile> [flags]
424
+
425
+ Execute a plan in parallel swarm mode. Runs steps concurrently based on
426
+ their dependency graph, verifying each step with evidence-based checks.
427
+
428
+ Arguments:
429
+ <planfile> Path to a plan JSON file (from \`swarm plan\` or \`swarm run\`)
430
+
431
+ Flags:
432
+ --model <name> Model to use (e.g., claude-opus-4.5, o3)
433
+ --pm Run PM agent plan review before execution
434
+ --strict-isolation Force per-task branch isolation
435
+ --lean Enable Delta Context Engine (reuse KB patterns)
436
+ --useInnerFleet Prefix all prompts with /fleet
437
+ --cost-estimate-only Print cost estimate and exit without executing
438
+ --max-premium-requests <n> Abort if estimated cost exceeds budget
439
+ --pr <auto|review> Create PRs instead of direct merge
440
+ --target <dir> Run in specified directory instead of cwd
441
+ --resume <id> Resume a paused or failed session
442
+ --hooks / --no-hooks Enable/disable per-step hook injection
443
+ --owasp-report Generate OWASP ASI compliance report
444
+ --tool <name> Agent tool: copilot, claude-code, claude-code-teams
445
+
446
+ Examples:
447
+ swarm swarm plan.json
448
+ swarm swarm plan.json --model claude-opus-4.5 --pm
449
+ swarm swarm plan.json --cost-estimate-only
450
+ swarm swarm plan.json --pr auto
451
+ `);
452
+ return 0;
453
+ }
454
+ if (args.length < 2 || !args[1]) {
455
+ logger.error('Error: Plan filename required\n');
456
+ (0, usage_1.showUsage)();
457
+ return 1;
458
+ }
459
+ const planFilename = args[1];
460
+ try {
461
+ const options = (0, flags_1.parseSwarmFlags)(args);
462
+ return await executeSwarm(planFilename, options);
463
+ }
464
+ catch (error) {
465
+ logger.error('Error executing swarm:', error instanceof Error ? error.message : error);
466
+ return 1;
467
+ }
468
+ }
469
+ async function handleQuickCommand(args) {
470
+ if (args.length < 2) {
471
+ logger.error('Error: Quick-fix mode requires a task');
472
+ logger.info('Usage: swarm quick "task description"');
473
+ logger.info('Example: swarm quick "fix typo in README"');
474
+ return 1;
475
+ }
476
+ const task = args[1];
477
+ const flags = {
478
+ model: args.includes('--model') ? args[args.indexOf('--model') + 1] : undefined,
479
+ agent: args.includes('--agent') ? args[args.indexOf('--agent') + 1] : undefined,
480
+ tool: args.includes('--tool') ? args[args.indexOf('--tool') + 1] : undefined,
481
+ skipVerify: args.includes('--skip-verify'),
482
+ yes: args.includes('--yes') || args.includes('-y')
483
+ };
484
+ const quickFix = new quick_fix_mode_1.default();
485
+ logger.info('⚔ Quick-Fix Mode\n');
486
+ const quickModel = flags.model || (0, adapters_1.defaultModelForAdapter)(flags.tool);
487
+ const { CostEstimator } = require('../cost-estimator');
488
+ const quickCostEstimator = new CostEstimator();
489
+ const quickEstimate = quickCostEstimator.estimate({
490
+ goal: task,
491
+ createdAt: new Date().toISOString(),
492
+ steps: [{
493
+ stepNumber: 1,
494
+ agentName: flags.agent || 'backend_master',
495
+ task,
496
+ dependencies: [],
497
+ expectedOutputs: []
498
+ }]
499
+ }, { modelName: quickModel });
500
+ logger.info(`šŸ’° Cost Estimate: ${quickEstimate.lowEstimate}-${quickEstimate.totalPremiumRequests} premium requests`);
501
+ logger.info(` 1 step | ${quickModel} (${quickEstimate.modelMultiplier}x)\n`);
502
+ // Gate: require explicit user confirmation before spending tokens
503
+ const quickConfirmed = await (0, cost_prompt_1.confirmCostPrompt)(quickEstimate.lowEstimate, quickEstimate.totalPremiumRequests, quickModel, flags.yes);
504
+ if (!quickConfirmed) {
505
+ logger.info('Cancelled.');
506
+ return 0;
507
+ }
508
+ const quickOpts = {
509
+ skipVerification: flags.skipVerify
510
+ };
511
+ if (flags.model)
512
+ quickOpts.model = flags.model;
513
+ if (flags.agent)
514
+ quickOpts.agent = flags.agent;
515
+ const result = await quickFix.execute(task, quickOpts);
516
+ if (!result.wasQuickFixEligible) {
517
+ logger.info(result.output);
518
+ return 1;
519
+ }
520
+ if (result.success) {
521
+ logger.info(`\nāœ… Quick-fix completed in ${(result.duration / 1000).toFixed(1)}s`);
522
+ logger.info(` Agent: ${result.agentUsed}`);
523
+ if (result.verificationPassed !== undefined) {
524
+ logger.info(` Verification: ${result.verificationPassed ? 'āœ“ Passed' : 'āœ— Failed'}`);
525
+ }
526
+ return 0;
527
+ }
528
+ logger.error(`\nāŒ Quick-fix failed (${(result.duration / 1000).toFixed(1)}s)`);
529
+ logger.error(` Agent: ${result.agentUsed}`);
530
+ if (result.reason) {
531
+ logger.error(` Reason: ${result.reason}`);
532
+ }
533
+ return 1;
534
+ }
535
+ async function handleRunCommand(args) {
536
+ // Flags that consume the next token as a value (must be skipped during goal extraction)
537
+ const valuedFlags = new Set([
538
+ '--model', '--resume', '--quality-gates-config', '--quality-gates-out',
539
+ '--pr', '--target', '--dir', '--tool', '--max-premium-requests',
540
+ '--sarif', '--goal', '--base-commit', '--agent-guidance',
541
+ ]);
542
+ // Extract positional tokens: skip the command name (args[0]) and any flag + value pairs
543
+ const positional = [];
544
+ for (let i = 1; i < args.length; i++) {
545
+ if (args[i].startsWith('--') || args[i] === '-y') {
546
+ if (valuedFlags.has(args[i]) && i + 1 < args.length)
547
+ i++; // skip the value
548
+ continue;
549
+ }
550
+ positional.push(args[i]);
551
+ }
552
+ // If --goal is explicit, extract its value directly
553
+ const goalIndex = args.indexOf('--goal');
554
+ let goal = '';
555
+ if (goalIndex !== -1) {
556
+ const afterGoal = args.slice(goalIndex + 1);
557
+ const tokens = [];
558
+ for (const tok of afterGoal) {
559
+ if (tok.startsWith('--'))
560
+ break;
561
+ tokens.push(tok);
562
+ }
563
+ goal = tokens.join(' ');
564
+ }
565
+ // Detect plan file: if the first positional arg resolves to an existing file, execute it directly
566
+ const firstPositional = positional[0];
567
+ if (!goal && firstPositional) {
568
+ let isPlanFile = false;
569
+ const resolved = path.resolve(process.cwd(), firstPositional);
570
+ // Check as a direct path (absolute or relative from cwd)
571
+ if (fs.existsSync(resolved)) {
572
+ try {
573
+ JSON.parse(fs.readFileSync(resolved, 'utf8'));
574
+ isPlanFile = true;
575
+ }
576
+ catch {
577
+ // Exists but not valid JSON; not a plan file
578
+ }
579
+ }
580
+ // Check under plans/ directory for bare filenames
581
+ if (!isPlanFile) {
582
+ try {
583
+ (0, plan_files_1.loadPlanFile)(firstPositional);
584
+ isPlanFile = true;
585
+ }
586
+ catch {
587
+ // Not found in plans/ either; not a plan file
588
+ }
589
+ }
590
+ if (isPlanFile) {
591
+ logger.info('šŸ Swarm Orchestrator - Execute Plan\n');
592
+ try {
593
+ const options = (0, flags_1.parseSwarmFlags)(args);
594
+ return await executeSwarm(firstPositional, options);
595
+ }
596
+ catch (error) {
597
+ logger.error('Error:', error instanceof Error ? error.message : error);
598
+ return 1;
599
+ }
600
+ }
601
+ }
602
+ // No plan file detected; use positional tokens as goal (or --goal value)
603
+ if (!goal) {
604
+ goal = positional.join(' ');
605
+ }
606
+ if (!goal) {
607
+ logger.error('Error: goal required\nUsage: swarm run --goal "Build a REST API"\n swarm run <planfile> [flags]');
608
+ return 1;
609
+ }
610
+ logger.info('šŸ Swarm Orchestrator - Plan & Execute\n');
611
+ logger.info(`Goal: ${goal}\n`);
612
+ // --agent-guidance: text that the planner prepends to every step's task
613
+ // but does NOT feed into goal classification. Used by benchmark harnesses
614
+ // (e.g. run_swebench.py's "do not modify test files" constraint) so the
615
+ // guidance doesn't poison the classifier into picking the wrong agent.
616
+ // See issue #27 Fix 1.
617
+ const guidanceIndex = args.indexOf('--agent-guidance');
618
+ let agentGuidance = '';
619
+ if (guidanceIndex !== -1 && guidanceIndex + 1 < args.length) {
620
+ agentGuidance = args[guidanceIndex + 1];
621
+ }
622
+ const configLoader = new config_loader_1.ConfigLoader();
623
+ const agents = configLoader.loadAllAgents();
624
+ const generator = new plan_generator_1.PlanGenerator(agents);
625
+ const plan = generator.createPlan(goal, undefined, {
626
+ ...(agentGuidance ? { agentGuidance } : {}),
627
+ });
628
+ const planFilename = (0, plan_files_1.savePlanFile)(plan);
629
+ logger.info(`Plan saved: ${planFilename} (${plan.steps.length} steps)\n`);
630
+ try {
631
+ const options = (0, flags_1.parseSwarmFlags)(args);
632
+ return await executeSwarm(path.basename(planFilename), options);
633
+ }
634
+ catch (error) {
635
+ logger.error('Error:', error instanceof Error ? error.message : error);
636
+ return 1;
637
+ }
638
+ }