@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.
Files changed (237) hide show
  1. package/README.md +73 -0
  2. package/bin/{specforge-mcp → specforge} +0 -5
  3. package/dist/ai-provider/circuit-breaker.d.ts +63 -0
  4. package/dist/ai-provider/circuit-breaker.d.ts.map +1 -0
  5. package/dist/ai-provider/circuit-breaker.js +160 -0
  6. package/dist/ai-provider/circuit-breaker.js.map +1 -0
  7. package/dist/ai-provider/cli-version.d.ts +50 -0
  8. package/dist/ai-provider/cli-version.d.ts.map +1 -0
  9. package/dist/ai-provider/cli-version.js +141 -0
  10. package/dist/ai-provider/cli-version.js.map +1 -0
  11. package/dist/ai-provider/config-loader.d.ts +45 -0
  12. package/dist/ai-provider/config-loader.d.ts.map +1 -0
  13. package/dist/ai-provider/config-loader.js +106 -0
  14. package/dist/ai-provider/config-loader.js.map +1 -0
  15. package/dist/ai-provider/errors.d.ts +48 -0
  16. package/dist/ai-provider/errors.d.ts.map +1 -0
  17. package/dist/ai-provider/errors.js +102 -0
  18. package/dist/ai-provider/errors.js.map +1 -0
  19. package/dist/ai-provider/events.d.ts +73 -0
  20. package/dist/ai-provider/events.d.ts.map +1 -0
  21. package/dist/ai-provider/events.js +75 -0
  22. package/dist/ai-provider/events.js.map +1 -0
  23. package/dist/ai-provider/factory.d.ts +31 -0
  24. package/dist/ai-provider/factory.d.ts.map +1 -0
  25. package/dist/ai-provider/factory.js +100 -0
  26. package/dist/ai-provider/factory.js.map +1 -0
  27. package/dist/ai-provider/index.d.ts +24 -0
  28. package/dist/ai-provider/index.d.ts.map +1 -0
  29. package/dist/ai-provider/index.js +46 -0
  30. package/dist/ai-provider/index.js.map +1 -0
  31. package/dist/ai-provider/instance-coordinator.d.ts +54 -0
  32. package/dist/ai-provider/instance-coordinator.d.ts.map +1 -0
  33. package/dist/ai-provider/instance-coordinator.js +199 -0
  34. package/dist/ai-provider/instance-coordinator.js.map +1 -0
  35. package/dist/ai-provider/jsonl-parser.d.ts +43 -0
  36. package/dist/ai-provider/jsonl-parser.d.ts.map +1 -0
  37. package/dist/ai-provider/jsonl-parser.js +107 -0
  38. package/dist/ai-provider/jsonl-parser.js.map +1 -0
  39. package/dist/ai-provider/lifecycle.d.ts +50 -0
  40. package/dist/ai-provider/lifecycle.d.ts.map +1 -0
  41. package/dist/ai-provider/lifecycle.js +145 -0
  42. package/dist/ai-provider/lifecycle.js.map +1 -0
  43. package/dist/ai-provider/logger.d.ts +69 -0
  44. package/dist/ai-provider/logger.d.ts.map +1 -0
  45. package/dist/ai-provider/logger.js +161 -0
  46. package/dist/ai-provider/logger.js.map +1 -0
  47. package/dist/ai-provider/metrics.d.ts +91 -0
  48. package/dist/ai-provider/metrics.d.ts.map +1 -0
  49. package/dist/ai-provider/metrics.js +187 -0
  50. package/dist/ai-provider/metrics.js.map +1 -0
  51. package/dist/ai-provider/process-manager.d.ts +97 -0
  52. package/dist/ai-provider/process-manager.d.ts.map +1 -0
  53. package/dist/ai-provider/process-manager.js +477 -0
  54. package/dist/ai-provider/process-manager.js.map +1 -0
  55. package/dist/ai-provider/providers/claude-code.d.ts +64 -0
  56. package/dist/ai-provider/providers/claude-code.d.ts.map +1 -0
  57. package/dist/ai-provider/providers/claude-code.js +205 -0
  58. package/dist/ai-provider/providers/claude-code.js.map +1 -0
  59. package/dist/ai-provider/retry-executor.d.ts +52 -0
  60. package/dist/ai-provider/retry-executor.d.ts.map +1 -0
  61. package/dist/ai-provider/retry-executor.js +138 -0
  62. package/dist/ai-provider/retry-executor.js.map +1 -0
  63. package/dist/ai-provider/safe-args.d.ts +58 -0
  64. package/dist/ai-provider/safe-args.d.ts.map +1 -0
  65. package/dist/ai-provider/safe-args.js +176 -0
  66. package/dist/ai-provider/safe-args.js.map +1 -0
  67. package/dist/ai-provider/semaphore.d.ts +50 -0
  68. package/dist/ai-provider/semaphore.d.ts.map +1 -0
  69. package/dist/ai-provider/semaphore.js +97 -0
  70. package/dist/ai-provider/semaphore.js.map +1 -0
  71. package/dist/ai-provider/tracer.d.ts +67 -0
  72. package/dist/ai-provider/tracer.d.ts.map +1 -0
  73. package/dist/ai-provider/tracer.js +209 -0
  74. package/dist/ai-provider/tracer.js.map +1 -0
  75. package/dist/ai-provider/types.d.ts +181 -0
  76. package/dist/ai-provider/types.d.ts.map +1 -0
  77. package/dist/ai-provider/types.js +8 -0
  78. package/dist/ai-provider/types.js.map +1 -0
  79. package/dist/autopilot/agents/agent-runner.d.ts +109 -0
  80. package/dist/autopilot/agents/agent-runner.d.ts.map +1 -0
  81. package/dist/autopilot/agents/agent-runner.js +731 -0
  82. package/dist/autopilot/agents/agent-runner.js.map +1 -0
  83. package/dist/autopilot/agents/agent-selector.d.ts +59 -0
  84. package/dist/autopilot/agents/agent-selector.d.ts.map +1 -0
  85. package/dist/autopilot/agents/agent-selector.js +234 -0
  86. package/dist/autopilot/agents/agent-selector.js.map +1 -0
  87. package/dist/autopilot/agents/model-selector.d.ts +49 -0
  88. package/dist/autopilot/agents/model-selector.d.ts.map +1 -0
  89. package/dist/autopilot/agents/model-selector.js +62 -0
  90. package/dist/autopilot/agents/model-selector.js.map +1 -0
  91. package/dist/autopilot/agents/profiles/builtin.d.ts +55 -0
  92. package/dist/autopilot/agents/profiles/builtin.d.ts.map +1 -0
  93. package/dist/autopilot/agents/profiles/builtin.js +323 -0
  94. package/dist/autopilot/agents/profiles/builtin.js.map +1 -0
  95. package/dist/autopilot/agents/profiles/types.d.ts +98 -0
  96. package/dist/autopilot/agents/profiles/types.d.ts.map +1 -0
  97. package/dist/autopilot/agents/profiles/types.js +17 -0
  98. package/dist/autopilot/agents/profiles/types.js.map +1 -0
  99. package/dist/autopilot/api/autopilot-api-client.d.ts +217 -0
  100. package/dist/autopilot/api/autopilot-api-client.d.ts.map +1 -0
  101. package/dist/autopilot/api/autopilot-api-client.js +402 -0
  102. package/dist/autopilot/api/autopilot-api-client.js.map +1 -0
  103. package/dist/autopilot/cli/abort.d.ts +20 -0
  104. package/dist/autopilot/cli/abort.d.ts.map +1 -0
  105. package/dist/autopilot/cli/abort.js +201 -0
  106. package/dist/autopilot/cli/abort.js.map +1 -0
  107. package/dist/autopilot/cli/display.d.ts +63 -0
  108. package/dist/autopilot/cli/display.d.ts.map +1 -0
  109. package/dist/autopilot/cli/display.js +260 -0
  110. package/dist/autopilot/cli/display.js.map +1 -0
  111. package/dist/autopilot/cli/index.d.ts +24 -0
  112. package/dist/autopilot/cli/index.d.ts.map +1 -0
  113. package/dist/autopilot/cli/index.js +79 -0
  114. package/dist/autopilot/cli/index.js.map +1 -0
  115. package/dist/autopilot/cli/pause.d.ts +18 -0
  116. package/dist/autopilot/cli/pause.d.ts.map +1 -0
  117. package/dist/autopilot/cli/pause.js +110 -0
  118. package/dist/autopilot/cli/pause.js.map +1 -0
  119. package/dist/autopilot/cli/resume.d.ts +22 -0
  120. package/dist/autopilot/cli/resume.d.ts.map +1 -0
  121. package/dist/autopilot/cli/resume.js +172 -0
  122. package/dist/autopilot/cli/resume.js.map +1 -0
  123. package/dist/autopilot/cli/run.d.ts +25 -0
  124. package/dist/autopilot/cli/run.d.ts.map +1 -0
  125. package/dist/autopilot/cli/run.js +220 -0
  126. package/dist/autopilot/cli/run.js.map +1 -0
  127. package/dist/autopilot/cli/status.d.ts +20 -0
  128. package/dist/autopilot/cli/status.d.ts.map +1 -0
  129. package/dist/autopilot/cli/status.js +217 -0
  130. package/dist/autopilot/cli/status.js.map +1 -0
  131. package/dist/autopilot/config.d.ts +45 -0
  132. package/dist/autopilot/config.d.ts.map +1 -0
  133. package/dist/autopilot/config.js +269 -0
  134. package/dist/autopilot/config.js.map +1 -0
  135. package/dist/autopilot/core/dependency-resolver.d.ts +108 -0
  136. package/dist/autopilot/core/dependency-resolver.d.ts.map +1 -0
  137. package/dist/autopilot/core/dependency-resolver.js +394 -0
  138. package/dist/autopilot/core/dependency-resolver.js.map +1 -0
  139. package/dist/autopilot/core/dispatcher.d.ts +215 -0
  140. package/dist/autopilot/core/dispatcher.d.ts.map +1 -0
  141. package/dist/autopilot/core/dispatcher.js +594 -0
  142. package/dist/autopilot/core/dispatcher.js.map +1 -0
  143. package/dist/autopilot/core/failure-handler.d.ts +145 -0
  144. package/dist/autopilot/core/failure-handler.d.ts.map +1 -0
  145. package/dist/autopilot/core/failure-handler.js +308 -0
  146. package/dist/autopilot/core/failure-handler.js.map +1 -0
  147. package/dist/autopilot/core/rate-limit-handler.d.ts +108 -0
  148. package/dist/autopilot/core/rate-limit-handler.d.ts.map +1 -0
  149. package/dist/autopilot/core/rate-limit-handler.js +195 -0
  150. package/dist/autopilot/core/rate-limit-handler.js.map +1 -0
  151. package/dist/autopilot/core/state-manager.d.ts +160 -0
  152. package/dist/autopilot/core/state-manager.d.ts.map +1 -0
  153. package/dist/autopilot/core/state-manager.js +393 -0
  154. package/dist/autopilot/core/state-manager.js.map +1 -0
  155. package/dist/autopilot/core/timeout-manager.d.ts +95 -0
  156. package/dist/autopilot/core/timeout-manager.d.ts.map +1 -0
  157. package/dist/autopilot/core/timeout-manager.js +188 -0
  158. package/dist/autopilot/core/timeout-manager.js.map +1 -0
  159. package/dist/autopilot/git/branch-manager.d.ts +117 -0
  160. package/dist/autopilot/git/branch-manager.d.ts.map +1 -0
  161. package/dist/autopilot/git/branch-manager.js +238 -0
  162. package/dist/autopilot/git/branch-manager.js.map +1 -0
  163. package/dist/autopilot/git/index.d.ts +9 -0
  164. package/dist/autopilot/git/index.d.ts.map +1 -0
  165. package/dist/autopilot/git/index.js +9 -0
  166. package/dist/autopilot/git/index.js.map +1 -0
  167. package/dist/autopilot/git/merge-manager.d.ts +118 -0
  168. package/dist/autopilot/git/merge-manager.d.ts.map +1 -0
  169. package/dist/autopilot/git/merge-manager.js +304 -0
  170. package/dist/autopilot/git/merge-manager.js.map +1 -0
  171. package/dist/autopilot/git/worktree-manager.d.ts +128 -0
  172. package/dist/autopilot/git/worktree-manager.d.ts.map +1 -0
  173. package/dist/autopilot/git/worktree-manager.js +298 -0
  174. package/dist/autopilot/git/worktree-manager.js.map +1 -0
  175. package/dist/autopilot/index.d.ts +30 -0
  176. package/dist/autopilot/index.d.ts.map +1 -0
  177. package/dist/autopilot/index.js +55 -0
  178. package/dist/autopilot/index.js.map +1 -0
  179. package/dist/autopilot/sync/index.d.ts +7 -0
  180. package/dist/autopilot/sync/index.d.ts.map +1 -0
  181. package/dist/autopilot/sync/index.js +7 -0
  182. package/dist/autopilot/sync/index.js.map +1 -0
  183. package/dist/autopilot/sync/sync-manager.d.ts +168 -0
  184. package/dist/autopilot/sync/sync-manager.d.ts.map +1 -0
  185. package/dist/autopilot/sync/sync-manager.js +303 -0
  186. package/dist/autopilot/sync/sync-manager.js.map +1 -0
  187. package/dist/autopilot/types.d.ts +454 -0
  188. package/dist/autopilot/types.d.ts.map +1 -0
  189. package/dist/autopilot/types.js +26 -0
  190. package/dist/autopilot/types.js.map +1 -0
  191. package/dist/autopilot/utils/audit-logger.d.ts +176 -0
  192. package/dist/autopilot/utils/audit-logger.d.ts.map +1 -0
  193. package/dist/autopilot/utils/audit-logger.js +308 -0
  194. package/dist/autopilot/utils/audit-logger.js.map +1 -0
  195. package/dist/autopilot/utils/cost-tracker.d.ts +162 -0
  196. package/dist/autopilot/utils/cost-tracker.d.ts.map +1 -0
  197. package/dist/autopilot/utils/cost-tracker.js +269 -0
  198. package/dist/autopilot/utils/cost-tracker.js.map +1 -0
  199. package/dist/autopilot/utils/index.d.ts +9 -0
  200. package/dist/autopilot/utils/index.d.ts.map +1 -0
  201. package/dist/autopilot/utils/index.js +9 -0
  202. package/dist/autopilot/utils/index.js.map +1 -0
  203. package/dist/autopilot/utils/progress-reporter.d.ts +132 -0
  204. package/dist/autopilot/utils/progress-reporter.d.ts.map +1 -0
  205. package/dist/autopilot/utils/progress-reporter.js +290 -0
  206. package/dist/autopilot/utils/progress-reporter.js.map +1 -0
  207. package/dist/autopilot/worker/worker-pool.d.ts +179 -0
  208. package/dist/autopilot/worker/worker-pool.d.ts.map +1 -0
  209. package/dist/autopilot/worker/worker-pool.js +331 -0
  210. package/dist/autopilot/worker/worker-pool.js.map +1 -0
  211. package/dist/autopilot/worker/worker-session.d.ts +171 -0
  212. package/dist/autopilot/worker/worker-session.d.ts.map +1 -0
  213. package/dist/autopilot/worker/worker-session.js +295 -0
  214. package/dist/autopilot/worker/worker-session.js.map +1 -0
  215. package/dist/cli/index.d.ts +1 -1
  216. package/dist/cli/index.d.ts.map +1 -1
  217. package/dist/cli/index.js +4 -1
  218. package/dist/cli/index.js.map +1 -1
  219. package/dist/index.js +0 -1
  220. package/dist/index.js.map +1 -1
  221. package/dist/tools/core/epic.js +1 -1
  222. package/dist/tools/core/epic.js.map +1 -1
  223. package/dist/tools/core/lookup.d.ts.map +1 -1
  224. package/dist/tools/core/lookup.js +3 -2
  225. package/dist/tools/core/lookup.js.map +1 -1
  226. package/dist/tools/core/specification.js +1 -1
  227. package/dist/tools/core/specification.js.map +1 -1
  228. package/dist/tools/core/ticket.d.ts.map +1 -1
  229. package/dist/tools/core/ticket.js +4 -6
  230. package/dist/tools/core/ticket.js.map +1 -1
  231. package/dist/tools/index.d.ts.map +1 -1
  232. package/dist/tools/index.js +60 -0
  233. package/dist/tools/index.js.map +1 -1
  234. package/dist/validation/index.d.ts.map +1 -1
  235. package/dist/validation/index.js +4 -1
  236. package/dist/validation/index.js.map +1 -1
  237. 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