@agile-vibe-coding/avc 0.2.3 → 0.3.1

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 (261) hide show
  1. package/cli/agents/agent-selector.md +23 -0
  2. package/cli/agents/code-implementer.md +117 -0
  3. package/cli/agents/code-validator.md +80 -0
  4. package/cli/agents/context-reviewer-epic.md +101 -0
  5. package/cli/agents/context-reviewer-story.md +92 -0
  6. package/cli/agents/context-writer-epic.md +145 -0
  7. package/cli/agents/context-writer-story.md +111 -0
  8. package/cli/agents/doc-writer-epic.md +42 -0
  9. package/cli/agents/doc-writer-story.md +43 -0
  10. package/cli/agents/duplicate-detector.md +110 -0
  11. package/cli/agents/epic-story-decomposer.md +318 -39
  12. package/cli/agents/mission-scope-generator.md +68 -4
  13. package/cli/agents/mission-scope-validator.md +40 -6
  14. package/cli/agents/project-context-extractor.md +21 -6
  15. package/cli/agents/scaffolding-generator.md +99 -0
  16. package/cli/agents/seed-validator.md +71 -0
  17. package/cli/agents/story-scope-reviewer.md +147 -0
  18. package/cli/agents/story-splitter.md +83 -0
  19. package/cli/agents/validator-documentation.json +31 -0
  20. package/cli/agents/validator-documentation.md +3 -1
  21. package/cli/api-reference-tool.js +368 -0
  22. package/cli/checks/catalog.json +76 -0
  23. package/cli/checks/code/quality.json +26 -0
  24. package/cli/checks/code/testing.json +14 -0
  25. package/cli/checks/code/traceability.json +26 -0
  26. package/cli/checks/cross-refs/epic.json +171 -0
  27. package/cli/checks/cross-refs/story.json +149 -0
  28. package/cli/checks/epic/api.json +114 -0
  29. package/cli/checks/epic/backend.json +126 -0
  30. package/cli/checks/epic/cloud.json +126 -0
  31. package/cli/checks/epic/data.json +102 -0
  32. package/cli/checks/epic/database.json +114 -0
  33. package/cli/checks/epic/developer.json +182 -0
  34. package/cli/checks/epic/devops.json +174 -0
  35. package/cli/checks/epic/frontend.json +162 -0
  36. package/cli/checks/epic/mobile.json +102 -0
  37. package/cli/checks/epic/qa.json +90 -0
  38. package/cli/checks/epic/security.json +184 -0
  39. package/cli/checks/epic/solution-architect.json +192 -0
  40. package/cli/checks/epic/test-architect.json +90 -0
  41. package/cli/checks/epic/ui.json +102 -0
  42. package/cli/checks/epic/ux.json +90 -0
  43. package/cli/checks/fixes/epic-fix-template.md +10 -0
  44. package/cli/checks/fixes/story-fix-template.md +10 -0
  45. package/cli/checks/story/api.json +186 -0
  46. package/cli/checks/story/backend.json +102 -0
  47. package/cli/checks/story/cloud.json +102 -0
  48. package/cli/checks/story/data.json +210 -0
  49. package/cli/checks/story/database.json +102 -0
  50. package/cli/checks/story/developer.json +168 -0
  51. package/cli/checks/story/devops.json +102 -0
  52. package/cli/checks/story/frontend.json +174 -0
  53. package/cli/checks/story/mobile.json +102 -0
  54. package/cli/checks/story/qa.json +210 -0
  55. package/cli/checks/story/security.json +198 -0
  56. package/cli/checks/story/solution-architect.json +230 -0
  57. package/cli/checks/story/test-architect.json +210 -0
  58. package/cli/checks/story/ui.json +102 -0
  59. package/cli/checks/story/ux.json +102 -0
  60. package/cli/coding-order.js +401 -0
  61. package/cli/dependency-checker.js +72 -0
  62. package/cli/epic-story-validator.js +284 -799
  63. package/cli/index.js +0 -0
  64. package/cli/init-model-config.js +17 -10
  65. package/cli/init.js +514 -92
  66. package/cli/kanban-server-manager.js +1 -2
  67. package/cli/llm-claude.js +98 -31
  68. package/cli/llm-gemini.js +29 -5
  69. package/cli/llm-local.js +493 -0
  70. package/cli/llm-openai.js +262 -41
  71. package/cli/llm-provider.js +147 -8
  72. package/cli/llm-token-limits.js +113 -4
  73. package/cli/llm-verifier.js +209 -1
  74. package/cli/llm-xiaomi.js +143 -0
  75. package/cli/message-constants.js +3 -12
  76. package/cli/messaging-api.js +6 -12
  77. package/cli/micro-check-fixer.js +335 -0
  78. package/cli/micro-check-runner.js +449 -0
  79. package/cli/micro-check-scorer.js +148 -0
  80. package/cli/micro-check-validator.js +538 -0
  81. package/cli/model-pricing.js +23 -0
  82. package/cli/model-selector.js +3 -2
  83. package/cli/prompt-logger.js +57 -0
  84. package/cli/repl-ink.js +106 -346
  85. package/cli/repl-old.js +1 -2
  86. package/cli/seed-processor.js +194 -24
  87. package/cli/sprint-planning-processor.js +2638 -289
  88. package/cli/template-processor.js +50 -3
  89. package/cli/token-tracker.js +50 -23
  90. package/cli/tools/generate-story-validators.js +1 -1
  91. package/cli/validation-router.js +70 -8
  92. package/cli/worktree-runner.js +654 -0
  93. package/kanban/client/dist/assets/index-D_KC5EQT.css +1 -0
  94. package/kanban/client/dist/assets/index-DjY5zqW7.js +351 -0
  95. package/kanban/client/dist/index.html +2 -2
  96. package/kanban/client/src/App.jsx +43 -14
  97. package/kanban/client/src/components/ceremony/AskArchPopup.jsx +7 -3
  98. package/kanban/client/src/components/ceremony/AskModelPopup.jsx +23 -10
  99. package/kanban/client/src/components/ceremony/CeremonyWorkflowModal.jsx +320 -133
  100. package/kanban/client/src/components/ceremony/ProviderSwitcherButton.jsx +290 -0
  101. package/kanban/client/src/components/ceremony/SponsorCallModal.jsx +80 -13
  102. package/kanban/client/src/components/ceremony/SprintPlanningModal.jsx +156 -22
  103. package/kanban/client/src/components/ceremony/steps/ArchitectureStep.jsx +11 -11
  104. package/kanban/client/src/components/ceremony/steps/CompleteStep.jsx +3 -21
  105. package/kanban/client/src/components/ceremony/steps/ReviewAnswersStep.jsx +214 -10
  106. package/kanban/client/src/components/ceremony/steps/RunningStep.jsx +23 -2
  107. package/kanban/client/src/components/kanban/CardDetailModal.jsx +97 -10
  108. package/kanban/client/src/components/kanban/GroupingSelector.jsx +7 -1
  109. package/kanban/client/src/components/kanban/KanbanCard.jsx +23 -14
  110. package/kanban/client/src/components/kanban/RefineWorkItemPopup.jsx +9 -14
  111. package/kanban/client/src/components/kanban/RunButton.jsx +162 -0
  112. package/kanban/client/src/components/kanban/SeedButton.jsx +176 -0
  113. package/kanban/client/src/components/settings/AgentsTab.jsx +103 -75
  114. package/kanban/client/src/components/settings/ApiKeysTab.jsx +31 -2
  115. package/kanban/client/src/components/settings/CeremonyModelsTab.jsx +9 -2
  116. package/kanban/client/src/components/settings/CheckEditorPopup.jsx +507 -0
  117. package/kanban/client/src/components/settings/CostThresholdsTab.jsx +3 -2
  118. package/kanban/client/src/components/settings/ModelPricingTab.jsx +72 -7
  119. package/kanban/client/src/components/settings/OpenAIAuthSection.jsx +412 -0
  120. package/kanban/client/src/components/settings/SettingsModal.jsx +4 -4
  121. package/kanban/client/src/components/stats/CostModal.jsx +34 -3
  122. package/kanban/client/src/hooks/useGrouping.js +59 -0
  123. package/kanban/client/src/lib/api.js +118 -4
  124. package/kanban/client/src/lib/status-grouping.js +10 -0
  125. package/kanban/client/src/store/kanbanStore.js +8 -0
  126. package/kanban/server/index.js +23 -2
  127. package/kanban/server/routes/ceremony.js +153 -4
  128. package/kanban/server/routes/costs.js +9 -3
  129. package/kanban/server/routes/openai-oauth.js +366 -0
  130. package/kanban/server/routes/settings.js +447 -14
  131. package/kanban/server/routes/websocket.js +7 -2
  132. package/kanban/server/routes/work-items.js +141 -1
  133. package/kanban/server/services/CeremonyService.js +275 -24
  134. package/kanban/server/services/TaskRunnerService.js +261 -0
  135. package/kanban/server/workers/run-task-worker.js +121 -0
  136. package/kanban/server/workers/seed-worker.js +94 -0
  137. package/kanban/server/workers/sponsor-call-worker.js +14 -6
  138. package/kanban/server/workers/sprint-planning-worker.js +94 -12
  139. package/package.json +2 -3
  140. package/cli/agents/solver-epic-api.json +0 -15
  141. package/cli/agents/solver-epic-api.md +0 -39
  142. package/cli/agents/solver-epic-backend.json +0 -15
  143. package/cli/agents/solver-epic-backend.md +0 -39
  144. package/cli/agents/solver-epic-cloud.json +0 -15
  145. package/cli/agents/solver-epic-cloud.md +0 -39
  146. package/cli/agents/solver-epic-data.json +0 -15
  147. package/cli/agents/solver-epic-data.md +0 -39
  148. package/cli/agents/solver-epic-database.json +0 -15
  149. package/cli/agents/solver-epic-database.md +0 -39
  150. package/cli/agents/solver-epic-developer.json +0 -15
  151. package/cli/agents/solver-epic-developer.md +0 -39
  152. package/cli/agents/solver-epic-devops.json +0 -15
  153. package/cli/agents/solver-epic-devops.md +0 -39
  154. package/cli/agents/solver-epic-frontend.json +0 -15
  155. package/cli/agents/solver-epic-frontend.md +0 -39
  156. package/cli/agents/solver-epic-mobile.json +0 -15
  157. package/cli/agents/solver-epic-mobile.md +0 -39
  158. package/cli/agents/solver-epic-qa.json +0 -15
  159. package/cli/agents/solver-epic-qa.md +0 -39
  160. package/cli/agents/solver-epic-security.json +0 -15
  161. package/cli/agents/solver-epic-security.md +0 -39
  162. package/cli/agents/solver-epic-solution-architect.json +0 -15
  163. package/cli/agents/solver-epic-solution-architect.md +0 -39
  164. package/cli/agents/solver-epic-test-architect.json +0 -15
  165. package/cli/agents/solver-epic-test-architect.md +0 -39
  166. package/cli/agents/solver-epic-ui.json +0 -15
  167. package/cli/agents/solver-epic-ui.md +0 -39
  168. package/cli/agents/solver-epic-ux.json +0 -15
  169. package/cli/agents/solver-epic-ux.md +0 -39
  170. package/cli/agents/solver-story-api.json +0 -15
  171. package/cli/agents/solver-story-api.md +0 -39
  172. package/cli/agents/solver-story-backend.json +0 -15
  173. package/cli/agents/solver-story-backend.md +0 -39
  174. package/cli/agents/solver-story-cloud.json +0 -15
  175. package/cli/agents/solver-story-cloud.md +0 -39
  176. package/cli/agents/solver-story-data.json +0 -15
  177. package/cli/agents/solver-story-data.md +0 -39
  178. package/cli/agents/solver-story-database.json +0 -15
  179. package/cli/agents/solver-story-database.md +0 -39
  180. package/cli/agents/solver-story-developer.json +0 -15
  181. package/cli/agents/solver-story-developer.md +0 -39
  182. package/cli/agents/solver-story-devops.json +0 -15
  183. package/cli/agents/solver-story-devops.md +0 -39
  184. package/cli/agents/solver-story-frontend.json +0 -15
  185. package/cli/agents/solver-story-frontend.md +0 -39
  186. package/cli/agents/solver-story-mobile.json +0 -15
  187. package/cli/agents/solver-story-mobile.md +0 -39
  188. package/cli/agents/solver-story-qa.json +0 -15
  189. package/cli/agents/solver-story-qa.md +0 -39
  190. package/cli/agents/solver-story-security.json +0 -15
  191. package/cli/agents/solver-story-security.md +0 -39
  192. package/cli/agents/solver-story-solution-architect.json +0 -15
  193. package/cli/agents/solver-story-solution-architect.md +0 -39
  194. package/cli/agents/solver-story-test-architect.json +0 -15
  195. package/cli/agents/solver-story-test-architect.md +0 -39
  196. package/cli/agents/solver-story-ui.json +0 -15
  197. package/cli/agents/solver-story-ui.md +0 -39
  198. package/cli/agents/solver-story-ux.json +0 -15
  199. package/cli/agents/solver-story-ux.md +0 -39
  200. package/cli/agents/validator-epic-api.json +0 -93
  201. package/cli/agents/validator-epic-api.md +0 -137
  202. package/cli/agents/validator-epic-backend.json +0 -93
  203. package/cli/agents/validator-epic-backend.md +0 -130
  204. package/cli/agents/validator-epic-cloud.json +0 -93
  205. package/cli/agents/validator-epic-cloud.md +0 -137
  206. package/cli/agents/validator-epic-data.json +0 -93
  207. package/cli/agents/validator-epic-data.md +0 -130
  208. package/cli/agents/validator-epic-database.json +0 -93
  209. package/cli/agents/validator-epic-database.md +0 -137
  210. package/cli/agents/validator-epic-developer.json +0 -74
  211. package/cli/agents/validator-epic-developer.md +0 -153
  212. package/cli/agents/validator-epic-devops.json +0 -74
  213. package/cli/agents/validator-epic-devops.md +0 -153
  214. package/cli/agents/validator-epic-frontend.json +0 -74
  215. package/cli/agents/validator-epic-frontend.md +0 -153
  216. package/cli/agents/validator-epic-mobile.json +0 -93
  217. package/cli/agents/validator-epic-mobile.md +0 -130
  218. package/cli/agents/validator-epic-qa.json +0 -93
  219. package/cli/agents/validator-epic-qa.md +0 -130
  220. package/cli/agents/validator-epic-security.json +0 -74
  221. package/cli/agents/validator-epic-security.md +0 -154
  222. package/cli/agents/validator-epic-solution-architect.json +0 -74
  223. package/cli/agents/validator-epic-solution-architect.md +0 -156
  224. package/cli/agents/validator-epic-test-architect.json +0 -93
  225. package/cli/agents/validator-epic-test-architect.md +0 -130
  226. package/cli/agents/validator-epic-ui.json +0 -93
  227. package/cli/agents/validator-epic-ui.md +0 -130
  228. package/cli/agents/validator-epic-ux.json +0 -93
  229. package/cli/agents/validator-epic-ux.md +0 -130
  230. package/cli/agents/validator-story-api.json +0 -104
  231. package/cli/agents/validator-story-api.md +0 -152
  232. package/cli/agents/validator-story-backend.json +0 -104
  233. package/cli/agents/validator-story-backend.md +0 -152
  234. package/cli/agents/validator-story-cloud.json +0 -104
  235. package/cli/agents/validator-story-cloud.md +0 -152
  236. package/cli/agents/validator-story-data.json +0 -104
  237. package/cli/agents/validator-story-data.md +0 -152
  238. package/cli/agents/validator-story-database.json +0 -104
  239. package/cli/agents/validator-story-database.md +0 -152
  240. package/cli/agents/validator-story-developer.json +0 -104
  241. package/cli/agents/validator-story-developer.md +0 -152
  242. package/cli/agents/validator-story-devops.json +0 -104
  243. package/cli/agents/validator-story-devops.md +0 -152
  244. package/cli/agents/validator-story-frontend.json +0 -104
  245. package/cli/agents/validator-story-frontend.md +0 -152
  246. package/cli/agents/validator-story-mobile.json +0 -104
  247. package/cli/agents/validator-story-mobile.md +0 -152
  248. package/cli/agents/validator-story-qa.json +0 -104
  249. package/cli/agents/validator-story-qa.md +0 -152
  250. package/cli/agents/validator-story-security.json +0 -104
  251. package/cli/agents/validator-story-security.md +0 -152
  252. package/cli/agents/validator-story-solution-architect.json +0 -104
  253. package/cli/agents/validator-story-solution-architect.md +0 -152
  254. package/cli/agents/validator-story-test-architect.json +0 -104
  255. package/cli/agents/validator-story-test-architect.md +0 -152
  256. package/cli/agents/validator-story-ui.json +0 -104
  257. package/cli/agents/validator-story-ui.md +0 -152
  258. package/cli/agents/validator-story-ux.json +0 -104
  259. package/cli/agents/validator-story-ux.md +0 -152
  260. package/kanban/client/dist/assets/index-CiD8PS2e.js +0 -306
  261. package/kanban/client/dist/assets/index-nLh0m82Q.css +0 -1
