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.
- package/LICENSE +15 -0
- package/README.md +199 -0
- package/agents/reviewer.agent.md +50 -0
- package/agents/worker.agent.md +41 -0
- package/config/default-agents.yaml +56 -0
- package/config/pm-agent.yaml +36 -0
- package/config/quality-gates.yaml +88 -0
- package/config/repair-agent.yaml +34 -0
- package/config/semgrep-rules/complexity-mismatch.yaml +6 -0
- package/config/semgrep-rules/exception-swallowing.yaml +13 -0
- package/config/semgrep-rules/hardcoded-answer.yaml +6 -0
- package/config/semgrep-rules/mock-mutation.yaml +6 -0
- package/config/semgrep-rules/test-modification.yaml +6 -0
- package/config/user-agents.yaml +23 -0
- package/dist/src/adapters/adapter-factory.js +29 -0
- package/dist/src/adapters/agent-adapter.js +32 -0
- package/dist/src/adapters/claude-code-adapter.js +126 -0
- package/dist/src/adapters/claude-code-teams.js +119 -0
- package/dist/src/adapters/codex-adapter.js +118 -0
- package/dist/src/adapters/copilot-adapter.js +224 -0
- package/dist/src/adapters/fatal-error-classifier.js +88 -0
- package/dist/src/adapters/index.js +44 -0
- package/dist/src/adapters/persistent-session.js +204 -0
- package/dist/src/adapters/process-supervisor.js +199 -0
- package/dist/src/agents-exporter.js +398 -0
- package/dist/src/analytics-log.js +161 -0
- package/dist/src/baseline-scanner.js +65 -0
- package/dist/src/bootstrap-evidence.js +88 -0
- package/dist/src/bootstrap-orchestrator.js +205 -0
- package/dist/src/bootstrap-types.js +5 -0
- package/dist/src/branch-merger.js +427 -0
- package/dist/src/cli/attest-handlers.js +44 -0
- package/dist/src/cli/cost-prompt.js +51 -0
- package/dist/src/cli/demo-handlers.js +274 -0
- package/dist/src/cli/flags.js +152 -0
- package/dist/src/cli/index.js +57 -0
- package/dist/src/cli/live-status.js +186 -0
- package/dist/src/cli/misc-handlers.js +242 -0
- package/dist/src/cli/plan-handlers.js +350 -0
- package/dist/src/cli/share-handlers.js +163 -0
- package/dist/src/cli/status-handlers.js +457 -0
- package/dist/src/cli/swarm-handlers.js +638 -0
- package/dist/src/cli/usage.js +72 -0
- package/dist/src/cli.js +217 -0
- package/dist/src/commit-pattern-detector.js +143 -0
- package/dist/src/commit-quality-analyzer.js +38 -0
- package/dist/src/config-loader.js +339 -0
- package/dist/src/context-broker.js +358 -0
- package/dist/src/copilot-cli-wrapper.js +258 -0
- package/dist/src/cost-estimator.js +241 -0
- package/dist/src/defaults.js +18 -0
- package/dist/src/demo-mode.js +180 -0
- package/dist/src/deployment-handler.js +78 -0
- package/dist/src/deployment-manager.js +228 -0
- package/dist/src/execution-queue.js +196 -0
- package/dist/src/external-tool-manager.js +184 -0
- package/dist/src/gate-prompt-builder.js +117 -0
- package/dist/src/gate-remediation.js +173 -0
- package/dist/src/github-issues-ingester.js +152 -0
- package/dist/src/github-mcp-integrator.js +164 -0
- package/dist/src/hook-generator.js +229 -0
- package/dist/src/index.js +48 -0
- package/dist/src/knowledge-base.js +300 -0
- package/dist/src/logger.js +111 -0
- package/dist/src/meta-analyzer.js +208 -0
- package/dist/src/metrics-collector.js +209 -0
- package/dist/src/metrics-types.js +5 -0
- package/dist/src/multi-repo-coordinator.js +179 -0
- package/dist/src/orchestrator/async-meta-analysis.js +84 -0
- package/dist/src/orchestrator/fatal-run-error.js +43 -0
- package/dist/src/orchestrator/final-gates-remediation.js +258 -0
- package/dist/src/orchestrator/git-state-utils.js +119 -0
- package/dist/src/orchestrator/pause-controller.js +55 -0
- package/dist/src/orchestrator/replan-runner.js +258 -0
- package/dist/src/orchestrator/step-executor.js +391 -0
- package/dist/src/orchestrator/wave-scheduler-loop.js +204 -0
- package/dist/src/owasp-mapper.js +153 -0
- package/dist/src/owasp-report-renderer.js +39 -0
- package/dist/src/plan-files.js +142 -0
- package/dist/src/plan-generator.js +1207 -0
- package/dist/src/pm-agent.js +296 -0
- package/dist/src/post-run-reporter.js +203 -0
- package/dist/src/pr-automation.js +103 -0
- package/dist/src/pr-manager.js +318 -0
- package/dist/src/presenter/index.js +209 -0
- package/dist/src/prompt-builder.js +173 -0
- package/dist/src/quality-gates/config-loader.js +196 -0
- package/dist/src/quality-gates/default-config.js +243 -0
- package/dist/src/quality-gates/file-utils.js +152 -0
- package/dist/src/quality-gates/gate-runner.js +171 -0
- package/dist/src/quality-gates/gates/accessibility.js +355 -0
- package/dist/src/quality-gates/gates/duplicate-blocks.js +144 -0
- package/dist/src/quality-gates/gates/hardcoded-config.js +52 -0
- package/dist/src/quality-gates/gates/readme-claims.js +62 -0
- package/dist/src/quality-gates/gates/runtime-checks.js +265 -0
- package/dist/src/quality-gates/gates/scaffold-defaults.js +108 -0
- package/dist/src/quality-gates/gates/test-coverage.js +240 -0
- package/dist/src/quality-gates/gates/test-file-protection.js +85 -0
- package/dist/src/quality-gates/gates/test-isolation.js +74 -0
- package/dist/src/quality-gates/index.js +17 -0
- package/dist/src/quality-gates/registry.js +205 -0
- package/dist/src/quality-gates/types.js +2 -0
- package/dist/src/quick-fix-mode.js +302 -0
- package/dist/src/recipe-loader.js +122 -0
- package/dist/src/repair-agent.js +396 -0
- package/dist/src/repo-analyzer.js +342 -0
- package/dist/src/report-generator.js +146 -0
- package/dist/src/report-renderer.js +90 -0
- package/dist/src/requirement-filter.js +52 -0
- package/dist/src/sarif-formatter.js +140 -0
- package/dist/src/scheduling/dependency-analyzer.js +178 -0
- package/dist/src/scheduling/work-stealing-queue.js +123 -0
- package/dist/src/secret-redactor.js +90 -0
- package/dist/src/session-executor.js +587 -0
- package/dist/src/session-manager.js +221 -0
- package/dist/src/share/transcript-verification.js +128 -0
- package/dist/src/share-parser.js +477 -0
- package/dist/src/spinner.js +146 -0
- package/dist/src/step-runner.js +365 -0
- package/dist/src/swarm-orchestrator.js +686 -0
- package/dist/src/task-classifier.js +134 -0
- package/dist/src/test-command-discovery.js +123 -0
- package/dist/src/text-similarity.js +24 -0
- package/dist/src/tier-maps.js +158 -0
- package/dist/src/types.js +5 -0
- package/dist/src/url-shortener.js +135 -0
- package/dist/src/verification/attestation.js +166 -0
- package/dist/src/verification/cheat-detector.js +240 -0
- package/dist/src/verification/command-runner.js +70 -0
- package/dist/src/verification/composite-score.js +157 -0
- package/dist/src/verification/cosign-attestation.js +137 -0
- package/dist/src/verification/diff-analysis.js +89 -0
- package/dist/src/verification/differential-gate.js +173 -0
- package/dist/src/verification/index.js +41 -0
- package/dist/src/verification/mutation-gate.js +242 -0
- package/dist/src/verification/property-gate.js +201 -0
- package/dist/src/verification/test-synthesizer.js +243 -0
- package/dist/src/verifier/outcome-checks.js +396 -0
- package/dist/src/verifier/transcript-checks.js +207 -0
- package/dist/src/verifier/verification-reporters.js +148 -0
- package/dist/src/verifier-engine.js +491 -0
- package/dist/src/wave-resizer.js +131 -0
- package/dist/src/wave-scheduler.js +44 -0
- package/dist/src/worktree-manager.js +368 -0
- package/dist/src/worktree-reserved-paths.js +97 -0
- package/package.json +80 -0
- package/plugin/agents/.gitkeep +0 -0
- package/plugin/agents/backend_master.agent.md +72 -0
- package/plugin/agents/devops_pro.agent.md +45 -0
- package/plugin/agents/frontend_expert.agent.md +59 -0
- package/plugin/agents/integrator_finalizer.agent.md +44 -0
- package/plugin/agents/meta_reviewer.agent.md +44 -0
- package/plugin/agents/security_auditor.agent.md +43 -0
- package/plugin/agents/tester_elite.agent.md +43 -0
- package/plugin/hooks/hooks.json +19 -0
- package/plugin/hooks/post-tool-use.json +9 -0
- package/plugin/hooks/pre-tool-use.json +9 -0
- package/plugin/plugin.json +47 -0
- package/plugin/skills/swarm-gates/SKILL.md +53 -0
- package/plugin/skills/swarm-orchestrate/SKILL.md +34 -0
- package/plugin/skills/swarm-verify/SKILL.md +44 -0
- package/templates/README.md +35 -0
- package/templates/cli-tool.json +31 -0
- package/templates/fullstack.json +52 -0
- package/templates/library.json +38 -0
- package/templates/react-app.json +45 -0
- package/templates/recipes/add-api-docs.json +35 -0
- package/templates/recipes/add-auth.json +47 -0
- package/templates/recipes/add-ci.json +39 -0
- package/templates/recipes/add-tests.json +39 -0
- package/templates/recipes/migrate-to-ts.json +42 -0
- package/templates/recipes/refactor-modularize.json +44 -0
- package/templates/recipes/security-audit.json +35 -0
- 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;
|