@agile-vibe-coding/avc 0.1.1 → 0.2.3

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 (289) hide show
  1. package/cli/agent-loader.js +21 -0
  2. package/cli/agents/agent-selector.md +129 -0
  3. package/cli/agents/architecture-recommender.md +418 -0
  4. package/cli/agents/database-deep-dive.md +470 -0
  5. package/cli/agents/database-recommender.md +634 -0
  6. package/cli/agents/doc-distributor.md +176 -0
  7. package/cli/agents/documentation-updater.md +203 -0
  8. package/cli/agents/epic-story-decomposer.md +280 -0
  9. package/cli/agents/feature-context-generator.md +91 -0
  10. package/cli/agents/gap-checker-epic.md +52 -0
  11. package/cli/agents/impact-checker-story.md +51 -0
  12. package/cli/agents/migration-guide-generator.md +305 -0
  13. package/cli/agents/mission-scope-generator.md +79 -0
  14. package/cli/agents/mission-scope-validator.md +112 -0
  15. package/cli/agents/project-context-extractor.md +107 -0
  16. package/cli/agents/project-documentation-creator.json +226 -0
  17. package/cli/agents/project-documentation-creator.md +595 -0
  18. package/cli/agents/question-prefiller.md +269 -0
  19. package/cli/agents/refiner-epic.md +39 -0
  20. package/cli/agents/refiner-story.md +42 -0
  21. package/cli/agents/solver-epic-api.json +15 -0
  22. package/cli/agents/solver-epic-api.md +39 -0
  23. package/cli/agents/solver-epic-backend.json +15 -0
  24. package/cli/agents/solver-epic-backend.md +39 -0
  25. package/cli/agents/solver-epic-cloud.json +15 -0
  26. package/cli/agents/solver-epic-cloud.md +39 -0
  27. package/cli/agents/solver-epic-data.json +15 -0
  28. package/cli/agents/solver-epic-data.md +39 -0
  29. package/cli/agents/solver-epic-database.json +15 -0
  30. package/cli/agents/solver-epic-database.md +39 -0
  31. package/cli/agents/solver-epic-developer.json +15 -0
  32. package/cli/agents/solver-epic-developer.md +39 -0
  33. package/cli/agents/solver-epic-devops.json +15 -0
  34. package/cli/agents/solver-epic-devops.md +39 -0
  35. package/cli/agents/solver-epic-frontend.json +15 -0
  36. package/cli/agents/solver-epic-frontend.md +39 -0
  37. package/cli/agents/solver-epic-mobile.json +15 -0
  38. package/cli/agents/solver-epic-mobile.md +39 -0
  39. package/cli/agents/solver-epic-qa.json +15 -0
  40. package/cli/agents/solver-epic-qa.md +39 -0
  41. package/cli/agents/solver-epic-security.json +15 -0
  42. package/cli/agents/solver-epic-security.md +39 -0
  43. package/cli/agents/solver-epic-solution-architect.json +15 -0
  44. package/cli/agents/solver-epic-solution-architect.md +39 -0
  45. package/cli/agents/solver-epic-test-architect.json +15 -0
  46. package/cli/agents/solver-epic-test-architect.md +39 -0
  47. package/cli/agents/solver-epic-ui.json +15 -0
  48. package/cli/agents/solver-epic-ui.md +39 -0
  49. package/cli/agents/solver-epic-ux.json +15 -0
  50. package/cli/agents/solver-epic-ux.md +39 -0
  51. package/cli/agents/solver-story-api.json +15 -0
  52. package/cli/agents/solver-story-api.md +39 -0
  53. package/cli/agents/solver-story-backend.json +15 -0
  54. package/cli/agents/solver-story-backend.md +39 -0
  55. package/cli/agents/solver-story-cloud.json +15 -0
  56. package/cli/agents/solver-story-cloud.md +39 -0
  57. package/cli/agents/solver-story-data.json +15 -0
  58. package/cli/agents/solver-story-data.md +39 -0
  59. package/cli/agents/solver-story-database.json +15 -0
  60. package/cli/agents/solver-story-database.md +39 -0
  61. package/cli/agents/solver-story-developer.json +15 -0
  62. package/cli/agents/solver-story-developer.md +39 -0
  63. package/cli/agents/solver-story-devops.json +15 -0
  64. package/cli/agents/solver-story-devops.md +39 -0
  65. package/cli/agents/solver-story-frontend.json +15 -0
  66. package/cli/agents/solver-story-frontend.md +39 -0
  67. package/cli/agents/solver-story-mobile.json +15 -0
  68. package/cli/agents/solver-story-mobile.md +39 -0
  69. package/cli/agents/solver-story-qa.json +15 -0
  70. package/cli/agents/solver-story-qa.md +39 -0
  71. package/cli/agents/solver-story-security.json +15 -0
  72. package/cli/agents/solver-story-security.md +39 -0
  73. package/cli/agents/solver-story-solution-architect.json +15 -0
  74. package/cli/agents/solver-story-solution-architect.md +39 -0
  75. package/cli/agents/solver-story-test-architect.json +15 -0
  76. package/cli/agents/solver-story-test-architect.md +39 -0
  77. package/cli/agents/solver-story-ui.json +15 -0
  78. package/cli/agents/solver-story-ui.md +39 -0
  79. package/cli/agents/solver-story-ux.json +15 -0
  80. package/cli/agents/solver-story-ux.md +39 -0
  81. package/cli/agents/story-doc-enricher.md +133 -0
  82. package/cli/agents/suggestion-business-analyst.md +88 -0
  83. package/cli/agents/suggestion-deployment-architect.md +263 -0
  84. package/cli/agents/suggestion-product-manager.md +129 -0
  85. package/cli/agents/suggestion-security-specialist.md +156 -0
  86. package/cli/agents/suggestion-technical-architect.md +269 -0
  87. package/cli/agents/suggestion-ux-researcher.md +93 -0
  88. package/cli/agents/task-subtask-decomposer.md +188 -0
  89. package/cli/agents/validator-documentation.json +152 -0
  90. package/cli/agents/validator-documentation.md +453 -0
  91. package/cli/agents/validator-epic-api.json +93 -0
  92. package/cli/agents/validator-epic-api.md +137 -0
  93. package/cli/agents/validator-epic-backend.json +93 -0
  94. package/cli/agents/validator-epic-backend.md +130 -0
  95. package/cli/agents/validator-epic-cloud.json +93 -0
  96. package/cli/agents/validator-epic-cloud.md +137 -0
  97. package/cli/agents/validator-epic-data.json +93 -0
  98. package/cli/agents/validator-epic-data.md +130 -0
  99. package/cli/agents/validator-epic-database.json +93 -0
  100. package/cli/agents/validator-epic-database.md +137 -0
  101. package/cli/agents/validator-epic-developer.json +74 -0
  102. package/cli/agents/validator-epic-developer.md +153 -0
  103. package/cli/agents/validator-epic-devops.json +74 -0
  104. package/cli/agents/validator-epic-devops.md +153 -0
  105. package/cli/agents/validator-epic-frontend.json +74 -0
  106. package/cli/agents/validator-epic-frontend.md +153 -0
  107. package/cli/agents/validator-epic-mobile.json +93 -0
  108. package/cli/agents/validator-epic-mobile.md +130 -0
  109. package/cli/agents/validator-epic-qa.json +93 -0
  110. package/cli/agents/validator-epic-qa.md +130 -0
  111. package/cli/agents/validator-epic-security.json +74 -0
  112. package/cli/agents/validator-epic-security.md +154 -0
  113. package/cli/agents/validator-epic-solution-architect.json +74 -0
  114. package/cli/agents/validator-epic-solution-architect.md +156 -0
  115. package/cli/agents/validator-epic-test-architect.json +93 -0
  116. package/cli/agents/validator-epic-test-architect.md +130 -0
  117. package/cli/agents/validator-epic-ui.json +93 -0
  118. package/cli/agents/validator-epic-ui.md +130 -0
  119. package/cli/agents/validator-epic-ux.json +93 -0
  120. package/cli/agents/validator-epic-ux.md +130 -0
  121. package/cli/agents/validator-selector.md +211 -0
  122. package/cli/agents/validator-story-api.json +104 -0
  123. package/cli/agents/validator-story-api.md +152 -0
  124. package/cli/agents/validator-story-backend.json +104 -0
  125. package/cli/agents/validator-story-backend.md +152 -0
  126. package/cli/agents/validator-story-cloud.json +104 -0
  127. package/cli/agents/validator-story-cloud.md +152 -0
  128. package/cli/agents/validator-story-data.json +104 -0
  129. package/cli/agents/validator-story-data.md +152 -0
  130. package/cli/agents/validator-story-database.json +104 -0
  131. package/cli/agents/validator-story-database.md +152 -0
  132. package/cli/agents/validator-story-developer.json +104 -0
  133. package/cli/agents/validator-story-developer.md +152 -0
  134. package/cli/agents/validator-story-devops.json +104 -0
  135. package/cli/agents/validator-story-devops.md +152 -0
  136. package/cli/agents/validator-story-frontend.json +104 -0
  137. package/cli/agents/validator-story-frontend.md +152 -0
  138. package/cli/agents/validator-story-mobile.json +104 -0
  139. package/cli/agents/validator-story-mobile.md +152 -0
  140. package/cli/agents/validator-story-qa.json +104 -0
  141. package/cli/agents/validator-story-qa.md +152 -0
  142. package/cli/agents/validator-story-security.json +104 -0
  143. package/cli/agents/validator-story-security.md +152 -0
  144. package/cli/agents/validator-story-solution-architect.json +104 -0
  145. package/cli/agents/validator-story-solution-architect.md +152 -0
  146. package/cli/agents/validator-story-test-architect.json +104 -0
  147. package/cli/agents/validator-story-test-architect.md +152 -0
  148. package/cli/agents/validator-story-ui.json +104 -0
  149. package/cli/agents/validator-story-ui.md +152 -0
  150. package/cli/agents/validator-story-ux.json +104 -0
  151. package/cli/agents/validator-story-ux.md +152 -0
  152. package/cli/ansi-colors.js +21 -0
  153. package/cli/build-docs.js +29 -8
  154. package/cli/ceremony-history.js +369 -0
  155. package/cli/command-logger.js +49 -12
  156. package/cli/components/static-output.js +63 -0
  157. package/cli/console-output-manager.js +94 -0
  158. package/cli/docs-sync.js +306 -0
  159. package/cli/epic-story-validator.js +1174 -0
  160. package/cli/evaluation-prompts.js +1008 -0
  161. package/cli/execution-context.js +195 -0
  162. package/cli/generate-summary-table.js +340 -0
  163. package/cli/index.js +0 -0
  164. package/cli/init-model-config.js +697 -0
  165. package/cli/init.js +1311 -274
  166. package/cli/kanban-server-manager.js +228 -0
  167. package/cli/llm-claude.js +83 -1
  168. package/cli/llm-gemini.js +85 -0
  169. package/cli/llm-mock.js +233 -0
  170. package/cli/llm-openai.js +233 -0
  171. package/cli/llm-provider.js +240 -3
  172. package/cli/llm-token-limits.js +102 -0
  173. package/cli/llm-verifier.js +454 -0
  174. package/cli/message-constants.js +58 -0
  175. package/cli/message-manager.js +334 -0
  176. package/cli/message-types.js +96 -0
  177. package/cli/messaging-api.js +297 -0
  178. package/cli/model-pricing.js +169 -0
  179. package/cli/model-query-engine.js +468 -0
  180. package/cli/model-recommendation-analyzer.js +495 -0
  181. package/cli/model-selector.js +269 -0
  182. package/cli/output-buffer.js +107 -0
  183. package/cli/process-manager.js +73 -2
  184. package/cli/repl-ink.js +4988 -1217
  185. package/cli/repl-old.js +4 -4
  186. package/cli/seed-processor.js +792 -0
  187. package/cli/sprint-planning-processor.js +1813 -0
  188. package/cli/template-processor.js +2102 -105
  189. package/cli/templates/project.md +25 -8
  190. package/cli/templates/vitepress-config.mts.template +5 -4
  191. package/cli/token-tracker.js +520 -0
  192. package/cli/tools/generate-story-validators.js +317 -0
  193. package/cli/tools/generate-validators.js +669 -0
  194. package/cli/update-checker.js +19 -17
  195. package/cli/update-notifier.js +4 -4
  196. package/cli/validation-router.js +605 -0
  197. package/cli/verification-tracker.js +563 -0
  198. package/kanban/README.md +386 -0
  199. package/kanban/client/README.md +205 -0
  200. package/kanban/client/components.json +20 -0
  201. package/kanban/client/dist/assets/index-CiD8PS2e.js +306 -0
  202. package/kanban/client/dist/assets/index-nLh0m82Q.css +1 -0
  203. package/kanban/client/dist/index.html +16 -0
  204. package/kanban/client/dist/vite.svg +1 -0
  205. package/kanban/client/index.html +15 -0
  206. package/kanban/client/package-lock.json +9442 -0
  207. package/kanban/client/package.json +44 -0
  208. package/kanban/client/postcss.config.js +6 -0
  209. package/kanban/client/public/vite.svg +1 -0
  210. package/kanban/client/src/App.jsx +622 -0
  211. package/kanban/client/src/components/ProjectFileEditorPopup.jsx +117 -0
  212. package/kanban/client/src/components/ceremony/AskArchPopup.jsx +416 -0
  213. package/kanban/client/src/components/ceremony/AskModelPopup.jsx +616 -0
  214. package/kanban/client/src/components/ceremony/CeremonyWorkflowModal.jsx +946 -0
  215. package/kanban/client/src/components/ceremony/EpicStorySelectionModal.jsx +254 -0
  216. package/kanban/client/src/components/ceremony/SponsorCallModal.jsx +619 -0
  217. package/kanban/client/src/components/ceremony/SprintPlanningModal.jsx +704 -0
  218. package/kanban/client/src/components/ceremony/steps/ArchitectureStep.jsx +150 -0
  219. package/kanban/client/src/components/ceremony/steps/CompleteStep.jsx +154 -0
  220. package/kanban/client/src/components/ceremony/steps/DatabaseStep.jsx +202 -0
  221. package/kanban/client/src/components/ceremony/steps/DeploymentStep.jsx +123 -0
  222. package/kanban/client/src/components/ceremony/steps/MissionStep.jsx +106 -0
  223. package/kanban/client/src/components/ceremony/steps/ReviewAnswersStep.jsx +125 -0
  224. package/kanban/client/src/components/ceremony/steps/RunningStep.jsx +228 -0
  225. package/kanban/client/src/components/kanban/CardDetailModal.jsx +559 -0
  226. package/kanban/client/src/components/kanban/EpicSection.jsx +146 -0
  227. package/kanban/client/src/components/kanban/FilterToolbar.jsx +222 -0
  228. package/kanban/client/src/components/kanban/GroupingSelector.jsx +57 -0
  229. package/kanban/client/src/components/kanban/KanbanBoard.jsx +211 -0
  230. package/kanban/client/src/components/kanban/KanbanCard.jsx +138 -0
  231. package/kanban/client/src/components/kanban/KanbanColumn.jsx +90 -0
  232. package/kanban/client/src/components/kanban/RefineWorkItemPopup.jsx +789 -0
  233. package/kanban/client/src/components/layout/LoadingScreen.jsx +82 -0
  234. package/kanban/client/src/components/process/ProcessMonitorBar.jsx +80 -0
  235. package/kanban/client/src/components/settings/AgentEditorPopup.jsx +171 -0
  236. package/kanban/client/src/components/settings/AgentsTab.jsx +353 -0
  237. package/kanban/client/src/components/settings/ApiKeysTab.jsx +113 -0
  238. package/kanban/client/src/components/settings/CeremonyModelsTab.jsx +98 -0
  239. package/kanban/client/src/components/settings/CostThresholdsTab.jsx +94 -0
  240. package/kanban/client/src/components/settings/ModelPricingTab.jsx +204 -0
  241. package/kanban/client/src/components/settings/ServersTab.jsx +121 -0
  242. package/kanban/client/src/components/settings/SettingsModal.jsx +84 -0
  243. package/kanban/client/src/components/stats/CostModal.jsx +353 -0
  244. package/kanban/client/src/components/ui/badge.jsx +27 -0
  245. package/kanban/client/src/components/ui/dialog.jsx +121 -0
  246. package/kanban/client/src/components/ui/tabs.jsx +85 -0
  247. package/kanban/client/src/hooks/__tests__/useGrouping.test.js +232 -0
  248. package/kanban/client/src/hooks/useGrouping.js +118 -0
  249. package/kanban/client/src/hooks/useWebSocket.js +120 -0
  250. package/kanban/client/src/lib/__tests__/api.test.js +196 -0
  251. package/kanban/client/src/lib/__tests__/status-grouping.test.js +94 -0
  252. package/kanban/client/src/lib/api.js +401 -0
  253. package/kanban/client/src/lib/status-grouping.js +144 -0
  254. package/kanban/client/src/lib/utils.js +11 -0
  255. package/kanban/client/src/main.jsx +10 -0
  256. package/kanban/client/src/store/__tests__/kanbanStore.test.js +164 -0
  257. package/kanban/client/src/store/ceremonyStore.js +172 -0
  258. package/kanban/client/src/store/filterStore.js +201 -0
  259. package/kanban/client/src/store/kanbanStore.js +115 -0
  260. package/kanban/client/src/store/processStore.js +65 -0
  261. package/kanban/client/src/store/sprintPlanningStore.js +33 -0
  262. package/kanban/client/src/styles/globals.css +59 -0
  263. package/kanban/client/tailwind.config.js +77 -0
  264. package/kanban/client/vite.config.js +28 -0
  265. package/kanban/client/vitest.config.js +28 -0
  266. package/kanban/dev-start.sh +47 -0
  267. package/kanban/package.json +12 -0
  268. package/kanban/server/index.js +516 -0
  269. package/kanban/server/routes/ceremony.js +305 -0
  270. package/kanban/server/routes/costs.js +157 -0
  271. package/kanban/server/routes/processes.js +50 -0
  272. package/kanban/server/routes/settings.js +303 -0
  273. package/kanban/server/routes/websocket.js +276 -0
  274. package/kanban/server/routes/work-items.js +347 -0
  275. package/kanban/server/services/CeremonyService.js +1190 -0
  276. package/kanban/server/services/FileSystemScanner.js +95 -0
  277. package/kanban/server/services/FileWatcher.js +144 -0
  278. package/kanban/server/services/HierarchyBuilder.js +196 -0
  279. package/kanban/server/services/ProcessRegistry.js +122 -0
  280. package/kanban/server/services/WorkItemReader.js +123 -0
  281. package/kanban/server/services/WorkItemRefineService.js +510 -0
  282. package/kanban/server/start.js +49 -0
  283. package/kanban/server/utils/kanban-logger.js +132 -0
  284. package/kanban/server/utils/markdown.js +91 -0
  285. package/kanban/server/utils/status-grouping.js +107 -0
  286. package/kanban/server/workers/sponsor-call-worker.js +84 -0
  287. package/kanban/server/workers/sprint-planning-worker.js +130 -0
  288. package/package.json +18 -5
  289. package/cli/agents/documentation.md +0 -302
