@specforge/mcp 2.6.0 → 3.0.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/README.md +73 -0
- package/bin/{specforge-mcp → specforge} +0 -5
- package/dist/ai-provider/circuit-breaker.d.ts +63 -0
- package/dist/ai-provider/circuit-breaker.d.ts.map +1 -0
- package/dist/ai-provider/circuit-breaker.js +160 -0
- package/dist/ai-provider/circuit-breaker.js.map +1 -0
- package/dist/ai-provider/cli-version.d.ts +50 -0
- package/dist/ai-provider/cli-version.d.ts.map +1 -0
- package/dist/ai-provider/cli-version.js +141 -0
- package/dist/ai-provider/cli-version.js.map +1 -0
- package/dist/ai-provider/config-loader.d.ts +45 -0
- package/dist/ai-provider/config-loader.d.ts.map +1 -0
- package/dist/ai-provider/config-loader.js +106 -0
- package/dist/ai-provider/config-loader.js.map +1 -0
- package/dist/ai-provider/errors.d.ts +48 -0
- package/dist/ai-provider/errors.d.ts.map +1 -0
- package/dist/ai-provider/errors.js +102 -0
- package/dist/ai-provider/errors.js.map +1 -0
- package/dist/ai-provider/events.d.ts +73 -0
- package/dist/ai-provider/events.d.ts.map +1 -0
- package/dist/ai-provider/events.js +75 -0
- package/dist/ai-provider/events.js.map +1 -0
- package/dist/ai-provider/factory.d.ts +31 -0
- package/dist/ai-provider/factory.d.ts.map +1 -0
- package/dist/ai-provider/factory.js +100 -0
- package/dist/ai-provider/factory.js.map +1 -0
- package/dist/ai-provider/index.d.ts +24 -0
- package/dist/ai-provider/index.d.ts.map +1 -0
- package/dist/ai-provider/index.js +46 -0
- package/dist/ai-provider/index.js.map +1 -0
- package/dist/ai-provider/instance-coordinator.d.ts +54 -0
- package/dist/ai-provider/instance-coordinator.d.ts.map +1 -0
- package/dist/ai-provider/instance-coordinator.js +199 -0
- package/dist/ai-provider/instance-coordinator.js.map +1 -0
- package/dist/ai-provider/jsonl-parser.d.ts +43 -0
- package/dist/ai-provider/jsonl-parser.d.ts.map +1 -0
- package/dist/ai-provider/jsonl-parser.js +107 -0
- package/dist/ai-provider/jsonl-parser.js.map +1 -0
- package/dist/ai-provider/lifecycle.d.ts +50 -0
- package/dist/ai-provider/lifecycle.d.ts.map +1 -0
- package/dist/ai-provider/lifecycle.js +145 -0
- package/dist/ai-provider/lifecycle.js.map +1 -0
- package/dist/ai-provider/logger.d.ts +69 -0
- package/dist/ai-provider/logger.d.ts.map +1 -0
- package/dist/ai-provider/logger.js +161 -0
- package/dist/ai-provider/logger.js.map +1 -0
- package/dist/ai-provider/metrics.d.ts +91 -0
- package/dist/ai-provider/metrics.d.ts.map +1 -0
- package/dist/ai-provider/metrics.js +187 -0
- package/dist/ai-provider/metrics.js.map +1 -0
- package/dist/ai-provider/process-manager.d.ts +97 -0
- package/dist/ai-provider/process-manager.d.ts.map +1 -0
- package/dist/ai-provider/process-manager.js +477 -0
- package/dist/ai-provider/process-manager.js.map +1 -0
- package/dist/ai-provider/providers/claude-code.d.ts +64 -0
- package/dist/ai-provider/providers/claude-code.d.ts.map +1 -0
- package/dist/ai-provider/providers/claude-code.js +205 -0
- package/dist/ai-provider/providers/claude-code.js.map +1 -0
- package/dist/ai-provider/retry-executor.d.ts +52 -0
- package/dist/ai-provider/retry-executor.d.ts.map +1 -0
- package/dist/ai-provider/retry-executor.js +138 -0
- package/dist/ai-provider/retry-executor.js.map +1 -0
- package/dist/ai-provider/safe-args.d.ts +58 -0
- package/dist/ai-provider/safe-args.d.ts.map +1 -0
- package/dist/ai-provider/safe-args.js +176 -0
- package/dist/ai-provider/safe-args.js.map +1 -0
- package/dist/ai-provider/semaphore.d.ts +50 -0
- package/dist/ai-provider/semaphore.d.ts.map +1 -0
- package/dist/ai-provider/semaphore.js +97 -0
- package/dist/ai-provider/semaphore.js.map +1 -0
- package/dist/ai-provider/tracer.d.ts +67 -0
- package/dist/ai-provider/tracer.d.ts.map +1 -0
- package/dist/ai-provider/tracer.js +209 -0
- package/dist/ai-provider/tracer.js.map +1 -0
- package/dist/ai-provider/types.d.ts +181 -0
- package/dist/ai-provider/types.d.ts.map +1 -0
- package/dist/ai-provider/types.js +8 -0
- package/dist/ai-provider/types.js.map +1 -0
- package/dist/autopilot/agents/agent-runner.d.ts +109 -0
- package/dist/autopilot/agents/agent-runner.d.ts.map +1 -0
- package/dist/autopilot/agents/agent-runner.js +731 -0
- package/dist/autopilot/agents/agent-runner.js.map +1 -0
- package/dist/autopilot/agents/agent-selector.d.ts +59 -0
- package/dist/autopilot/agents/agent-selector.d.ts.map +1 -0
- package/dist/autopilot/agents/agent-selector.js +234 -0
- package/dist/autopilot/agents/agent-selector.js.map +1 -0
- package/dist/autopilot/agents/model-selector.d.ts +49 -0
- package/dist/autopilot/agents/model-selector.d.ts.map +1 -0
- package/dist/autopilot/agents/model-selector.js +62 -0
- package/dist/autopilot/agents/model-selector.js.map +1 -0
- package/dist/autopilot/agents/profiles/builtin.d.ts +55 -0
- package/dist/autopilot/agents/profiles/builtin.d.ts.map +1 -0
- package/dist/autopilot/agents/profiles/builtin.js +323 -0
- package/dist/autopilot/agents/profiles/builtin.js.map +1 -0
- package/dist/autopilot/agents/profiles/types.d.ts +98 -0
- package/dist/autopilot/agents/profiles/types.d.ts.map +1 -0
- package/dist/autopilot/agents/profiles/types.js +17 -0
- package/dist/autopilot/agents/profiles/types.js.map +1 -0
- package/dist/autopilot/api/autopilot-api-client.d.ts +217 -0
- package/dist/autopilot/api/autopilot-api-client.d.ts.map +1 -0
- package/dist/autopilot/api/autopilot-api-client.js +402 -0
- package/dist/autopilot/api/autopilot-api-client.js.map +1 -0
- package/dist/autopilot/cli/abort.d.ts +20 -0
- package/dist/autopilot/cli/abort.d.ts.map +1 -0
- package/dist/autopilot/cli/abort.js +201 -0
- package/dist/autopilot/cli/abort.js.map +1 -0
- package/dist/autopilot/cli/display.d.ts +63 -0
- package/dist/autopilot/cli/display.d.ts.map +1 -0
- package/dist/autopilot/cli/display.js +260 -0
- package/dist/autopilot/cli/display.js.map +1 -0
- package/dist/autopilot/cli/index.d.ts +24 -0
- package/dist/autopilot/cli/index.d.ts.map +1 -0
- package/dist/autopilot/cli/index.js +79 -0
- package/dist/autopilot/cli/index.js.map +1 -0
- package/dist/autopilot/cli/pause.d.ts +18 -0
- package/dist/autopilot/cli/pause.d.ts.map +1 -0
- package/dist/autopilot/cli/pause.js +110 -0
- package/dist/autopilot/cli/pause.js.map +1 -0
- package/dist/autopilot/cli/resume.d.ts +22 -0
- package/dist/autopilot/cli/resume.d.ts.map +1 -0
- package/dist/autopilot/cli/resume.js +172 -0
- package/dist/autopilot/cli/resume.js.map +1 -0
- package/dist/autopilot/cli/run.d.ts +25 -0
- package/dist/autopilot/cli/run.d.ts.map +1 -0
- package/dist/autopilot/cli/run.js +220 -0
- package/dist/autopilot/cli/run.js.map +1 -0
- package/dist/autopilot/cli/status.d.ts +20 -0
- package/dist/autopilot/cli/status.d.ts.map +1 -0
- package/dist/autopilot/cli/status.js +217 -0
- package/dist/autopilot/cli/status.js.map +1 -0
- package/dist/autopilot/config.d.ts +45 -0
- package/dist/autopilot/config.d.ts.map +1 -0
- package/dist/autopilot/config.js +269 -0
- package/dist/autopilot/config.js.map +1 -0
- package/dist/autopilot/core/dependency-resolver.d.ts +108 -0
- package/dist/autopilot/core/dependency-resolver.d.ts.map +1 -0
- package/dist/autopilot/core/dependency-resolver.js +394 -0
- package/dist/autopilot/core/dependency-resolver.js.map +1 -0
- package/dist/autopilot/core/dispatcher.d.ts +215 -0
- package/dist/autopilot/core/dispatcher.d.ts.map +1 -0
- package/dist/autopilot/core/dispatcher.js +594 -0
- package/dist/autopilot/core/dispatcher.js.map +1 -0
- package/dist/autopilot/core/failure-handler.d.ts +145 -0
- package/dist/autopilot/core/failure-handler.d.ts.map +1 -0
- package/dist/autopilot/core/failure-handler.js +308 -0
- package/dist/autopilot/core/failure-handler.js.map +1 -0
- package/dist/autopilot/core/rate-limit-handler.d.ts +108 -0
- package/dist/autopilot/core/rate-limit-handler.d.ts.map +1 -0
- package/dist/autopilot/core/rate-limit-handler.js +195 -0
- package/dist/autopilot/core/rate-limit-handler.js.map +1 -0
- package/dist/autopilot/core/state-manager.d.ts +160 -0
- package/dist/autopilot/core/state-manager.d.ts.map +1 -0
- package/dist/autopilot/core/state-manager.js +393 -0
- package/dist/autopilot/core/state-manager.js.map +1 -0
- package/dist/autopilot/core/timeout-manager.d.ts +95 -0
- package/dist/autopilot/core/timeout-manager.d.ts.map +1 -0
- package/dist/autopilot/core/timeout-manager.js +188 -0
- package/dist/autopilot/core/timeout-manager.js.map +1 -0
- package/dist/autopilot/git/branch-manager.d.ts +117 -0
- package/dist/autopilot/git/branch-manager.d.ts.map +1 -0
- package/dist/autopilot/git/branch-manager.js +238 -0
- package/dist/autopilot/git/branch-manager.js.map +1 -0
- package/dist/autopilot/git/index.d.ts +9 -0
- package/dist/autopilot/git/index.d.ts.map +1 -0
- package/dist/autopilot/git/index.js +9 -0
- package/dist/autopilot/git/index.js.map +1 -0
- package/dist/autopilot/git/merge-manager.d.ts +118 -0
- package/dist/autopilot/git/merge-manager.d.ts.map +1 -0
- package/dist/autopilot/git/merge-manager.js +304 -0
- package/dist/autopilot/git/merge-manager.js.map +1 -0
- package/dist/autopilot/git/worktree-manager.d.ts +128 -0
- package/dist/autopilot/git/worktree-manager.d.ts.map +1 -0
- package/dist/autopilot/git/worktree-manager.js +298 -0
- package/dist/autopilot/git/worktree-manager.js.map +1 -0
- package/dist/autopilot/index.d.ts +30 -0
- package/dist/autopilot/index.d.ts.map +1 -0
- package/dist/autopilot/index.js +55 -0
- package/dist/autopilot/index.js.map +1 -0
- package/dist/autopilot/sync/index.d.ts +7 -0
- package/dist/autopilot/sync/index.d.ts.map +1 -0
- package/dist/autopilot/sync/index.js +7 -0
- package/dist/autopilot/sync/index.js.map +1 -0
- package/dist/autopilot/sync/sync-manager.d.ts +168 -0
- package/dist/autopilot/sync/sync-manager.d.ts.map +1 -0
- package/dist/autopilot/sync/sync-manager.js +303 -0
- package/dist/autopilot/sync/sync-manager.js.map +1 -0
- package/dist/autopilot/types.d.ts +454 -0
- package/dist/autopilot/types.d.ts.map +1 -0
- package/dist/autopilot/types.js +26 -0
- package/dist/autopilot/types.js.map +1 -0
- package/dist/autopilot/utils/audit-logger.d.ts +176 -0
- package/dist/autopilot/utils/audit-logger.d.ts.map +1 -0
- package/dist/autopilot/utils/audit-logger.js +308 -0
- package/dist/autopilot/utils/audit-logger.js.map +1 -0
- package/dist/autopilot/utils/cost-tracker.d.ts +162 -0
- package/dist/autopilot/utils/cost-tracker.d.ts.map +1 -0
- package/dist/autopilot/utils/cost-tracker.js +269 -0
- package/dist/autopilot/utils/cost-tracker.js.map +1 -0
- package/dist/autopilot/utils/index.d.ts +9 -0
- package/dist/autopilot/utils/index.d.ts.map +1 -0
- package/dist/autopilot/utils/index.js +9 -0
- package/dist/autopilot/utils/index.js.map +1 -0
- package/dist/autopilot/utils/progress-reporter.d.ts +132 -0
- package/dist/autopilot/utils/progress-reporter.d.ts.map +1 -0
- package/dist/autopilot/utils/progress-reporter.js +290 -0
- package/dist/autopilot/utils/progress-reporter.js.map +1 -0
- package/dist/autopilot/worker/worker-pool.d.ts +179 -0
- package/dist/autopilot/worker/worker-pool.d.ts.map +1 -0
- package/dist/autopilot/worker/worker-pool.js +331 -0
- package/dist/autopilot/worker/worker-pool.js.map +1 -0
- package/dist/autopilot/worker/worker-session.d.ts +171 -0
- package/dist/autopilot/worker/worker-session.d.ts.map +1 -0
- package/dist/autopilot/worker/worker-session.js +295 -0
- package/dist/autopilot/worker/worker-session.js.map +1 -0
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +4 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/dist/tools/core/epic.js +1 -1
- package/dist/tools/core/epic.js.map +1 -1
- package/dist/tools/core/lookup.d.ts.map +1 -1
- package/dist/tools/core/lookup.js +3 -2
- package/dist/tools/core/lookup.js.map +1 -1
- package/dist/tools/core/specification.js +1 -1
- package/dist/tools/core/specification.js.map +1 -1
- package/dist/tools/core/ticket.d.ts.map +1 -1
- package/dist/tools/core/ticket.js +4 -6
- package/dist/tools/core/ticket.js.map +1 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +60 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/validation/index.d.ts.map +1 -1
- package/dist/validation/index.js +4 -1
- package/dist/validation/index.js.map +1 -1
- package/package.json +8 -4
|
@@ -0,0 +1,731 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Runner
|
|
3
|
+
*
|
|
4
|
+
* Executes ticket implementation using AI Provider.
|
|
5
|
+
*/
|
|
6
|
+
import { exec } from 'child_process';
|
|
7
|
+
import { promisify } from 'util';
|
|
8
|
+
import { randomUUID } from 'crypto';
|
|
9
|
+
import { AgentSelector } from './agent-selector.js';
|
|
10
|
+
import { selectModelByComplexity, estimateModelCost } from './model-selector.js';
|
|
11
|
+
import { parseSessionTokens } from '../../ai-provider/jsonl-parser.js';
|
|
12
|
+
const execAsync = promisify(exec);
|
|
13
|
+
/**
|
|
14
|
+
* Agent Runner
|
|
15
|
+
*/
|
|
16
|
+
export class AgentRunner {
|
|
17
|
+
aiProvider;
|
|
18
|
+
workingDirectory;
|
|
19
|
+
agentSelector;
|
|
20
|
+
autoCommit;
|
|
21
|
+
timeout;
|
|
22
|
+
constructor(options) {
|
|
23
|
+
this.aiProvider = options.aiProvider;
|
|
24
|
+
this.workingDirectory = options.workingDirectory;
|
|
25
|
+
this.agentSelector = options.agentSelector || new AgentSelector();
|
|
26
|
+
this.autoCommit = options.autoCommit ?? true;
|
|
27
|
+
this.timeout = options.timeout ?? 15 * 60 * 1000; // 15 minutes
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Run implementation for a ticket
|
|
31
|
+
*/
|
|
32
|
+
async run(context) {
|
|
33
|
+
const startTime = Date.now();
|
|
34
|
+
try {
|
|
35
|
+
// Select agent profile
|
|
36
|
+
const affectedFiles = AgentSelector.extractAffectedFiles({
|
|
37
|
+
ticket: context.ticket,
|
|
38
|
+
epic: context.epic,
|
|
39
|
+
specification: context.specification,
|
|
40
|
+
});
|
|
41
|
+
const profile = this.agentSelector.selectProfile({
|
|
42
|
+
ticket: context.ticket,
|
|
43
|
+
epic: context.epic,
|
|
44
|
+
specification: context.specification,
|
|
45
|
+
affectedFiles,
|
|
46
|
+
});
|
|
47
|
+
// Select model based on complexity (or use profile model if specified)
|
|
48
|
+
const model = profile.model ||
|
|
49
|
+
selectModelByComplexity(context.ticket.complexity);
|
|
50
|
+
// Build prompt
|
|
51
|
+
const prompt = this.buildPrompt(context, profile);
|
|
52
|
+
// Generate unique session ID for JSONL log linking
|
|
53
|
+
const sessionId = randomUUID();
|
|
54
|
+
// Run AI implementation
|
|
55
|
+
const aiResult = await this.aiProvider.run(prompt, {
|
|
56
|
+
workingDirectory: this.workingDirectory,
|
|
57
|
+
timeout: profile.timeoutMs || this.timeout,
|
|
58
|
+
model,
|
|
59
|
+
sessionId,
|
|
60
|
+
});
|
|
61
|
+
if (!aiResult.success) {
|
|
62
|
+
return {
|
|
63
|
+
success: false,
|
|
64
|
+
error: aiResult.error || 'AI implementation failed',
|
|
65
|
+
durationMs: Date.now() - startTime,
|
|
66
|
+
modelUsed: model,
|
|
67
|
+
sessionId,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
// Parse discoveries from AI output
|
|
71
|
+
const discoveries = this.parseDiscoveries(aiResult.output || '');
|
|
72
|
+
// Run validation commands
|
|
73
|
+
const validationResults = await this.runValidations(profile);
|
|
74
|
+
const allValidationsPassed = validationResults.every(r => r.success || !profile.validationCommands?.find(v => v.name === r.name)?.required);
|
|
75
|
+
// Extract file changes from output
|
|
76
|
+
const { created, modified, deleted } = this.extractFileChanges(aiResult.output);
|
|
77
|
+
if (!allValidationsPassed) {
|
|
78
|
+
const failedValidations = validationResults.filter(r => !r.success);
|
|
79
|
+
return {
|
|
80
|
+
success: false,
|
|
81
|
+
output: aiResult.output,
|
|
82
|
+
error: `Validation failed: ${failedValidations.map(v => v.name).join(', ')}`,
|
|
83
|
+
filesCreated: created,
|
|
84
|
+
filesModified: modified,
|
|
85
|
+
filesDeleted: deleted,
|
|
86
|
+
testsPassed: false,
|
|
87
|
+
durationMs: Date.now() - startTime,
|
|
88
|
+
modelUsed: model,
|
|
89
|
+
discoveries,
|
|
90
|
+
sessionId,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
// Auto-commit if enabled and capture SHA
|
|
94
|
+
let commitSha;
|
|
95
|
+
if (this.autoCommit) {
|
|
96
|
+
await this.commit(context.ticket);
|
|
97
|
+
commitSha = await this.getLastCommitSha();
|
|
98
|
+
}
|
|
99
|
+
// Estimate tokens as fallback
|
|
100
|
+
const estimatedTokens = {
|
|
101
|
+
inputTokens: Math.ceil(prompt.length / 4),
|
|
102
|
+
outputTokens: Math.ceil((aiResult.output?.length || 0) / 4),
|
|
103
|
+
};
|
|
104
|
+
// Try to read actual token usage from JSONL logs
|
|
105
|
+
let actualTokens = estimatedTokens;
|
|
106
|
+
let actualCost = estimateModelCost(model, estimatedTokens);
|
|
107
|
+
const parsedTokens = await parseSessionTokens(sessionId, this.workingDirectory);
|
|
108
|
+
if (parsedTokens) {
|
|
109
|
+
// Use actual tokens from JSONL (include cache tokens in input count)
|
|
110
|
+
actualTokens = {
|
|
111
|
+
inputTokens: parsedTokens.inputTokens + parsedTokens.cacheCreationTokens + parsedTokens.cacheReadTokens,
|
|
112
|
+
outputTokens: parsedTokens.outputTokens,
|
|
113
|
+
};
|
|
114
|
+
// Use actual cost if available from JSONL
|
|
115
|
+
if (parsedTokens.costUSD > 0) {
|
|
116
|
+
actualCost = parsedTokens.costUSD;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
success: true,
|
|
121
|
+
output: aiResult.output,
|
|
122
|
+
filesCreated: created,
|
|
123
|
+
filesModified: modified,
|
|
124
|
+
filesDeleted: deleted,
|
|
125
|
+
testsPassed: allValidationsPassed,
|
|
126
|
+
tokensUsed: actualTokens,
|
|
127
|
+
costUsd: actualCost,
|
|
128
|
+
durationMs: Date.now() - startTime,
|
|
129
|
+
modelUsed: model,
|
|
130
|
+
commitSha,
|
|
131
|
+
discoveries,
|
|
132
|
+
sessionId,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
return {
|
|
137
|
+
success: false,
|
|
138
|
+
error: error.message,
|
|
139
|
+
durationMs: Date.now() - startTime,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Build the implementation prompt with full ticket data
|
|
145
|
+
*/
|
|
146
|
+
buildPrompt(context, profile) {
|
|
147
|
+
const { ticket, epic, specification } = context;
|
|
148
|
+
const extendedContext = context;
|
|
149
|
+
const parts = [];
|
|
150
|
+
// Header
|
|
151
|
+
parts.push(`# Implementation Task: ${ticket.title}\n`);
|
|
152
|
+
// Context
|
|
153
|
+
parts.push(`## Context`);
|
|
154
|
+
parts.push(`- **Specification**: ${specification.title}`);
|
|
155
|
+
parts.push(`- **Epic**: ${epic.title} (Epic ${epic.epicNumber})`);
|
|
156
|
+
parts.push(`- **Ticket**: ${ticket.title} (#${ticket.ticketNumber})`);
|
|
157
|
+
if (ticket.complexity) {
|
|
158
|
+
parts.push(`- **Complexity**: ${ticket.complexity}`);
|
|
159
|
+
}
|
|
160
|
+
if (ticket.priority) {
|
|
161
|
+
parts.push(`- **Priority**: ${ticket.priority}`);
|
|
162
|
+
}
|
|
163
|
+
parts.push('');
|
|
164
|
+
// Description
|
|
165
|
+
if (ticket.description) {
|
|
166
|
+
parts.push(`## Description`);
|
|
167
|
+
parts.push(ticket.description);
|
|
168
|
+
parts.push('');
|
|
169
|
+
}
|
|
170
|
+
// Acceptance Criteria
|
|
171
|
+
if (ticket.acceptanceCriteria && ticket.acceptanceCriteria.length > 0) {
|
|
172
|
+
parts.push(`## Acceptance Criteria`);
|
|
173
|
+
for (const criterion of ticket.acceptanceCriteria) {
|
|
174
|
+
if (typeof criterion === 'string') {
|
|
175
|
+
parts.push(`- [ ] ${criterion}`);
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
parts.push(`- [ ] ${criterion.description}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
parts.push('');
|
|
182
|
+
}
|
|
183
|
+
// Implementation Steps
|
|
184
|
+
if (ticket.implementation?.steps && ticket.implementation.steps.length > 0) {
|
|
185
|
+
parts.push(`## Implementation Steps`);
|
|
186
|
+
for (const step of ticket.implementation.steps) {
|
|
187
|
+
parts.push(`${step.order}. **${step.title}** (${step.action})`);
|
|
188
|
+
parts.push(` ${step.detail}`);
|
|
189
|
+
if (step.targetFile) {
|
|
190
|
+
parts.push(` File: \`${step.targetFile}\``);
|
|
191
|
+
}
|
|
192
|
+
if (step.codeSnippet) {
|
|
193
|
+
parts.push(` \`\`\`\n ${step.codeSnippet}\n \`\`\``);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
parts.push('');
|
|
197
|
+
}
|
|
198
|
+
// Code Examples (from implementation.codeExamples)
|
|
199
|
+
if (ticket.implementation?.codeExamples && ticket.implementation.codeExamples.length > 0) {
|
|
200
|
+
parts.push(`## Code Examples`);
|
|
201
|
+
for (const example of ticket.implementation.codeExamples) {
|
|
202
|
+
parts.push(`### ${example.name}`);
|
|
203
|
+
if (example.description) {
|
|
204
|
+
parts.push(example.description);
|
|
205
|
+
}
|
|
206
|
+
parts.push(`\`\`\`${example.language}`);
|
|
207
|
+
parts.push(example.code);
|
|
208
|
+
parts.push('```');
|
|
209
|
+
parts.push('');
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
// Implementation Prerequisites
|
|
213
|
+
if (ticket.implementation?.prerequisites && ticket.implementation.prerequisites.length > 0) {
|
|
214
|
+
parts.push(`## Prerequisites`);
|
|
215
|
+
for (const prereq of ticket.implementation.prerequisites) {
|
|
216
|
+
parts.push(`- ${prereq}`);
|
|
217
|
+
}
|
|
218
|
+
parts.push('');
|
|
219
|
+
}
|
|
220
|
+
// Technical Details
|
|
221
|
+
if (ticket.technicalDetails) {
|
|
222
|
+
parts.push(`## Technical Details`);
|
|
223
|
+
if (ticket.technicalDetails.files?.create) {
|
|
224
|
+
parts.push(`### Files to Create`);
|
|
225
|
+
for (const file of ticket.technicalDetails.files.create) {
|
|
226
|
+
parts.push(`- \`${file.path}\`: ${file.purpose}`);
|
|
227
|
+
}
|
|
228
|
+
parts.push('');
|
|
229
|
+
}
|
|
230
|
+
if (ticket.technicalDetails.files?.modify) {
|
|
231
|
+
parts.push(`### Files to Modify`);
|
|
232
|
+
for (const file of ticket.technicalDetails.files.modify) {
|
|
233
|
+
parts.push(`- \`${file.path}\`: ${file.change}`);
|
|
234
|
+
}
|
|
235
|
+
parts.push('');
|
|
236
|
+
}
|
|
237
|
+
if (ticket.technicalDetails.files?.delete) {
|
|
238
|
+
parts.push(`### Files to Delete`);
|
|
239
|
+
for (const file of ticket.technicalDetails.files.delete) {
|
|
240
|
+
parts.push(`- \`${file.path}\`: ${file.reason}`);
|
|
241
|
+
}
|
|
242
|
+
parts.push('');
|
|
243
|
+
}
|
|
244
|
+
if (ticket.technicalDetails.patterns) {
|
|
245
|
+
parts.push(`### Patterns to Follow`);
|
|
246
|
+
for (const [name, desc] of Object.entries(ticket.technicalDetails.patterns)) {
|
|
247
|
+
parts.push(`- **${name}**: ${desc}`);
|
|
248
|
+
}
|
|
249
|
+
parts.push('');
|
|
250
|
+
}
|
|
251
|
+
// Required imports
|
|
252
|
+
if (ticket.technicalDetails.imports && ticket.technicalDetails.imports.length > 0) {
|
|
253
|
+
parts.push(`### Required Imports`);
|
|
254
|
+
for (const imp of ticket.technicalDetails.imports) {
|
|
255
|
+
parts.push(`- \`${imp}\``);
|
|
256
|
+
}
|
|
257
|
+
parts.push('');
|
|
258
|
+
}
|
|
259
|
+
// API Endpoints
|
|
260
|
+
if (ticket.technicalDetails.endpoints && ticket.technicalDetails.endpoints.length > 0) {
|
|
261
|
+
parts.push(`### API Endpoints`);
|
|
262
|
+
for (const endpoint of ticket.technicalDetails.endpoints) {
|
|
263
|
+
parts.push(`- **${endpoint.method}** \`${endpoint.path}\`: ${endpoint.description}`);
|
|
264
|
+
}
|
|
265
|
+
parts.push('');
|
|
266
|
+
}
|
|
267
|
+
// Database Operations
|
|
268
|
+
if (ticket.technicalDetails.database && ticket.technicalDetails.database.length > 0) {
|
|
269
|
+
parts.push(`### Database Operations`);
|
|
270
|
+
for (const op of ticket.technicalDetails.database) {
|
|
271
|
+
parts.push(`- **${op.type}** on \`${op.table}\`: ${op.description}`);
|
|
272
|
+
}
|
|
273
|
+
parts.push('');
|
|
274
|
+
}
|
|
275
|
+
// Environment Variables
|
|
276
|
+
if (ticket.technicalDetails.envVars && ticket.technicalDetails.envVars.length > 0) {
|
|
277
|
+
parts.push(`### Environment Variables`);
|
|
278
|
+
for (const env of ticket.technicalDetails.envVars) {
|
|
279
|
+
const required = env.required ? ' (required)' : ' (optional)';
|
|
280
|
+
parts.push(`- \`${env.name}\`${required}: ${env.description}`);
|
|
281
|
+
}
|
|
282
|
+
parts.push('');
|
|
283
|
+
}
|
|
284
|
+
// External Services
|
|
285
|
+
if (ticket.technicalDetails.externalServices && ticket.technicalDetails.externalServices.length > 0) {
|
|
286
|
+
parts.push(`### External Services`);
|
|
287
|
+
for (const service of ticket.technicalDetails.externalServices) {
|
|
288
|
+
parts.push(`- **${service.name}**: ${service.purpose}`);
|
|
289
|
+
}
|
|
290
|
+
parts.push('');
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
// Structured Notes (from ticket.notes when object)
|
|
294
|
+
if (ticket.notes) {
|
|
295
|
+
const notesObj = typeof ticket.notes === 'string'
|
|
296
|
+
? this.tryParseStructuredNotes(ticket.notes)
|
|
297
|
+
: ticket.notes;
|
|
298
|
+
if (notesObj) {
|
|
299
|
+
if (notesObj.warnings && notesObj.warnings.length > 0) {
|
|
300
|
+
parts.push(`## Warnings`);
|
|
301
|
+
for (const warning of notesObj.warnings) {
|
|
302
|
+
parts.push(`- ⚠️ ${warning}`);
|
|
303
|
+
}
|
|
304
|
+
parts.push('');
|
|
305
|
+
}
|
|
306
|
+
if (notesObj.bestPractices && notesObj.bestPractices.length > 0) {
|
|
307
|
+
parts.push(`## Best Practices`);
|
|
308
|
+
for (const practice of notesObj.bestPractices) {
|
|
309
|
+
parts.push(`- ${practice}`);
|
|
310
|
+
}
|
|
311
|
+
parts.push('');
|
|
312
|
+
}
|
|
313
|
+
if (notesObj.alternatives && notesObj.alternatives.length > 0) {
|
|
314
|
+
parts.push(`## Alternatives Considered`);
|
|
315
|
+
for (const alt of notesObj.alternatives) {
|
|
316
|
+
parts.push(`- ${alt}`);
|
|
317
|
+
}
|
|
318
|
+
parts.push('');
|
|
319
|
+
}
|
|
320
|
+
if (notesObj.references && notesObj.references.length > 0) {
|
|
321
|
+
parts.push(`## References`);
|
|
322
|
+
for (const ref of notesObj.references) {
|
|
323
|
+
parts.push(`- ${ref}`);
|
|
324
|
+
}
|
|
325
|
+
parts.push('');
|
|
326
|
+
}
|
|
327
|
+
if (notesObj.limitations && notesObj.limitations.length > 0) {
|
|
328
|
+
parts.push(`## Limitations`);
|
|
329
|
+
for (const limitation of notesObj.limitations) {
|
|
330
|
+
parts.push(`- ${limitation}`);
|
|
331
|
+
}
|
|
332
|
+
parts.push('');
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
else if (typeof ticket.notes === 'string') {
|
|
336
|
+
// Plain string notes
|
|
337
|
+
parts.push(`## Notes`);
|
|
338
|
+
parts.push(ticket.notes);
|
|
339
|
+
parts.push('');
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
// Epic-Level Context
|
|
343
|
+
if (epic.sharedPatterns || epic.additionalImports || epic.commonFiles) {
|
|
344
|
+
parts.push(`## Epic Context`);
|
|
345
|
+
if (epic.sharedPatterns) {
|
|
346
|
+
parts.push(`### Shared Patterns for ${epic.title}`);
|
|
347
|
+
const patterns = epic.sharedPatterns;
|
|
348
|
+
for (const [key, value] of Object.entries(patterns)) {
|
|
349
|
+
if (value)
|
|
350
|
+
parts.push(`- **${key}**: ${value}`);
|
|
351
|
+
}
|
|
352
|
+
parts.push('');
|
|
353
|
+
}
|
|
354
|
+
if (epic.additionalImports && epic.additionalImports.length > 0) {
|
|
355
|
+
parts.push(`### Epic Imports`);
|
|
356
|
+
for (const imp of epic.additionalImports) {
|
|
357
|
+
parts.push(`- \`${imp}\``);
|
|
358
|
+
}
|
|
359
|
+
parts.push('');
|
|
360
|
+
}
|
|
361
|
+
if (epic.commonFiles) {
|
|
362
|
+
parts.push(`### Common Files`);
|
|
363
|
+
const files = epic.commonFiles;
|
|
364
|
+
for (const [key, path] of Object.entries(files)) {
|
|
365
|
+
if (path)
|
|
366
|
+
parts.push(`- **${key}**: \`${path}\``);
|
|
367
|
+
}
|
|
368
|
+
parts.push('');
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
// Specification Architecture
|
|
372
|
+
if (specification.architecture || specification.fileStructure || specification.goals) {
|
|
373
|
+
parts.push(`## Specification Architecture`);
|
|
374
|
+
if (specification.architecture) {
|
|
375
|
+
parts.push(`### Architecture Overview`);
|
|
376
|
+
parts.push(specification.architecture);
|
|
377
|
+
parts.push('');
|
|
378
|
+
}
|
|
379
|
+
if (specification.fileStructure) {
|
|
380
|
+
parts.push(`### File Structure`);
|
|
381
|
+
parts.push('```');
|
|
382
|
+
parts.push(specification.fileStructure);
|
|
383
|
+
parts.push('```');
|
|
384
|
+
parts.push('');
|
|
385
|
+
}
|
|
386
|
+
if (specification.goals && specification.goals.length > 0) {
|
|
387
|
+
parts.push(`### Goals`);
|
|
388
|
+
for (const goal of specification.goals) {
|
|
389
|
+
parts.push(`- ${goal}`);
|
|
390
|
+
}
|
|
391
|
+
parts.push('');
|
|
392
|
+
}
|
|
393
|
+
if (specification.requirements && specification.requirements.length > 0) {
|
|
394
|
+
parts.push(`### Requirements`);
|
|
395
|
+
for (const req of specification.requirements) {
|
|
396
|
+
parts.push(`- ${req}`);
|
|
397
|
+
}
|
|
398
|
+
parts.push('');
|
|
399
|
+
}
|
|
400
|
+
if (specification.constraints && specification.constraints.length > 0) {
|
|
401
|
+
parts.push(`### Constraints`);
|
|
402
|
+
for (const constraint of specification.constraints) {
|
|
403
|
+
parts.push(`- ${constraint}`);
|
|
404
|
+
}
|
|
405
|
+
parts.push('');
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
// Code Standards from Specification
|
|
409
|
+
if (specification.codeStandards) {
|
|
410
|
+
parts.push(`## Code Standards`);
|
|
411
|
+
const standards = specification.codeStandards;
|
|
412
|
+
if (standards.language)
|
|
413
|
+
parts.push(`- **Language**: ${standards.language}`);
|
|
414
|
+
if (standards.asyncPattern)
|
|
415
|
+
parts.push(`- **Async Pattern**: ${standards.asyncPattern}`);
|
|
416
|
+
if (standards.errorHandling)
|
|
417
|
+
parts.push(`- **Error Handling**: ${standards.errorHandling}`);
|
|
418
|
+
if (standards.testing)
|
|
419
|
+
parts.push(`- **Testing**: ${standards.testing}`);
|
|
420
|
+
if (standards.documentation)
|
|
421
|
+
parts.push(`- **Documentation**: ${standards.documentation}`);
|
|
422
|
+
if (standards.stateManagement)
|
|
423
|
+
parts.push(`- **State Management**: ${standards.stateManagement}`);
|
|
424
|
+
if (standards.apiPattern)
|
|
425
|
+
parts.push(`- **API Pattern**: ${standards.apiPattern}`);
|
|
426
|
+
if (standards.logging)
|
|
427
|
+
parts.push(`- **Logging**: ${standards.logging}`);
|
|
428
|
+
if (standards.validation)
|
|
429
|
+
parts.push(`- **Validation**: ${standards.validation}`);
|
|
430
|
+
parts.push('');
|
|
431
|
+
}
|
|
432
|
+
// Previous Attempts (from context.previousAttempts)
|
|
433
|
+
if (extendedContext.previousAttempts && extendedContext.previousAttempts.length > 0) {
|
|
434
|
+
parts.push(`## Previous Attempts`);
|
|
435
|
+
parts.push(`The following approaches were tried before and failed:`);
|
|
436
|
+
for (const attempt of extendedContext.previousAttempts) {
|
|
437
|
+
parts.push(`- **${attempt.approach}**: ${attempt.reason}`);
|
|
438
|
+
}
|
|
439
|
+
parts.push('');
|
|
440
|
+
}
|
|
441
|
+
// Related Discoveries (from context.relatedDiscoveries)
|
|
442
|
+
if (extendedContext.relatedDiscoveries && extendedContext.relatedDiscoveries.length > 0) {
|
|
443
|
+
parts.push(`## Related Discoveries`);
|
|
444
|
+
parts.push(`Be aware of these known issues/blockers:`);
|
|
445
|
+
for (const discovery of extendedContext.relatedDiscoveries) {
|
|
446
|
+
const severity = discovery.severity ? ` [${discovery.severity}]` : '';
|
|
447
|
+
parts.push(`- **${discovery.type}${severity}**: ${discovery.title}`);
|
|
448
|
+
if (discovery.description) {
|
|
449
|
+
parts.push(` ${discovery.description}`);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
parts.push('');
|
|
453
|
+
}
|
|
454
|
+
// Dependencies
|
|
455
|
+
if (context.dependencies.blockedBy.length > 0) {
|
|
456
|
+
parts.push(`## Dependencies (Already Completed)`);
|
|
457
|
+
for (const dep of context.dependencies.blockedBy) {
|
|
458
|
+
parts.push(`- ${dep.title} (${dep.status})`);
|
|
459
|
+
}
|
|
460
|
+
parts.push('');
|
|
461
|
+
}
|
|
462
|
+
// Profile-specific additions
|
|
463
|
+
if (profile.systemPromptAdditions) {
|
|
464
|
+
parts.push(`## Additional Guidelines`);
|
|
465
|
+
parts.push(profile.systemPromptAdditions.trim());
|
|
466
|
+
parts.push('');
|
|
467
|
+
}
|
|
468
|
+
// Discovery Reporting Instructions
|
|
469
|
+
parts.push(`## Reporting Discoveries`);
|
|
470
|
+
parts.push(`If you encounter issues during implementation, report them using these markers:`);
|
|
471
|
+
parts.push('');
|
|
472
|
+
parts.push('```');
|
|
473
|
+
parts.push('DISCOVERY: [type] [severity]');
|
|
474
|
+
parts.push('Title: <title>');
|
|
475
|
+
parts.push('Description: <description>');
|
|
476
|
+
parts.push('```');
|
|
477
|
+
parts.push('');
|
|
478
|
+
parts.push('Types: bug, technical_debt, new_requirement, question, blocker, improvement');
|
|
479
|
+
parts.push('Severity: low, medium, high, critical');
|
|
480
|
+
parts.push('');
|
|
481
|
+
parts.push('Example:');
|
|
482
|
+
parts.push('```');
|
|
483
|
+
parts.push('DISCOVERY: technical_debt medium');
|
|
484
|
+
parts.push('Title: Legacy API needs migration');
|
|
485
|
+
parts.push('Description: The current API uses deprecated endpoints that should be updated.');
|
|
486
|
+
parts.push('```');
|
|
487
|
+
parts.push('');
|
|
488
|
+
// Instructions
|
|
489
|
+
parts.push(`## Instructions`);
|
|
490
|
+
parts.push(`Please implement this ticket following the steps and requirements above.`);
|
|
491
|
+
parts.push(`Make sure to:`);
|
|
492
|
+
parts.push(`1. Follow the existing code patterns in the codebase`);
|
|
493
|
+
parts.push(`2. Use proper TypeScript types`);
|
|
494
|
+
parts.push(`3. Handle errors appropriately`);
|
|
495
|
+
parts.push(`4. Write clean, maintainable code`);
|
|
496
|
+
parts.push(`5. Report any discoveries using the markers above`);
|
|
497
|
+
parts.push('');
|
|
498
|
+
return parts.join('\n');
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Try to parse structured notes from a string
|
|
502
|
+
*/
|
|
503
|
+
tryParseStructuredNotes(notes) {
|
|
504
|
+
try {
|
|
505
|
+
const parsed = JSON.parse(notes);
|
|
506
|
+
if (typeof parsed === 'object' && parsed !== null) {
|
|
507
|
+
return parsed;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
catch {
|
|
511
|
+
// Not JSON, return null
|
|
512
|
+
}
|
|
513
|
+
return null;
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* Run validation commands
|
|
517
|
+
*/
|
|
518
|
+
async runValidations(profile) {
|
|
519
|
+
if (!profile.validationCommands || profile.validationCommands.length === 0) {
|
|
520
|
+
return [];
|
|
521
|
+
}
|
|
522
|
+
const results = [];
|
|
523
|
+
for (const validation of profile.validationCommands) {
|
|
524
|
+
const result = await this.runValidationCommand(validation);
|
|
525
|
+
results.push(result);
|
|
526
|
+
// Stop on required validation failure
|
|
527
|
+
if (!result.success && validation.required) {
|
|
528
|
+
break;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
return results;
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Run a single validation command
|
|
535
|
+
*/
|
|
536
|
+
async runValidationCommand(validation) {
|
|
537
|
+
const startTime = Date.now();
|
|
538
|
+
const cwd = validation.cwd
|
|
539
|
+
? `${this.workingDirectory}/${validation.cwd}`
|
|
540
|
+
: this.workingDirectory;
|
|
541
|
+
try {
|
|
542
|
+
const { stdout, stderr } = await execAsync(validation.command, {
|
|
543
|
+
cwd,
|
|
544
|
+
timeout: validation.timeoutMs || 60000,
|
|
545
|
+
});
|
|
546
|
+
return {
|
|
547
|
+
command: validation.command,
|
|
548
|
+
name: validation.name,
|
|
549
|
+
success: true,
|
|
550
|
+
output: stdout + stderr,
|
|
551
|
+
durationMs: Date.now() - startTime,
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
catch (error) {
|
|
555
|
+
const execError = error;
|
|
556
|
+
return {
|
|
557
|
+
command: validation.command,
|
|
558
|
+
name: validation.name,
|
|
559
|
+
success: false,
|
|
560
|
+
output: execError.stdout,
|
|
561
|
+
error: execError.stderr || execError.message,
|
|
562
|
+
durationMs: Date.now() - startTime,
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Commit changes for a ticket
|
|
568
|
+
*/
|
|
569
|
+
async commit(ticket) {
|
|
570
|
+
try {
|
|
571
|
+
// Stage all changes
|
|
572
|
+
await execAsync('git add -A', { cwd: this.workingDirectory });
|
|
573
|
+
// Create commit
|
|
574
|
+
const message = `feat: ${ticket.title}\n\nTicket: #${ticket.ticketNumber}\n\nCo-Authored-By: SpecForge Autopilot <autopilot@specforge.dev>`;
|
|
575
|
+
await execAsync(`git commit -m "${message.replace(/"/g, '\\"')}"`, {
|
|
576
|
+
cwd: this.workingDirectory,
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
catch (error) {
|
|
580
|
+
// Ignore if nothing to commit
|
|
581
|
+
const execError = error;
|
|
582
|
+
if (!execError.stderr?.includes('nothing to commit')) {
|
|
583
|
+
throw error;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Parse discoveries from AI output using explicit markers
|
|
589
|
+
*/
|
|
590
|
+
parseDiscoveries(output) {
|
|
591
|
+
if (!output)
|
|
592
|
+
return [];
|
|
593
|
+
const discoveries = [];
|
|
594
|
+
// Parse discoveries - note: using [\s\S] instead of 's' flag for ES compatibility
|
|
595
|
+
const regex = /DISCOVERY:\s*(\w+)\s+(\w+)\s*\nTitle:\s*([^\n]+)\nDescription:\s*([\s\S]+?)(?=\n\nDISCOVERY:|\n\n\n|$)/gi;
|
|
596
|
+
let match;
|
|
597
|
+
while ((match = regex.exec(output)) !== null) {
|
|
598
|
+
const [, typeStr, severityStr, title, description] = match;
|
|
599
|
+
const type = this.parseDiscoveryType(typeStr.trim().toLowerCase());
|
|
600
|
+
const severity = this.parseDiscoverySeverity(severityStr.trim().toLowerCase());
|
|
601
|
+
if (type && severity) {
|
|
602
|
+
discoveries.push({
|
|
603
|
+
type,
|
|
604
|
+
severity,
|
|
605
|
+
title: title.trim(),
|
|
606
|
+
description: description.trim(),
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
return discoveries;
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* Parse and validate discovery type
|
|
614
|
+
*/
|
|
615
|
+
parseDiscoveryType(type) {
|
|
616
|
+
const validTypes = [
|
|
617
|
+
'bug', 'technical_debt', 'new_requirement', 'question', 'blocker', 'improvement'
|
|
618
|
+
];
|
|
619
|
+
return validTypes.includes(type) ? type : null;
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Parse and validate discovery severity
|
|
623
|
+
*/
|
|
624
|
+
parseDiscoverySeverity(severity) {
|
|
625
|
+
const validSeverities = ['low', 'medium', 'high', 'critical'];
|
|
626
|
+
return validSeverities.includes(severity) ? severity : null;
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* Extract file changes (created, modified, deleted) from AI output
|
|
630
|
+
*/
|
|
631
|
+
extractFileChanges(output) {
|
|
632
|
+
if (!output)
|
|
633
|
+
return { created: [], modified: [], deleted: [] };
|
|
634
|
+
const created = [];
|
|
635
|
+
const modified = [];
|
|
636
|
+
const deleted = [];
|
|
637
|
+
// Patterns for created files
|
|
638
|
+
const createPatterns = [
|
|
639
|
+
/Created?\s+(?:file\s+)?[`']?([^\s`']+\.\w+)[`']?/gi,
|
|
640
|
+
/New file[:\s]+[`']?([^\s`']+\.\w+)[`']?/gi,
|
|
641
|
+
/Added\s+[`']?([^\s`']+\.\w+)[`']?/gi,
|
|
642
|
+
];
|
|
643
|
+
// Patterns for modified files
|
|
644
|
+
const modifyPatterns = [
|
|
645
|
+
/Modified?\s+(?:file\s+)?[`']?([^\s`']+\.\w+)[`']?/gi,
|
|
646
|
+
/Updated?\s+(?:file\s+)?[`']?([^\s`']+\.\w+)[`']?/gi,
|
|
647
|
+
/Edited?\s+(?:file\s+)?[`']?([^\s`']+\.\w+)[`']?/gi,
|
|
648
|
+
/Changed?\s+(?:file\s+)?[`']?([^\s`']+\.\w+)[`']?/gi,
|
|
649
|
+
];
|
|
650
|
+
// Patterns for deleted files
|
|
651
|
+
const deletePatterns = [
|
|
652
|
+
/Deleted?\s+(?:file\s+)?[`']?([^\s`']+\.\w+)[`']?/gi,
|
|
653
|
+
/Removed?\s+(?:file\s+)?[`']?([^\s`']+\.\w+)[`']?/gi,
|
|
654
|
+
];
|
|
655
|
+
for (const pattern of createPatterns) {
|
|
656
|
+
let match;
|
|
657
|
+
while ((match = pattern.exec(output)) !== null) {
|
|
658
|
+
created.push(match[1]);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
for (const pattern of modifyPatterns) {
|
|
662
|
+
let match;
|
|
663
|
+
while ((match = pattern.exec(output)) !== null) {
|
|
664
|
+
modified.push(match[1]);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
for (const pattern of deletePatterns) {
|
|
668
|
+
let match;
|
|
669
|
+
while ((match = pattern.exec(output)) !== null) {
|
|
670
|
+
deleted.push(match[1]);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
return {
|
|
674
|
+
created: [...new Set(created)],
|
|
675
|
+
modified: [...new Set(modified)],
|
|
676
|
+
deleted: [...new Set(deleted)],
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* Get the SHA of the last commit
|
|
681
|
+
*/
|
|
682
|
+
async getLastCommitSha() {
|
|
683
|
+
try {
|
|
684
|
+
const { stdout } = await execAsync('git rev-parse HEAD', {
|
|
685
|
+
cwd: this.workingDirectory,
|
|
686
|
+
});
|
|
687
|
+
return stdout.trim();
|
|
688
|
+
}
|
|
689
|
+
catch {
|
|
690
|
+
return undefined;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Extract modified files from AI output (legacy method for backwards compatibility)
|
|
695
|
+
*/
|
|
696
|
+
extractModifiedFiles(output) {
|
|
697
|
+
const { created, modified, deleted } = this.extractFileChanges(output);
|
|
698
|
+
return [...new Set([...created, ...modified, ...deleted])];
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* Get AI provider
|
|
702
|
+
*/
|
|
703
|
+
getAIProvider() {
|
|
704
|
+
return this.aiProvider;
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* Get agent selector
|
|
708
|
+
*/
|
|
709
|
+
getAgentSelector() {
|
|
710
|
+
return this.agentSelector;
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
713
|
+
* Update working directory
|
|
714
|
+
*/
|
|
715
|
+
setWorkingDirectory(dir) {
|
|
716
|
+
this.workingDirectory = dir;
|
|
717
|
+
}
|
|
718
|
+
/**
|
|
719
|
+
* Get working directory
|
|
720
|
+
*/
|
|
721
|
+
getWorkingDirectory() {
|
|
722
|
+
return this.workingDirectory;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* Create an agent runner
|
|
727
|
+
*/
|
|
728
|
+
export function createAgentRunner(options) {
|
|
729
|
+
return new AgentRunner(options);
|
|
730
|
+
}
|
|
731
|
+
//# sourceMappingURL=agent-runner.js.map
|