@agile-vibe-coding/avc 0.1.1 → 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 (239) hide show
  1. package/cli/agent-loader.js +21 -0
  2. package/cli/agents/agent-selector.md +152 -0
  3. package/cli/agents/architecture-recommender.md +418 -0
  4. package/cli/agents/code-implementer.md +117 -0
  5. package/cli/agents/code-validator.md +80 -0
  6. package/cli/agents/context-reviewer-epic.md +101 -0
  7. package/cli/agents/context-reviewer-story.md +92 -0
  8. package/cli/agents/context-writer-epic.md +145 -0
  9. package/cli/agents/context-writer-story.md +111 -0
  10. package/cli/agents/database-deep-dive.md +470 -0
  11. package/cli/agents/database-recommender.md +634 -0
  12. package/cli/agents/doc-distributor.md +176 -0
  13. package/cli/agents/doc-writer-epic.md +42 -0
  14. package/cli/agents/doc-writer-story.md +43 -0
  15. package/cli/agents/documentation-updater.md +203 -0
  16. package/cli/agents/duplicate-detector.md +110 -0
  17. package/cli/agents/epic-story-decomposer.md +559 -0
  18. package/cli/agents/feature-context-generator.md +91 -0
  19. package/cli/agents/gap-checker-epic.md +52 -0
  20. package/cli/agents/impact-checker-story.md +51 -0
  21. package/cli/agents/migration-guide-generator.md +305 -0
  22. package/cli/agents/mission-scope-generator.md +143 -0
  23. package/cli/agents/mission-scope-validator.md +146 -0
  24. package/cli/agents/project-context-extractor.md +122 -0
  25. package/cli/agents/project-documentation-creator.json +226 -0
  26. package/cli/agents/project-documentation-creator.md +595 -0
  27. package/cli/agents/question-prefiller.md +269 -0
  28. package/cli/agents/refiner-epic.md +39 -0
  29. package/cli/agents/refiner-story.md +42 -0
  30. package/cli/agents/scaffolding-generator.md +99 -0
  31. package/cli/agents/seed-validator.md +71 -0
  32. package/cli/agents/story-doc-enricher.md +133 -0
  33. package/cli/agents/story-scope-reviewer.md +147 -0
  34. package/cli/agents/story-splitter.md +83 -0
  35. package/cli/agents/suggestion-business-analyst.md +88 -0
  36. package/cli/agents/suggestion-deployment-architect.md +263 -0
  37. package/cli/agents/suggestion-product-manager.md +129 -0
  38. package/cli/agents/suggestion-security-specialist.md +156 -0
  39. package/cli/agents/suggestion-technical-architect.md +269 -0
  40. package/cli/agents/suggestion-ux-researcher.md +93 -0
  41. package/cli/agents/task-subtask-decomposer.md +188 -0
  42. package/cli/agents/validator-documentation.json +183 -0
  43. package/cli/agents/validator-documentation.md +455 -0
  44. package/cli/agents/validator-selector.md +211 -0
  45. package/cli/ansi-colors.js +21 -0
  46. package/cli/api-reference-tool.js +368 -0
  47. package/cli/build-docs.js +29 -8
  48. package/cli/ceremony-history.js +369 -0
  49. package/cli/checks/catalog.json +76 -0
  50. package/cli/checks/code/quality.json +26 -0
  51. package/cli/checks/code/testing.json +14 -0
  52. package/cli/checks/code/traceability.json +26 -0
  53. package/cli/checks/cross-refs/epic.json +171 -0
  54. package/cli/checks/cross-refs/story.json +149 -0
  55. package/cli/checks/epic/api.json +114 -0
  56. package/cli/checks/epic/backend.json +126 -0
  57. package/cli/checks/epic/cloud.json +126 -0
  58. package/cli/checks/epic/data.json +102 -0
  59. package/cli/checks/epic/database.json +114 -0
  60. package/cli/checks/epic/developer.json +182 -0
  61. package/cli/checks/epic/devops.json +174 -0
  62. package/cli/checks/epic/frontend.json +162 -0
  63. package/cli/checks/epic/mobile.json +102 -0
  64. package/cli/checks/epic/qa.json +90 -0
  65. package/cli/checks/epic/security.json +184 -0
  66. package/cli/checks/epic/solution-architect.json +192 -0
  67. package/cli/checks/epic/test-architect.json +90 -0
  68. package/cli/checks/epic/ui.json +102 -0
  69. package/cli/checks/epic/ux.json +90 -0
  70. package/cli/checks/fixes/epic-fix-template.md +10 -0
  71. package/cli/checks/fixes/story-fix-template.md +10 -0
  72. package/cli/checks/story/api.json +186 -0
  73. package/cli/checks/story/backend.json +102 -0
  74. package/cli/checks/story/cloud.json +102 -0
  75. package/cli/checks/story/data.json +210 -0
  76. package/cli/checks/story/database.json +102 -0
  77. package/cli/checks/story/developer.json +168 -0
  78. package/cli/checks/story/devops.json +102 -0
  79. package/cli/checks/story/frontend.json +174 -0
  80. package/cli/checks/story/mobile.json +102 -0
  81. package/cli/checks/story/qa.json +210 -0
  82. package/cli/checks/story/security.json +198 -0
  83. package/cli/checks/story/solution-architect.json +230 -0
  84. package/cli/checks/story/test-architect.json +210 -0
  85. package/cli/checks/story/ui.json +102 -0
  86. package/cli/checks/story/ux.json +102 -0
  87. package/cli/coding-order.js +401 -0
  88. package/cli/command-logger.js +49 -12
  89. package/cli/components/static-output.js +63 -0
  90. package/cli/console-output-manager.js +94 -0
  91. package/cli/dependency-checker.js +72 -0
  92. package/cli/docs-sync.js +306 -0
  93. package/cli/epic-story-validator.js +659 -0
  94. package/cli/evaluation-prompts.js +1008 -0
  95. package/cli/execution-context.js +195 -0
  96. package/cli/generate-summary-table.js +340 -0
  97. package/cli/init-model-config.js +704 -0
  98. package/cli/init.js +1737 -278
  99. package/cli/kanban-server-manager.js +227 -0
  100. package/cli/llm-claude.js +150 -1
  101. package/cli/llm-gemini.js +109 -0
  102. package/cli/llm-local.js +493 -0
  103. package/cli/llm-mock.js +233 -0
  104. package/cli/llm-openai.js +454 -0
  105. package/cli/llm-provider.js +379 -3
  106. package/cli/llm-token-limits.js +211 -0
  107. package/cli/llm-verifier.js +662 -0
  108. package/cli/llm-xiaomi.js +143 -0
  109. package/cli/message-constants.js +49 -0
  110. package/cli/message-manager.js +334 -0
  111. package/cli/message-types.js +96 -0
  112. package/cli/messaging-api.js +291 -0
  113. package/cli/micro-check-fixer.js +335 -0
  114. package/cli/micro-check-runner.js +449 -0
  115. package/cli/micro-check-scorer.js +148 -0
  116. package/cli/micro-check-validator.js +538 -0
  117. package/cli/model-pricing.js +192 -0
  118. package/cli/model-query-engine.js +468 -0
  119. package/cli/model-recommendation-analyzer.js +495 -0
  120. package/cli/model-selector.js +270 -0
  121. package/cli/output-buffer.js +107 -0
  122. package/cli/process-manager.js +73 -2
  123. package/cli/prompt-logger.js +57 -0
  124. package/cli/repl-ink.js +4625 -1094
  125. package/cli/repl-old.js +3 -4
  126. package/cli/seed-processor.js +962 -0
  127. package/cli/sprint-planning-processor.js +4162 -0
  128. package/cli/template-processor.js +2149 -105
  129. package/cli/templates/project.md +25 -8
  130. package/cli/templates/vitepress-config.mts.template +5 -4
  131. package/cli/token-tracker.js +547 -0
  132. package/cli/tools/generate-story-validators.js +317 -0
  133. package/cli/tools/generate-validators.js +669 -0
  134. package/cli/update-checker.js +19 -17
  135. package/cli/update-notifier.js +4 -4
  136. package/cli/validation-router.js +667 -0
  137. package/cli/verification-tracker.js +563 -0
  138. package/cli/worktree-runner.js +654 -0
  139. package/kanban/README.md +386 -0
  140. package/kanban/client/README.md +205 -0
  141. package/kanban/client/components.json +20 -0
  142. package/kanban/client/dist/assets/index-D_KC5EQT.css +1 -0
  143. package/kanban/client/dist/assets/index-DjY5zqW7.js +351 -0
  144. package/kanban/client/dist/index.html +16 -0
  145. package/kanban/client/dist/vite.svg +1 -0
  146. package/kanban/client/index.html +15 -0
  147. package/kanban/client/package-lock.json +9442 -0
  148. package/kanban/client/package.json +44 -0
  149. package/kanban/client/postcss.config.js +6 -0
  150. package/kanban/client/public/vite.svg +1 -0
  151. package/kanban/client/src/App.jsx +651 -0
  152. package/kanban/client/src/components/ProjectFileEditorPopup.jsx +117 -0
  153. package/kanban/client/src/components/ceremony/AskArchPopup.jsx +420 -0
  154. package/kanban/client/src/components/ceremony/AskModelPopup.jsx +629 -0
  155. package/kanban/client/src/components/ceremony/CeremonyWorkflowModal.jsx +1133 -0
  156. package/kanban/client/src/components/ceremony/EpicStorySelectionModal.jsx +254 -0
  157. package/kanban/client/src/components/ceremony/ProviderSwitcherButton.jsx +290 -0
  158. package/kanban/client/src/components/ceremony/SponsorCallModal.jsx +686 -0
  159. package/kanban/client/src/components/ceremony/SprintPlanningModal.jsx +838 -0
  160. package/kanban/client/src/components/ceremony/steps/ArchitectureStep.jsx +150 -0
  161. package/kanban/client/src/components/ceremony/steps/CompleteStep.jsx +136 -0
  162. package/kanban/client/src/components/ceremony/steps/DatabaseStep.jsx +202 -0
  163. package/kanban/client/src/components/ceremony/steps/DeploymentStep.jsx +123 -0
  164. package/kanban/client/src/components/ceremony/steps/MissionStep.jsx +106 -0
  165. package/kanban/client/src/components/ceremony/steps/ReviewAnswersStep.jsx +329 -0
  166. package/kanban/client/src/components/ceremony/steps/RunningStep.jsx +249 -0
  167. package/kanban/client/src/components/kanban/CardDetailModal.jsx +646 -0
  168. package/kanban/client/src/components/kanban/EpicSection.jsx +146 -0
  169. package/kanban/client/src/components/kanban/FilterToolbar.jsx +222 -0
  170. package/kanban/client/src/components/kanban/GroupingSelector.jsx +63 -0
  171. package/kanban/client/src/components/kanban/KanbanBoard.jsx +211 -0
  172. package/kanban/client/src/components/kanban/KanbanCard.jsx +147 -0
  173. package/kanban/client/src/components/kanban/KanbanColumn.jsx +90 -0
  174. package/kanban/client/src/components/kanban/RefineWorkItemPopup.jsx +784 -0
  175. package/kanban/client/src/components/kanban/RunButton.jsx +162 -0
  176. package/kanban/client/src/components/kanban/SeedButton.jsx +176 -0
  177. package/kanban/client/src/components/layout/LoadingScreen.jsx +82 -0
  178. package/kanban/client/src/components/process/ProcessMonitorBar.jsx +80 -0
  179. package/kanban/client/src/components/settings/AgentEditorPopup.jsx +171 -0
  180. package/kanban/client/src/components/settings/AgentsTab.jsx +381 -0
  181. package/kanban/client/src/components/settings/ApiKeysTab.jsx +142 -0
  182. package/kanban/client/src/components/settings/CeremonyModelsTab.jsx +105 -0
  183. package/kanban/client/src/components/settings/CheckEditorPopup.jsx +507 -0
  184. package/kanban/client/src/components/settings/CostThresholdsTab.jsx +95 -0
  185. package/kanban/client/src/components/settings/ModelPricingTab.jsx +269 -0
  186. package/kanban/client/src/components/settings/OpenAIAuthSection.jsx +412 -0
  187. package/kanban/client/src/components/settings/ServersTab.jsx +121 -0
  188. package/kanban/client/src/components/settings/SettingsModal.jsx +84 -0
  189. package/kanban/client/src/components/stats/CostModal.jsx +384 -0
  190. package/kanban/client/src/components/ui/badge.jsx +27 -0
  191. package/kanban/client/src/components/ui/dialog.jsx +121 -0
  192. package/kanban/client/src/components/ui/tabs.jsx +85 -0
  193. package/kanban/client/src/hooks/__tests__/useGrouping.test.js +232 -0
  194. package/kanban/client/src/hooks/useGrouping.js +177 -0
  195. package/kanban/client/src/hooks/useWebSocket.js +120 -0
  196. package/kanban/client/src/lib/__tests__/api.test.js +196 -0
  197. package/kanban/client/src/lib/__tests__/status-grouping.test.js +94 -0
  198. package/kanban/client/src/lib/api.js +515 -0
  199. package/kanban/client/src/lib/status-grouping.js +154 -0
  200. package/kanban/client/src/lib/utils.js +11 -0
  201. package/kanban/client/src/main.jsx +10 -0
  202. package/kanban/client/src/store/__tests__/kanbanStore.test.js +164 -0
  203. package/kanban/client/src/store/ceremonyStore.js +172 -0
  204. package/kanban/client/src/store/filterStore.js +201 -0
  205. package/kanban/client/src/store/kanbanStore.js +123 -0
  206. package/kanban/client/src/store/processStore.js +65 -0
  207. package/kanban/client/src/store/sprintPlanningStore.js +33 -0
  208. package/kanban/client/src/styles/globals.css +59 -0
  209. package/kanban/client/tailwind.config.js +77 -0
  210. package/kanban/client/vite.config.js +28 -0
  211. package/kanban/client/vitest.config.js +28 -0
  212. package/kanban/dev-start.sh +47 -0
  213. package/kanban/package.json +12 -0
  214. package/kanban/server/index.js +537 -0
  215. package/kanban/server/routes/ceremony.js +454 -0
  216. package/kanban/server/routes/costs.js +163 -0
  217. package/kanban/server/routes/openai-oauth.js +366 -0
  218. package/kanban/server/routes/processes.js +50 -0
  219. package/kanban/server/routes/settings.js +736 -0
  220. package/kanban/server/routes/websocket.js +281 -0
  221. package/kanban/server/routes/work-items.js +487 -0
  222. package/kanban/server/services/CeremonyService.js +1441 -0
  223. package/kanban/server/services/FileSystemScanner.js +95 -0
  224. package/kanban/server/services/FileWatcher.js +144 -0
  225. package/kanban/server/services/HierarchyBuilder.js +196 -0
  226. package/kanban/server/services/ProcessRegistry.js +122 -0
  227. package/kanban/server/services/TaskRunnerService.js +261 -0
  228. package/kanban/server/services/WorkItemReader.js +123 -0
  229. package/kanban/server/services/WorkItemRefineService.js +510 -0
  230. package/kanban/server/start.js +49 -0
  231. package/kanban/server/utils/kanban-logger.js +132 -0
  232. package/kanban/server/utils/markdown.js +91 -0
  233. package/kanban/server/utils/status-grouping.js +107 -0
  234. package/kanban/server/workers/run-task-worker.js +121 -0
  235. package/kanban/server/workers/seed-worker.js +94 -0
  236. package/kanban/server/workers/sponsor-call-worker.js +92 -0
  237. package/kanban/server/workers/sprint-planning-worker.js +212 -0
  238. package/package.json +19 -7
  239. package/cli/agents/documentation.md +0 -302
