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,318 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PRManager = void 0;
4
+ const child_process_1 = require("child_process");
5
+ const defaults_1 = require("./defaults");
6
+ const GH_AUTH_CHECK_TIMEOUT_MS = 1_000;
7
+ /**
8
+ * Per-step PR lifecycle management.
9
+ *
10
+ * Creates PRs for verified step branches, attaches verification evidence
11
+ * and cost attribution as comments, and handles auto-merge or review-wait
12
+ * depending on the configured mode.
13
+ */
14
+ class PRManager {
15
+ workingDir;
16
+ ghAvailable = null;
17
+ constructor(workingDir) {
18
+ this.workingDir = workingDir || process.cwd();
19
+ }
20
+ /**
21
+ * Check whether the gh CLI is installed and authenticated.
22
+ * Caches the result for the lifetime of this instance.
23
+ */
24
+ isGhAvailable() {
25
+ if (this.ghAvailable !== null)
26
+ return this.ghAvailable;
27
+ try {
28
+ (0, child_process_1.execSync)('gh auth status', {
29
+ cwd: this.workingDir,
30
+ stdio: 'pipe',
31
+ timeout: GH_AUTH_CHECK_TIMEOUT_MS,
32
+ });
33
+ this.ghAvailable = true;
34
+ }
35
+ catch {
36
+ this.ghAvailable = false;
37
+ }
38
+ return this.ghAvailable;
39
+ }
40
+ /**
41
+ * Create a PR for a step branch with verification evidence attached.
42
+ */
43
+ createStepPR(branchName, baseBranch, stepNumber, agentName, taskDescription, content) {
44
+ if (!this.isGhAvailable()) {
45
+ return { success: false, error: 'gh CLI not available or not authenticated. Install gh and run "gh auth login" to use --pr mode.' };
46
+ }
47
+ const title = `[Swarm Step ${stepNumber}] ${agentName}: ${this.truncate(taskDescription, 60)}`;
48
+ const body = this.formatPRBody(stepNumber, agentName, taskDescription, content);
49
+ try {
50
+ // Push the branch to remote before creating the PR
51
+ try {
52
+ (0, child_process_1.execSync)(`git push -u origin "${branchName}"`, {
53
+ cwd: this.workingDir,
54
+ stdio: 'pipe',
55
+ timeout: defaults_1.DEFAULT_CONTEXT_LOCK_WAIT_MS
56
+ });
57
+ }
58
+ catch (pushErr) {
59
+ const pushMsg = pushErr instanceof Error ? pushErr.message : String(pushErr);
60
+ // If the branch already exists on remote, that's fine
61
+ if (!pushMsg.includes('already exists') && !pushMsg.includes('Everything up-to-date')) {
62
+ return { success: false, error: `Failed to push branch ${branchName}: ${pushMsg}` };
63
+ }
64
+ }
65
+ const output = (0, child_process_1.execSync)(`gh pr create --base "${baseBranch}" --head "${branchName}" --title "${this.escapeShell(title)}" --body "${this.escapeShell(body)}"`, {
66
+ cwd: this.workingDir,
67
+ encoding: 'utf8',
68
+ stdio: 'pipe',
69
+ timeout: defaults_1.DEFAULT_CONTEXT_LOCK_WAIT_MS
70
+ });
71
+ const url = this.extractUrl(output);
72
+ const prNumber = this.extractPRNumber(url || output);
73
+ const result = { success: true };
74
+ if (url)
75
+ result.url = url;
76
+ if (prNumber !== undefined)
77
+ result.number = prNumber;
78
+ return result;
79
+ }
80
+ catch (err) {
81
+ const msg = err instanceof Error ? err.message : String(err);
82
+ return { success: false, error: `PR creation failed for ${branchName}: ${msg}` };
83
+ }
84
+ }
85
+ /**
86
+ * Add a cost attribution comment to an existing PR.
87
+ */
88
+ addCostComment(prNumber, costRecord) {
89
+ const comment = this.formatCostComment(costRecord);
90
+ return this.addComment(prNumber, comment);
91
+ }
92
+ /**
93
+ * Add quality gate results as a comment to an existing PR.
94
+ */
95
+ addGateResultsComment(prNumber, gateResults) {
96
+ const comment = this.formatGateResultsComment(gateResults);
97
+ return this.addComment(prNumber, comment);
98
+ }
99
+ /**
100
+ * Auto-merge a PR if all checks pass, using the merge commit strategy.
101
+ */
102
+ autoMergePR(prNumber) {
103
+ try {
104
+ (0, child_process_1.execSync)(`gh pr merge ${prNumber} --merge --auto`, {
105
+ cwd: this.workingDir,
106
+ stdio: 'pipe',
107
+ timeout: defaults_1.DEFAULT_BRANCH_SWITCH_TIMEOUT_MS
108
+ });
109
+ return true;
110
+ }
111
+ catch {
112
+ // Auto-merge may not be enabled on the repo; fall back to immediate merge
113
+ try {
114
+ (0, child_process_1.execSync)(`gh pr merge ${prNumber} --merge`, {
115
+ cwd: this.workingDir,
116
+ stdio: 'pipe',
117
+ timeout: defaults_1.DEFAULT_BRANCH_SWITCH_TIMEOUT_MS
118
+ });
119
+ return true;
120
+ }
121
+ catch {
122
+ return false;
123
+ }
124
+ }
125
+ }
126
+ /**
127
+ * Poll PR status until it is approved or merged.
128
+ * Returns once the PR leaves the OPEN+unapproved state.
129
+ * Times out after maxWaitMs (default: 30 minutes).
130
+ */
131
+ async waitForApproval(prNumber, pollIntervalMs = defaults_1.DEFAULT_BRANCH_SWITCH_TIMEOUT_MS, maxWaitMs = 1_800_000) {
132
+ const deadline = Date.now() + maxWaitMs;
133
+ while (Date.now() < deadline) {
134
+ const status = this.getPRStatus(prNumber);
135
+ if (status.state === 'MERGED' || status.state === 'CLOSED') {
136
+ return status;
137
+ }
138
+ if (status.approved) {
139
+ return status;
140
+ }
141
+ await this.sleep(pollIntervalMs);
142
+ }
143
+ return { state: 'OPEN', approved: false, reviewDecision: 'TIMEOUT' };
144
+ }
145
+ /**
146
+ * Get current PR status, review decision, and mergeability.
147
+ */
148
+ getPRStatus(prNumber) {
149
+ try {
150
+ const output = (0, child_process_1.execSync)(`gh pr view ${prNumber} --json state,reviewDecision,mergeable`, {
151
+ cwd: this.workingDir,
152
+ encoding: 'utf8',
153
+ stdio: 'pipe',
154
+ timeout: defaults_1.DEFAULT_CONTEXT_LOCK_WAIT_MS
155
+ });
156
+ const data = JSON.parse(output);
157
+ return {
158
+ state: data.state || 'OPEN',
159
+ approved: data.reviewDecision === 'APPROVED',
160
+ reviewDecision: data.reviewDecision,
161
+ mergeable: data.mergeable
162
+ };
163
+ }
164
+ catch {
165
+ return { state: 'OPEN', approved: false };
166
+ }
167
+ }
168
+ // ---------------------------------------------------------------------------
169
+ // Formatting
170
+ // ---------------------------------------------------------------------------
171
+ /**
172
+ * Compose the PR body with verification evidence.
173
+ */
174
+ formatPRBody(stepNumber, agentName, taskDescription, content) {
175
+ const lines = [];
176
+ lines.push(`## Swarm Step ${stepNumber}: ${agentName}`);
177
+ lines.push('');
178
+ lines.push(`**Task:** ${taskDescription}`);
179
+ lines.push('');
180
+ if (content.verification) {
181
+ lines.push('### Verification');
182
+ lines.push('');
183
+ lines.push(this.formatVerificationEvidence(content.verification));
184
+ }
185
+ if (content.costRecord) {
186
+ lines.push('### Cost Attribution');
187
+ lines.push('');
188
+ lines.push(this.formatCostComment(content.costRecord));
189
+ }
190
+ if (content.gateResults && content.gateResults.length > 0) {
191
+ lines.push('### Quality Gates');
192
+ lines.push('');
193
+ lines.push(this.formatGateResultsComment(content.gateResults));
194
+ }
195
+ lines.push('---');
196
+ lines.push('*Generated by Swarm Orchestrator*');
197
+ return lines.join('\n');
198
+ }
199
+ /**
200
+ * Format a VerificationResult into a markdown evidence summary.
201
+ */
202
+ formatVerificationEvidence(result) {
203
+ const lines = [];
204
+ const icon = result.passed ? '✅' : '❌';
205
+ lines.push(`${icon} **Verification ${result.passed ? 'Passed' : 'Failed'}**`);
206
+ lines.push('');
207
+ if (result.checks.length > 0) {
208
+ lines.push('| Check | Type | Required | Status | Evidence |');
209
+ lines.push('|-------|------|----------|--------|----------|');
210
+ for (const check of result.checks) {
211
+ const statusIcon = check.passed ? '✓' : '✗';
212
+ const evidence = check.evidence ? this.truncate(check.evidence, 80) : (check.reason || '');
213
+ lines.push(`| ${check.description} | ${check.type} | ${check.required ? 'Yes' : 'No'} | ${statusIcon} | ${evidence} |`);
214
+ }
215
+ lines.push('');
216
+ }
217
+ if (result.unverifiedClaims.length > 0) {
218
+ lines.push(`**Unverified claims (${result.unverifiedClaims.length}):**`);
219
+ for (const claim of result.unverifiedClaims) {
220
+ lines.push(`- ${this.truncate(claim, 120)}`);
221
+ }
222
+ lines.push('');
223
+ }
224
+ if (result.gracefulFailure) {
225
+ lines.push(`> **Note:** This step was allowed to continue despite verification failure.`);
226
+ if (result.degradationReason) {
227
+ lines.push(`> Reason: ${result.degradationReason}`);
228
+ }
229
+ lines.push('');
230
+ }
231
+ return lines.join('\n');
232
+ }
233
+ /**
234
+ * Format cost attribution for a comment.
235
+ */
236
+ formatCostComment(record) {
237
+ const lines = [];
238
+ lines.push(`| Metric | Value |`);
239
+ lines.push(`|--------|-------|`);
240
+ lines.push(`| Estimated Premium Requests | ${record.estimatedPremiumRequests} |`);
241
+ lines.push(`| Actual Premium Requests | ${record.actualPremiumRequests} |`);
242
+ lines.push(`| Retry Count | ${record.retryCount} |`);
243
+ lines.push(`| Prompt Tokens | ${record.promptTokens} |`);
244
+ lines.push(`| Fleet Mode | ${record.fleetMode ? 'Yes' : 'No'} |`);
245
+ lines.push(`| Duration | ${(record.durationMs / 1000).toFixed(1)}s |`);
246
+ return lines.join('\n');
247
+ }
248
+ /**
249
+ * Format quality gate results for a PR comment.
250
+ */
251
+ formatGateResultsComment(gateResults) {
252
+ const lines = [];
253
+ const allPassed = gateResults.every(g => g.status !== 'fail');
254
+ const icon = allPassed ? '✅' : '⚠️';
255
+ lines.push(`${icon} **Quality Gates: ${allPassed ? 'All Passed' : 'Issues Found'}**`);
256
+ lines.push('');
257
+ lines.push('| Gate | Status | Issues |');
258
+ lines.push('|------|--------|--------|');
259
+ for (const gate of gateResults) {
260
+ const statusIcon = gate.status === 'pass' ? '✓' : gate.status === 'fail' ? '✗' : '⊘';
261
+ lines.push(`| ${gate.title} | ${statusIcon} ${gate.status} | ${gate.issues.length} |`);
262
+ }
263
+ // Show top issues for failed gates
264
+ const failedGates = gateResults.filter(g => g.status === 'fail');
265
+ if (failedGates.length > 0) {
266
+ lines.push('');
267
+ lines.push('**Issues:**');
268
+ for (const gate of failedGates) {
269
+ const topIssues = gate.issues.slice(0, 5);
270
+ for (const issue of topIssues) {
271
+ const loc = issue.filePath ? `${issue.filePath}${issue.line ? ':' + issue.line : ''}` : '';
272
+ lines.push(`- ${loc ? `\`${loc}\` ` : ''}${issue.message}`);
273
+ }
274
+ if (gate.issues.length > 5) {
275
+ lines.push(`- ... and ${gate.issues.length - 5} more`);
276
+ }
277
+ }
278
+ }
279
+ return lines.join('\n');
280
+ }
281
+ // ---------------------------------------------------------------------------
282
+ // Helpers
283
+ // ---------------------------------------------------------------------------
284
+ addComment(prNumber, body) {
285
+ try {
286
+ (0, child_process_1.execSync)(`gh pr comment ${prNumber} --body "${this.escapeShell(body)}"`, {
287
+ cwd: this.workingDir,
288
+ stdio: 'pipe',
289
+ timeout: defaults_1.DEFAULT_BRANCH_SWITCH_TIMEOUT_MS
290
+ });
291
+ return true;
292
+ }
293
+ catch {
294
+ return false;
295
+ }
296
+ }
297
+ extractUrl(output) {
298
+ const match = output.match(/(https:\/\/github\.com\/[^\s]+)/);
299
+ return match ? match[1] : undefined;
300
+ }
301
+ extractPRNumber(text) {
302
+ const match = text.match(/\/pull\/(\d+)/);
303
+ return match ? parseInt(match[1], 10) : undefined;
304
+ }
305
+ truncate(text, maxLen) {
306
+ if (text.length <= maxLen)
307
+ return text;
308
+ return text.substring(0, maxLen - 3) + '...';
309
+ }
310
+ escapeShell(str) {
311
+ return str.replace(/"/g, '\\"').replace(/\$/g, '\\$').replace(/`/g, '\\`');
312
+ }
313
+ sleep(ms) {
314
+ return new Promise(resolve => setTimeout(resolve, ms));
315
+ }
316
+ }
317
+ exports.PRManager = PRManager;
318
+ exports.default = PRManager;
@@ -0,0 +1,209 @@
1
+ "use strict";
2
+ /**
3
+ * Presenter: owns the user-facing CLI surface (banners, summary blocks,
4
+ * results, footers). Writes to stdout. Color and glyph aware. Quiet-respecting.
5
+ *
6
+ * The logger (`src/logger.ts`) is for diagnostics and goes to stderr (when
7
+ * routed there); the presenter is for users and goes to stdout. These are
8
+ * two different concerns. Today's code conflates them.
9
+ *
10
+ * Live progress (spinner, in-place step status) lives in `cli/live-status.ts`.
11
+ * The presenter handles static surfaces; LiveStatus handles the moving parts.
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.Presenter = void 0;
15
+ exports.getPresenter = getPresenter;
16
+ exports.configurePresenter = configurePresenter;
17
+ exports._setPresenterForTest = _setPresenterForTest;
18
+ const ESC = '\x1b[';
19
+ const UTF8_GLYPHS = {
20
+ check: '✓',
21
+ cross: '✗',
22
+ bang: '!',
23
+ dot: '·',
24
+ sep: '·',
25
+ };
26
+ const ASCII_GLYPHS = {
27
+ check: 'v',
28
+ cross: 'x',
29
+ bang: '!',
30
+ dot: '.',
31
+ sep: '.',
32
+ };
33
+ function detectAscii() {
34
+ if (process.env.SWARM_ASCII === '1')
35
+ return true;
36
+ const lc = (process.env.LC_ALL || process.env.LC_CTYPE || process.env.LANG || '').toLowerCase();
37
+ if (!lc)
38
+ return false;
39
+ return !lc.includes('utf');
40
+ }
41
+ function detectColor(stream) {
42
+ if (process.env.NO_COLOR)
43
+ return false;
44
+ if (process.env.CI)
45
+ return false;
46
+ return Boolean(stream.isTTY);
47
+ }
48
+ class Presenter {
49
+ out;
50
+ theme;
51
+ quiet;
52
+ constructor(opts = {}) {
53
+ this.out = opts.out ?? process.stdout;
54
+ this.theme = {
55
+ color: opts.color ?? detectColor(this.out),
56
+ ascii: opts.ascii ?? detectAscii(),
57
+ };
58
+ this.quiet = opts.quiet ?? false;
59
+ }
60
+ /** Public for the live-status / debug paths that need to render a glyph. */
61
+ glyph(g) {
62
+ return this.theme.ascii ? ASCII_GLYPHS[g] : UTF8_GLYPHS[g];
63
+ }
64
+ dim(s) {
65
+ return this.theme.color ? `${ESC}2m${s}${ESC}22m` : s;
66
+ }
67
+ bold(s) {
68
+ return this.theme.color ? `${ESC}1m${s}${ESC}22m` : s;
69
+ }
70
+ green(s) {
71
+ return this.theme.color ? `${ESC}32m${s}${ESC}39m` : s;
72
+ }
73
+ red(s) {
74
+ return this.theme.color ? `${ESC}31m${s}${ESC}39m` : s;
75
+ }
76
+ yellow(s) {
77
+ return this.theme.color ? `${ESC}33m${s}${ESC}39m` : s;
78
+ }
79
+ cyan(s) {
80
+ return this.theme.color ? `${ESC}36m${s}${ESC}39m` : s;
81
+ }
82
+ /** Raw write of a line. The static surface emits through this. */
83
+ print(line) {
84
+ if (this.quiet)
85
+ return;
86
+ this.out.write(line + '\n');
87
+ }
88
+ blank() {
89
+ if (this.quiet)
90
+ return;
91
+ this.out.write('\n');
92
+ }
93
+ /** Goal echo at the start of a `run` invocation. */
94
+ runHeader(goal) {
95
+ if (this.quiet)
96
+ return;
97
+ this.print(`swarm run`);
98
+ this.print(`${this.dim(this.glyph('dot'))} ${goal}`);
99
+ this.blank();
100
+ }
101
+ /** Goal echo at the start of a `swarm <plan>` invocation. */
102
+ swarmHeader(goal) {
103
+ if (this.quiet)
104
+ return;
105
+ this.print(`swarm`);
106
+ this.print(`${this.dim(this.glyph('dot'))} ${goal}`);
107
+ this.blank();
108
+ }
109
+ planSummary(s) {
110
+ if (this.quiet)
111
+ return;
112
+ const stepsLabel = `${s.steps} step${s.steps === 1 ? '' : 's'}`;
113
+ const modelFrag = `${s.model} ${s.modelMultiplier}×`;
114
+ const retryFrag = `${s.retryPct}% retry buffer`;
115
+ this.print(` ${this.dim('plan')} ${stepsLabel} ${this.dim(this.glyph('sep'))} ${modelFrag} ${this.dim(this.glyph('sep'))} ${retryFrag}`);
116
+ this.print(` ${this.dim('cost')} ${s.costRange} premium request${s.costRange === '1' ? '' : 's'}`);
117
+ if (s.remediationBuffer && s.remediationBuffer > 0) {
118
+ this.print(` ${this.dim(`+${s.remediationBuffer} remediation buffer`)}`);
119
+ }
120
+ if (s.innerFleet) {
121
+ this.print(` ${this.dim('/fleet subagent multiplier applied')}`);
122
+ }
123
+ if (s.target) {
124
+ this.print(` ${this.dim('target')} ${s.target}`);
125
+ }
126
+ this.blank();
127
+ }
128
+ runStarted(r) {
129
+ if (this.quiet)
130
+ return;
131
+ this.print(` ${this.dim('run')} ${r.runDir}`);
132
+ this.blank();
133
+ }
134
+ /** Single-line status while final quality gates run. */
135
+ gateRunningStart() {
136
+ if (this.quiet)
137
+ return;
138
+ this.print(` ${this.dim('running quality gates...')}`);
139
+ }
140
+ cancelled() {
141
+ if (this.quiet)
142
+ return;
143
+ this.print(` ${this.dim('cancelled')}`);
144
+ }
145
+ finalSummary(f) {
146
+ const sep = this.dim(this.glyph('sep'));
147
+ const headlineGlyph = f.ok ? this.green(this.glyph('check')) : this.red(this.glyph('cross'));
148
+ const headlineWord = f.ok ? 'done' : `${f.failedSteps?.length ?? f.total - f.completed} failed`;
149
+ const fragments = [`${f.completed}/${f.total} step${f.total === 1 ? '' : 's'}`];
150
+ if (f.duration)
151
+ fragments.push(f.duration);
152
+ fragments.push(`${f.premiumRequests} premium request${f.premiumRequests === 1 ? '' : 's'}`);
153
+ if (typeof f.gatesPassed === 'number' || typeof f.gatesFailed === 'number') {
154
+ const passed = f.gatesPassed ?? 0;
155
+ const failed = f.gatesFailed ?? 0;
156
+ const gateNote = failed === 0
157
+ ? `${passed} gate${passed === 1 ? '' : 's'} passed`
158
+ : `${passed} gates passed ${this.dim(this.glyph('sep'))} ${failed} failed`;
159
+ fragments.push(gateNote);
160
+ }
161
+ // Headline always prints, even in quiet mode (it's the result).
162
+ this.out.write(` ${headlineGlyph} ${this.bold(headlineWord)} ${sep} ${fragments.join(` ${sep} `)}\n`);
163
+ if (this.quiet)
164
+ return;
165
+ if (f.failedSteps && f.failedSteps.length > 0) {
166
+ this.blank();
167
+ for (const fs of f.failedSteps) {
168
+ this.print(` ${this.red(this.glyph('cross'))} step-${fs.stepNumber} ${this.dim(fs.agentName)} ${sep} ${fs.reason}`);
169
+ }
170
+ }
171
+ if (f.estimateExceededPct !== undefined && f.estimateExceededPct > 20) {
172
+ this.print(` ${this.dim(`exceeded estimate by ${f.estimateExceededPct}% (estimate ${f.estimateLow ?? '?'}–${f.estimateHigh ?? '?'})`)}`);
173
+ }
174
+ if (f.remediationSteps && f.remediationSteps > 0) {
175
+ this.print(` ${this.dim(`${f.plannedSteps} planned + ${f.remediationSteps} remediation`)}`);
176
+ }
177
+ this.print(` ${this.dim('artifacts')} ${f.artifactsDir}`);
178
+ if (f.prUrls && f.prUrls.size > 0) {
179
+ for (const [stepNum, url] of f.prUrls) {
180
+ this.print(` ${this.dim(`pr step-${stepNum}`)} ${url}`);
181
+ }
182
+ }
183
+ if (!f.ok && f.inspectCommand) {
184
+ this.blank();
185
+ this.print(` ${this.dim(`inspect: ${f.inspectCommand}`)}`);
186
+ }
187
+ this.blank();
188
+ }
189
+ }
190
+ exports.Presenter = Presenter;
191
+ let singleton;
192
+ /**
193
+ * Returns the process-wide presenter singleton. Created on first access with
194
+ * default options (color/ascii detected from env, quiet=false). Tests should
195
+ * use `_setPresenterForTest` to inject a controlled instance.
196
+ */
197
+ function getPresenter() {
198
+ if (!singleton)
199
+ singleton = new Presenter();
200
+ return singleton;
201
+ }
202
+ /** Reconfigure the singleton (e.g. after parsing --quiet from argv). */
203
+ function configurePresenter(opts) {
204
+ singleton = new Presenter(opts);
205
+ }
206
+ /** Test hook. */
207
+ function _setPresenterForTest(instance) {
208
+ singleton = instance;
209
+ }