@@ -0,0 +1,261 @@
1
+ /**
2
+ * TaskRunnerService — Manages seed and run-task processes independently from CeremonyService.
3
+ *
4
+ * Supports multiple concurrent runs (each tracked by processId).
5
+ * Uses the same fork → IPC → WebSocket pattern as CeremonyService.
6
+ */
7
+
8
+ import { fork } from 'child_process';
9
+ import path from 'path';
10
+ import { fileURLToPath } from 'url';
11
+
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = path.dirname(__filename);
14
+
15
+ let _nextId = 1;
16
+
17
+ export class TaskRunnerService {
18
+ constructor(projectRoot) {
19
+ this.projectRoot = projectRoot;
20
+ this._runs = new Map(); // processId → { type, itemId, child, status, progress }
21
+ this.websocket = null;
22
+ this._reloadCallback = null;
23
+ }
24
+
25
+ setWebSocket(ws) { this.websocket = ws; }
26
+ setReloadCallback(fn) { this._reloadCallback = fn; }
27
+
28
+ /**
29
+ * Get the status of a specific run.
30
+ */
31
+ getRunStatus(processId) {
32
+ const run = this._runs.get(processId);
33
+ if (!run) return null;
34
+ return {
35
+ processId,
36
+ type: run.type,
37
+ itemId: run.itemId,
38
+ status: run.status,
39
+ progress: run.progress,
40
+ };
41
+ }
42
+
43
+ /**
44
+ * Get all active runs for a given item.
45
+ */
46
+ getRunsForItem(itemId) {
47
+ const runs = [];
48
+ for (const [processId, run] of this._runs) {
49
+ if (run.itemId === itemId) {
50
+ runs.push({ processId, type: run.type, status: run.status });
51
+ }
52
+ }
53
+ return runs;
54
+ }
55
+
56
+ /**
57
+ * List all active/recent runs.
58
+ */
59
+ listRuns() {
60
+ return [...this._runs.entries()].map(([processId, run]) => ({
61
+ processId,
62
+ type: run.type,
63
+ itemId: run.itemId,
64
+ status: run.status,
65
+ }));
66
+ }
67
+
68
+ /**
69
+ * Check if a specific item has an active run.
70
+ */
71
+ isRunning(itemId) {
72
+ for (const run of this._runs.values()) {
73
+ if (run.itemId === itemId && run.status === 'running') return true;
74
+ }
75
+ return false;
76
+ }
77
+
78
+ /**
79
+ * Fork a seed worker for a story.
80
+ * @param {string} storyId
81
+ * @returns {string} processId
82
+ */
83
+ runSeed(storyId) {
84
+ if (this.isRunning(storyId)) {
85
+ throw new Error(`Seed already running for ${storyId}`);
86
+ }
87
+
88
+ const processId = `seed-${_nextId++}`;
89
+ const workerPath = path.join(__dirname, '../workers/seed-worker.js');
90
+
91
+ const child = fork(workerPath, [], {
92
+ stdio: ['ignore', 'pipe', 'pipe', 'ipc'],
93
+ cwd: this.projectRoot,
94
+ env: { ...process.env },
95
+ });
96
+ child.stdout?.on('data', d => process.stdout.write(d));
97
+ child.stderr?.on('data', d => process.stderr.write(d));
98
+
99
+ const run = {
100
+ type: 'seed',
101
+ itemId: storyId,
102
+ child,
103
+ status: 'running',
104
+ progress: [],
105
+ };
106
+ this._runs.set(processId, run);
107
+
108
+ child.send({ type: 'init', storyId });
109
+
110
+ child.on('message', (msg) => this._handleMessage(processId, msg));
111
+ child.on('exit', (code) => this._handleExit(processId, code));
112
+
113
+ return processId;
114
+ }
115
+
116
+ /**
117
+ * Fork a run-task worker for a task.
118
+ * @param {string} taskId
119
+ * @returns {string} processId
120
+ */
121
+ runTask(taskId) {
122
+ if (this.isRunning(taskId)) {
123
+ throw new Error(`Run already active for ${taskId}`);
124
+ }
125
+
126
+ const processId = `run-${_nextId++}`;
127
+ const workerPath = path.join(__dirname, '../workers/run-task-worker.js');
128
+
129
+ const child = fork(workerPath, [], {
130
+ stdio: ['ignore', 'pipe', 'pipe', 'ipc'],
131
+ cwd: this.projectRoot,
132
+ env: { ...process.env },
133
+ });
134
+ child.stdout?.on('data', d => process.stdout.write(d));
135
+ child.stderr?.on('data', d => process.stderr.write(d));
136
+
137
+ const run = {
138
+ type: 'run-task',
139
+ itemId: taskId,
140
+ child,
141
+ status: 'running',
142
+ progress: [],
143
+ };
144
+ this._runs.set(processId, run);
145
+
146
+ child.send({ type: 'init', taskId });
147
+
148
+ child.on('message', (msg) => this._handleMessage(processId, msg));
149
+ child.on('exit', (code) => this._handleExit(processId, code));
150
+
151
+ return processId;
152
+ }
153
+
154
+ /**
155
+ * Cancel a running process.
156
+ */
157
+ cancel(processId) {
158
+ const run = this._runs.get(processId);
159
+ if (!run || run.status !== 'running') return false;
160
+
161
+ try { run.child.send({ type: 'cancel' }); } catch {}
162
+ run.status = 'cancelling';
163
+
164
+ // Safety net: SIGTERM after 10s, SIGKILL after 13s
165
+ const killTimer = setTimeout(() => {
166
+ if (run.status === 'cancelling') {
167
+ try { run.child.kill('SIGTERM'); } catch {}
168
+ setTimeout(() => {
169
+ if (run.status === 'cancelling') {
170
+ try { run.child.kill('SIGKILL'); } catch {}
171
+ }
172
+ }, 3000);
173
+ }
174
+ }, 10000);
175
+ run._killTimer = killTimer;
176
+
177
+ return true;
178
+ }
179
+
180
+ /**
181
+ * Handle IPC messages from workers.
182
+ */
183
+ _handleMessage(processId, msg) {
184
+ const run = this._runs.get(processId);
185
+ if (!run) return;
186
+
187
+ switch (msg.type) {
188
+ case 'progress':
189
+ run.progress.push(msg.message);
190
+ this.websocket?.broadcast({
191
+ type: `${run.type}:progress`,
192
+ processId,
193
+ itemId: run.itemId,
194
+ message: msg.message,
195
+ });
196
+ break;
197
+
198
+ case 'item-written':
199
+ this._reloadCallback?.();
200
+ this.websocket?.broadcastRefresh();
201
+ break;
202
+
203
+ case 'complete':
204
+ run.status = 'complete';
205
+ if (run._killTimer) clearTimeout(run._killTimer);
206
+ this._reloadCallback?.();
207
+ this.websocket?.broadcastRefresh();
208
+ this.websocket?.broadcast({
209
+ type: `${run.type}:complete`,
210
+ processId,
211
+ itemId: run.itemId,
212
+ result: msg.result,
213
+ });
214
+ break;
215
+
216
+ case 'cancelled':
217
+ run.status = 'cancelled';
218
+ if (run._killTimer) clearTimeout(run._killTimer);
219
+ this.websocket?.broadcast({
220
+ type: `${run.type}:cancelled`,
221
+ processId,
222
+ itemId: run.itemId,
223
+ });
224
+ break;
225
+
226
+ case 'error':
227
+ run.status = 'error';
228
+ if (run._killTimer) clearTimeout(run._killTimer);
229
+ this.websocket?.broadcast({
230
+ type: `${run.type}:error`,
231
+ processId,
232
+ itemId: run.itemId,
233
+ error: msg.error?.message || 'Unknown error',
234
+ });
235
+ break;
236
+ }
237
+ }
238
+
239
+ /**
240
+ * Handle worker exit.
241
+ */
242
+ _handleExit(processId, code) {
243
+ const run = this._runs.get(processId);
244
+ if (!run) return;
245
+
246
+ if (run._killTimer) clearTimeout(run._killTimer);
247
+
248
+ if (run.status === 'running' || run.status === 'cancelling') {
249
+ run.status = 'error';
250
+ this.websocket?.broadcast({
251
+ type: `${run.type}:error`,
252
+ processId,
253
+ itemId: run.itemId,
254
+ error: `Worker exited unexpectedly (code ${code})`,
255
+ });
256
+ }
257
+
258
+ // Clean up old runs after 5 minutes
259
+ setTimeout(() => this._runs.delete(processId), 5 * 60 * 1000);
260
+ }
261
+ }
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Run Task Worker
3
+ * Forked by TaskRunnerService.runTask().
4
+ * Creates a git worktree, implements code via LLM, runs tests, commits, merges.
5
+ *
6
+ * Parent → Worker:
7
+ * { type: 'init', taskId }
8
+ * { type: 'cancel' }
9
+ *
10
+ * Worker → Parent:
11
+ * { type: 'progress', message }
12
+ * { type: 'complete', result: { success, taskId } }
13
+ * { type: 'cancelled' }
14
+ * { type: 'error', error }
15
+ */
16
+
17
+ import { CommandLogger } from '../../../cli/command-logger.js';
18
+ import { readFileSync, writeFileSync, existsSync } from 'fs';
19
+ import { join } from 'path';
20
+
21
+ let _cancelled = false;
22
+
23
+ process.on('disconnect', () => {
24
+ _cancelled = true;
25
+ setTimeout(() => process.exit(1), 5000).unref();
26
+ });
27
+
28
+ process.on('message', async (msg) => {
29
+ if (msg.type === 'init') {
30
+ run(msg.taskId);
31
+ } else if (msg.type === 'cancel') {
32
+ _cancelled = true;
33
+ }
34
+ });
35
+
36
+ /**
37
+ * Update work.json status on disk.
38
+ */
39
+ function updateStatus(taskId, status) {
40
+ const projectRoot = process.cwd();
41
+ // Find the work.json by walking the hierarchy
42
+ // Task ID: context-0001-0001-0001 → path: .avc/project/context-0001/context-0001-0001/context-0001-0001-0001/work.json
43
+ const idParts = taskId.replace('context-', '').split('-');
44
+ let dir = join(projectRoot, '.avc', 'project');
45
+ let current = 'context';
46
+ for (const part of idParts) {
47
+ current += `-${part}`;
48
+ dir = join(dir, current);
49
+ }
50
+ const workJsonPath = join(dir, 'work.json');
51
+
52
+ if (existsSync(workJsonPath)) {
53
+ try {
54
+ const workJson = JSON.parse(readFileSync(workJsonPath, 'utf8'));
55
+ workJson.status = status;
56
+ workJson.updated = new Date().toISOString();
57
+ writeFileSync(workJsonPath, JSON.stringify(workJson, null, 2), 'utf8');
58
+ } catch {}
59
+ }
60
+ }
61
+
62
+ async function run(taskId) {
63
+ const logger = new CommandLogger('run-task', process.cwd());
64
+ logger.start();
65
+
66
+ try {
67
+ const { WorktreeRunner } = await import('../../../cli/worktree-runner.js');
68
+
69
+ // Set status to implementing
70
+ updateStatus(taskId, 'implementing');
71
+ try { process.send({ type: 'progress', message: `Starting implementation of ${taskId}` }); } catch {}
72
+
73
+ const runner = new WorktreeRunner(taskId, process.cwd());
74
+
75
+ const progressCallback = (message) => {
76
+ if (_cancelled) return;
77
+ try { process.send({ type: 'progress', message }); } catch {}
78
+ };
79
+
80
+ const cancelledCheck = () => _cancelled;
81
+
82
+ const result = await runner.execute(progressCallback, cancelledCheck);
83
+
84
+ if (_cancelled) {
85
+ updateStatus(taskId, 'planned'); // Reset on cancel
86
+ try { process.send({ type: 'cancelled' }); } catch {}
87
+ } else if (result.success) {
88
+ updateStatus(taskId, 'completed');
89
+ try {
90
+ process.send({
91
+ type: 'complete',
92
+ result: { success: true, taskId },
93
+ });
94
+ } catch {}
95
+ } else {
96
+ updateStatus(taskId, 'failed');
97
+ try {
98
+ process.send({
99
+ type: 'error',
100
+ error: { message: result.error || 'Implementation failed' },
101
+ });
102
+ } catch {}
103
+ }
104
+ } catch (error) {
105
+ if (error.message === 'CANCELLED') {
106
+ updateStatus(taskId, 'planned');
107
+ try { process.send({ type: 'cancelled' }); } catch {}
108
+ } else {
109
+ updateStatus(taskId, 'failed');
110
+ try {
111
+ process.send({
112
+ type: 'error',
113
+ error: { message: error.message, stack: error.stack },
114
+ });
115
+ } catch {}
116
+ }
117
+ } finally {
118
+ logger.stop();
119
+ setTimeout(() => process.exit(0), 500).unref();
120
+ }
121
+ }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Seed Worker
3
+ * Forked by TaskRunnerService.runSeed().
4
+ * Communicates with parent via IPC (process.send / process.on('message')).
5
+ *
6
+ * Parent → Worker:
7
+ * { type: 'init', storyId }
8
+ * { type: 'cancel' }
9
+ *
10
+ * Worker → Parent:
11
+ * { type: 'progress', message }
12
+ * { type: 'item-written', itemId, itemType }
13
+ * { type: 'complete', result: { taskCount, subtaskCount, taskIds } }
14
+ * { type: 'cancelled' }
15
+ * { type: 'error', error }
16
+ */
17
+
18
+ import { CommandLogger } from '../../../cli/command-logger.js';
19
+
20
+ let _cancelled = false;
21
+
22
+ process.on('disconnect', () => {
23
+ _cancelled = true;
24
+ setTimeout(() => process.exit(1), 5000).unref();
25
+ });
26
+
27
+ process.on('message', async (msg) => {
28
+ if (msg.type === 'init') {
29
+ run(msg.storyId);
30
+ } else if (msg.type === 'cancel') {
31
+ _cancelled = true;
32
+ }
33
+ });
34
+
35
+ async function run(storyId) {
36
+ const logger = new CommandLogger('seed', process.cwd());
37
+ logger.start();
38
+
39
+ try {
40
+ // Dynamic import to avoid loading CLI modules at fork time
41
+ const { SeedProcessor } = await import('../../../cli/seed-processor.js');
42
+
43
+ const processor = new SeedProcessor(storyId);
44
+
45
+ const progressCallback = (message) => {
46
+ if (_cancelled) return;
47
+ try { process.send({ type: 'progress', message }); } catch {}
48
+ };
49
+
50
+ const itemWrittenCallback = ({ itemId, itemType }) => {
51
+ if (_cancelled) return;
52
+ try { process.send({ type: 'item-written', itemId, itemType }); } catch {}
53
+ };
54
+
55
+ const cancelledCheck = () => _cancelled;
56
+
57
+ const result = await processor.executeWithCallback(
58
+ progressCallback,
59
+ itemWrittenCallback,
60
+ cancelledCheck
61
+ );
62
+
63
+ if (_cancelled) {
64
+ try { process.send({ type: 'cancelled' }); } catch {}
65
+ } else {
66
+ try {
67
+ process.send({
68
+ type: 'complete',
69
+ result: {
70
+ storyId,
71
+ taskCount: result.taskCount,
72
+ subtaskCount: result.subtaskCount,
73
+ taskIds: result.taskIds,
74
+ },
75
+ });
76
+ } catch {}
77
+ }
78
+ } catch (error) {
79
+ if (error.message === 'CEREMONY_CANCELLED') {
80
+ try { process.send({ type: 'cancelled' }); } catch {}
81
+ } else {
82
+ try {
83
+ process.send({
84
+ type: 'error',
85
+ error: { message: error.message, stack: error.stack },
86
+ });
87
+ } catch {}
88
+ }
89
+ } finally {
90
+ logger.stop();
91
+ // Give parent time to receive the final message
92
+ setTimeout(() => process.exit(0), 500).unref();
93
+ }
94
+ }
@@ -70,15 +70,23 @@ async function run() {
70
70
  costLimitReachedCallback,
71
71
  });
