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,184 @@
|
|
|
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.ExternalToolManager = void 0;
|
|
37
|
+
const child_process_1 = require("child_process");
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const logger_1 = require("./logger");
|
|
41
|
+
const logger = (0, logger_1.getLogger)('external-tool-manager');
|
|
42
|
+
/**
|
|
43
|
+
* External Tool Manager - handles external CLI tool execution with safety gating
|
|
44
|
+
* All external commands require explicit user confirmation via enableExternal flag
|
|
45
|
+
*/
|
|
46
|
+
class ExternalToolManager {
|
|
47
|
+
config;
|
|
48
|
+
executionLog = [];
|
|
49
|
+
toolAvailability;
|
|
50
|
+
constructor(config) {
|
|
51
|
+
this.config = config;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Detect which external tools are available
|
|
55
|
+
*/
|
|
56
|
+
async detectAvailableTools() {
|
|
57
|
+
if (this.toolAvailability) {
|
|
58
|
+
return this.toolAvailability;
|
|
59
|
+
}
|
|
60
|
+
const availability = {
|
|
61
|
+
gh: await this.isToolAvailable('gh'),
|
|
62
|
+
vercel: await this.isToolAvailable('vercel'),
|
|
63
|
+
netlify: await this.isToolAvailable('netlify')
|
|
64
|
+
};
|
|
65
|
+
this.toolAvailability = availability;
|
|
66
|
+
return availability;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Check if a specific tool is available
|
|
70
|
+
*/
|
|
71
|
+
async isToolAvailable(toolName) {
|
|
72
|
+
return new Promise((resolve) => {
|
|
73
|
+
const proc = (0, child_process_1.spawn)('which', [toolName]);
|
|
74
|
+
proc.on('close', (code) => {
|
|
75
|
+
resolve(code === 0);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Execute external command with safety gating
|
|
81
|
+
*/
|
|
82
|
+
async executeCommand(command, args, options = {}) {
|
|
83
|
+
const execution = {
|
|
84
|
+
command,
|
|
85
|
+
args: this.sanitizeArgs(args), // Remove any potential secrets
|
|
86
|
+
workingDir: options.workingDir || process.cwd(),
|
|
87
|
+
timestamp: new Date().toISOString()
|
|
88
|
+
};
|
|
89
|
+
// Check tool availability if required
|
|
90
|
+
if (options.requireTool) {
|
|
91
|
+
const tools = await this.detectAvailableTools();
|
|
92
|
+
if (!tools[options.requireTool]) {
|
|
93
|
+
execution.exitCode = -1;
|
|
94
|
+
execution.error = `Tool '${options.requireTool}' not available`;
|
|
95
|
+
this.logExecution(execution);
|
|
96
|
+
return execution;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// Dry run mode: log but don't execute
|
|
100
|
+
if (this.config.dryRun) {
|
|
101
|
+
logger.info(`[DRY RUN] Would execute: ${command} ${args.join(' ')}`);
|
|
102
|
+
logger.info(` Working dir: ${execution.workingDir}`);
|
|
103
|
+
execution.exitCode = 0;
|
|
104
|
+
execution.output = '[DRY RUN - not executed]';
|
|
105
|
+
this.logExecution(execution);
|
|
106
|
+
return execution;
|
|
107
|
+
}
|
|
108
|
+
// Safety gate: require explicit external permission
|
|
109
|
+
if (!this.config.enableExternal) {
|
|
110
|
+
execution.exitCode = -1;
|
|
111
|
+
execution.error = 'External tool execution disabled. Use --enable-external flag.';
|
|
112
|
+
this.logExecution(execution);
|
|
113
|
+
return execution;
|
|
114
|
+
}
|
|
115
|
+
// Execute command
|
|
116
|
+
const startTime = Date.now();
|
|
117
|
+
return new Promise((resolve) => {
|
|
118
|
+
const proc = (0, child_process_1.spawn)(command, args, {
|
|
119
|
+
cwd: execution.workingDir,
|
|
120
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
121
|
+
});
|
|
122
|
+
let stdout = '';
|
|
123
|
+
let stderr = '';
|
|
124
|
+
proc.stdout.on('data', (data) => {
|
|
125
|
+
stdout += data.toString();
|
|
126
|
+
});
|
|
127
|
+
proc.stderr.on('data', (data) => {
|
|
128
|
+
stderr += data.toString();
|
|
129
|
+
});
|
|
130
|
+
proc.on('close', (code) => {
|
|
131
|
+
execution.exitCode = code || 0;
|
|
132
|
+
execution.output = stdout;
|
|
133
|
+
if (stderr)
|
|
134
|
+
execution.error = stderr;
|
|
135
|
+
execution.duration = Date.now() - startTime;
|
|
136
|
+
this.logExecution(execution);
|
|
137
|
+
resolve(execution);
|
|
138
|
+
});
|
|
139
|
+
proc.on('error', (err) => {
|
|
140
|
+
execution.exitCode = -1;
|
|
141
|
+
execution.error = err.message;
|
|
142
|
+
execution.duration = Date.now() - startTime;
|
|
143
|
+
this.logExecution(execution);
|
|
144
|
+
resolve(execution);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Sanitize command arguments to remove secrets
|
|
150
|
+
*/
|
|
151
|
+
sanitizeArgs(args) {
|
|
152
|
+
return args.map(arg => {
|
|
153
|
+
// Hide tokens and keys
|
|
154
|
+
if (arg.includes('token=') || arg.includes('key=') || arg.includes('secret=')) {
|
|
155
|
+
const parts = arg.split('=');
|
|
156
|
+
return `${parts[0]}=[REDACTED]`;
|
|
157
|
+
}
|
|
158
|
+
return arg;
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Log command execution
|
|
163
|
+
*/
|
|
164
|
+
logExecution(execution) {
|
|
165
|
+
this.executionLog.push(execution);
|
|
166
|
+
// Write to log file if specified
|
|
167
|
+
if (this.config.logFile) {
|
|
168
|
+
const logDir = path.dirname(this.config.logFile);
|
|
169
|
+
if (!fs.existsSync(logDir)) {
|
|
170
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
171
|
+
}
|
|
172
|
+
const logEntry = JSON.stringify(execution, null, 2) + '\n';
|
|
173
|
+
fs.appendFileSync(this.config.logFile, logEntry);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Get execution log
|
|
178
|
+
*/
|
|
179
|
+
getExecutionLog() {
|
|
180
|
+
return [...this.executionLog];
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
exports.ExternalToolManager = ExternalToolManager;
|
|
184
|
+
exports.default = ExternalToolManager;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.classifyStep = classifyStep;
|
|
4
|
+
exports.buildGateClauses = buildGateClauses;
|
|
5
|
+
exports.getGateRequirements = getGateRequirements;
|
|
6
|
+
exports.requiresTestStep = requiresTestStep;
|
|
7
|
+
/** Agent names that indicate frontend-facing work. */
|
|
8
|
+
const FRONTEND_AGENTS = new Set();
|
|
9
|
+
/** Agent names that indicate test-writing work. */
|
|
10
|
+
const TEST_AGENTS = new Set();
|
|
11
|
+
/** Agent names that indicate documentation/review work. */
|
|
12
|
+
const DOC_AGENTS = new Set(['reviewer']);
|
|
13
|
+
/**
|
|
14
|
+
* Classify a step by its agent name to determine which gate requirements apply.
|
|
15
|
+
* @param agentName - The agent assigned to the step
|
|
16
|
+
* @returns The step category
|
|
17
|
+
*/
|
|
18
|
+
function classifyStep(agentName) {
|
|
19
|
+
if (TEST_AGENTS.has(agentName))
|
|
20
|
+
return 'test';
|
|
21
|
+
if (FRONTEND_AGENTS.has(agentName))
|
|
22
|
+
return 'frontend';
|
|
23
|
+
if (DOC_AGENTS.has(agentName))
|
|
24
|
+
return 'documentation';
|
|
25
|
+
return 'code-generation';
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Build gate-aware prompt clauses from resolved quality gate configuration.
|
|
29
|
+
* Returns an array of clauses, each with its applicable step categories.
|
|
30
|
+
* Only produces clauses for gates that are enabled.
|
|
31
|
+
* @param config - Resolved quality gates configuration
|
|
32
|
+
* @returns Array of gate-derived prompt clauses
|
|
33
|
+
*/
|
|
34
|
+
function buildGateClauses(config) {
|
|
35
|
+
if (!config.enabled)
|
|
36
|
+
return [];
|
|
37
|
+
const clauses = [];
|
|
38
|
+
const gates = config.gates;
|
|
39
|
+
if (gates.scaffoldDefaults.enabled) {
|
|
40
|
+
clauses.push({
|
|
41
|
+
appliesTo: ['code-generation', 'test', 'frontend', 'documentation', 'all'],
|
|
42
|
+
text: 'Remove all TODO comments, placeholder text, and default scaffold values before completing.',
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
if (gates.duplicateBlocks.enabled) {
|
|
46
|
+
clauses.push({
|
|
47
|
+
appliesTo: ['code-generation', 'frontend'],
|
|
48
|
+
text: 'Avoid duplicating code blocks. Extract shared logic into utility functions.',
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
if (gates.hardcodedConfig.enabled) {
|
|
52
|
+
clauses.push({
|
|
53
|
+
appliesTo: ['code-generation', 'frontend'],
|
|
54
|
+
text: 'Use environment variables for configuration values. No hardcoded URLs, ports, credentials, or API keys.',
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
if (gates.testCoverage.enabled) {
|
|
58
|
+
clauses.push({
|
|
59
|
+
appliesTo: ['test'],
|
|
60
|
+
text: `Achieve thorough test coverage. Test every exported function and major code path.`,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
if (gates.testIsolation.enabled) {
|
|
64
|
+
clauses.push({
|
|
65
|
+
appliesTo: ['test'],
|
|
66
|
+
text: 'Each test must be independently runnable. No shared mutable state between tests.',
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
if (gates.accessibility.enabled) {
|
|
70
|
+
clauses.push({
|
|
71
|
+
appliesTo: ['frontend'],
|
|
72
|
+
text: 'Include ARIA attributes, keyboard navigation, and semantic HTML.',
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
if (gates.readmeClaims.enabled) {
|
|
76
|
+
clauses.push({
|
|
77
|
+
appliesTo: ['documentation'],
|
|
78
|
+
text: 'README claims must match actual implemented functionality.',
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
if (gates.runtimeChecks.enabled) {
|
|
82
|
+
clauses.push({
|
|
83
|
+
appliesTo: ['code-generation', 'test', 'frontend', 'documentation', 'all'],
|
|
84
|
+
text: 'Code must build and pass all tests before completion.',
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return clauses;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Get requirement lines to append to a step's prompt based on gate config.
|
|
91
|
+
* Filters clauses to those applicable to the step's category, then formats
|
|
92
|
+
* them as a bulleted list. Returns empty string if no clauses apply.
|
|
93
|
+
* @param config - Resolved quality gates configuration
|
|
94
|
+
* @param agentName - Agent assigned to the step (used for classification)
|
|
95
|
+
* @returns Formatted requirement text to append to the step prompt, or empty string
|
|
96
|
+
*/
|
|
97
|
+
function getGateRequirements(config, agentName) {
|
|
98
|
+
const clauses = buildGateClauses(config);
|
|
99
|
+
if (clauses.length === 0)
|
|
100
|
+
return '';
|
|
101
|
+
const category = classifyStep(agentName);
|
|
102
|
+
const applicable = clauses.filter(c => c.appliesTo.includes(category) || c.appliesTo.includes('all'));
|
|
103
|
+
if (applicable.length === 0)
|
|
104
|
+
return '';
|
|
105
|
+
const lines = applicable.map(c => `- ${c.text}`).join('\n');
|
|
106
|
+
return `\n\nQuality gate requirements:\n${lines}`;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Check whether the gate config indicates a need for an explicit test step.
|
|
110
|
+
* Returns true when testCoverage is enabled, which signals the planner
|
|
111
|
+
* should verify a worker test step exists in the plan.
|
|
112
|
+
* @param config - Resolved quality gates configuration
|
|
113
|
+
* @returns Whether a test step should be ensured
|
|
114
|
+
*/
|
|
115
|
+
function requiresTestStep(config) {
|
|
116
|
+
return config.enabled && config.gates.testCoverage.enabled;
|
|
117
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildRemediationStep = buildRemediationStep;
|
|
4
|
+
exports.runQualityGateRemediation = runQualityGateRemediation;
|
|
5
|
+
const logger_1 = require("./logger");
|
|
6
|
+
const quality_gates_1 = require("./quality-gates");
|
|
7
|
+
const logger = (0, logger_1.getLogger)('gate-remediation');
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// buildRemediationStep
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
/**
|
|
12
|
+
* Inspect a single gate result and, if it failed and auto-fix is enabled
|
|
13
|
+
* (and hasn't already been attempted), return a remediation step descriptor.
|
|
14
|
+
* Returns `null` when no action is needed.
|
|
15
|
+
*/
|
|
16
|
+
function buildRemediationStep(gateResult, configEnabled, triggeredFlag, triggeredFlags, agents, resolveAgent, taskDescription, warningMessage, afterStep, fallbackAgent) {
|
|
17
|
+
if (!gateResult || gateResult.status !== 'fail')
|
|
18
|
+
return null;
|
|
19
|
+
if (!configEnabled)
|
|
20
|
+
return null;
|
|
21
|
+
if (triggeredFlags[triggeredFlag])
|
|
22
|
+
return null;
|
|
23
|
+
const preferredAgent = resolveAgent(agents, 'integrator_finalizer')
|
|
24
|
+
? 'integrator_finalizer'
|
|
25
|
+
: fallbackAgent;
|
|
26
|
+
logger.warn(warningMessage);
|
|
27
|
+
triggeredFlags[triggeredFlag] = true;
|
|
28
|
+
// Append specific findings so the remediation agent knows exactly what to fix
|
|
29
|
+
let taskWithFindings = taskDescription;
|
|
30
|
+
if (gateResult.issues && gateResult.issues.length > 0) {
|
|
31
|
+
const findings = gateResult.issues.map(issue => {
|
|
32
|
+
let finding = `- ${issue.message}`;
|
|
33
|
+
if (issue.filePath)
|
|
34
|
+
finding += ` (${issue.filePath})`;
|
|
35
|
+
if (issue.hint)
|
|
36
|
+
finding += ` -- hint: ${issue.hint}`;
|
|
37
|
+
return finding;
|
|
38
|
+
}).join('\n');
|
|
39
|
+
taskWithFindings += `\n\nSpecific findings from the gate:\n${findings}`;
|
|
40
|
+
}
|
|
41
|
+
return { agent: preferredAgent, task: taskWithFindings, afterStep };
|
|
42
|
+
}
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
// runQualityGateRemediation
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
/**
|
|
47
|
+
* Run quality gates, check if auto-fix is possible, build remediation steps,
|
|
48
|
+
* consolidate them into a single step if multiple are needed, call
|
|
49
|
+
* `executeReplan`, re-run gates, and if still failing do a second attempt
|
|
50
|
+
* with specific findings.
|
|
51
|
+
*
|
|
52
|
+
* Returns whether the final gate run passed and the latest gate results.
|
|
53
|
+
*/
|
|
54
|
+
async function runQualityGateRemediation(params) {
|
|
55
|
+
const { workingDir, gatesConfig, gatesOut, baselineFiles, baseCommit, skippedReqIds, context, agents, options, resolveAgent, executeReplan, } = params;
|
|
56
|
+
// Initial quality-gate run
|
|
57
|
+
let gatesResult = await (0, quality_gates_1.run_quality_gates)(workingDir, gatesConfig, gatesOut, baselineFiles, baseCommit, skippedReqIds);
|
|
58
|
+
context.finalGateResults = gatesResult.results;
|
|
59
|
+
if (!gatesResult.passed && gatesConfig.failOnIssues) {
|
|
60
|
+
const failedIds = new Set(gatesResult.results.filter(r => r.status === 'fail').map(r => r.id));
|
|
61
|
+
let remediationAttempted = false;
|
|
62
|
+
const triggeredFlags = context.qualityGatesTriggered || {};
|
|
63
|
+
const canAutoFix = !!context.qualityGatesTriggered && ((failedIds.has('duplicate-blocks') && gatesConfig.autoAddRefactorStepOnDuplicateBlocks && !triggeredFlags.duplicateRefactorAdded) ||
|
|
64
|
+
(failedIds.has('readme-claims') && gatesConfig.autoAddReadmeTruthStepOnReadmeClaims && !triggeredFlags.readmeTruthAdded) ||
|
|
65
|
+
(failedIds.has('scaffold-defaults') && gatesConfig.autoAddScaffoldFixStepOnScaffoldDefaults && !triggeredFlags.scaffoldFixAdded) ||
|
|
66
|
+
(failedIds.has('hardcoded-config') && gatesConfig.autoAddConfigFixStepOnHardcodedConfig && !triggeredFlags.configFixAdded) ||
|
|
67
|
+
(failedIds.has('accessibility') && gatesConfig.autoAddAccessibilityFixStepOnAccessibility && !triggeredFlags.accessibilityFixAdded) ||
|
|
68
|
+
(failedIds.has('test-coverage') && gatesConfig.autoAddTestCoverageStepOnTestCoverage && !triggeredFlags.testCoverageFixAdded));
|
|
69
|
+
if (canAutoFix) {
|
|
70
|
+
// Remediation steps should depend only on steps that actually completed.
|
|
71
|
+
// Using the max step number can create dependencies on failed/blocked steps,
|
|
72
|
+
// causing the remediation to wait forever then timeout.
|
|
73
|
+
const completedStepNumbers = context.results
|
|
74
|
+
.filter(r => r.status === 'completed')
|
|
75
|
+
.map(r => r.stepNumber);
|
|
76
|
+
const maxCompletedStep = completedStepNumbers.length > 0
|
|
77
|
+
? Math.max(...completedStepNumbers)
|
|
78
|
+
: 0; // no dependency if nothing completed
|
|
79
|
+
const lastAgent = context.plan.steps[context.plan.steps.length - 1]?.agentName || 'integrator_finalizer';
|
|
80
|
+
const addSteps = [];
|
|
81
|
+
const dupStep = buildRemediationStep(gatesResult.results.find(r => r.id === 'duplicate-blocks'), gatesConfig.autoAddRefactorStepOnDuplicateBlocks, 'duplicateRefactorAdded', triggeredFlags, agents, resolveAgent, 'Quality gates flagged repeated code blocks. Extract shared utilities/hooks/middleware and refactor duplicates away. Use the gate report as the source of truth. Re-run tests and ensure quality gates pass.', '⚠️ Final quality gates: duplicate blocks detected; scheduling refactor', maxCompletedStep, lastAgent);
|
|
82
|
+
if (dupStep)
|
|
83
|
+
addSteps.push(dupStep);
|
|
84
|
+
const readmeStep = buildRemediationStep(gatesResult.results.find(r => r.id === 'readme-claims'), gatesConfig.autoAddReadmeTruthStepOnReadmeClaims, 'readmeTruthAdded', triggeredFlags, agents, resolveAgent, 'Quality gates flagged README claims that are not backed by code. Either implement the missing features or downgrade/remove the claims. Use the gate report as the source of truth. Re-run tests and ensure quality gates pass.', '⚠️ Final quality gates: README claims mismatch; scheduling truth step', maxCompletedStep, lastAgent);
|
|
85
|
+
if (readmeStep)
|
|
86
|
+
addSteps.push(readmeStep);
|
|
87
|
+
const scaffoldStep = buildRemediationStep(gatesResult.results.find(r => r.id === 'scaffold-defaults'), gatesConfig.autoAddScaffoldFixStepOnScaffoldDefaults, 'scaffoldFixAdded', triggeredFlags, agents, resolveAgent, 'Quality gates flagged scaffold defaults. Remove placeholder assets and generic scaffold README sections, and ensure HTML title/app metadata are meaningful. Use the gate report as the source of truth. Re-run tests and ensure quality gates pass.', '⚠️ Final quality gates: scaffold defaults detected; scheduling cleanup', maxCompletedStep, lastAgent);
|
|
88
|
+
if (scaffoldStep)
|
|
89
|
+
addSteps.push(scaffoldStep);
|
|
90
|
+
const configStep = buildRemediationStep(gatesResult.results.find(r => r.id === 'hardcoded-config'), gatesConfig.autoAddConfigFixStepOnHardcodedConfig, 'configFixAdded', triggeredFlags, agents, resolveAgent, 'Quality gates flagged hardcoded config values. Move API base URLs, ports, retry counts, timeouts, and environment-specific values into env/typed config. For Vite proxy targets, prefer import.meta.env with a safe default. Use the gate report as the source of truth. Re-run tests and ensure quality gates pass.', '⚠️ Final quality gates: hardcoded config detected; scheduling cleanup', maxCompletedStep, lastAgent);
|
|
91
|
+
if (configStep)
|
|
92
|
+
addSteps.push(configStep);
|
|
93
|
+
const a11yStep = buildRemediationStep(gatesResult.results.find(r => r.id === 'accessibility'), gatesConfig.autoAddAccessibilityFixStepOnAccessibility, 'accessibilityFixAdded', triggeredFlags, agents, resolveAgent, 'Quality gates flagged accessibility issues. Fix all items from the gate report: skip-to-content link, heading hierarchy, aria-labels, focus-visible styles, meta description + theme-color tags, responsive CSS (viewport meta, media queries or relative units), CSS custom properties on :root with prefers-color-scheme:dark override, semantic HTML landmarks (main, nav, header), img alt attributes. Use the gate report as the source of truth. Re-run tests and ensure quality gates pass.', '⚠️ Final quality gates: accessibility issues detected; scheduling fix', maxCompletedStep, lastAgent);
|
|
94
|
+
if (a11yStep)
|
|
95
|
+
addSteps.push(a11yStep);
|
|
96
|
+
const testCovStep = buildRemediationStep(gatesResult.results.find(r => r.id === 'test-coverage'), gatesConfig.autoAddTestCoverageStepOnTestCoverage, 'testCoverageFixAdded', triggeredFlags, agents, resolveAgent, 'Quality gates flagged missing test coverage. Add tests for uncovered source files, ensure each test file contains real assertions, and add component-level tests for React projects. Use the gate report as the source of truth. Re-run tests and ensure quality gates pass.', '⚠️ Final quality gates: test coverage gaps detected; scheduling fix', maxCompletedStep, lastAgent);
|
|
97
|
+
if (testCovStep)
|
|
98
|
+
addSteps.push(testCovStep);
|
|
99
|
+
// Consolidate multiple gate failures into a single remediation step
|
|
100
|
+
// to avoid burning one premium request per gate failure.
|
|
101
|
+
if (addSteps.length > 1) {
|
|
102
|
+
const combinedTask = addSteps.map(s => s.task).join('\n\nALSO: ');
|
|
103
|
+
const singleStep = {
|
|
104
|
+
agent: addSteps[0].agent,
|
|
105
|
+
task: combinedTask,
|
|
106
|
+
};
|
|
107
|
+
if (addSteps[0].afterStep !== undefined) {
|
|
108
|
+
singleStep.afterStep = addSteps[0].afterStep;
|
|
109
|
+
}
|
|
110
|
+
addSteps.length = 0;
|
|
111
|
+
addSteps.push(singleStep);
|
|
112
|
+
logger.warn(`⚠️ Final quality gates failed (${failedIds.size} gates); scheduling single consolidated remediation step...`);
|
|
113
|
+
}
|
|
114
|
+
else if (addSteps.length === 1) {
|
|
115
|
+
logger.warn('⚠️ Final quality gates failed; attempting one remediation pass...');
|
|
116
|
+
}
|
|
117
|
+
if (addSteps.length > 0) {
|
|
118
|
+
remediationAttempted = true;
|
|
119
|
+
await executeReplan(context, { retrySteps: [], addSteps }, agents, options);
|
|
120
|
+
gatesResult = await (0, quality_gates_1.run_quality_gates)(workingDir, gatesConfig, gatesOut, baselineFiles, baseCommit, skippedReqIds);
|
|
121
|
+
context.finalGateResults = gatesResult.results;
|
|
122
|
+
// Second remediation attempt if gates still fail after first fix
|
|
123
|
+
if (!gatesResult.passed && gatesConfig.failOnIssues) {
|
|
124
|
+
const stillFailed = gatesResult.results.filter(r => r.status === 'fail');
|
|
125
|
+
if (stillFailed.length > 0) {
|
|
126
|
+
const findings = stillFailed.flatMap(gate => gate.issues.map(issue => {
|
|
127
|
+
let desc = `[${gate.id}] ${issue.message}`;
|
|
128
|
+
if (issue.filePath)
|
|
129
|
+
desc += ` (${issue.filePath})`;
|
|
130
|
+
if (issue.hint)
|
|
131
|
+
desc += ` -- hint: ${issue.hint}`;
|
|
132
|
+
return desc;
|
|
133
|
+
})).join('\n');
|
|
134
|
+
const retryTask = [
|
|
135
|
+
'The previous remediation attempt did not fully resolve the quality gate failures.',
|
|
136
|
+
'The following issues remain. Fix each one specifically:',
|
|
137
|
+
'',
|
|
138
|
+
findings,
|
|
139
|
+
'',
|
|
140
|
+
'Verify your fixes by checking that the specific issues listed above are resolved.',
|
|
141
|
+
'Run tests to ensure nothing is broken.',
|
|
142
|
+
].join('\n');
|
|
143
|
+
const retryAgent = resolveAgent(agents, 'integrator_finalizer')
|
|
144
|
+
? 'integrator_finalizer'
|
|
145
|
+
: lastAgent;
|
|
146
|
+
const retryMaxStep = Math.max(...context.plan.steps.map(s => s.stepNumber));
|
|
147
|
+
logger.warn('⚠️ First remediation did not resolve all gate failures. Running second attempt with specific findings...');
|
|
148
|
+
await executeReplan(context, {
|
|
149
|
+
retrySteps: [],
|
|
150
|
+
addSteps: [{ agent: retryAgent, task: retryTask, afterStep: retryMaxStep }],
|
|
151
|
+
}, agents, options);
|
|
152
|
+
gatesResult = await (0, quality_gates_1.run_quality_gates)(workingDir, gatesConfig, gatesOut, baselineFiles, baseCommit, skippedReqIds);
|
|
153
|
+
context.finalGateResults = gatesResult.results;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (!gatesResult.passed) {
|
|
159
|
+
if (remediationAttempted) {
|
|
160
|
+
// Remediation was attempted but couldn't fully resolve all issues.
|
|
161
|
+
// Downgrade to a warning so callers can decide how to handle.
|
|
162
|
+
const remaining = gatesResult.results
|
|
163
|
+
.filter(r => r.status === 'fail')
|
|
164
|
+
.map(r => `${r.id} (${r.issues.length} issues)`);
|
|
165
|
+
logger.warn(`⚠️ Quality gates still have issues after remediation: ${remaining.join(', ')}`);
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
logger.error('❌ Quality gates failed. See report in:', gatesOut);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return { passed: gatesResult.passed, results: gatesResult.results };
|
|
173
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GitHubIssuesIngester = void 0;
|
|
4
|
+
const child_process_1 = require("child_process");
|
|
5
|
+
const logger_1 = require("./logger");
|
|
6
|
+
const logger = (0, logger_1.getLogger)('github-issues-ingester');
|
|
7
|
+
/**
|
|
8
|
+
* GitHub Issues Ingester - fetch open issues via gh CLI
|
|
9
|
+
* Only used if gh CLI is available and repo has .git directory
|
|
10
|
+
*/
|
|
11
|
+
class GitHubIssuesIngester {
|
|
12
|
+
/**
|
|
13
|
+
* Check if gh CLI is available
|
|
14
|
+
*/
|
|
15
|
+
async isGhCliAvailable() {
|
|
16
|
+
return new Promise((resolve) => {
|
|
17
|
+
const proc = (0, child_process_1.spawn)('which', ['gh']);
|
|
18
|
+
proc.on('close', (code) => {
|
|
19
|
+
resolve(code === 0);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Fetch open issues for a repository
|
|
25
|
+
*/
|
|
26
|
+
async fetchIssues(repoPath) {
|
|
27
|
+
const ghAvailable = await this.isGhCliAvailable();
|
|
28
|
+
if (!ghAvailable) {
|
|
29
|
+
logger.warn('gh CLI not available, skipping issue ingestion');
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
return new Promise((resolve, _reject) => {
|
|
33
|
+
const proc = (0, child_process_1.spawn)('gh', ['issue', 'list', '--json', 'number,title,url,labels,createdAt', '--limit', '50'], {
|
|
34
|
+
cwd: repoPath,
|
|
35
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
36
|
+
});
|
|
37
|
+
let stdout = '';
|
|
38
|
+
let stderr = '';
|
|
39
|
+
proc.stdout.on('data', (data) => {
|
|
40
|
+
stdout += data.toString();
|
|
41
|
+
});
|
|
42
|
+
proc.stderr.on('data', (data) => {
|
|
43
|
+
stderr += data.toString();
|
|
44
|
+
});
|
|
45
|
+
proc.on('close', (code) => {
|
|
46
|
+
if (code !== 0) {
|
|
47
|
+
logger.warn(`gh issue list failed: ${stderr}`);
|
|
48
|
+
resolve([]);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
const rawIssues = JSON.parse(stdout);
|
|
53
|
+
const issues = rawIssues.map((issue) => ({
|
|
54
|
+
number: issue.number,
|
|
55
|
+
title: issue.title,
|
|
56
|
+
url: issue.url,
|
|
57
|
+
labels: issue.labels?.map((l) => typeof l === 'string' ? l : l.name || '') || [],
|
|
58
|
+
repoName: this.extractRepoName(issue.url),
|
|
59
|
+
createdAt: issue.createdAt
|
|
60
|
+
}));
|
|
61
|
+
resolve(issues);
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
logger.warn(`Failed to parse gh issue output: ${error}`);
|
|
65
|
+
resolve([]);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Extract repo name from GitHub URL
|
|
72
|
+
*/
|
|
73
|
+
extractRepoName(url) {
|
|
74
|
+
const match = url.match(/github\.com\/([^/]+\/[^/]+)/);
|
|
75
|
+
return match ? match[1] : 'unknown';
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Link issues to plan context based on keywords
|
|
79
|
+
*/
|
|
80
|
+
linkIssuesToTasks(issues, goal) {
|
|
81
|
+
// Simple keyword matching - find issues relevant to goal
|
|
82
|
+
const goalKeywords = goal.toLowerCase().split(/\s+/).filter(w => w.length > 3);
|
|
83
|
+
return issues.filter(issue => {
|
|
84
|
+
const searchText = `${issue.title} ${issue.labels.join(' ')}`.toLowerCase();
|
|
85
|
+
return goalKeywords.some(keyword => searchText.includes(keyword));
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Close an issue with a comment
|
|
90
|
+
*/
|
|
91
|
+
async closeIssue(issueNumber, comment, repoPath) {
|
|
92
|
+
const ghAvailable = await this.isGhCliAvailable();
|
|
93
|
+
if (!ghAvailable) {
|
|
94
|
+
return { success: false, error: 'gh CLI not available' };
|
|
95
|
+
}
|
|
96
|
+
return new Promise((resolve) => {
|
|
97
|
+
// Add comment first
|
|
98
|
+
const commentProc = (0, child_process_1.spawn)('gh', ['issue', 'comment', issueNumber.toString(), '--body', comment], {
|
|
99
|
+
cwd: repoPath,
|
|
100
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
101
|
+
});
|
|
102
|
+
let commentError = '';
|
|
103
|
+
commentProc.stderr.on('data', (data) => {
|
|
104
|
+
commentError += data.toString();
|
|
105
|
+
});
|
|
106
|
+
commentProc.on('close', (commentCode) => {
|
|
107
|
+
if (commentCode !== 0) {
|
|
108
|
+
resolve({ success: false, error: `Failed to add comment: ${commentError}` });
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
// Then close the issue
|
|
112
|
+
const closeProc = (0, child_process_1.spawn)('gh', ['issue', 'close', issueNumber.toString()], {
|
|
113
|
+
cwd: repoPath,
|
|
114
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
115
|
+
});
|
|
116
|
+
let closeError = '';
|
|
117
|
+
closeProc.stderr.on('data', (data) => {
|
|
118
|
+
closeError += data.toString();
|
|
119
|
+
});
|
|
120
|
+
closeProc.on('close', (closeCode) => {
|
|
121
|
+
if (closeCode !== 0) {
|
|
122
|
+
resolve({ success: false, error: `Failed to close issue: ${closeError}` });
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
resolve({ success: true });
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Close multiple issues resolved by a step
|
|
132
|
+
*/
|
|
133
|
+
async closeResolvedIssues(issueNumbers, stepNumber, agentName, repoPath) {
|
|
134
|
+
const comment = `✅ Resolved by swarm orchestrator - Step ${stepNumber} (${agentName})`;
|
|
135
|
+
let closed = 0;
|
|
136
|
+
let failed = 0;
|
|
137
|
+
for (const issueNumber of issueNumbers) {
|
|
138
|
+
const result = await this.closeIssue(issueNumber, comment, repoPath);
|
|
139
|
+
if (result.success) {
|
|
140
|
+
closed++;
|
|
141
|
+
logger.info(` ✓ Closed issue #${issueNumber}`);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
failed++;
|
|
145
|
+
logger.warn(` ✗ Failed to close issue #${issueNumber}: ${result.error}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return { closed, failed };
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
exports.GitHubIssuesIngester = GitHubIssuesIngester;
|
|
152
|
+
exports.default = GitHubIssuesIngester;
|