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,131 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AdaptiveConcurrencyManager = exports.WaveResizer = void 0;
4
+ /**
5
+ * dynamic wave resizer - splits waves when rate limits hit
6
+ * or merges small waves for efficiency
7
+ */
8
+ class WaveResizer {
9
+ resizeHistory = [];
10
+ /**
11
+ * split wave into smaller chunks
12
+ */
13
+ splitWave(wave, chunkSize, reason) {
14
+ const splitWaves = [];
15
+ for (let i = 0; i < wave.length; i += chunkSize) {
16
+ splitWaves.push(wave.slice(i, i + chunkSize));
17
+ }
18
+ const event = {
19
+ originalWave: wave,
20
+ splitWaves,
21
+ reason,
22
+ timestamp: new Date().toISOString()
23
+ };
24
+ this.resizeHistory.push(event);
25
+ return splitWaves;
26
+ }
27
+ /**
28
+ * merge small waves together if safe
29
+ */
30
+ mergeWaves(waves, dependencies) {
31
+ const merged = [];
32
+ let currentMerge = [];
33
+ for (const wave of waves) {
34
+ // check if we can safely merge this wave with current
35
+ const canMerge = wave.every(step => {
36
+ const deps = dependencies.get(step) || [];
37
+ return deps.every(dep => currentMerge.includes(dep) === false ||
38
+ merged.flat().includes(dep));
39
+ });
40
+ if (canMerge && currentMerge.length + wave.length <= 5) {
41
+ currentMerge.push(...wave);
42
+ }
43
+ else {
44
+ if (currentMerge.length > 0) {
45
+ merged.push(currentMerge);
46
+ }
47
+ currentMerge = [...wave];
48
+ }
49
+ }
50
+ if (currentMerge.length > 0) {
51
+ merged.push(currentMerge);
52
+ }
53
+ return merged;
54
+ }
55
+ /**
56
+ * calculate optimal chunk size based on recent failures
57
+ */
58
+ calculateOptimalChunkSize(waveSize, recentFailures, queueStats) {
59
+ // start conservative after failures
60
+ if (recentFailures >= 3) {
61
+ return 1; // one at a time
62
+ }
63
+ else if (recentFailures >= 1) {
64
+ return Math.max(1, Math.floor(waveSize / 3));
65
+ }
66
+ // normal operation - respect max concurrency
67
+ return Math.min(waveSize, queueStats.maxConcurrency);
68
+ }
69
+ /**
70
+ * get resize history
71
+ */
72
+ getResizeHistory() {
73
+ return [...this.resizeHistory];
74
+ }
75
+ }
76
+ exports.WaveResizer = WaveResizer;
77
+ /**
78
+ * adaptive concurrency manager - adjusts limits based on observed behavior
79
+ * Starts at maxConcurrency (aggressive) and only backs off on failures.
80
+ */
81
+ class AdaptiveConcurrencyManager {
82
+ currentLimit;
83
+ minLimit = 1;
84
+ maxLimit;
85
+ consecutiveSuccesses = 0;
86
+ consecutiveFailures = 0;
87
+ constructor(initialLimit = 3, maxLimit = 10) {
88
+ // Start aggressive: use the configured limit immediately
89
+ this.currentLimit = initialLimit;
90
+ this.maxLimit = maxLimit;
91
+ }
92
+ /**
93
+ * record successful execution
94
+ */
95
+ recordSuccess() {
96
+ this.consecutiveSuccesses++;
97
+ this.consecutiveFailures = 0;
98
+ // Recover toward maxLimit after backoff (2 successes to step up)
99
+ if (this.consecutiveSuccesses >= 2 && this.currentLimit < this.maxLimit) {
100
+ this.currentLimit = Math.min(this.currentLimit + 1, this.maxLimit);
101
+ this.consecutiveSuccesses = 0;
102
+ }
103
+ }
104
+ /**
105
+ * record failure (rate limit or error)
106
+ */
107
+ recordFailure(reason) {
108
+ this.consecutiveFailures++;
109
+ this.consecutiveSuccesses = 0;
110
+ // aggressive backoff on rate limits
111
+ if (reason === 'rate_limit') {
112
+ this.currentLimit = Math.max(this.minLimit, Math.floor(this.currentLimit / 2));
113
+ }
114
+ else if (this.consecutiveFailures >= 3) {
115
+ this.currentLimit = Math.max(this.minLimit, this.currentLimit - 1);
116
+ }
117
+ }
118
+ /**
119
+ * get current recommended limit
120
+ */
121
+ getCurrentLimit() {
122
+ return this.currentLimit;
123
+ }
124
+ /**
125
+ * force set limit
126
+ */
127
+ setLimit(limit) {
128
+ this.currentLimit = Math.max(this.minLimit, Math.min(limit, this.maxLimit));
129
+ }
130
+ }
131
+ exports.AdaptiveConcurrencyManager = AdaptiveConcurrencyManager;
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildDependencyGraph = buildDependencyGraph;
4
+ exports.identifyExecutionWaves = identifyExecutionWaves;
5
+ /**
6
+ * Build a dependency graph from an execution plan.
7
+ * Returns a map of step number → array of dependency step numbers.
8
+ */
9
+ function buildDependencyGraph(plan) {
10
+ const graph = new Map();
11
+ plan.steps.forEach(step => {
12
+ graph.set(step.stepNumber, step.dependencies);
13
+ });
14
+ return graph;
15
+ }
16
+ /**
17
+ * Identify waves of parallel execution (topological sort by levels).
18
+ * Each wave contains steps whose dependencies have all been completed
19
+ * in prior waves.
20
+ */
21
+ function identifyExecutionWaves(graph) {
22
+ const waves = [];
23
+ const completed = new Set();
24
+ const allSteps = Array.from(graph.keys());
25
+ while (completed.size < allSteps.length) {
26
+ const currentWave = [];
27
+ // find steps whose dependencies are all completed
28
+ for (const step of allSteps) {
29
+ if (completed.has(step))
30
+ continue;
31
+ const deps = graph.get(step) || [];
32
+ const allDepsCompleted = deps.every(dep => completed.has(dep));
33
+ if (allDepsCompleted) {
34
+ currentWave.push(step);
35
+ }
36
+ }
37
+ if (currentWave.length === 0) {
38
+ throw new Error('Circular dependency detected or graph issue');
39
+ }
40
+ waves.push(currentWave);
41
+ currentWave.forEach(step => completed.add(step));
42
+ }
43
+ return waves;
44
+ }
@@ -0,0 +1,368 @@
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.WorktreeManager = void 0;
37
+ const child_process_1 = require("child_process");
38
+ const fs = __importStar(require("fs"));
39
+ const path = __importStar(require("path"));
40
+ const defaults_1 = require("./defaults");
41
+ const logger_1 = require("./logger");
42
+ const logger = (0, logger_1.getLogger)('worktree-manager');
43
+ /**
44
+ * Manages git worktrees, branches, and initial repo setup for parallel agent isolation.
45
+ * Extracted from SwarmOrchestrator to keep git operations in a single focused module.
46
+ */
47
+ class WorktreeManager {
48
+ workingDir;
49
+ constructor(workingDir) {
50
+ this.workingDir = workingDir;
51
+ }
52
+ /**
53
+ * Create an isolated git worktree for a step's agent to work in.
54
+ * When repoDir differs from workingDir, the worktree forks from
55
+ * the target repo's history (e.g. bootstrap targeting an external project).
56
+ */
57
+ async createAgentWorktree(branchName, fromBranch, runDir, stepNumber, repoDir) {
58
+ let gitDir = repoDir || this.workingDir;
59
+ const worktreePath = path.join(runDir, 'worktrees', `step-${stepNumber}`);
60
+ const worktreesDir = path.dirname(worktreePath);
61
+ if (!fs.existsSync(worktreesDir)) {
62
+ fs.mkdirSync(worktreesDir, { recursive: true });
63
+ }
64
+ // Guard: verify the resolved git root matches the intended target directory.
65
+ // Without this, a parent repo (e.g. /home/user/.git) silently captures all
66
+ // git operations and worktrees fork from the wrong history.
67
+ gitDir = this.ensureOwnGitRepo(gitDir);
68
+ // Ensure the target repo has at least one commit (handles freshly-init'd repos)
69
+ try {
70
+ (0, child_process_1.execSync)('git rev-parse HEAD', { cwd: gitDir, stdio: 'pipe' });
71
+ }
72
+ catch {
73
+ try {
74
+ (0, child_process_1.execSync)('git add -A', { cwd: gitDir, stdio: 'pipe' });
75
+ (0, child_process_1.execSync)('git commit --allow-empty -m "chore: initialize repository"', { cwd: gitDir, stdio: 'pipe' });
76
+ }
77
+ catch {
78
+ try {
79
+ (0, child_process_1.execSync)('git commit --allow-empty -m "chore: initialize repository"', { cwd: gitDir, stdio: 'pipe' });
80
+ }
81
+ catch {
82
+ // Truly cannot commit; worktree add will fail with a clear error
83
+ }
84
+ }
85
+ }
86
+ // Resolve fromBranch: the target repo may not have the same branch name.
87
+ // Also handle detached-HEAD repos (SWE-bench checks out a specific commit).
88
+ let resolvedFromBranch = fromBranch;
89
+ try {
90
+ const targetBranch = (0, child_process_1.execSync)('git branch --show-current', { cwd: gitDir, encoding: 'utf8', stdio: 'pipe' }).trim();
91
+ if (targetBranch) {
92
+ resolvedFromBranch = targetBranch;
93
+ }
94
+ else {
95
+ // Detached HEAD — use the commit SHA directly as the start point.
96
+ // `git rev-parse --abbrev-ref HEAD` returns literal "HEAD" in detached state,
97
+ // which is valid as a start point, but using the full SHA is more explicit.
98
+ resolvedFromBranch = (0, child_process_1.execSync)('git rev-parse HEAD', { cwd: gitDir, encoding: 'utf8', stdio: 'pipe' }).trim();
99
+ }
100
+ }
101
+ catch {
102
+ // Keep the original fromBranch as best-effort fallback
103
+ }
104
+ // Create the branch without checkout
105
+ try {
106
+ (0, child_process_1.execSync)(`git branch ${branchName} ${resolvedFromBranch}`, { cwd: gitDir, stdio: 'pipe' });
107
+ }
108
+ catch {
109
+ // Branch already exists from a prior retry; safe to reuse
110
+ }
111
+ // Clean up existing worktree if present (e.g. from a retry)
112
+ if (fs.existsSync(worktreePath)) {
113
+ try {
114
+ (0, child_process_1.execSync)(`git worktree remove "${worktreePath}" --force`, { cwd: gitDir, stdio: 'pipe' });
115
+ }
116
+ catch {
117
+ try {
118
+ (0, child_process_1.execSync)('git worktree prune', { cwd: gitDir, stdio: 'pipe' });
119
+ }
120
+ catch {
121
+ // Prune is best-effort cleanup
122
+ }
123
+ fs.rmSync(worktreePath, { recursive: true, force: true });
124
+ }
125
+ }
126
+ return new Promise((resolve, reject) => {
127
+ const proc = (0, child_process_1.spawn)('git', ['worktree', 'add', worktreePath, branchName], {
128
+ cwd: gitDir
129
+ });
130
+ let stderr = '';
131
+ if (proc.stderr) {
132
+ proc.stderr.on('data', (data) => {
133
+ stderr += data.toString();
134
+ });
135
+ }
136
+ proc.on('close', (code) => {
137
+ if (code === 0) {
138
+ resolve(worktreePath);
139
+ }
140
+ else {
141
+ reject(new Error(`Failed to create worktree for step ${stepNumber}: ${stderr}`));
142
+ }
143
+ });
144
+ });
145
+ }
146
+ /**
147
+ * Remove a git worktree. Non-throwing: always resolves even on failure
148
+ * since worktree cleanup is best-effort post-merge.
149
+ */
150
+ async removeAgentWorktree(worktreePath) {
151
+ return new Promise((resolve) => {
152
+ const proc = (0, child_process_1.spawn)('git', ['worktree', 'remove', worktreePath, '--force'], {
153
+ cwd: this.workingDir
154
+ });
155
+ proc.on('close', () => {
156
+ resolve();
157
+ });
158
+ });
159
+ }
160
+ /**
161
+ * Create a new git branch (legacy path; kept for replan retry branches).
162
+ * Stashes uncommitted changes before switching to avoid conflicts from
163
+ * parallel agents.
164
+ */
165
+ async createAgentBranch(branchName, fromBranch) {
166
+ try {
167
+ (0, child_process_1.execSync)('git stash --include-untracked', { cwd: this.workingDir, stdio: 'pipe' });
168
+ }
169
+ catch {
170
+ // Stash fails when working tree is clean; expected during normal execution
171
+ }
172
+ return new Promise((resolve, reject) => {
173
+ const proc = (0, child_process_1.spawn)('git', ['checkout', '-b', branchName, fromBranch], {
174
+ cwd: this.workingDir
175
+ });
176
+ let stderr = '';
177
+ if (proc.stderr) {
178
+ proc.stderr.on('data', (data) => {
179
+ stderr += data.toString();
180
+ });
181
+ }
182
+ proc.on('close', (code) => {
183
+ if (code === 0) {
184
+ resolve();
185
+ }
186
+ else {
187
+ reject(new Error(`Failed to create branch ${branchName}: ${stderr}`));
188
+ }
189
+ });
190
+ });
191
+ }
192
+ /**
193
+ * Switch to a git branch with a 15s timeout safety net.
194
+ */
195
+ async switchBranch(branchName) {
196
+ return new Promise((resolve, reject) => {
197
+ const proc = (0, child_process_1.spawn)('git', ['checkout', branchName], {
198
+ cwd: this.workingDir,
199
+ stdio: ['pipe', 'pipe', 'pipe'],
200
+ env: { ...process.env, GIT_TERMINAL_PROMPT: '0' }
201
+ });
202
+ const timeout = setTimeout(() => {
203
+ proc.kill('SIGKILL');
204
+ reject(new Error(`Timeout switching to branch ${branchName}`));
205
+ }, defaults_1.DEFAULT_BRANCH_SWITCH_TIMEOUT_MS);
206
+ proc.on('close', (code) => {
207
+ clearTimeout(timeout);
208
+ if (code === 0) {
209
+ resolve();
210
+ }
211
+ else {
212
+ reject(new Error(`Failed to switch to branch ${branchName}`));
213
+ }
214
+ });
215
+ proc.on('error', (err) => {
216
+ clearTimeout(timeout);
217
+ reject(err);
218
+ });
219
+ });
220
+ }
221
+ getCurrentBranch() {
222
+ const result = (0, child_process_1.execSync)('git branch --show-current', {
223
+ cwd: this.workingDir,
224
+ encoding: 'utf8'
225
+ });
226
+ return result.trim() || 'main';
227
+ }
228
+ /**
229
+ * Resolve the repo's default branch — the one the orchestrator treats as the
230
+ * integration target for merges and rebases.
231
+ *
232
+ * Resolution order:
233
+ * 1. `git symbolic-ref refs/remotes/origin/HEAD` — the repo's upstream default
234
+ * (returns `refs/remotes/origin/master` or `refs/remotes/origin/main` etc.)
235
+ * 2. `git branch --show-current` — whatever branch is currently checked out
236
+ * 3. The literal "main" as a last resort for brand-new empty repos where
237
+ * neither of the above resolves.
238
+ *
239
+ * This is what replaced the old `getCurrentBranch() || 'main'` shortcut. The
240
+ * shortcut broke on any repo whose default isn't `main` (sympy, scikit-learn,
241
+ * numpy pre-2022, astropy pre-2022, and anything else that predates the
242
+ * main-rename wave) and on detached-HEAD checkouts like SWE-bench's, where
243
+ * `git branch --show-current` returns empty.
244
+ */
245
+ resolveDefaultBranch() {
246
+ try {
247
+ const head = (0, child_process_1.execSync)('git symbolic-ref refs/remotes/origin/HEAD', {
248
+ cwd: this.workingDir,
249
+ encoding: 'utf8',
250
+ stdio: ['pipe', 'pipe', 'pipe'],
251
+ }).trim();
252
+ // format: refs/remotes/origin/<branch>
253
+ const prefix = 'refs/remotes/origin/';
254
+ if (head.startsWith(prefix)) {
255
+ return head.slice(prefix.length);
256
+ }
257
+ }
258
+ catch {
259
+ // origin/HEAD not set — repo has no remote or never fetched it; fall through
260
+ }
261
+ try {
262
+ const current = (0, child_process_1.execSync)('git branch --show-current', {
263
+ cwd: this.workingDir,
264
+ encoding: 'utf8',
265
+ stdio: ['pipe', 'pipe', 'pipe'],
266
+ }).trim();
267
+ if (current)
268
+ return current;
269
+ }
270
+ catch {
271
+ // current-branch lookup failed; fall through to default
272
+ }
273
+ return 'main';
274
+ }
275
+ /**
276
+ * Ensure repo has at least one commit (required for branch creation).
277
+ * Creates an initial commit with a .gitignore if repo is empty.
278
+ * Also verifies the working directory is its own git root first.
279
+ */
280
+ ensureInitialCommit() {
281
+ // Validate git root before any operations; prevents silently
282
+ // committing into a parent repo like /home/user/.git
283
+ this.ensureOwnGitRepo(this.workingDir);
284
+ try {
285
+ (0, child_process_1.execSync)('git rev-parse HEAD', {
286
+ cwd: this.workingDir,
287
+ encoding: 'utf8',
288
+ stdio: ['pipe', 'pipe', 'pipe']
289
+ });
290
+ return;
291
+ }
292
+ catch {
293
+ // rev-parse fails when repo has no commits; fall through to create one
294
+ }
295
+ logger.info(' \u{1F4DD} Empty repo detected, creating initial commit...');
296
+ const gitignorePath = path.join(this.workingDir, '.gitignore');
297
+ if (!fs.existsSync(gitignorePath)) {
298
+ fs.writeFileSync(gitignorePath, `# Swarm orchestrator artifacts
299
+ plans/
300
+ runs/
301
+ proof/
302
+ .quickfix/
303
+ .context/
304
+ .locks/
305
+ node_modules/
306
+ `);
307
+ }
308
+ try {
309
+ (0, child_process_1.execSync)('git add .gitignore', { cwd: this.workingDir, stdio: 'pipe' });
310
+ (0, child_process_1.execSync)('git commit -m "chore: initialize repository"', { cwd: this.workingDir, stdio: 'pipe' });
311
+ logger.info(' \u2705 Initial commit created');
312
+ }
313
+ catch {
314
+ try {
315
+ (0, child_process_1.execSync)('git add -A', { cwd: this.workingDir, stdio: 'pipe' });
316
+ (0, child_process_1.execSync)('git commit --allow-empty -m "chore: initialize repository"', { cwd: this.workingDir, stdio: 'pipe' });
317
+ logger.info(' \u2705 Initial commit created (empty)');
318
+ }
319
+ catch (innerErr) {
320
+ const msg = innerErr instanceof Error ? innerErr.message : String(innerErr);
321
+ logger.warn(`[init] Could not create initial commit: ${msg}`);
322
+ }
323
+ }
324
+ }
325
+ /**
326
+ * Verify that targetDir is its own git repository root, not a subdirectory
327
+ * of a parent repo. If git resolves to a different root (e.g. /home/user),
328
+ * initialize a dedicated repo at targetDir so worktrees fork from the
329
+ * correct history instead of the parent's.
330
+ */
331
+ ensureOwnGitRepo(targetDir) {
332
+ const resolved = path.resolve(targetDir);
333
+ try {
334
+ const gitRoot = (0, child_process_1.execSync)('git rev-parse --show-toplevel', {
335
+ cwd: resolved, encoding: 'utf8', stdio: 'pipe'
336
+ }).trim();
337
+ if (path.resolve(gitRoot) === resolved) {
338
+ return resolved;
339
+ }
340
+ // Git resolved to a parent directory; targetDir is not its own repo.
341
+ // Initialize a fresh repo so worktrees use the right history.
342
+ logger.info(` ⚠️ ${resolved} is inside parent repo ${gitRoot}; initializing dedicated git repo`);
343
+ }
344
+ catch {
345
+ // No git repo found at all; initialize one
346
+ logger.info(` 📂 No git repo at ${resolved}; initializing`);
347
+ }
348
+ (0, child_process_1.execSync)('git init', { cwd: resolved, stdio: 'pipe' });
349
+ (0, child_process_1.execSync)('git config user.email "swarm-orchestrator@local"', { cwd: resolved, stdio: 'pipe' });
350
+ (0, child_process_1.execSync)('git config user.name "Swarm Orchestrator"', { cwd: resolved, stdio: 'pipe' });
351
+ (0, child_process_1.execSync)('git add -A', { cwd: resolved, stdio: 'pipe' });
352
+ try {
353
+ (0, child_process_1.execSync)('git commit -m "initialize project for swarm orchestration"', {
354
+ cwd: resolved, stdio: 'pipe',
355
+ env: { ...process.env, GIT_TERMINAL_PROMPT: '0' }
356
+ });
357
+ }
358
+ catch {
359
+ (0, child_process_1.execSync)('git commit --allow-empty -m "initialize project for swarm orchestration"', {
360
+ cwd: resolved, stdio: 'pipe',
361
+ env: { ...process.env, GIT_TERMINAL_PROMPT: '0' }
362
+ });
363
+ }
364
+ return resolved;
365
+ }
366
+ }
367
+ exports.WorktreeManager = WorktreeManager;
368
+ exports.default = WorktreeManager;
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ /**
3
+ * Reserved paths for per-step commit-level exclusion (issue #27 / PR 2).
4
+ *
5
+ * Mirrors benchmarks/swe-bench/evaluation-scripts/worktree_reserved_paths.py
6
+ * one-for-one so that commit-time exclusion (TS side) stays in parity with
7
+ * capture-time exclusion (Python side). If you update one, update the other.
8
+ *
9
+ * Three categories tracked separately so audit decisions stay legible:
10
+ *
11
+ * ORCHESTRATOR_RESERVED_PATHS — paths this codebase writes to during normal
12
+ * operation. Scraped from src/**\/*.ts. Update when a new code path starts
13
+ * writing to a new top-level directory inside the worktree.
14
+ *
15
+ * BUILD_ARTIFACT_RESERVED_PATHS — paths universal build tooling writes
16
+ * (package managers, compilers, test runners, venv tools). Not
17
+ * orchestrator-specific; reasonable to exclude from any commit regardless.
18
+ *
19
+ * FILE_GLOB_EXCLUDES — pure file-glob patterns (not directory prefixes).
20
+ * Appended separately from the directory dual-form expansion below.
21
+ *
22
+ * The consumer is gitPathspecExcludes(), which is called at the per-step
23
+ * commit site in swarm-orchestrator.ts. See the parity test in
24
+ * test/worktree-reserved-paths.test.ts for the cross-language invariant.
25
+ */
26
+ Object.defineProperty(exports, "__esModule", { value: true });
27
+ exports.FILE_GLOB_EXCLUDES = exports.BUILD_ARTIFACT_RESERVED_PATHS = exports.ORCHESTRATOR_RESERVED_PATHS = void 0;
28
+ exports.gitPathspecExcludes = gitPathspecExcludes;
29
+ exports.ORCHESTRATOR_RESERVED_PATHS = [
30
+ // runDir tree — holds everything orchestrator-scoped:
31
+ // .context/shared-context.json (per-step manifest)
32
+ // .locks/ (git serialization locks)
33
+ // worktrees/step-N/ (per-step isolated worktrees)
34
+ // steps/step-N/share.md (transcripts)
35
+ // session-state.json, metrics.json, cost-attribution.json
36
+ // quality-gates/ (gate output)
37
+ // Excluding "runs" excludes every nested path; no need to list sub-dirs.
38
+ 'runs',
39
+ // quick-fix-mode session scratch (src/quick-fix-mode.ts)
40
+ '.quickfix',
41
+ // CLI plan commands write plan-*.json here
42
+ // (src/plan-files.ts)
43
+ 'plans',
44
+ ];
45
+ exports.BUILD_ARTIFACT_RESERVED_PATHS = [
46
+ // Node
47
+ 'node_modules',
48
+ // Python
49
+ '__pycache__',
50
+ '.venv',
51
+ 'venv',
52
+ 'env',
53
+ // JS build tools
54
+ '.next',
55
+ '.turbo',
56
+ '.cache',
57
+ // Generic build output
58
+ 'dist',
59
+ 'build',
60
+ // Test coverage
61
+ 'coverage',
62
+ ];
63
+ exports.FILE_GLOB_EXCLUDES = [
64
+ '**/*.pyc',
65
+ '**/*.pyo',
66
+ '**/*.egg-info',
67
+ '**/*.egg-info/**',
68
+ // .copilot-instructions.md — written to the repo root by prompt-builder.ts
69
+ // and committed before step-1 runs. Orchestrator scaffolding, not agent work.
70
+ // Must be excluded from SWE-bench patches or `git apply` fails in /testbed.
71
+ // Mirrors Python _FILE_GLOB_EXCLUDES entry added in smoke8 post-mortem fix.
72
+ '.copilot-instructions.md',
73
+ ];
74
+ /**
75
+ * Return git pathspec :(exclude) directives covering every reserved path.
76
+ *
77
+ * For each directory entry, emits both the top-level form (`:(exclude)runs`)
78
+ * and a tree glob (`:(exclude)runs/**`). Git requires both when the exclude
79
+ * should match the directory itself AND everything under it; the top-level
80
+ * form alone can miss deeply nested files on some git versions.
81
+ *
82
+ * Emission count: 14 directories × 2 (dual-form) + 4 file globs = 32 args.
83
+ * Matches Python's git_pathspec_excludes() output one-for-one.
84
+ *
85
+ * Consumers pass this list as trailing arguments to `git add` after a
86
+ * positive pathspec (typically `--`). Note: git add for commits does NOT
87
+ * need the `.` positive pathspec that capture uses; `git add -A` already
88
+ * stages the whole tree and the excludes suppress the reserved paths.
89
+ */
90
+ function gitPathspecExcludes() {
91
+ const directoryExcludes = [
92
+ ...exports.ORCHESTRATOR_RESERVED_PATHS,
93
+ ...exports.BUILD_ARTIFACT_RESERVED_PATHS,
94
+ ].flatMap(dir => [`:(exclude)${dir}`, `:(exclude)${dir}/**`]);
95
+ const globExcludes = exports.FILE_GLOB_EXCLUDES.map(g => `:(exclude)${g}`);
96
+ return [...directoryExcludes, ...globExcludes];
97
+ }