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,180 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.DemoMode = void 0;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ /**
40
+ * Pre-configured demo scenarios for showcasing swarm orchestrator
41
+ */
42
+ class DemoMode {
43
+ scenariosDir;
44
+ constructor(scenariosDir) {
45
+ this.scenariosDir = scenariosDir || path.join(process.cwd(), 'demos');
46
+ }
47
+ /**
48
+ * Get all available demo scenarios
49
+ */
50
+ getAvailableScenarios() {
51
+ return [
52
+ this.getDemoFastScenario(),
53
+ this.getApiQuickScenario()
54
+ ];
55
+ }
56
+ /**
57
+ * Demo Fast - "hello world" swarm
58
+ * Two agents, one wave, minimal work.
59
+ * Both steps are truly independent - no shared files.
60
+ */
61
+ getDemoFastScenario() {
62
+ return {
63
+ name: 'demo-fast',
64
+ description: 'Two independent utility files, one wave',
65
+ goal: 'Two independent micro-tasks: each agent adds one small TypeScript utility file.',
66
+ expectedDuration: '20-30 seconds',
67
+ steps: [
68
+ {
69
+ stepNumber: 1,
70
+ agentName: 'backend_master',
71
+ task: 'Create a tiny TypeScript utility module at src/string-utils.ts that exports a function greet(name: string): string which returns "Hello, <name>!". Keep it boring. No new deps. Add a short top-of-file comment. Commit your work.',
72
+ dependencies: [],
73
+ expectedOutputs: [
74
+ 'src/string-utils.ts with exported greet() function'
75
+ ]
76
+ },
77
+ {
78
+ stepNumber: 2,
79
+ agentName: 'frontend_expert',
80
+ task: 'Create a tiny TypeScript utility module at src/number-utils.ts that exports a function double(n: number): number which returns n * 2. Keep it boring. No new deps. Add a short top-of-file comment. Commit your work.',
81
+ dependencies: [],
82
+ expectedOutputs: [
83
+ 'src/number-utils.ts with exported double() function'
84
+ ]
85
+ }
86
+ ]
87
+ };
88
+ }
89
+ /**
90
+ * API Quick: 3-step REST API build showing wave dependencies.
91
+ * Step 1: Worker builds the endpoints.
92
+ * Step 2: Worker adds tests (depends on step 1).
93
+ * Step 3: Worker adds a Dockerfile (depends on step 1).
94
+ */
95
+ getApiQuickScenario() {
96
+ return {
97
+ name: 'api-quick',
98
+ description: 'REST API with tests and Dockerfile, dependency-chained across two waves',
99
+ goal: 'Build a minimal REST API with health and items CRUD, add tests, and containerize with Docker',
100
+ expectedDuration: '4-6 minutes',
101
+ steps: [
102
+ {
103
+ stepNumber: 1,
104
+ agentName: 'backend_master',
105
+ task: 'Create a Node.js REST API with Express. Endpoints: GET /health returning { status: "ok" }, GET /api/items returning an in-memory array, POST /api/items accepting { name } and returning the created item with a generated id. Add input validation (reject empty name). Export the app for testing. Add a start script to package.json. Commit your work.',
106
+ dependencies: [],
107
+ expectedOutputs: [
108
+ 'server.js with /health, GET /api/items, POST /api/items',
109
+ 'package.json with start script',
110
+ 'Input validation for name field'
111
+ ]
112
+ },
113
+ {
114
+ stepNumber: 2,
115
+ agentName: 'tester_elite',
116
+ task: 'Add tests for the REST API created in step 1. Use the Node.js built-in test runner (node:test and node:assert/strict). Test: GET /health returns 200 and { status: "ok" }, GET /api/items returns empty array initially, POST /api/items with valid name returns 201, POST /api/items with empty name returns 400, GET /api/items after POST includes the new item. Import the app from server.js and start/stop it in before/after hooks. Add a test script to package.json. Commit your work.',
117
+ dependencies: [1],
118
+ expectedOutputs: [
119
+ 'test/api.test.js with 5+ test cases',
120
+ 'Tests use node:test and node:assert/strict',
121
+ 'package.json test script'
122
+ ]
123
+ },
124
+ {
125
+ stepNumber: 3,
126
+ agentName: 'devops_pro',
127
+ task: 'Add a Dockerfile for the Node.js REST API. Use node:20-alpine base image, copy package.json first for layer caching, run npm ci --omit=dev, copy source files, expose port 3000, set NODE_ENV=production, and use CMD ["node", "server.js"]. Add a .dockerignore excluding node_modules, .git, and test/. Commit your work.',
128
+ dependencies: [1],
129
+ expectedOutputs: [
130
+ 'Dockerfile with multi-layer caching',
131
+ '.dockerignore'
132
+ ]
133
+ }
134
+ ]
135
+ };
136
+ }
137
+ /**
138
+ * Get scenario by name
139
+ */
140
+ getScenario(name) {
141
+ const scenarios = this.getAvailableScenarios();
142
+ return scenarios.find(s => s.name === name);
143
+ }
144
+ /**
145
+ * Save scenario to file
146
+ */
147
+ saveScenario(scenario) {
148
+ if (!fs.existsSync(this.scenariosDir)) {
149
+ fs.mkdirSync(this.scenariosDir, { recursive: true });
150
+ }
151
+ const filePath = path.join(this.scenariosDir, `${scenario.name}.json`);
152
+ fs.writeFileSync(filePath, JSON.stringify(scenario, null, 2), 'utf8');
153
+ }
154
+ /**
155
+ * Load scenario from file
156
+ */
157
+ loadScenarioFromFile(name) {
158
+ const filePath = path.join(this.scenariosDir, `${name}.json`);
159
+ if (!fs.existsSync(filePath)) {
160
+ return undefined;
161
+ }
162
+ const content = fs.readFileSync(filePath, 'utf8');
163
+ return JSON.parse(content);
164
+ }
165
+ /**
166
+ * Convert scenario to ExecutionPlan
167
+ */
168
+ scenarioToPlan(scenario) {
169
+ return {
170
+ goal: scenario.goal,
171
+ createdAt: new Date().toISOString(),
172
+ steps: scenario.steps,
173
+ metadata: {
174
+ totalSteps: scenario.steps.length,
175
+ estimatedDuration: scenario.expectedDuration
176
+ }
177
+ };
178
+ }
179
+ }
180
+ exports.DemoMode = DemoMode;
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.executeOptionalDeployment = executeOptionalDeployment;
7
+ const path_1 = __importDefault(require("path"));
8
+ const logger_1 = require("./logger");
9
+ const deployment_manager_1 = require("./deployment-manager");
10
+ const external_tool_manager_1 = require("./external-tool-manager");
11
+ const logger = (0, logger_1.getLogger)('deployment');
12
+ async function executeOptionalDeployment(workingDir, step, agent, context, options) {
13
+ logger.info(` 🚀 Deploying preview (--confirm-deploy)...`);
14
+ const toolManager = new external_tool_manager_1.ExternalToolManager({
15
+ enableExternal: options.enableExternal || true,
16
+ dryRun: options.dryRun || false,
17
+ logFile: path_1.default.join(context.runDir, 'deployment-commands.log')
18
+ });
19
+ const deploymentManager = new deployment_manager_1.DeploymentManager(toolManager, workingDir);
20
+ try {
21
+ const branchName = context.results.find(r => r.stepNumber === step.stepNumber)?.branchName || 'unknown';
22
+ // tag HEAD before deploy for rollback safety
23
+ let preDeployTag;
24
+ try {
25
+ preDeployTag = deploymentManager.tagPreDeploy(context.executionId);
26
+ logger.info(` 🏷️ Tagged: ${preDeployTag}`);
27
+ }
28
+ catch (err) {
29
+ const msg = err instanceof Error ? err.message : String(err);
30
+ logger.info(` [deploy] Could not tag pre-deploy (non-fatal): ${msg}`);
31
+ }
32
+ const deployResult = await deploymentManager.deployPreview(branchName);
33
+ if (deployResult.success && deployResult.previewUrl) {
34
+ logger.info(` ✅ Preview deployed: ${deployResult.previewUrl}`);
35
+ // health check the preview URL
36
+ const healthy = await deploymentManager.runHealthCheck(deployResult.previewUrl);
37
+ if (!healthy && preDeployTag) {
38
+ logger.warn(` ⚠️ Health check failed, rolling back...`);
39
+ try {
40
+ deploymentManager.rollbackToTag(preDeployTag);
41
+ }
42
+ catch (rollbackErr) {
43
+ const msg = rollbackErr instanceof Error ? rollbackErr.message : String(rollbackErr);
44
+ logger.warn(` ⚠️ Rollback failed: ${msg}`);
45
+ }
46
+ return;
47
+ }
48
+ else if (healthy) {
49
+ logger.info(` ✅ Health check passed`);
50
+ }
51
+ // store deployment metadata
52
+ const metadata = {
53
+ stepNumber: step.stepNumber,
54
+ agentName: agent.name,
55
+ timestamp: new Date().toISOString(),
56
+ platform: deployResult.platform,
57
+ previewUrl: deployResult.previewUrl,
58
+ branchName
59
+ };
60
+ deploymentManager.saveDeploymentMetadata(context.runDir, metadata);
61
+ // add to context deployments
62
+ if (!context.deployments) {
63
+ context.deployments = [];
64
+ }
65
+ context.deployments.push(metadata);
66
+ }
67
+ else if (deployResult.platform === 'none') {
68
+ logger.info(` ℹ️ No deployment platform detected (vercel/netlify), skipping`);
69
+ }
70
+ else {
71
+ logger.warn(` ⚠️ Deployment failed: ${deployResult.error}`);
72
+ }
73
+ }
74
+ catch (error) {
75
+ const err = error;
76
+ logger.warn(` ⚠️ Deployment error: ${err.message}`);
77
+ }
78
+ }
@@ -0,0 +1,228 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.DeploymentManager = void 0;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const child_process_1 = require("child_process");
40
+ const logger_1 = require("./logger");
41
+ const logger = (0, logger_1.getLogger)('deployment-manager');
42
+ /**
43
+ * Deployment Manager - handles preview deployments for Vercel and Netlify
44
+ */
45
+ class DeploymentManager {
46
+ toolManager;
47
+ workingDir;
48
+ constructor(toolManager, workingDir) {
49
+ this.toolManager = toolManager;
50
+ this.workingDir = workingDir || process.cwd();
51
+ }
52
+ /**
53
+ * Detect which deployment platform is configured.
54
+ * Config files take priority over CLI availability (project-specific > global).
55
+ */
56
+ async detectPlatform() {
57
+ // 1. Check config files first (project-specific, most reliable)
58
+ const hasVercelConfig = fs.existsSync(path.join(this.workingDir, 'vercel.json'));
59
+ const hasNetlifyConfig = fs.existsSync(path.join(this.workingDir, 'netlify.toml'));
60
+ if (hasVercelConfig)
61
+ return 'vercel';
62
+ if (hasNetlifyConfig)
63
+ return 'netlify';
64
+ // 2. Fall back to CLI availability only when no config files present
65
+ const tools = await this.toolManager.detectAvailableTools();
66
+ if (tools.vercel)
67
+ return 'vercel';
68
+ if (tools.netlify)
69
+ return 'netlify';
70
+ return 'none';
71
+ }
72
+ /**
73
+ * Deploy preview for a branch
74
+ */
75
+ async deployPreview(branchName) {
76
+ const platform = await this.detectPlatform();
77
+ if (platform === 'none') {
78
+ return {
79
+ success: false,
80
+ platform: 'none',
81
+ error: 'No deployment platform detected (Vercel or Netlify)'
82
+ };
83
+ }
84
+ if (platform === 'vercel') {
85
+ return this.deployVercelPreview(branchName);
86
+ }
87
+ else {
88
+ return this.deployNetlifyPreview(branchName);
89
+ }
90
+ }
91
+ /**
92
+ * Deploy Vercel preview
93
+ */
94
+ async deployVercelPreview(_branchName) {
95
+ const execution = await this.toolManager.executeCommand('vercel', ['deploy', '--yes'], {
96
+ workingDir: this.workingDir,
97
+ requireTool: 'vercel'
98
+ });
99
+ if (execution.exitCode !== 0) {
100
+ return {
101
+ success: false,
102
+ platform: 'vercel',
103
+ error: execution.error || 'Vercel deployment failed',
104
+ ...(execution.output && { output: execution.output })
105
+ };
106
+ }
107
+ // Extract preview URL from Vercel output
108
+ const previewUrl = this.extractVercelUrl(execution.output || '');
109
+ return {
110
+ success: true,
111
+ platform: 'vercel',
112
+ ...(previewUrl && { previewUrl }),
113
+ ...(execution.output && { output: execution.output })
114
+ };
115
+ }
116
+ /**
117
+ * Deploy Netlify preview
118
+ */
119
+ async deployNetlifyPreview(_branchName) {
120
+ const execution = await this.toolManager.executeCommand('netlify', ['deploy', '--build'], {
121
+ workingDir: this.workingDir,
122
+ requireTool: 'netlify'
123
+ });
124
+ if (execution.exitCode !== 0) {
125
+ return {
126
+ success: false,
127
+ platform: 'netlify',
128
+ error: execution.error || 'Netlify deployment failed',
129
+ ...(execution.output && { output: execution.output })
130
+ };
131
+ }
132
+ // Extract draft URL from Netlify output
133
+ const previewUrl = this.extractNetlifyUrl(execution.output || '');
134
+ return {
135
+ success: true,
136
+ platform: 'netlify',
137
+ ...(previewUrl && { previewUrl }),
138
+ ...(execution.output && { output: execution.output })
139
+ };
140
+ }
141
+ /**
142
+ * Extract Vercel preview URL from CLI output
143
+ */
144
+ extractVercelUrl(output) {
145
+ // Vercel outputs: "Preview: https://..."
146
+ const match = output.match(/(?:Preview|Inspect):\s+(https:\/\/[^\s]+)/i);
147
+ return match ? match[1] : undefined;
148
+ }
149
+ /**
150
+ * Extract Netlify preview URL from CLI output
151
+ */
152
+ extractNetlifyUrl(output) {
153
+ // Netlify outputs: "Draft deploy URL: https://..."
154
+ const match = output.match(/(?:Draft\s+deploy\s+URL|Website\s+Draft\s+URL):\s+(https:\/\/[^\s]+)/i);
155
+ return match ? match[1] : undefined;
156
+ }
157
+ /**
158
+ * Save deployment metadata to run artifacts
159
+ */
160
+ saveDeploymentMetadata(runDir, metadata) {
161
+ const deploymentsDir = path.join(runDir, 'deployments');
162
+ if (!fs.existsSync(deploymentsDir)) {
163
+ fs.mkdirSync(deploymentsDir, { recursive: true });
164
+ }
165
+ const metadataPath = path.join(deploymentsDir, `step-${metadata.stepNumber}-${metadata.platform}.json`);
166
+ fs.writeFileSync(metadataPath, JSON.stringify(metadata, null, 2), 'utf8');
167
+ return metadataPath;
168
+ }
169
+ /**
170
+ * Load all deployment metadata from run
171
+ */
172
+ loadDeploymentMetadata(runDir) {
173
+ const deploymentsDir = path.join(runDir, 'deployments');
174
+ if (!fs.existsSync(deploymentsDir)) {
175
+ return [];
176
+ }
177
+ const files = fs.readdirSync(deploymentsDir);
178
+ const metadata = [];
179
+ for (const file of files) {
180
+ if (file.endsWith('.json')) {
181
+ const content = fs.readFileSync(path.join(deploymentsDir, file), 'utf8');
182
+ metadata.push(JSON.parse(content));
183
+ }
184
+ }
185
+ return metadata.sort((a, b) => a.stepNumber - b.stepNumber);
186
+ }
187
+ /**
188
+ * Tag HEAD before deployment so we can roll back if the health check fails.
189
+ */
190
+ tagPreDeploy(executionId) {
191
+ const tag = `pre-deploy/${executionId}`;
192
+ (0, child_process_1.execSync)(`git tag -f ${tag}`, { cwd: this.workingDir, encoding: 'utf8' });
193
+ return tag;
194
+ }
195
+ /**
196
+ * HTTP health check against a preview URL. Retries on non-200 responses.
197
+ */
198
+ async runHealthCheck(url, retries = 3, intervalMs = 20000) {
199
+ for (let attempt = 1; attempt <= retries; attempt++) {
200
+ try {
201
+ const response = await fetch(url, { signal: AbortSignal.timeout(10000) });
202
+ if (response.ok)
203
+ return true;
204
+ logger.info(` [health-check] Attempt ${attempt}/${retries}: HTTP ${response.status}`);
205
+ }
206
+ catch (err) {
207
+ const msg = err instanceof Error ? err.message : String(err);
208
+ logger.info(` [health-check] Attempt ${attempt}/${retries}: ${msg}`);
209
+ }
210
+ if (attempt < retries) {
211
+ await new Promise(resolve => setTimeout(resolve, intervalMs));
212
+ }
213
+ }
214
+ return false;
215
+ }
216
+ /**
217
+ * Roll back HEAD by reverting the latest commit and recording the event.
218
+ */
219
+ rollbackToTag(tag) {
220
+ (0, child_process_1.execSync)('git revert --no-commit HEAD', { cwd: this.workingDir, encoding: 'utf8' });
221
+ (0, child_process_1.execSync)(`git commit -m "rollback: revert to ${tag} after failed health check"`, {
222
+ cwd: this.workingDir, encoding: 'utf8'
223
+ });
224
+ logger.info(` [rollback] Reverted HEAD, tagged from ${tag}`);
225
+ }
226
+ }
227
+ exports.DeploymentManager = DeploymentManager;
228
+ exports.default = DeploymentManager;
@@ -0,0 +1,196 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ExecutionQueue = void 0;
4
+ const events_1 = require("events");
5
+ /**
6
+ * execution queue with concurrency control and retry logic
7
+ * handles rate limits and quota exhaustion gracefully
8
+ */
9
+ class ExecutionQueue extends events_1.EventEmitter {
10
+ queue = [];
11
+ running = new Map();
12
+ maxConcurrency;
13
+ retryConfig;
14
+ stats = {
15
+ completed: 0,
16
+ failed: 0
17
+ };
18
+ constructor(maxConcurrency = 3, retryConfig) {
19
+ super();
20
+ // High step counts generate many concurrent task-completed listeners
21
+ this.setMaxListeners(50);
22
+ this.maxConcurrency = maxConcurrency;
23
+ this.retryConfig = {
24
+ maxRetries: retryConfig?.maxRetries ?? 3,
25
+ baseDelay: retryConfig?.baseDelay ?? 5000,
26
+ maxDelay: retryConfig?.maxDelay ?? 60000,
27
+ rateLimitPatterns: retryConfig?.rateLimitPatterns ?? [
28
+ /rate limit/i,
29
+ /quota.*exceed/i,
30
+ /too many requests/i,
31
+ /429/,
32
+ /throttle/i
33
+ ]
34
+ };
35
+ }
36
+ /**
37
+ * add task to queue with priority
38
+ */
39
+ async enqueue(id, task, options) {
40
+ return new Promise((resolve, reject) => {
41
+ const queuedTask = {
42
+ id,
43
+ priority: options?.priority ?? 0,
44
+ execute: task,
45
+ resolve,
46
+ reject,
47
+ retries: 0,
48
+ maxRetries: options?.maxRetries ?? this.retryConfig.maxRetries,
49
+ metadata: options?.metadata || {}
50
+ };
51
+ // insert based on priority (higher priority first)
52
+ const insertIndex = this.queue.findIndex(t => t.priority < queuedTask.priority);
53
+ if (insertIndex === -1) {
54
+ this.queue.push(queuedTask);
55
+ }
56
+ else {
57
+ this.queue.splice(insertIndex, 0, queuedTask);
58
+ }
59
+ this.emit('enqueued', { id, stats: this.getStats() });
60
+ this.processQueue();
61
+ });
62
+ }
63
+ /**
64
+ * process queued tasks up to concurrency limit
65
+ */
66
+ async processQueue() {
67
+ while (this.running.size < this.maxConcurrency && this.queue.length > 0) {
68
+ const task = this.queue.shift();
69
+ if (!task)
70
+ break;
71
+ this.running.set(task.id, task);
72
+ this.emit('started', { id: task.id, stats: this.getStats() });
73
+ this.executeTask(task);
74
+ }
75
+ }
76
+ /**
77
+ * execute single task with retry on rate limits
78
+ */
79
+ async executeTask(task) {
80
+ try {
81
+ const result = await task.execute();
82
+ this.running.delete(task.id);
83
+ this.stats.completed++;
84
+ this.emit('completed', { id: task.id, stats: this.getStats() });
85
+ task.resolve(result);
86
+ this.processQueue();
87
+ }
88
+ catch (error) {
89
+ const errorMessage = error instanceof Error ? error.message : String(error);
90
+ const isRateLimit = this.retryConfig.rateLimitPatterns.some(pattern => pattern.test(errorMessage));
91
+ if (isRateLimit && task.retries < task.maxRetries) {
92
+ // exponential backoff
93
+ task.retries++;
94
+ const delay = Math.min(this.retryConfig.baseDelay * Math.pow(2, task.retries - 1), this.retryConfig.maxDelay);
95
+ this.emit('retrying', {
96
+ id: task.id,
97
+ attempt: task.retries,
98
+ maxRetries: task.maxRetries,
99
+ delay,
100
+ reason: 'rate_limit',
101
+ stats: this.getStats()
102
+ });
103
+ this.running.delete(task.id);
104
+ // re-enqueue after delay
105
+ setTimeout(() => {
106
+ this.queue.unshift(task);
107
+ this.processQueue();
108
+ }, delay);
109
+ }
110
+ else {
111
+ // permanent failure
112
+ this.running.delete(task.id);
113
+ this.stats.failed++;
114
+ this.emit('failed', {
115
+ id: task.id,
116
+ error: errorMessage,
117
+ retries: task.retries,
118
+ stats: this.getStats()
119
+ });
120
+ task.reject(error instanceof Error ? error : new Error(errorMessage));
121
+ this.processQueue();
122
+ }
123
+ }
124
+ }
125
+ /**
126
+ * get current queue statistics
127
+ */
128
+ getStats() {
129
+ return {
130
+ running: this.running.size,
131
+ queued: this.queue.length,
132
+ completed: this.stats.completed,
133
+ failed: this.stats.failed,
134
+ maxConcurrency: this.maxConcurrency
135
+ };
136
+ }
137
+ /**
138
+ * update max concurrency dynamically
139
+ */
140
+ setMaxConcurrency(limit) {
141
+ if (limit < 1) {
142
+ throw new Error('Max concurrency must be at least 1');
143
+ }
144
+ this.maxConcurrency = limit;
145
+ this.emit('concurrency_changed', { maxConcurrency: limit });
146
+ this.processQueue();
147
+ }
148
+ /**
149
+ * pause queue processing
150
+ */
151
+ pause() {
152
+ this.maxConcurrency = 0;
153
+ this.emit('paused');
154
+ }
155
+ /**
156
+ * resume queue processing
157
+ */
158
+ resume(concurrency) {
159
+ this.maxConcurrency = concurrency ?? 3;
160
+ this.emit('resumed', { maxConcurrency: this.maxConcurrency });
161
+ this.processQueue();
162
+ }
163
+ /**
164
+ * clear all queued tasks
165
+ */
166
+ clear() {
167
+ const cleared = this.queue.length;
168
+ this.queue.forEach(task => {
169
+ task.reject(new Error('Queue cleared'));
170
+ });
171
+ this.queue = [];
172
+ this.emit('cleared', { cleared });
173
+ }
174
+ /**
175
+ * wait for all tasks to complete
176
+ */
177
+ async drain() {
178
+ return new Promise((resolve) => {
179
+ const checkDrained = () => {
180
+ if (this.running.size === 0 && this.queue.length === 0) {
181
+ this.removeListener('completed', checkDrained);
182
+ this.removeListener('failed', checkDrained);
183
+ resolve();
184
+ }
185
+ };
186
+ if (this.running.size === 0 && this.queue.length === 0) {
187
+ resolve();
188
+ }
189
+ else {
190
+ this.on('completed', checkDrained);
191
+ this.on('failed', checkDrained);
192
+ }
193
+ });
194
+ }
195
+ }
196
+ exports.ExecutionQueue = ExecutionQueue;