72
72
  logger.stop();
73
- process.send({ type: 'complete', result });
73
+ // sponsorCallWithAnswers returns { error: true, message } on validation failure instead of throwing
74
+ if (result?.error === true) {
75
+ process.send({ type: 'error', error: result.message || 'Ceremony failed' });
76
+ } else {
77
+ process.send({ type: 'complete', result });
78
+ }
74
79
  process.exit(0);
75
80
  } catch (err) {
76
81
  logger.stop();
77
- if (err.message === 'CEREMONY_CANCELLED') {
78
- process.send({ type: 'cancelled' });
79
- } else {
80
- process.send({ type: 'error', error: err.message });
82
+ const msg = err.message === 'CEREMONY_CANCELLED'
83
+ ? { type: 'cancelled' }
84
+ : { type: 'error', error: err.message };
85
+ // Guard against ERR_IPC_CHANNEL_CLOSED if the parent already disconnected.
86
+ try {
87
+ process.send(msg, () => process.exit(0));
88
+ } catch (_) {
89
+ process.exit(0);
81
90
  }
82
- process.exit(0);
83
91
  }
84
92
  }
@@ -17,6 +17,7 @@
17
17
  * { type: 'paused' }
18
18
  * { type: 'resumed' }
19
19
  * { type: 'decomposition-complete', hierarchy }