@@ -0,0 +1,792 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { LLMProvider } from './llm-provider.js';
4
+ import { TokenTracker } from './token-tracker.js';
5
+ import { fileURLToPath } from 'url';
6
+ import { getCeremonyHeader } from './message-constants.js';
7
+ import { sendError, sendWarning, sendSuccess, sendInfo, sendOutput, sendIndented, sendSectionHeader } from './messaging-api.js';
8
+
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = path.dirname(__filename);
11
+
12
+ /**
13
+ * SeedProcessor - Decomposes a Story into Tasks and Subtasks
14
+ */
15
+ class SeedProcessor {
16
+ /**
17
+ * Write structured entry to active command log file only.
18
+ * Uses [DEBUG] prefix so ConsoleOutputManager routes to file, not terminal.
19
+ */
20
+ debug(message, data = null) {
21
+ const ts = new Date().toISOString();
22
+ if (data !== null) {
23
+ console.log(`[DEBUG][${ts}] ${message}`, JSON.stringify(data, null, 2));
24
+ } else {
25
+ console.log(`[DEBUG][${ts}] ${message}`);
26
+ }
27
+ }
28
+
29
+ constructor(storyId) {
30
+ this.storyId = storyId;
31
+ this.ceremonyName = 'seed';
32
+ this.avcPath = path.join(process.cwd(), '.avc');
33
+ this.projectPath = path.join(this.avcPath, 'project');
34
+
35
+ // Extract epic ID from story ID (context-0001-0001 → context-0001)
36
+ const epicId = this.extractEpicId(storyId);
37
+
38
+ // Build nested path: .avc/project/context-0001/context-0001-0001
39
+ this.storyPath = path.join(this.projectPath, epicId, storyId);
40
+
41
+ this.avcConfigPath = path.join(this.avcPath, 'avc.json');
42
+ this.agentsPath = path.join(__dirname, 'agents');
43
+
44
+ // Read ceremony config
45
+ const { provider, model, stagesConfig } = this.readCeremonyConfig();
46
+ this._providerName = provider;
47
+ this._modelName = model;
48
+ this.stagesConfig = stagesConfig;
49
+ this.llmProvider = null;
50
+ this._stageProviders = {};
51
+
52
+ // Initialize token tracker
53
+ this.tokenTracker = new TokenTracker(this.avcPath);
54
+ this.tokenTracker.init();
55
+ }
56
+
57
+ /**
58
+ * Extract Epic ID from Story ID
59
+ * @param {string} storyId - Story ID (e.g., context-0001-0001)
60
+ * @returns {string} Epic ID (e.g., context-0001)
61
+ */
62
+ extractEpicId(storyId) {
63
+ // Story format: context-XXXX-YYYY
64
+ // Epic format: context-XXXX
65
+ const match = storyId.match(/^(context-\d+)-\d+$/);
66
+
67
+ if (!match) {
68
+ throw new Error(`Invalid story ID format: ${storyId}. Expected: context-XXXX-YYYY`);
69
+ }
70
+
71
+ return match[1];
72
+ }
73
+
74
+ readCeremonyConfig() {
75
+ try {
76
+ const config = JSON.parse(fs.readFileSync(this.avcConfigPath, 'utf8'));
77
+ const ceremony = config.settings?.ceremonies?.find(c => c.name === this.ceremonyName);
78
+
79
+ if (!ceremony) {
80
+ sendWarning(`Ceremony '${this.ceremonyName}' not found in config, using defaults`);
81
+ this.debug('[WARNING] Ceremony not found in config — using defaults', { ceremonyName: this.ceremonyName });
82
+ return { provider: 'claude', model: 'claude-sonnet-4-5-20250929' };
83
+ }
84
+
85
+ const result = {
86
+ provider: ceremony.provider || 'claude',
87
+ model: ceremony.defaultModel || 'claude-sonnet-4-5-20250929',
88
+ stagesConfig: ceremony.stages || {}
89
+ };
90
+ this.debug('[INFO] Ceremony config loaded', { provider: result.provider, model: result.model, ceremonyName: this.ceremonyName });
91
+ return result;
92
+ } catch (error) {
93
+ sendWarning(`Could not read ceremony config: ${error.message}`);
94
+ this.debug('[ERROR] Failed to read ceremony config', { error: error.message });
95
+ return { provider: 'claude', model: 'claude-sonnet-4-5-20250929' };
96
+ }
97
+ }
98
+
99
+ async initializeLLMProvider() {
100
+ try {
101
+ this.llmProvider = await LLMProvider.create(this._providerName, this._modelName);
102
+ this.llmProvider.onCall((delta) => this.tokenTracker.addIncremental(this.ceremonyName, delta));
103
+ return this.llmProvider;
104
+ } catch (error) {
105
+ sendWarning(`Could not initialize ${this._providerName} provider`);
106
+ sendOutput(`${error.message}`);
107
+ return null;
108
+ }
109
+ }
110
+
111
+ async retryWithBackoff(fn, operation, maxRetries = 3) {
112
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
113
+ try {
114
+ return await fn();
115
+ } catch (error) {
116
+ const isLastAttempt = attempt === maxRetries;
117
+ const isRetriable = error.message?.includes('rate limit') ||
118
+ error.message?.includes('timeout') ||
119
+ error.message?.includes('503');
120
+
121
+ if (isLastAttempt || !isRetriable) {
122
+ throw error;
123
+ }
124
+
125
+ const delay = Math.pow(2, attempt) * 1000;
126
+ sendWarning(`Retry ${attempt}/${maxRetries} in ${delay/1000}s: ${operation}`);
127
+ sendOutput(`Error: ${error.message}`);
128
+ await new Promise(resolve => setTimeout(resolve, delay));
129
+ }
130
+ }
131
+ }
132
+
133
+ /**
134
+ * Get or create a provider for a specific stage, using stage-specific model if configured.
135
+ * Falls back to ceremony-level model if the stage has no explicit config.
136
+ */
137
+ async getProviderForStageInstance(stageName) {
138
+ const stageConfig = this.stagesConfig?.[stageName] || {};
139
+ const provider = stageConfig.provider || this._providerName;
140
+ const model = stageConfig.model || this._modelName;
141
+ const cacheKey = `${stageName}:${provider}:${model}`;
142
+
143
+ if (this._stageProviders[cacheKey]) return this._stageProviders[cacheKey];
144
+
145
+ const instance = await LLMProvider.create(provider, model);
146
+ instance.onCall((delta) => this.tokenTracker.addIncremental(this.ceremonyName, delta));
147
+ this._stageProviders[cacheKey] = instance;
148
+ return instance;
149
+ }
150
+
151
+ /**
152
+ * Distribute documentation content from a parent doc.md to a child item's doc.md.
153
+ * Mirrors the same method in SprintPlanningProcessor.
154
+ *
155
+ * @param {string} parentDocContent - Current content of the parent doc.md
156
+ * @param {Object} childItem - Task or Subtask object
157
+ * @param {'task'|'subtask'} childType - Type of child item
158
+ * @returns {Promise<{childDoc: string, parentDoc: string}>}
159
+ */
160
+ async distributeDocContent(parentDocContent, childItem, childType) {
161
+ const agentPath = path.join(this.agentsPath, 'doc-distributor.md');
162
+ const agentInstructions = fs.readFileSync(agentPath, 'utf8');
163
+
164
+ let itemDescription;
165
+ if (childType === 'task') {
166
+ const acceptance = (childItem.acceptance || []).map(a => `- ${a}`).join('\n') || 'none specified';
167
+ itemDescription = `Type: task
168
+ Name: ${childItem.name}
169
+ Category: ${childItem.category || 'general'}
170
+ Description: ${childItem.description || ''}
171
+ Technical Scope: ${childItem.technicalScope || ''}
172
+ Acceptance criteria:
173
+ ${acceptance}`;
174
+ } else {
175
+ const acceptance = (childItem.acceptance || []).map(a => `- ${a}`).join('\n') || 'none specified';
176
+ itemDescription = `Type: subtask
177
+ Name: ${childItem.name}
178
+ Description: ${childItem.description || ''}
179
+ Technical Details: ${childItem.technicalDetails || ''}
180
+ Acceptance criteria:
181
+ ${acceptance}`;
182
+ }
183
+
184
+ const prompt = `## Parent Document
185
+
186
+ ${parentDocContent}
187
+
188
+ ---
189
+
190
+ ## Child Item to Create Documentation For
191
+
192
+ ${itemDescription}
193
+
194
+ ---
195
+
196
+ Extract content specifically about this ${childType} from the parent document into the child's \`doc.md\`. Remove the extracted content from the parent document. Return JSON with \`child_doc\` and \`parent_doc\` fields.`;
197
+
198
+ try {
199
+ const provider = await this.getProviderForStageInstance('doc-distribution');
200
+ const result = await this.retryWithBackoff(
201
+ () => provider.generateJSON(prompt, agentInstructions),
202
+ `doc distribution for ${childType}: ${childItem.name}`
203
+ );
204
+
205
+ const childDoc = (typeof result.child_doc === 'string' && result.child_doc.trim())
206
+ ? result.child_doc
207
+ : `# ${childItem.name}\n\n${childItem.description || ''}\n`;
208
+
209
+ const parentDoc = (typeof result.parent_doc === 'string' && result.parent_doc.trim())
210
+ ? result.parent_doc
211
+ : parentDocContent;
212
+
213
+ return { childDoc, parentDoc };
214
+ } catch (err) {
215
+ this.debug('[WARNING] Doc distribution failed — using stub doc', { error: err.message, childType, name: childItem.name });
216
+ return {
217
+ childDoc: `# ${childItem.name}\n\n${childItem.description || ''}\n`,
218
+ parentDoc: parentDocContent
219
+ };
220
+ }
221
+ }
222
+
223
+ // STAGE 1: Validate prerequisites
224
+ validatePrerequisites() {
225
+ this.debug('[INFO] validatePrerequisites() called', { storyId: this.storyId, storyPath: this.storyPath });
226
+
227
+ // Check Story ID format
228
+ if (!this.storyId || !this.storyId.match(/^context-\d{4}-\d{4}$/)) {
229
+ this.debug('[ERROR] Invalid story ID format', { storyId: this.storyId });
230
+ throw new Error(
231
+ `Invalid Story ID format: ${this.storyId}\nExpected format: context-XXXX-XXXX (e.g., context-0001-0001)`
232
+ );
233
+ }
234
+
235
+ // Check Story directory exists
236
+ const storyDirExists = fs.existsSync(this.storyPath);
237
+ this.debug('[DEBUG] Story directory check', { storyPath: this.storyPath, exists: storyDirExists });
238
+ if (!storyDirExists) {
239
+ throw new Error(
240
+ `Story ${this.storyId} not found.\nPlease run /sprint-planning first to create Stories.`
241
+ );
242
+ }
243
+
244
+ // Check Story work.json exists
245
+ const storyWorkJsonPath = path.join(this.storyPath, 'work.json');
246
+ const workJsonExists = fs.existsSync(storyWorkJsonPath);
247
+ this.debug('[DEBUG] Story work.json check', { path: storyWorkJsonPath, exists: workJsonExists });
248
+ if (!workJsonExists) {
249
+ throw new Error(
250
+ `Story metadata not found: ${this.storyId}/work.json`
251
+ );
252
+ }
253
+
254
+ // Check Story has no existing tasks
255
+ const storyWork = JSON.parse(fs.readFileSync(storyWorkJsonPath, 'utf8'));
256
+ this.debug('[DEBUG] Story work.json content', {
257
+ name: storyWork.name,
258
+ type: storyWork.type,
259
+ existingChildren: storyWork.children?.length || 0,
260
+ children: storyWork.children || [],
261
+ });
262
+ if (storyWork.children && storyWork.children.length > 0) {
263
+ this.debug('[ERROR] Story already has tasks — cannot re-seed', { children: storyWork.children });
264
+ throw new Error(
265
+ `Story ${this.storyId} already has tasks\nChildren: ${storyWork.children.join(', ')}\nCannot re-seed a Story that already has Tasks.`
266
+ );
267
+ }
268
+
269
+ // Check Story context.md exists
270
+ const storyContextPath = path.join(this.storyPath, 'context.md');
271
+ const contextExists = fs.existsSync(storyContextPath);
272
+ this.debug('[DEBUG] Story context.md check', { path: storyContextPath, exists: contextExists });
273
+ if (!contextExists) {
274
+ throw new Error(
275
+ `Story context not found: ${this.storyId}/context.md`
276
+ );
277
+ }
278
+
279
+ this.debug('[INFO] validatePrerequisites() passed — all checks OK');
280
+ }
281
+
282
+ // STAGE 2: Read Story context and hierarchy
283
+ readStoryContext() {
284
+ this.debug('[INFO] readStoryContext() called', { storyPath: this.storyPath });
285
+
286
+ // Read Story work.json
287
+ const storyWorkJsonPath = path.join(this.storyPath, 'work.json');
288
+ const storyWork = JSON.parse(fs.readFileSync(storyWorkJsonPath, 'utf8'));
289
+ this.debug('[DEBUG] Story work.json loaded', {
290
+ name: storyWork.name,
291
+ type: storyWork.type,
292
+ id: storyWork.id,
293
+ description: storyWork.description?.substring(0, 100),
294
+ });
295
+
296
+ // Read Story context.md
297
+ const storyContextPath = path.join(this.storyPath, 'context.md');
298
+ const storyContext = fs.readFileSync(storyContextPath, 'utf8');
299
+ this.debug('[DEBUG] Story context.md loaded', { sizeChars: storyContext.length });
300
+
301
+ // Extract Epic ID from Story ID (context-0001-0001 → context-0001)
302
+ const epicId = this.extractEpicId(this.storyId);
303
+ const epicPath = path.join(this.projectPath, epicId);
304
+
305
+ // Read Epic context.md
306
+ const epicContextPath = path.join(epicPath, 'context.md');
307
+ let epicContext = '';
308
+ if (fs.existsSync(epicContextPath)) {
309
+ epicContext = fs.readFileSync(epicContextPath, 'utf8');
310
+ this.debug('[DEBUG] Epic context.md loaded', { epicId, sizeChars: epicContext.length });
311
+ } else {
312
+ this.debug('[WARNING] Epic context.md not found', { epicContextPath });
313
+ }
314
+
315
+ // Read Epic work.json
316
+ const epicWorkJsonPath = path.join(epicPath, 'work.json');
317
+ let epicWork = {};
318
+ if (fs.existsSync(epicWorkJsonPath)) {
319
+ epicWork = JSON.parse(fs.readFileSync(epicWorkJsonPath, 'utf8'));
320
+ this.debug('[DEBUG] Epic work.json loaded', { epicName: epicWork.name, storyCount: epicWork.children?.length });
321
+ } else {
322
+ this.debug('[WARNING] Epic work.json not found', { epicWorkJsonPath });
323
+ }
324
+
325
+ // Read Project context.md
326
+ const projectContextPath = path.join(this.projectPath, 'project/context.md');
327
+ let projectContext = '';
328
+ if (fs.existsSync(projectContextPath)) {
329
+ projectContext = fs.readFileSync(projectContextPath, 'utf8');
330
+ this.debug('[DEBUG] Project context.md loaded', { sizeChars: projectContext.length });
331
+ } else {
332
+ this.debug('[WARNING] Project context.md not found', { projectContextPath });
333
+ }
334
+
335
+ this.debug('[INFO] readStoryContext() complete', {
336
+ storyName: storyWork.name,
337
+ epicId,
338
+ hasEpicContext: !!epicContext,
339
+ hasProjectContext: !!projectContext,
340
+ });
341
+
342
+ return {
343
+ storyWork,
344
+ storyContext,
345
+ epicWork,
346
+ epicContext,
347
+ projectContext
348
+ };
349
+ }
350
+
351
+ // STAGE 3: Decompose Story → Tasks + Subtasks
352
+ async decomposeIntoTasksSubtasks(contextData) {
353
+ const startTime = Date.now();
354
+ this.debug('[INFO] decomposeIntoTasksSubtasks() called', { storyId: this.storyId });
355
+
356
+ if (!this.llmProvider) {
357
+ this.debug('[INFO] Initializing LLM provider', { provider: this._providerName, model: this._modelName });
358
+ await this.initializeLLMProvider();
359
+ }
360
+
361
+ if (!this.llmProvider) {
362
+ this.debug('[ERROR] LLM provider initialization failed');
363
+ throw new Error('LLM provider required for decomposition');
364
+ }
365
+ this.debug('[INFO] LLM provider ready', { provider: this._providerName, model: this._modelName });
366
+
367
+ // Read agent instructions
368
+ const taskSubtaskDecomposerAgent = fs.readFileSync(
369
+ path.join(this.agentsPath, 'task-subtask-decomposer.md'),
370
+ 'utf8'
371
+ );
372
+
373
+ // Build prompt
374
+ const { storyWork, storyContext, epicWork, epicContext, projectContext } = contextData;
375
+
376
+ let prompt = `Given the following Story, decompose it into Tasks and Subtasks:
377
+
378
+ **Story ID:** ${this.storyId}
379
+ **Story Name:** ${storyWork.name}
380
+ **Story Description:** ${storyWork.description}
381
+ **User Type:** ${storyWork.userType}
382
+ **Acceptance Criteria:**
383
+ ${storyWork.acceptance.map((ac, i) => `${i + 1}. ${ac}`).join('\n')}
384
+
385
+ **Story Context:**
386
+ ${storyContext}
387
+
388
+ **Epic Context (${epicWork.name}):**
389
+ ${epicContext}
390
+
391
+ **Project Context:**
392
+ ${projectContext}
393
+
394
+ Decompose this Story into:
395
+ - 2-5 Tasks (technical components: backend, frontend, database, testing, infrastructure)
396
+ - 1-3 Subtasks per Task (atomic work units, implementable in 1-4 hours)
397
+
398
+ Return your response as JSON following the exact structure specified in your instructions.`;
399
+
400
+ this.debug('[INFO] Calling LLM for Task/Subtask decomposition', {
401
+ provider: this._providerName,
402
+ model: this._modelName,
403
+ storyName: contextData.storyWork?.name,
404
+ promptLengthChars: prompt.length,
405
+ });
406
+
407
+ const hierarchy = await this.retryWithBackoff(
408
+ () => this.llmProvider.generateJSON(prompt, taskSubtaskDecomposerAgent),
409
+ 'Task/Subtask decomposition'
410
+ );
411
+
412
+ if (!hierarchy.tasks || !Array.isArray(hierarchy.tasks)) {
413
+ this.debug('[ERROR] Invalid LLM response — missing tasks array', { responseKeys: Object.keys(hierarchy) });
414
+ throw new Error('Invalid decomposition response: missing tasks array');
415
+ }
416
+
417
+ // Calculate total subtasks
418
+ const totalSubtasks = hierarchy.tasks.reduce((sum, task) => sum + (task.subtasks?.length || 0), 0);
419
+
420
+ this.debug('[INFO] Decomposition complete', {
421
+ taskCount: hierarchy.tasks.length,
422
+ totalSubtasks,
423
+ taskNames: hierarchy.tasks.map(t => t.name),
424
+ duration: `${Date.now() - startTime}ms`,
425
+ });
426
+
427
+
428
+ return hierarchy;
429
+ }
430
+
431
+ // STAGE 4: Validate Task/Subtask structure
432
+ validateTaskSubtaskStructure(hierarchy) {
433
+ this.debug('[INFO] validateTaskSubtaskStructure() called', { taskCount: hierarchy.tasks?.length });
434
+
435
+ const { tasks } = hierarchy;
436
+
437
+ if (tasks.length < 2 || tasks.length > 5) {
438
+ this.debug('[WARNING] Unexpected task count', { count: tasks.length, expected: '2-5' });
439
+ sendWarning(`Expected 2-5 Tasks, got ${tasks.length}`);
440
+ }
441
+
442
+ for (const task of tasks) {
443
+ const subtaskCount = task.subtasks?.length || 0;
444
+ if (subtaskCount < 1 || subtaskCount > 3) {
445
+ this.debug('[WARNING] Unexpected subtask count', { task: task.name, count: subtaskCount, expected: '1-3' });
446
+ sendWarning(`Task ${task.name} has ${subtaskCount} Subtasks (expected 1-3)`);
447
+ }
448
+
449
+ // Validate Task ID format (context-XXXX-XXXX-XXXX)
450
+ if (!task.id || !task.id.match(/^context-\d{4}-\d{4}-\d{4}$/)) {
451
+ throw new Error(`Invalid Task ID format: ${task.id}`);
452
+ }
453
+
454
+ // Validate Subtask ID format (context-XXXX-XXXX-XXXX-XXXX)
455
+ for (const subtask of task.subtasks || []) {
456
+ if (!subtask.id || !subtask.id.match(/^context-\d{4}-\d{4}-\d{4}-\d{4}$/)) {
457
+ throw new Error(`Invalid Subtask ID format: ${subtask.id}`);
458
+ }
459
+ }
460
+ }
461
+ }
462
+
463
+ // STAGE 5-6: Generate contexts
464
+ async generateContext(level, id, data, agentInstructions) {
465
+ const prompt = this.buildContextPrompt(level, id, data);
466
+ const result = await this.llmProvider.generateJSON(prompt, agentInstructions);
467
+
468
+ if (!result.contextMarkdown || !result.tokenCount) {
469
+ throw new Error(`Invalid context response for ${id}: missing required fields`);
470
+ }
471
+
472
+ return result;
473
+ }
474
+
475
+ buildContextPrompt(level, id, data) {
476
+ const { projectContext, epicContext, storyContext, task, subtask } = data;
477
+
478
+ let prompt = `Generate a context.md file for the following ${level}:\n\n`;
479
+ prompt += `**Level:** ${level}\n`;
480
+ prompt += `**ID:** ${id}\n\n`;
481
+
482
+ if (level === 'task') {
483
+ prompt += `**Task Name:** ${task.name}\n`;
484
+ prompt += `**Task Category:** ${task.category}\n`;
485
+ prompt += `**Task Description:** ${task.description}\n`;
486
+ prompt += `**Technical Scope:** ${task.technicalScope}\n`;
487
+ prompt += `**Acceptance Criteria:**\n${task.acceptance.map((ac, i) => `${i + 1}. ${ac}`).join('\n')}\n\n`;
488
+ prompt += `**Story Context:**\n${storyContext}\n\n`;
489
+ prompt += `**Epic Context:**\n${epicContext}\n\n`;
490
+ prompt += `**Project Context:**\n${projectContext}\n\n`;
491
+ } else if (level === 'subtask') {
492
+ prompt += `**Subtask Name:** ${subtask.name}\n`;
493
+ prompt += `**Subtask Description:** ${subtask.description}\n`;
494
+ prompt += `**Technical Details:** ${subtask.technicalDetails}\n`;
495
+ prompt += `**Acceptance Criteria:**\n${subtask.acceptance.map((ac, i) => `${i + 1}. ${ac}`).join('\n')}\n\n`;
496
+ prompt += `**Task Context (${task.name}):**\n`;
497
+ prompt += `- Category: ${task.category}\n`;
498
+ prompt += `- Technical Scope: ${task.technicalScope}\n\n`;
499
+ prompt += `**Story Context:**\n${storyContext}\n\n`;
500
+ prompt += `**Epic Context:**\n${epicContext}\n\n`;
501
+ prompt += `**Project Context:**\n${projectContext}\n\n`;
502
+ }
503
+
504
+ prompt += `Return your response as JSON following the exact structure specified in your instructions.`;
505
+
506
+ return prompt;
507
+ }
508
+
509
+ // STAGE 7: Write Task/Subtask files
510
+ async writeTaskSubtaskFiles(hierarchy, contextData) {
511
+ // Read agent
512
+ const featureContextGeneratorAgent = fs.readFileSync(
513
+ path.join(this.agentsPath, 'feature-context-generator.md'),
514
+ 'utf8'
515
+ );
516
+
517
+ const { storyContext, epicContext, projectContext } = contextData;
518
+
519
+ // Read story doc.md for hierarchical doc distribution (story → task → subtask)
520
+ const storyDocPath = path.join(this.storyPath, 'doc.md');
521
+ let storyDocContent = '';
522
+ const doDistribute = fs.existsSync(storyDocPath);
523
+ if (doDistribute) {
524
+ storyDocContent = fs.readFileSync(storyDocPath, 'utf8');
525
+ this.debug('[INFO] Story doc.md loaded for doc distribution', { sizeChars: storyDocContent.length });
526
+ } else {
527
+ this.debug('[WARNING] Story doc.md not found — doc distribution skipped, using stub task docs');
528
+ }
529
+
530
+ let taskCount = 0;
531
+ let subtaskCount = 0;
532
+ const taskIds = [];
533
+
534
+ for (const task of hierarchy.tasks) {
535
+ const taskDir = path.join(this.projectPath, task.id);
536
+
537
+ if (!fs.existsSync(taskDir)) {
538
+ fs.mkdirSync(taskDir, { recursive: true });
539
+ }
540
+
541
+ // Distribute documentation: extract task-specific content from story doc
542
+ let taskDocContent;
543
+ if (doDistribute) {
544
+ this.debug(`[INFO] Distributing docs: story/doc.md → ${task.id}/doc.md`);
545
+ const distributed = await this.distributeDocContent(storyDocContent, task, 'task');
546
+ taskDocContent = distributed.childDoc;
547
+ storyDocContent = distributed.parentDoc;
548
+ this.debug(`[INFO] After distribution: story doc ${storyDocContent.length} bytes, task doc ${taskDocContent.length} bytes`);
549
+ } else {
550
+ taskDocContent = `# ${task.name}\n\n${task.description || ''}\n`;
551
+ }
552
+
553
+ // Generate and write Task context.md
554
+ const taskContext = await this.retryWithBackoff(
555
+ () => this.generateContext('task', task.id, { projectContext, epicContext, storyContext, task }, featureContextGeneratorAgent),
556
+ `Task ${task.id} context`
557
+ );
558
+ fs.writeFileSync(
559
+ path.join(taskDir, 'context.md'),
560
+ taskContext.contextMarkdown,
561
+ 'utf8'
562
+ );
563
+
564
+ // Write Task work.json
565
+ const taskWorkJson = {
566
+ id: task.id,
567
+ name: task.name,
568
+ type: 'task',
569
+ category: task.category,
570
+ description: task.description,
571
+ technicalScope: task.technicalScope,
572
+ acceptance: task.acceptance,
573
+ status: 'planned',
574
+ dependencies: task.dependencies || [],
575
+ children: (task.subtasks || []).map(s => s.id),
576
+ metadata: {
577
+ created: new Date().toISOString(),
578
+ ceremony: this.ceremonyName,
579
+ tokenBudget: taskContext.tokenCount
580
+ }
581
+ };
582
+ fs.writeFileSync(
583
+ path.join(taskDir, 'work.json'),
584
+ JSON.stringify(taskWorkJson, null, 2),
585
+ 'utf8'
586
+ );
587
+
588
+ taskCount++;
589
+ taskIds.push(task.id);
590
+
591
+ // Write Subtask files
592
+ for (const subtask of task.subtasks || []) {
593
+ const subtaskDir = path.join(this.projectPath, subtask.id);
594
+
595
+ if (!fs.existsSync(subtaskDir)) {
596
+ fs.mkdirSync(subtaskDir, { recursive: true });
597
+ }
598
+
599
+ // Distribute documentation: extract subtask-specific content from task doc
600
+ let subtaskDocContent;
601
+ if (doDistribute) {
602
+ this.debug(`[INFO] Distributing docs: ${task.id}/doc.md → ${subtask.id}/doc.md`);
603
+ const distributed = await this.distributeDocContent(taskDocContent, subtask, 'subtask');
604
+ subtaskDocContent = distributed.childDoc;
605
+ taskDocContent = distributed.parentDoc;
606
+ this.debug(`[INFO] After distribution: task doc ${taskDocContent.length} bytes, subtask doc ${subtaskDocContent.length} bytes`);
607
+ } else {
608
+ subtaskDocContent = `# ${subtask.name}\n\n${subtask.description || ''}\n`;
609
+ }
610
+
611
+ // Write Subtask doc.md with distributed content
612
+ fs.writeFileSync(
613
+ path.join(subtaskDir, 'doc.md'),
614
+ subtaskDocContent,
615
+ 'utf8'
616
+ );
617
+
618
+ // Generate and write Subtask context.md
619
+ const subtaskContext = await this.retryWithBackoff(
620
+ () => this.generateContext('subtask', subtask.id, { projectContext, epicContext, storyContext, task, subtask }, featureContextGeneratorAgent),
621
+ `Subtask ${subtask.id} context`
622
+ );
623
+ fs.writeFileSync(
624
+ path.join(subtaskDir, 'context.md'),
625
+ subtaskContext.contextMarkdown,
626
+ 'utf8'
627
+ );
628
+
629
+ // Write Subtask work.json
630
+ const subtaskWorkJson = {
631
+ id: subtask.id,
632
+ name: subtask.name,
633
+ type: 'subtask',
634
+ description: subtask.description,
635
+ technicalDetails: subtask.technicalDetails,
636
+ acceptance: subtask.acceptance,
637
+ status: 'planned',
638
+ dependencies: subtask.dependencies || [],
639
+ children: [], // Subtasks have no children
640
+ metadata: {
641
+ created: new Date().toISOString(),
642
+ ceremony: this.ceremonyName,
643
+ tokenBudget: subtaskContext.tokenCount
644
+ }
645
+ };
646
+ fs.writeFileSync(
647
+ path.join(subtaskDir, 'work.json'),
648
+ JSON.stringify(subtaskWorkJson, null, 2),
649
+ 'utf8'
650
+ );
651
+
652
+ subtaskCount++;
653
+ }
654
+
655
+ // Write Task doc.md AFTER all subtasks have extracted their portions
656
+ fs.writeFileSync(
657
+ path.join(taskDir, 'doc.md'),
658
+ taskDocContent,
659
+ 'utf8'
660
+ );
661
+ this.debug(`[INFO] Wrote ${task.id}/doc.md (${taskDocContent.length} bytes)`);
662
+ }
663
+
664
+ // Write final story doc.md AFTER all tasks extracted their portions
665
+ if (doDistribute) {
666
+ fs.writeFileSync(storyDocPath, storyDocContent, 'utf8');
667
+ this.debug(`[INFO] Updated story doc.md after task extraction (${storyDocContent.length} bytes)`);
668
+ }
669
+
670
+ return { taskCount, subtaskCount, taskIds };
671
+ }
672
+
673
+ // STAGE 8: Update Story work.json
674
+ updateStoryWorkJson(taskIds) {
675
+ const storyWorkJsonPath = path.join(this.storyPath, 'work.json');
676
+ const storyWork = JSON.parse(fs.readFileSync(storyWorkJsonPath, 'utf8'));
677
+
678
+ storyWork.children = taskIds;
679
+ storyWork.metadata.lastUpdated = new Date().toISOString();
680
+ storyWork.metadata.seeded = true;
681
+
682
+ fs.writeFileSync(
683
+ storyWorkJsonPath,
684
+ JSON.stringify(storyWork, null, 2),
685
+ 'utf8'
686
+ );
687
+
688
+ }
689
+
690
+ // Display summary
691
+ displaySummary(hierarchy, contextData, taskCount, subtaskCount) {
692
+ sendOutput(`${contextData.storyWork.name}: ${taskCount} Tasks, ${subtaskCount} Subtasks created.`);
693
+ sendOutput('Run /seed on each task to continue decomposition.');
694
+ for (const task of hierarchy.tasks) {
695
+ sendIndented(`Task: ${task.name} (${task.id})`, 1);
696
+ for (const subtask of task.subtasks || []) {
697
+ sendIndented(`- Subtask: ${subtask.name}`, 2);
698
+ }
699
+ }
700
+ }
701
+
702
+ // Main execution method
703
+ async execute() {
704
+ const execStartTime = Date.now();
705
+ this.debug('[INFO] SeedProcessor.execute() started', {
706
+ storyId: this.storyId,
707
+ storyPath: this.storyPath,
708
+ provider: this._providerName,
709
+ model: this._modelName,
710
+ });
711
+
712
+ try {
713
+ const header = getCeremonyHeader('seed');
714
+ console.log(`\n${header.title}\n`);
715
+ console.log(`Story: ${this.storyId}\n`);
716
+
717
+ // Stage 1: Validate
718
+ this.debug('[INFO] Stage 1/3: Validating prerequisites');
719
+ this.validatePrerequisites();
720
+
721
+ // Stage 2: Read Story context
722
+ this.debug('[INFO] Stage 2a: Reading Story context files');
723
+ sendInfo('Reading Story context...');
724
+ const contextData = this.readStoryContext();
725
+
726
+ // Stage 3: Decompose
727
+ this.debug('[INFO] Stage 2b: Decomposing Story into Tasks/Subtasks via LLM');
728
+ let hierarchy = await this.decomposeIntoTasksSubtasks(contextData);
729
+
730
+ // Stage 4: Validate
731
+ this.debug('[INFO] Stage 2c: Validating decomposition structure');
732
+ this.validateTaskSubtaskStructure(hierarchy);
733
+
734
+ // Stage 5-7: Generate contexts and write files
735
+ this.debug('[INFO] Stage 2/3: Generating context files and writing to disk');
736
+ sendSectionHeader('Stage 2/3: Generating context files');
737
+ const { taskCount, subtaskCount, taskIds } = await this.writeTaskSubtaskFiles(hierarchy, contextData);
738
+ this.debug('[INFO] Files written', { taskCount, subtaskCount, taskIds });
739
+
740
+ // Stage 8: Update Story work.json
741
+ this.debug('[INFO] Stage 3/3: Updating Story work.json with task IDs');
742
+ this.updateStoryWorkJson(taskIds);
743
+
744
+ // Display summary
745
+ this.displaySummary(hierarchy, contextData, taskCount, subtaskCount);
746
+
747
+ // Display token usage
748
+ if (this.llmProvider) {
749
+ const usage = this.llmProvider.getTokenUsage();
750
+ this.debug('[INFO] Token usage summary', {
751
+ inputTokens: usage.inputTokens,
752
+ outputTokens: usage.outputTokens,
753
+ totalTokens: usage.totalTokens,
754
+ apiCalls: usage.totalCalls,
755
+ });
756
+
757
+ sendSectionHeader('Token Usage');
758
+ sendIndented(`Input: ${usage.inputTokens.toLocaleString()} tokens`, 1);
759
+ sendIndented(`Output: ${usage.outputTokens.toLocaleString()} tokens`, 1);
760
+ sendIndented(`Total: ${usage.totalTokens.toLocaleString()} tokens`, 1);
761
+ sendIndented(`API Calls: ${usage.totalCalls}`, 1);
762
+
763
+ this.tokenTracker.finalizeRun(this.ceremonyName);
764
+ this.debug('[INFO] Token history finalized in .avc/token-history.json');
765
+ sendSuccess('Token history updated');
766
+ }
767
+
768
+ sendSectionHeader('Next steps');
769
+ sendIndented('1. Review Task/Subtask breakdown in .avc/project/', 1);
770
+ sendIndented('2. Start implementing Subtasks (smallest work units)', 1);
771
+
772
+ this.debug('[INFO] SeedProcessor.execute() complete', {
773
+ storyId: this.storyId,
774
+ duration: `${Date.now() - execStartTime}ms`,
775
+ taskCount,
776
+ subtaskCount,
777
+ });
778
+
779
+ } catch (error) {
780
+ this.debug('[ERROR] SeedProcessor.execute() failed', {
781
+ error: error.message,
782
+ stack: error.stack,
783
+ storyId: this.storyId,
784
+ duration: `${Date.now() - execStartTime}ms`,
785
+ });
786
+ sendError(`Seed ceremony failed: ${error.message}`);
787
+ throw error;
788
+ }
789
+ }
790
+ }
791
+
792
+ export { SeedProcessor };