@@ -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,123 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ import { extractDescriptionFromDoc } from '../utils/markdown.js';
4
+
5
+ /**
6
+ * WorkItemReader
7
+ * Parses work.json files and builds hierarchical work item structure
8
+ */
9
+ export class WorkItemReader {
10
+ /**
11
+ * @param {string} projectRoot - Absolute path to project root directory
12
+ */
13
+ constructor(projectRoot) {
14
+ this.projectRoot = projectRoot;
15
+ this.avcProjectPath = path.join(projectRoot, '.avc', 'project');
16
+ }
17
+
18
+ /**
19
+ * Read and parse a single work.json file
20
+ * @param {string} filePath - Absolute path to work.json
21
+ * @returns {Promise<object|null>} Parsed work item or null if invalid
22
+ */
23
+ async readWorkItem(filePath) {
24
+ try {
25
+ const content = await fs.readFile(filePath, 'utf8');
26
+ const workItem = JSON.parse(content);
27
+
28
+ // Enhance with computed fields
29
+ workItem._filePath = filePath;
30
+ workItem._dirPath = path.dirname(filePath);
31
+
32
+ return workItem;
33
+ } catch (error) {
34
+ console.error(`Error reading work item from ${filePath}:`, error.message);
35
+ return null;
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Read all work items from an array of file paths
41
+ * @param {Array<string>} filePaths - Array of absolute paths to work.json files
42
+ * @returns {Promise<Array<object>>} Array of parsed work items
43
+ */
44
+ async readAllWorkItems(filePaths) {
45
+ const promises = filePaths.map((filePath) => this.readWorkItem(filePath));
46
+ const results = await Promise.all(promises);
47
+
48
+ // Filter out null results (failed reads)
49
+ return results.filter((item) => item !== null);
50
+ }
51
+
52
+ /**
53
+ * Determine work item type based on ID depth
54
+ * Example: context-0001 → epic, context-0001-0001 → story, etc.
55
+ * @param {string} id - Work item ID
56
+ * @returns {string} Type: 'epic' | 'story' | 'task' | 'subtask'
57
+ */
58
+ getWorkItemType(id) {
59
+ const parts = id.split('-');
60
+ const depth = parts.length - 1; // Subtract 1 for 'context' prefix
61
+
62
+ switch (depth) {
63
+ case 1:
64
+ return 'epic';
65
+ case 2:
66
+ return 'story';
67
+ case 3:
68
+ return 'task';
69
+ case 4:
70
+ return 'subtask';
71
+ default:
72
+ return 'unknown';
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Get parent ID from a work item ID
78
+ * Example: context-0001-0001-0001 → context-0001-0001
79
+ * @param {string} id - Work item ID
80
+ * @returns {string|null} Parent ID or null if root level
81
+ */
82
+ getParentId(id) {
83
+ const parts = id.split('-');
84
+
85
+ if (parts.length <= 2) {
86
+ // Root level (epic), no parent
87
+ return null;
88
+ }
89
+
90
+ // Remove the last segment
91
+ return parts.slice(0, -1).join('-');
92
+ }
93
+
94
+ /**
95
+ * Read doc.md file for a work item
96
+ * @param {string} workItemPath - Directory path of work item
97
+ * @returns {Promise<string|null>} Markdown content or null if not found
98
+ */
99
+ async readDocumentation(workItemPath) {
100
+ try {
101
+ const docPath = path.join(workItemPath, 'doc.md');
102
+ return await fs.readFile(docPath, 'utf8');
103
+ } catch (error) {
104
+ return null;
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Get full details for a work item (including doc.md)
110
+ * @param {object} workItem - Base work item object
111
+ * @returns {Promise<object>} Enhanced work item with documentation
112
+ */
113
+ async getFullDetails(workItem) {
114
+ const dirPath = workItem._dirPath;
115
+ const doc = await this.readDocumentation(dirPath);
116
+ return {
117
+ ...workItem,
118
+ // doc.md opening paragraph is canonical; fall back to work.json value if no doc.md
119
+ description: doc ? extractDescriptionFromDoc(doc) || workItem.description : workItem.description,
120
+ documentation: doc,
121
+ };
122
+ }
123
+ }