20
+ * { type: 'hierarchy-written', epicCount, storyCount }
20
21
  * { type: 'complete', result }
21
22
  * { type: 'cancelled' }
22
23
  * { type: 'error', error }
@@ -24,6 +25,33 @@
24
25
 
25
26
  import { ProjectInitiator } from '../../../cli/init.js';
26
27
  import { CommandLogger } from '../../../cli/command-logger.js';
28
+ import { readFileSync } from 'fs';
29
+ import { join } from 'path';
30
+
31
+ function _isQuotaOrRateLimit(msg) {
32
+ const m = (msg || '').toLowerCase();
33
+ return m.includes('429') || m.includes('quota') || m.includes('rate limit') ||
34
+ m.includes('resource exhausted') || m.includes('resource_exhausted') ||
35
+ m.includes('too many requests') ||
36
+ // Anthropic credit-balance exhausted (400 invalid_request_error)
37
+ m.includes('credit balance is too low') || m.includes('credit balance') ||
38
+ m.includes('billing') || m.includes('insufficient_quota') ||
39
+ // Transient connection errors — surface as resumable to allow provider switch
40
+ m.includes('connection error') || m.includes('econnreset') ||
41
+ m.includes('econnrefused') || m.includes('network error') || m.includes('fetch failed');
42
+ }
43
+
44
+ function _getCurrentStageInfo(stageName) {
45
+ try {
46
+ const cfg = JSON.parse(readFileSync(join(process.cwd(), '.avc', 'avc.json'), 'utf8'));
47
+ const ceremony = cfg.settings?.ceremonies?.find(c => c.name === 'sprint-planning');
48
+ const stage = ceremony?.stages?.[stageName];
49
+ return {
50
+ provider: stage?.provider || ceremony?.provider || 'unknown',
51
+ model: stage?.model || ceremony?.defaultModel || 'unknown',
52
+ };
53
+ } catch { return { provider: 'unknown', model: 'unknown' }; }
54
+ }
27
55
 
