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,587 @@
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.SessionExecutor = void 0;
37
+ const child_process_1 = require("child_process");
38
+ const fs = __importStar(require("fs"));
39
+ const path = __importStar(require("path"));
40
+ const copilot_adapter_1 = require("./adapters/copilot-adapter");
41
+ const baseline_scanner_1 = require("./baseline-scanner");
42
+ const hook_generator_1 = require("./hook-generator");
43
+ const secret_redactor_1 = require("./secret-redactor");
44
+ const test_command_discovery_1 = require("./test-command-discovery");
45
+ const defaults_1 = require("./defaults");
46
+ const logger_1 = require("./logger");
47
+ const logger = (0, logger_1.getLogger)('session-executor');
48
+ /**
49
+ * Executes GitHub Copilot CLI sessions programmatically
50
+ * Uses the real `copilot -p` command with automation flags
51
+ */
52
+ class SessionExecutor {
53
+ copilotBin;
54
+ workingDir;
55
+ adapter;
56
+ // Copilot CLI outputs these when an agent tries to access paths outside its sandbox.
57
+ // Expected behavior during scoped execution; noisy and confusing to users.
58
+ static SCOPE_NOISE_PATTERNS = [
59
+ /Permission denied and could not request permission/i,
60
+ /could not request permission from user/i,
61
+ ];
62
+ constructor(workingDir, adapter) {
63
+ this.copilotBin = 'copilot';
64
+ this.workingDir = workingDir || process.cwd();
65
+ this.adapter = adapter;
66
+ }
67
+ isScopeEnforcementNoise(line) {
68
+ return SessionExecutor.SCOPE_NOISE_PATTERNS.some(p => p.test(line));
69
+ }
70
+ /**
71
+ * Execute a Copilot CLI session with a prompt
72
+ * Returns full output and transcript path if --share used
73
+ */
74
+ async executeSession(prompt, options = {}) {
75
+ // When an adapter is set, delegate to it instead of the hardcoded copilot path.
76
+ // This lets non-Copilot tools (Claude Code, Codex) plug in transparently.
77
+ if (this.adapter) {
78
+ return this.executeViaAdapter(prompt, options);
79
+ }
80
+ const startTime = Date.now();
81
+ // build command args based on real copilot CLI flags
82
+ const args = ['-p', prompt];
83
+ if (options.model) {
84
+ args.push('--model', options.model);
85
+ }
86
+ if (options.silent) {
87
+ args.push('--silent');
88
+ }
89
+ if (options.allowAllTools) {
90
+ // --allow-all covers tools, paths, and URLs so the subprocess
91
+ // never blocks waiting for interactive approval on stdin.
92
+ args.push('--allow-all');
93
+ }
94
+ if (options.availableTools && options.availableTools.length > 0) {
95
+ args.push('--available-tools', ...options.availableTools);
96
+ }
97
+ if (options.mcpConfig) {
98
+ args.push('--additional-mcp-config', options.mcpConfig);
99
+ }
100
+ if (options.agent) {
101
+ args.push('--agent', options.agent);
102
+ }
103
+ if (options.shareToFile) {
104
+ args.push('--share', options.shareToFile);
105
+ }
106
+ // Append any extra CLI args (e.g., --plugin-dir from hook injection)
107
+ if (options.additionalArgs && options.additionalArgs.length > 0) {
108
+ args.push(...options.additionalArgs);
109
+ }
110
+ // execute copilot command with optional log prefix for parallelism visibility
111
+ const result = await this.runCommand(this.copilotBin, args, options.logPrefix, options.additionalEnv, options.onAgentLine);
112
+ const duration = Date.now() - startTime;
113
+ return {
114
+ success: result.exitCode === 0,
115
+ output: result.stdout + result.stderr,
116
+ error: result.exitCode !== 0 ? result.stderr : undefined,
117
+ transcriptPath: options.shareToFile && result.exitCode === 0 ? options.shareToFile : undefined,
118
+ exitCode: result.exitCode,
119
+ duration
120
+ };
121
+ }
122
+ /**
123
+ * Delegate session execution to the pluggable adapter.
124
+ * Maps SessionOptions to AgentSpawnOptions and AgentResult back to SessionResult.
125
+ */
126
+ async executeViaAdapter(prompt, options) {
127
+ const agentResult = await this.adapter.spawn({
128
+ prompt,
129
+ workdir: this.workingDir,
130
+ model: options.model,
131
+ copilotAgent: options.agent,
132
+ executionMode: options.executionMode,
133
+ persistentSessionId: options.persistentSessionId,
134
+ persistentTurnTimeoutMs: options.persistentTurnTimeoutMs,
135
+ onAgentLine: options.onAgentLine,
136
+ logPrefix: options.logPrefix,
137
+ });
138
+ // Write transcript to the share file if the adapter produced one,
139
+ // or if the caller requested shareToFile. Always capture output even on
140
+ // non-zero exit so the verification pipeline can inspect agent work.
141
+ // For non-Copilot adapters shareTranscriptPath is always undefined,
142
+ // so we generate a fallback transcript from stdout.
143
+ let transcriptPath;
144
+ if (agentResult.shareTranscriptPath) {
145
+ transcriptPath = agentResult.shareTranscriptPath;
146
+ }
147
+ else if (options.shareToFile) {
148
+ const dir = path.dirname(options.shareToFile);
149
+ if (!fs.existsSync(dir)) {
150
+ fs.mkdirSync(dir, { recursive: true });
151
+ }
152
+ // Include stderr when stdout is empty so diagnostic messages
153
+ // (e.g. model-not-available errors that slip through) are visible
154
+ const transcriptBody = agentResult.stdout
155
+ || (agentResult.stderr ? `stderr:\n${agentResult.stderr}` : 'No output captured');
156
+ fs.writeFileSync(options.shareToFile, `# Agent Session Transcript\n\nSession output:\n\`\`\`\n${transcriptBody}\n\`\`\`\n`, 'utf8');
157
+ transcriptPath = options.shareToFile;
158
+ }
159
+ // Scrub known secret values from the transcript before anything reads it
160
+ if (transcriptPath) {
161
+ (0, secret_redactor_1.redactFile)(transcriptPath);
162
+ }
163
+ const sessionResult = {
164
+ success: agentResult.exitCode === 0,
165
+ output: agentResult.stdout + agentResult.stderr,
166
+ exitCode: agentResult.exitCode,
167
+ duration: agentResult.durationMs,
168
+ premiumRequestsConsumed: agentResult.premiumRequestsConsumed,
169
+ };
170
+ if (agentResult.exitCode !== 0) {
171
+ sessionResult.error = agentResult.stderr;
172
+ }
173
+ if (transcriptPath) {
174
+ sessionResult.transcriptPath = transcriptPath;
175
+ }
176
+ if (agentResult.fatalError) {
177
+ sessionResult.fatalError = agentResult.fatalError;
178
+ }
179
+ return sessionResult;
180
+ }
181
+ /**
182
+ * Execute a step with automatic session creation
183
+ * Includes human-like commit instructions
184
+ */
185
+ async executeStep(step, agent, context, options = {}) {
186
+ // generate comprehensive prompt with human-like commit instructions
187
+ let prompt = this.buildStepPrompt(step, agent, context);
188
+ // /fleet prefix for inner parallelism when enabled
189
+ if (options.useInnerFleet) {
190
+ prompt = `/fleet ${prompt}`;
191
+ }
192
+ // Generate per-step hooks for scope enforcement and evidence capture
193
+ let generatedHooks;
194
+ if (options.hooksEnabled && options.hooksRunDir) {
195
+ const hookGen = new hook_generator_1.HookGenerator();
196
+ generatedHooks = hookGen.generateStepHooks({
197
+ step,
198
+ agent,
199
+ executionId: options.hooksExecutionId || 'unknown',
200
+ runDir: options.hooksRunDir,
201
+ stepBranch: options.hooksBranch || `step-${step.stepNumber}`,
202
+ workingDir: this.workingDir
203
+ });
204
+ }
205
+ // set up transcript path
206
+ const transcriptPath = path.join(this.workingDir, 'proof', `step-${step.stepNumber}-${agent.name.toLowerCase()}.md`);
207
+ // ensure proof directory exists
208
+ const proofDir = path.dirname(transcriptPath);
209
+ if (!fs.existsSync(proofDir)) {
210
+ fs.mkdirSync(proofDir, { recursive: true });
211
+ }
212
+ // Hooks are auto-loaded by Copilot CLI from <gitRoot>/.github/hooks/
213
+ // No additional args needed; the hook file was written to workingDir.
214
+ // merge options with defaults
215
+ const sessionOptions = {
216
+ silent: false,
217
+ allowAllTools: true, // enable git, npm, test commands
218
+ shareToFile: transcriptPath,
219
+ ...options
220
+ };
221
+ try {
222
+ const result = await this.executeSession(prompt, sessionOptions);
223
+ // Attach evidence log path to the result for verifier consumption
224
+ if (generatedHooks) {
225
+ result.evidenceLogPath = generatedHooks.evidenceLogPath;
226
+ }
227
+ return result;
228
+ }
229
+ finally {
230
+ // Cleanup hook files (evidence log in runDir persists)
231
+ if (generatedHooks) {
232
+ const hookGen = new hook_generator_1.HookGenerator();
233
+ hookGen.cleanupHooks(generatedHooks.hooksFilePath);
234
+ }
235
+ }
236
+ }
237
+ /**
238
+ * Build comprehensive step prompt with human-like commit guidance
239
+ */
240
+ buildStepPrompt(step, agent, context) {
241
+ const sections = [];
242
+ sections.push('=== COPILOT CLI SESSION - Step ' + step.stepNumber + ' ===\n');
243
+ sections.push('You are operating as a GitHub Copilot CLI custom agent within a supervised,');
244
+ sections.push('sequential workflow.\n');
245
+ sections.push('Context');
246
+ sections.push('-------');
247
+ sections.push('- This repository is: ' + context.plan.goal);
248
+ sections.push('- Execution ID: ' + context.executionId);
249
+ sections.push('- Step: ' + step.stepNumber + ' of ' + context.plan.steps.length);
250
+ sections.push('- Your work must be fully auditable through session transcripts, git history,');
251
+ sections.push(' and test output.\n');
252
+ sections.push('Your assigned role');
253
+ sections.push('------------------');
254
+ sections.push('- Agent name: ' + agent.name);
255
+ sections.push('- Domain scope: ' + agent.purpose);
256
+ sections.push('- You must stay strictly within this domain.');
257
+ sections.push('- If a task exceeds this scope, say so and stop.\n');
258
+ sections.push('Your specific task for this step');
259
+ sections.push('---------------------------------');
260
+ sections.push(step.task + '\n');
261
+ sections.push('Scope (what you ARE responsible for)');
262
+ sections.push('-------------------------------------');
263
+ agent.scope.forEach(item => {
264
+ sections.push('- ' + item);
265
+ });
266
+ sections.push('');
267
+ sections.push('Boundaries (what you should NOT do)');
268
+ sections.push('-----------------------------------');
269
+ agent.boundaries.forEach(item => {
270
+ sections.push('- ' + item);
271
+ });
272
+ sections.push('');
273
+ // Surface existing test files so agents know what not to break
274
+ const baseline = (0, baseline_scanner_1.scanBaseline)(this.workingDir);
275
+ if (baseline.testFiles.length > 0) {
276
+ sections.push('EXISTING TESTS (must still pass after your changes)');
277
+ sections.push('---------------------------------------------------');
278
+ for (const f of baseline.testFiles) {
279
+ sections.push('- ' + f);
280
+ }
281
+ sections.push('');
282
+ }
283
+ // Discover the target project's full test gate so the agent runs
284
+ // `pnpm test` (or equivalent) instead of a subset like `npx vitest --run`
285
+ // that would skip lint and type checks.
286
+ const testDiscovery = (0, test_command_discovery_1.discoverTestCommand)(this.workingDir);
287
+ if (testDiscovery.warning && !(0, logger_1.isPrettyMode)()) {
288
+ logger.warn(`test command discovery: ${testDiscovery.warning}`);
289
+ }
290
+ sections.push((0, test_command_discovery_1.renderVerifyCommandSection)(testDiscovery));
291
+ // CRITICAL: Human-like commit instructions
292
+ sections.push('Git Commit Requirements (CRITICAL)');
293
+ sections.push('-----------------------------------');
294
+ sections.push('Make INCREMENTAL commits throughout your work, not one giant commit at the end.');
295
+ sections.push('');
296
+ sections.push('Each commit message must be:');
297
+ sections.push('- Natural and human-written (avoid AI patterns like perfect grammar or templates)');
298
+ sections.push('- Descriptive and imperative ("add feature" not "added feature")');
299
+ sections.push('- Contextual and specific to what changed');
300
+ sections.push('- Varied in style (mix of conventional-commits, casual, technical)');
301
+ sections.push('');
302
+ sections.push('Good commit message examples:');
303
+ sections.push(' "add user authentication module"');
304
+ sections.push(' "fix: handle edge case in validator"');
305
+ sections.push(' "update deps and tweak config"');
306
+ sections.push(' "implement todo list component with tests"');
307
+ sections.push(' "refactor api client, clean up error handling"');
308
+ sections.push('');
309
+ sections.push('Commit frequently in logical chunks:');
310
+ sections.push(' git add <files>');
311
+ sections.push(' git commit -m "your natural message"');
312
+ sections.push('');
313
+ sections.push('Feel free to tweak configs (package.json, tsconfig.json, etc.) and commit them naturally.');
314
+ sections.push('');
315
+ sections.push('Hard rules');
316
+ sections.push('----------');
317
+ sections.push('1. Do not invent features, flags, APIs, or tool behavior.');
318
+ sections.push('2. If you are uncertain about anything, explicitly say you are uncertain');
319
+ sections.push(' and list how to verify it.');
320
+ sections.push('3. Do not claim tests passed unless you actually ran them and can show');
321
+ sections.push(' the command output.');
322
+ sections.push('4. Do not say "done" unless all required artifacts exist.');
323
+ sections.push('5. Prefer small, reviewable changes over large refactors.');
324
+ sections.push('6. Make multiple small commits with varied, natural messages.');
325
+ sections.push('7. ALWAYS verify your branch before committing: git branch --show-current\n');
326
+ sections.push('Quality bar (applies when relevant to your scope)');
327
+ sections.push('-----------------------------------------------');
328
+ sections.push('- Extract-before-repeat: if you copy the same logic more than twice, stop and refactor into a shared util/hook/middleware.');
329
+ sections.push('- Config-first: do not hardcode API base URLs, timeouts, retry counts, or environment-specific values. Prefer env vars or a typed config module.');
330
+ sections.push('- README truth: do not claim features that are not implemented. If you are unsure, downgrade the claim and include how to verify.');
331
+ sections.push('- Keep it boring and verifiable: request logging, correlation id propagation, and consistent error responses when building HTTP APIs.');
332
+ sections.push('- For frontends: use a real HTML title, include responsive meta viewport, and centralize fetch error handling (retry/backoff only if implemented).');
333
+ sections.push('');
334
+ sections.push('Code comments (Required)');
335
+ sections.push('------------------------');
336
+ sections.push('- Add a 1-2 line purpose comment at top of each new file');
337
+ sections.push('- Add brief inline comments for non-obvious logic');
338
+ sections.push('- Use natural, casual language - not robotic/formal');
339
+ sections.push('- Good: "// bail early if no items"');
340
+ sections.push('- Bad: "// This conditional statement checks if the array is empty..."');
341
+ sections.push('- For functions: brief docstring explaining purpose\n');
342
+ sections.push('Refusal rules (when to stop and ask)');
343
+ ;
344
+ sections.push('-------------------------------------');
345
+ agent.refusal_rules.forEach(rule => {
346
+ sections.push('- ' + rule);
347
+ });
348
+ sections.push('');
349
+ if (step.dependencies.length > 0) {
350
+ sections.push('Dependencies (steps you can rely on)');
351
+ sections.push('------------------------------------');
352
+ sections.push('This step depends on: Steps ' + step.dependencies.join(', '));
353
+ if (context.priorContext.length > 0) {
354
+ sections.push('');
355
+ sections.push('Context from prior steps:');
356
+ context.priorContext.forEach(ctx => {
357
+ sections.push(' ' + ctx);
358
+ });
359
+ }
360
+ sections.push('');
361
+ }
362
+ sections.push('Done definition (when you can say "done")');
363
+ sections.push('------------------------------------------');
364
+ agent.done_definition.forEach(item => {
365
+ sections.push('- ' + item);
366
+ });
367
+ sections.push('');
368
+ sections.push('Required artifacts for this step');
369
+ sections.push('--------------------------------');
370
+ sections.push('1. Implementation changes (code, config, etc.)');
371
+ sections.push('2. Multiple small commits with varied, natural messages');
372
+ sections.push('3. A verification section that includes:');
373
+ sections.push(' - What commands you ran');
374
+ sections.push(' - What tests were executed');
375
+ sections.push(' - The results of those tests');
376
+ sections.push(' - Any gaps or risks that remain');
377
+ sections.push('');
378
+ sections.push('Expected outputs:');
379
+ step.expectedOutputs.forEach(output => {
380
+ sections.push(' - ' + output);
381
+ });
382
+ sections.push('');
383
+ sections.push('If any requirement cannot be met, stop and explain why before proceeding.');
384
+ sections.push('');
385
+ sections.push('=== BEGIN WORK ===');
386
+ return sections.join('\n');
387
+ }
388
+ /**
389
+ * Run a command and capture output.
390
+ * Uses line buffering to prevent mid-word breaks when streaming output.
391
+ * Each complete line gets prefixed with [Agent:Step] for parallelism visibility.
392
+ * A heartbeat indicator prints during quiet periods so users know the agent
393
+ * is still working (e.g. reading large files or thinking).
394
+ */
395
+ // Maximum seconds of silence before killing a stalled subprocess.
396
+ // Copilot CLI can go quiet for several minutes during extended tool-use
397
+ // or thinking phases, so this needs headroom beyond typical inference time.
398
+ static STALL_TIMEOUT_MS = defaults_1.DEFAULT_COMMAND_TIMEOUT_MS * 2 + 60_000;
399
+ runCommand(command, args, logPrefix, additionalEnv, onAgentLine) {
400
+ return new Promise((resolve) => {
401
+ const options = {
402
+ cwd: this.workingDir,
403
+ env: {
404
+ ...(0, copilot_adapter_1.scrubCopilotHostileTokens)(process.env),
405
+ // ensure copilot can authenticate
406
+ COPILOT_ALLOW_ALL: 'true',
407
+ ...additionalEnv
408
+ }
409
+ };
410
+ const proc = (0, child_process_1.spawn)(command, args, options);
411
+ // Close stdin immediately so the subprocess never blocks waiting for
412
+ // interactive input. Non-interactive mode should never need stdin.
413
+ if (proc.stdin) {
414
+ proc.stdin.end();
415
+ }
416
+ let stdout = '';
417
+ let stderr = '';
418
+ let resolved = false;
419
+ // line buffers prevent mid-word breaks in streamed output
420
+ let stdoutBuffer = '';
421
+ let stderrBuffer = '';
422
+ // Heartbeat: print activity status during quiet periods (no output for 10s)
423
+ let lineCount = 0;
424
+ let lastOutputTime = Date.now();
425
+ const cmdStartTime = Date.now();
426
+ let heartbeatInterval = null;
427
+ let stallCheckInterval = null;
428
+ const cleanup = () => {
429
+ if (heartbeatInterval)
430
+ clearInterval(heartbeatInterval);
431
+ if (stallCheckInterval)
432
+ clearInterval(stallCheckInterval);
433
+ };
434
+ // Stall detection: kill subprocess if no output for STALL_TIMEOUT_MS
435
+ stallCheckInterval = setInterval(() => {
436
+ const silentMs = Date.now() - lastOutputTime;
437
+ if (silentMs >= SessionExecutor.STALL_TIMEOUT_MS) {
438
+ const elapsed = Math.round((Date.now() - cmdStartTime) / 1000);
439
+ const stallSec = Math.round(silentMs / 1000);
440
+ if (logPrefix) {
441
+ logger.info(`${logPrefix} STALL DETECTED: no output for ${stallSec}s (total ${elapsed}s, ${lineCount} lines). Killing process.`);
442
+ }
443
+ cleanup();
444
+ proc.kill('SIGTERM');
445
+ // give it 5s to exit gracefully, then force kill
446
+ setTimeout(() => {
447
+ try {
448
+ proc.kill('SIGKILL');
449
+ }
450
+ catch { /* already dead */ }
451
+ }, defaults_1.DEFAULT_SIGKILL_DELAY_MS);
452
+ if (!resolved) {
453
+ resolved = true;
454
+ resolve({
455
+ stdout,
456
+ stderr: stderr + `\nProcess killed after ${stallSec}s of no output (stall timeout)`,
457
+ exitCode: 1
458
+ });
459
+ }
460
+ }
461
+ }, defaults_1.DEFAULT_HEARTBEAT_QUIET_THRESHOLD_MS);
462
+ if (logPrefix) {
463
+ heartbeatInterval = setInterval(() => {
464
+ const silentMs = Date.now() - lastOutputTime;
465
+ if (silentMs >= defaults_1.DEFAULT_HEARTBEAT_QUIET_THRESHOLD_MS) {
466
+ const elapsed = Math.round((Date.now() - cmdStartTime) / 1000);
467
+ logger.info(`${logPrefix} ... still working (${elapsed}s, ${lineCount} lines so far)`);
468
+ lastOutputTime = Date.now(); // reset so we don't spam
469
+ }
470
+ }, defaults_1.DEFAULT_HEARTBEAT_INTERVAL_MS);
471
+ }
472
+ if (proc.stdout) {
473
+ proc.stdout.on('data', (data) => {
474
+ const text = data.toString();
475
+ stdout += text;
476
+ lastOutputTime = Date.now();
477
+ // buffer partial lines, only print complete ones
478
+ if (logPrefix) {
479
+ stdoutBuffer += text;
480
+ const lines = stdoutBuffer.split('\n');
481
+ // keep last (possibly incomplete) line in buffer
482
+ stdoutBuffer = lines.pop() || '';
483
+ // print complete lines with prefix
484
+ for (const line of lines) {
485
+ if (line.trim() && !this.isScopeEnforcementNoise(line)) {
486
+ lineCount++;
487
+ const prefixed = `${logPrefix} ${line}`;
488
+ logger.info(prefixed);
489
+ if (onAgentLine)
490
+ onAgentLine(prefixed);
491
+ }
492
+ }
493
+ }
494
+ });
495
+ }
496
+ if (proc.stderr) {
497
+ proc.stderr.on('data', (data) => {
498
+ const text = data.toString();
499
+ stderr += text;
500
+ lastOutputTime = Date.now();
501
+ // buffer partial lines, only print complete ones
502
+ if (logPrefix) {
503
+ stderrBuffer += text;
504
+ const lines = stderrBuffer.split('\n');
505
+ // keep last (possibly incomplete) line in buffer
506
+ stderrBuffer = lines.pop() || '';
507
+ // print complete lines with prefix
508
+ for (const line of lines) {
509
+ if (line.trim() && !this.isScopeEnforcementNoise(line)) {
510
+ lineCount++;
511
+ const prefixed = `${logPrefix} ${line}`;
512
+ logger.error(prefixed);
513
+ if (onAgentLine)
514
+ onAgentLine(prefixed);
515
+ }
516
+ }
517
+ }
518
+ });
519
+ }
520
+ proc.on('close', (code) => {
521
+ cleanup();
522
+ // flush any remaining buffered content
523
+ if (logPrefix) {
524
+ if (stdoutBuffer.trim()) {
525
+ lineCount++;
526
+ logger.info(`${logPrefix} ${stdoutBuffer}`);
527
+ }
528
+ if (stderrBuffer.trim()) {
529
+ lineCount++;
530
+ logger.error(`${logPrefix} ${stderrBuffer}`);
531
+ }
532
+ }
533
+ if (!resolved) {
534
+ resolved = true;
535
+ resolve({
536
+ stdout,
537
+ stderr,
538
+ exitCode: code || 0
539
+ });
540
+ }
541
+ });
542
+ proc.on('error', (err) => {
543
+ cleanup();
544
+ // flush buffers on error too
545
+ if (logPrefix) {
546
+ if (stdoutBuffer.trim()) {
547
+ logger.info(`${logPrefix} ${stdoutBuffer}`);
548
+ }
549
+ if (stderrBuffer.trim()) {
550
+ logger.error(`${logPrefix} ${stderrBuffer}`);
551
+ }
552
+ }
553
+ if (!resolved) {
554
+ resolved = true;
555
+ resolve({
556
+ stdout,
557
+ stderr: stderr + '\n' + err.message,
558
+ exitCode: 1
559
+ });
560
+ }
561
+ });
562
+ });
563
+ }
564
+ /**
565
+ * Retry a session execution up to N times on failure
566
+ */
567
+ async executeWithRetry(prompt, options = {}, maxRetries = 3) {
568
+ let lastResult = null;
569
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
570
+ lastResult = await this.executeSession(prompt, options);
571
+ if (lastResult.success) {
572
+ return lastResult;
573
+ }
574
+ if (attempt < maxRetries) {
575
+ logger.error(`Attempt ${attempt} failed, retrying... (${maxRetries - attempt} left)`);
576
+ // wait before retry (exponential backoff)
577
+ await this.sleep(Math.pow(2, attempt) * 1000);
578
+ }
579
+ }
580
+ return lastResult;
581
+ }
582
+ sleep(ms) {
583
+ return new Promise(resolve => setTimeout(resolve, ms));
584
+ }
585
+ }
586
+ exports.SessionExecutor = SessionExecutor;
587
+ exports.default = SessionExecutor;