28
56
  let _paused = false;
29
57
  let _cancelled = false;
@@ -31,6 +59,8 @@ let _costThreshold = null;
31
59
  let _waitingCostLimit = false;
32
60
  let _waitingSelection = false;
33
61
  let _selectionResult = null;
62
+ let _waitingQuota = false;
63
+ let _quotaResolution = null;
34
64
 
35
65
  // Parent server stopped — exit rather than running as an orphan.
36
66
  process.on('disconnect', () => {
@@ -42,7 +72,7 @@ process.on('disconnect', () => {
42
72
  process.on('message', async (msg) => {
43
73
  if (msg.type === 'init') {
44
74
  _costThreshold = msg.costThreshold ?? null;
45
- run();
75
+ run(msg.resumeFrom || null);
46
76
  } else if (msg.type === 'pause') {
47
77
  _paused = true;
48
78
  process.send({ type: 'paused' });
@@ -53,13 +83,18 @@ process.on('message', async (msg) => {
53
83
  _cancelled = true;
54
84
  } else if (msg.type === 'cost-limit-continue') {
55
85
  _waitingCostLimit = false;
86
+ } else if (msg.type === 'quota-continue') {
87
+ _quotaResolution = msg.newProvider
88
+ ? { newProvider: msg.newProvider, newModel: msg.newModel }
89
+ : null;
90
+ _waitingQuota = false;
56
91
  } else if (msg.type === 'selection-confirmed') {
57
92
  _selectionResult = { selectedEpicIds: msg.selectedEpicIds, selectedStoryIds: msg.selectedStoryIds };
58
93
  _waitingSelection = false;
59
94
  }
60
95
  });
61
96
 
62
- async function run() {
97
+ async function run(resumeFrom = null) {
63
98
  const logger = new CommandLogger('sprint-planning', process.cwd());
64
99
  logger.start();
65
100
  try {
@@ -96,11 +131,54 @@ async function run() {
96
131
  return _selectionResult;
97
132
  };
98
133
 
99
- const result = await initiator.sprintPlanningWithCallback(progressCallback, {
100
- costThreshold: _costThreshold,
101
- costLimitReachedCallback,
102
- selectionCallback,
103
- });
134
+ const hierarchyWrittenCallback = async ({ epicCount, storyCount }) => {
135
+ process.send({ type: 'hierarchy-written', epicCount, storyCount });
136
+ };
137
+
138
+ const itemWrittenCallback = async ({ itemId, itemType }) => {
139
+ process.send({ type: 'item-written', itemId, itemType });
140
+ };
141
+
142
+ const quotaExceededCallback = async ({ validatorName, errMsg, provider, model }) => {
143
+ _waitingQuota = true;
144
+ _quotaResolution = null;
145
+ process.send({ type: 'quota-limit', validatorName, errMsg, provider, model });
146
+ while (_waitingQuota) {
147
+ await new Promise(r => setTimeout(r, 200));
148
+ if (_cancelled) throw new Error('CEREMONY_CANCELLED');
149
+ }
150
+ return _quotaResolution; // null = retry same; { newProvider, newModel } = switch
151
+ };
152
+
153
+ // Outer retry loop: catches quota errors from pre-validator stages (decomposition, etc.)
154
+ // The processor is reconstructed on each retry so it picks up any avc.json model changes.
155
+ let result;
156
+ while (true) {
157
+ try {
158
+ result = await initiator.sprintPlanningWithCallback(progressCallback, {
159
+ costThreshold: _costThreshold,
160
+ costLimitReachedCallback,
161
+ selectionCallback,
162
+ hierarchyWrittenCallback,
163
+ itemWrittenCallback,
164
+ quotaExceededCallback,
165
+ resumeFrom,
166
+ });
167
+ break; // success — exit retry loop
168
+ } catch (err) {
169
+ if (err.message === 'CEREMONY_CANCELLED') throw err;
170
+ const errMsg = err.message || '';
171
+ if (_isQuotaOrRateLimit(errMsg)) {
172
+ // Quota error from a stage not covered by the validator callback (e.g. decomposition).
173
+ // Pause and wait for user to add credits or switch model via Configure Models.
174
+ const { provider, model } = _getCurrentStageInfo('decomposition');
175
+ await quotaExceededCallback({ validatorName: 'decomposition', errMsg: errMsg.split('\n')[0], provider, model });
176
+ // Retry — processor reconstructs and re-reads avc.json (picks up any model changes).
177
+ continue;
178
+ }
179
+ throw err; // non-quota error: propagate to outer catch
180
+ }
181
+ }
104
182
  logger.stop();
105
183
  process.send({ type: 'complete', result });
106
184
 
@@ -120,11 +198,15 @@ async function run() {
120
198
  process.exit(0);
121
199
  } catch (err) {
122
200
  logger.stop();
123
- if (err.message === 'CEREMONY_CANCELLED') {
124
- process.send({ type: 'cancelled' });
125
- } else {
126
- process.send({ type: 'error', error: err.message });
201
+ const msg = err.message === 'CEREMONY_CANCELLED'
202
+ ? { type: 'cancelled' }
203
+ : { type: 'error', error: err.message };
204
+ // Use callback to ensure the IPC message is flushed before exiting.
205
+ // Guard against ERR_IPC_CHANNEL_CLOSED if the parent already disconnected.
206
+ try {
207
+ process.send(msg, () => process.exit(0));
208
+ } catch (_) {
209
+ process.exit(0);
127
210
  }
128
- process.exit(0);
129
211
  }
130
212
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agile-vibe-coding/avc",
3
- "version": "0.2.3",
3
+ "version": "0.3.1",
4
4
  "description": "Agile Vibe Coding (AVC) - Framework for managing AI agent-based software development projects",
5
5
  "type": "module",
6
6
  "main": "cli/index.js",
@@ -44,8 +44,7 @@
44
44
  ],
45
45
  "author": "Nacho Coll",
46
46
  "license": "PolyForm-Noncommercial-1.0.0",
47
- "homepage": "https://agilevibecoding.org",
48
- "repository": {
47
+ "repository": {
49
48
  "type": "git",
50
49
  "url": "git+https://github.com/NachoColl/agilevibecoding.git",
51
50
  "directory": "src"
@@ -1,15 +0,0 @@
1
- {
2
- "agentName": "solver-epic-api",
3
- "version": "1.0.0",
4
- "description": "Output schema for API Designer epic solver",
5
- "requiredFields": ["id", "name", "domain", "description", "features", "dependencies", "improvementNotes"],
6
- "fieldValidation": {
7
- "id": { "type": "string" },
8
- "name": { "type": "string" },
9
- "domain": { "type": "string" },
10
- "description": { "type": "string" },
11
- "features": { "type": "array", "minLength": 1 },
12
- "dependencies": { "type": "array" },
13
- "improvementNotes": { "type": "string" }
14
- }
15
- }