task-o-matic-core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (447) hide show
  1. package/README.md +646 -0
  2. package/dist/index.d.ts +27 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +46 -0
  5. package/dist/lib/ai-service/ai-operations.d.ts +45 -0
  6. package/dist/lib/ai-service/ai-operations.d.ts.map +1 -0
  7. package/dist/lib/ai-service/ai-operations.js +60 -0
  8. package/dist/lib/ai-service/base-operations.d.ts +43 -0
  9. package/dist/lib/ai-service/base-operations.d.ts.map +1 -0
  10. package/dist/lib/ai-service/base-operations.js +119 -0
  11. package/dist/lib/ai-service/documentation-operations.d.ts +18 -0
  12. package/dist/lib/ai-service/documentation-operations.d.ts.map +1 -0
  13. package/dist/lib/ai-service/documentation-operations.js +308 -0
  14. package/dist/lib/ai-service/filesystem-tools.d.ts +69 -0
  15. package/dist/lib/ai-service/filesystem-tools.d.ts.map +1 -0
  16. package/dist/lib/ai-service/filesystem-tools.js +70 -0
  17. package/dist/lib/ai-service/json-parser.d.ts +34 -0
  18. package/dist/lib/ai-service/json-parser.d.ts.map +1 -0
  19. package/dist/lib/ai-service/json-parser.js +177 -0
  20. package/dist/lib/ai-service/mcp-client.d.ts +9 -0
  21. package/dist/lib/ai-service/mcp-client.d.ts.map +1 -0
  22. package/dist/lib/ai-service/mcp-client.js +48 -0
  23. package/dist/lib/ai-service/model-provider.d.ts +12 -0
  24. package/dist/lib/ai-service/model-provider.d.ts.map +1 -0
  25. package/dist/lib/ai-service/model-provider.js +146 -0
  26. package/dist/lib/ai-service/prd-operations.d.ts +25 -0
  27. package/dist/lib/ai-service/prd-operations.d.ts.map +1 -0
  28. package/dist/lib/ai-service/prd-operations.js +592 -0
  29. package/dist/lib/ai-service/research-tools.d.ts +4 -0
  30. package/dist/lib/ai-service/research-tools.d.ts.map +1 -0
  31. package/dist/lib/ai-service/research-tools.js +8 -0
  32. package/dist/lib/ai-service/retry-handler.d.ts +8 -0
  33. package/dist/lib/ai-service/retry-handler.d.ts.map +1 -0
  34. package/dist/lib/ai-service/retry-handler.js +63 -0
  35. package/dist/lib/ai-service/task-operations.d.ts +13 -0
  36. package/dist/lib/ai-service/task-operations.d.ts.map +1 -0
  37. package/dist/lib/ai-service/task-operations.js +220 -0
  38. package/dist/lib/benchmark/registry.d.ts +11 -0
  39. package/dist/lib/benchmark/registry.d.ts.map +1 -0
  40. package/dist/lib/benchmark/registry.js +212 -0
  41. package/dist/lib/benchmark/runner.d.ts +6 -0
  42. package/dist/lib/benchmark/runner.d.ts.map +1 -0
  43. package/dist/lib/benchmark/runner.js +150 -0
  44. package/dist/lib/benchmark/storage.d.ts +13 -0
  45. package/dist/lib/benchmark/storage.d.ts.map +1 -0
  46. package/dist/lib/benchmark/storage.js +100 -0
  47. package/dist/lib/benchmark/types.d.ts +104 -0
  48. package/dist/lib/benchmark/types.d.ts.map +1 -0
  49. package/dist/lib/benchmark/types.js +2 -0
  50. package/dist/lib/better-t-stack-cli.d.ts +50 -0
  51. package/dist/lib/better-t-stack-cli.d.ts.map +1 -0
  52. package/dist/lib/better-t-stack-cli.js +428 -0
  53. package/dist/lib/bootstrap/cli-bootstrap.d.ts +14 -0
  54. package/dist/lib/bootstrap/cli-bootstrap.d.ts.map +1 -0
  55. package/dist/lib/bootstrap/cli-bootstrap.js +322 -0
  56. package/dist/lib/bootstrap/index.d.ts +3 -0
  57. package/dist/lib/bootstrap/index.d.ts.map +1 -0
  58. package/dist/lib/bootstrap/index.js +18 -0
  59. package/dist/lib/bootstrap/medusa-bootstrap.d.ts +14 -0
  60. package/dist/lib/bootstrap/medusa-bootstrap.d.ts.map +1 -0
  61. package/dist/lib/bootstrap/medusa-bootstrap.js +215 -0
  62. package/dist/lib/config-validation.d.ts +215 -0
  63. package/dist/lib/config-validation.d.ts.map +1 -0
  64. package/dist/lib/config-validation.js +254 -0
  65. package/dist/lib/config.d.ts +55 -0
  66. package/dist/lib/config.d.ts.map +1 -0
  67. package/dist/lib/config.js +351 -0
  68. package/dist/lib/context-builder.d.ts +66 -0
  69. package/dist/lib/context-builder.d.ts.map +1 -0
  70. package/dist/lib/context-builder.js +322 -0
  71. package/dist/lib/executors/claude-code-executor.d.ts +9 -0
  72. package/dist/lib/executors/claude-code-executor.d.ts.map +1 -0
  73. package/dist/lib/executors/claude-code-executor.js +69 -0
  74. package/dist/lib/executors/codex-executor.d.ts +9 -0
  75. package/dist/lib/executors/codex-executor.d.ts.map +1 -0
  76. package/dist/lib/executors/codex-executor.js +73 -0
  77. package/dist/lib/executors/executor-factory.d.ts +5 -0
  78. package/dist/lib/executors/executor-factory.d.ts.map +1 -0
  79. package/dist/lib/executors/executor-factory.js +27 -0
  80. package/dist/lib/executors/gemini-executor.d.ts +9 -0
  81. package/dist/lib/executors/gemini-executor.d.ts.map +1 -0
  82. package/dist/lib/executors/gemini-executor.js +67 -0
  83. package/dist/lib/executors/kilo-executor.d.ts +9 -0
  84. package/dist/lib/executors/kilo-executor.d.ts.map +1 -0
  85. package/dist/lib/executors/kilo-executor.js +69 -0
  86. package/dist/lib/executors/opencode-executor.d.ts +9 -0
  87. package/dist/lib/executors/opencode-executor.d.ts.map +1 -0
  88. package/dist/lib/executors/opencode-executor.js +67 -0
  89. package/dist/lib/git-utils.d.ts +88 -0
  90. package/dist/lib/git-utils.d.ts.map +1 -0
  91. package/dist/lib/git-utils.js +242 -0
  92. package/dist/lib/hooks.d.ts +73 -0
  93. package/dist/lib/hooks.d.ts.map +1 -0
  94. package/dist/lib/hooks.js +62 -0
  95. package/dist/lib/index.d.ts +100 -0
  96. package/dist/lib/index.d.ts.map +1 -0
  97. package/dist/lib/index.js +143 -0
  98. package/dist/lib/logger.d.ts +20 -0
  99. package/dist/lib/logger.d.ts.map +1 -0
  100. package/dist/lib/logger.js +32 -0
  101. package/dist/lib/notifications.d.ts +7 -0
  102. package/dist/lib/notifications.d.ts.map +1 -0
  103. package/dist/lib/notifications.js +81 -0
  104. package/dist/lib/prompt-builder.d.ts +70 -0
  105. package/dist/lib/prompt-builder.d.ts.map +1 -0
  106. package/dist/lib/prompt-builder.js +344 -0
  107. package/dist/lib/prompt-registry.d.ts +22 -0
  108. package/dist/lib/prompt-registry.d.ts.map +1 -0
  109. package/dist/lib/prompt-registry.js +409 -0
  110. package/dist/lib/provider-defaults.json +32 -0
  111. package/dist/lib/storage/file-system.d.ts +57 -0
  112. package/dist/lib/storage/file-system.d.ts.map +1 -0
  113. package/dist/lib/storage/file-system.js +638 -0
  114. package/dist/lib/storage/storage-callbacks.d.ts +17 -0
  115. package/dist/lib/storage/storage-callbacks.d.ts.map +1 -0
  116. package/dist/lib/storage/storage-callbacks.js +94 -0
  117. package/dist/lib/storage/types.d.ts +43 -0
  118. package/dist/lib/storage/types.d.ts.map +1 -0
  119. package/dist/lib/storage/types.js +2 -0
  120. package/dist/lib/task-execution-core.d.ts +7 -0
  121. package/dist/lib/task-execution-core.d.ts.map +1 -0
  122. package/dist/lib/task-execution-core.js +381 -0
  123. package/dist/lib/task-execution.d.ts +7 -0
  124. package/dist/lib/task-execution.d.ts.map +1 -0
  125. package/dist/lib/task-execution.js +40 -0
  126. package/dist/lib/task-loop-execution.d.ts +7 -0
  127. package/dist/lib/task-loop-execution.d.ts.map +1 -0
  128. package/dist/lib/task-loop-execution.js +156 -0
  129. package/dist/lib/task-planning.d.ts +29 -0
  130. package/dist/lib/task-planning.d.ts.map +1 -0
  131. package/dist/lib/task-planning.js +103 -0
  132. package/dist/lib/task-review.d.ts +27 -0
  133. package/dist/lib/task-review.d.ts.map +1 -0
  134. package/dist/lib/task-review.js +103 -0
  135. package/dist/lib/validation.d.ts +26 -0
  136. package/dist/lib/validation.d.ts.map +1 -0
  137. package/dist/lib/validation.js +98 -0
  138. package/dist/prompts/documentation-detection.d.ts +2 -0
  139. package/dist/prompts/documentation-detection.d.ts.map +1 -0
  140. package/dist/prompts/documentation-detection.js +24 -0
  141. package/dist/prompts/documentation-recap.d.ts +3 -0
  142. package/dist/prompts/documentation-recap.d.ts.map +1 -0
  143. package/dist/prompts/documentation-recap.js +13 -0
  144. package/dist/prompts/index.d.ts +15 -0
  145. package/dist/prompts/index.d.ts.map +1 -0
  146. package/dist/prompts/index.js +30 -0
  147. package/dist/prompts/prd-combination.d.ts +2 -0
  148. package/dist/prompts/prd-combination.d.ts.map +1 -0
  149. package/dist/prompts/prd-combination.js +35 -0
  150. package/dist/prompts/prd-generation.d.ts +2 -0
  151. package/dist/prompts/prd-generation.d.ts.map +1 -0
  152. package/dist/prompts/prd-generation.js +49 -0
  153. package/dist/prompts/prd-parsing.d.ts +3 -0
  154. package/dist/prompts/prd-parsing.d.ts.map +1 -0
  155. package/dist/prompts/prd-parsing.js +172 -0
  156. package/dist/prompts/prd-question-answer.d.ts +3 -0
  157. package/dist/prompts/prd-question-answer.d.ts.map +1 -0
  158. package/dist/prompts/prd-question-answer.js +27 -0
  159. package/dist/prompts/prd-question.d.ts +3 -0
  160. package/dist/prompts/prd-question.d.ts.map +1 -0
  161. package/dist/prompts/prd-question.js +40 -0
  162. package/dist/prompts/prd-rework.d.ts +3 -0
  163. package/dist/prompts/prd-rework.d.ts.map +1 -0
  164. package/dist/prompts/prd-rework.js +81 -0
  165. package/dist/prompts/prd-suggest-stack.d.ts +3 -0
  166. package/dist/prompts/prd-suggest-stack.d.ts.map +1 -0
  167. package/dist/prompts/prd-suggest-stack.js +99 -0
  168. package/dist/prompts/task-breakdown.d.ts +3 -0
  169. package/dist/prompts/task-breakdown.d.ts.map +1 -0
  170. package/dist/prompts/task-breakdown.js +151 -0
  171. package/dist/prompts/task-enhancement.d.ts +3 -0
  172. package/dist/prompts/task-enhancement.d.ts.map +1 -0
  173. package/dist/prompts/task-enhancement.js +140 -0
  174. package/dist/prompts/task-execution.d.ts +3 -0
  175. package/dist/prompts/task-execution.d.ts.map +1 -0
  176. package/dist/prompts/task-execution.js +24 -0
  177. package/dist/prompts/task-planning.d.ts +3 -0
  178. package/dist/prompts/task-planning.d.ts.map +1 -0
  179. package/dist/prompts/task-planning.js +66 -0
  180. package/dist/prompts/workflow-assistance.d.ts +32 -0
  181. package/dist/prompts/workflow-assistance.d.ts.map +1 -0
  182. package/dist/prompts/workflow-assistance.js +130 -0
  183. package/dist/prompts/workflow-prompts.d.ts +9 -0
  184. package/dist/prompts/workflow-prompts.d.ts.map +1 -0
  185. package/dist/prompts/workflow-prompts.js +93 -0
  186. package/dist/services/benchmark.d.ts +26 -0
  187. package/dist/services/benchmark.d.ts.map +1 -0
  188. package/dist/services/benchmark.js +343 -0
  189. package/dist/services/prd.d.ts +136 -0
  190. package/dist/services/prd.d.ts.map +1 -0
  191. package/dist/services/prd.js +550 -0
  192. package/dist/services/tasks.d.ts +388 -0
  193. package/dist/services/tasks.d.ts.map +1 -0
  194. package/dist/services/tasks.js +1150 -0
  195. package/dist/services/workflow-ai-assistant.d.ts +74 -0
  196. package/dist/services/workflow-ai-assistant.d.ts.map +1 -0
  197. package/dist/services/workflow-ai-assistant.js +175 -0
  198. package/dist/services/workflow-benchmark.d.ts +34 -0
  199. package/dist/services/workflow-benchmark.d.ts.map +1 -0
  200. package/dist/services/workflow-benchmark.js +318 -0
  201. package/dist/services/workflow.d.ts +107 -0
  202. package/dist/services/workflow.d.ts.map +1 -0
  203. package/dist/services/workflow.js +580 -0
  204. package/dist/test/hooks.test.d.ts +2 -0
  205. package/dist/test/hooks.test.d.ts.map +1 -0
  206. package/dist/test/hooks.test.js +67 -0
  207. package/dist/test/integration/callbacks.test.d.ts +2 -0
  208. package/dist/test/integration/callbacks.test.d.ts.map +1 -0
  209. package/dist/test/integration/callbacks.test.js +64 -0
  210. package/dist/test/lib/ai-service/task-operations.test.d.ts +2 -0
  211. package/dist/test/lib/ai-service/task-operations.test.d.ts.map +1 -0
  212. package/dist/test/lib/ai-service/task-operations.test.js +362 -0
  213. package/dist/test/lib/config.test.d.ts +2 -0
  214. package/dist/test/lib/config.test.d.ts.map +1 -0
  215. package/dist/test/lib/config.test.js +128 -0
  216. package/dist/test/lib/git-utils.test.d.ts +2 -0
  217. package/dist/test/lib/git-utils.test.d.ts.map +1 -0
  218. package/dist/test/lib/git-utils.test.js +168 -0
  219. package/dist/test/mocks/mock-ai-operations.d.ts +15 -0
  220. package/dist/test/mocks/mock-ai-operations.d.ts.map +1 -0
  221. package/dist/test/mocks/mock-ai-operations.js +107 -0
  222. package/dist/test/mocks/mock-context-builder.d.ts +10 -0
  223. package/dist/test/mocks/mock-context-builder.d.ts.map +1 -0
  224. package/dist/test/mocks/mock-context-builder.js +81 -0
  225. package/dist/test/mocks/mock-model-provider.d.ts +7 -0
  226. package/dist/test/mocks/mock-model-provider.d.ts.map +1 -0
  227. package/dist/test/mocks/mock-model-provider.js +21 -0
  228. package/dist/test/mocks/mock-service-factory.d.ts +11 -0
  229. package/dist/test/mocks/mock-service-factory.d.ts.map +1 -0
  230. package/dist/test/mocks/mock-service-factory.js +61 -0
  231. package/dist/test/mocks/mock-storage.d.ts +50 -0
  232. package/dist/test/mocks/mock-storage.d.ts.map +1 -0
  233. package/dist/test/mocks/mock-storage.js +145 -0
  234. package/dist/test/model-parsing.test.d.ts +2 -0
  235. package/dist/test/model-parsing.test.d.ts.map +1 -0
  236. package/dist/test/model-parsing.test.js +73 -0
  237. package/dist/test/services/task-service.test.d.ts +2 -0
  238. package/dist/test/services/task-service.test.d.ts.map +1 -0
  239. package/dist/test/services/task-service.test.js +459 -0
  240. package/dist/test/storage.test.d.ts +2 -0
  241. package/dist/test/storage.test.d.ts.map +1 -0
  242. package/dist/test/storage.test.js +207 -0
  243. package/dist/test/task-loop-git.test.d.ts +2 -0
  244. package/dist/test/task-loop-git.test.d.ts.map +1 -0
  245. package/dist/test/task-loop-git.test.js +95 -0
  246. package/dist/test/test-mock-setup.d.ts +26 -0
  247. package/dist/test/test-mock-setup.d.ts.map +1 -0
  248. package/dist/test/test-mock-setup.js +41 -0
  249. package/dist/test/test-setup.d.ts +9 -0
  250. package/dist/test/test-setup.d.ts.map +1 -0
  251. package/dist/test/test-setup.js +44 -0
  252. package/dist/test/test-utils.d.ts +22 -0
  253. package/dist/test/test-utils.d.ts.map +1 -0
  254. package/dist/test/test-utils.js +37 -0
  255. package/dist/test/utils/ai-operation-utility.test.d.ts +2 -0
  256. package/dist/test/utils/ai-operation-utility.test.d.ts.map +1 -0
  257. package/dist/test/utils/ai-operation-utility.test.js +290 -0
  258. package/dist/test/utils/error-handling.test.d.ts +2 -0
  259. package/dist/test/utils/error-handling.test.d.ts.map +1 -0
  260. package/dist/test/utils/error-handling.test.js +231 -0
  261. package/dist/test/utils/file-utils.test.d.ts +2 -0
  262. package/dist/test/utils/file-utils.test.d.ts.map +1 -0
  263. package/dist/test/utils/file-utils.test.js +76 -0
  264. package/dist/test/utils/id-generator.test.d.ts +2 -0
  265. package/dist/test/utils/id-generator.test.d.ts.map +1 -0
  266. package/dist/test/utils/id-generator.test.js +41 -0
  267. package/dist/test/utils/model-parser.test.d.ts +2 -0
  268. package/dist/test/utils/model-parser.test.d.ts.map +1 -0
  269. package/dist/test/utils/model-parser.test.js +65 -0
  270. package/dist/test/validation.test.d.ts +2 -0
  271. package/dist/test/validation.test.d.ts.map +1 -0
  272. package/dist/test/validation.test.js +22 -0
  273. package/dist/types/callbacks.d.ts +30 -0
  274. package/dist/types/callbacks.d.ts.map +1 -0
  275. package/dist/types/callbacks.js +2 -0
  276. package/dist/types/index.d.ts +435 -0
  277. package/dist/types/index.d.ts.map +1 -0
  278. package/dist/types/index.js +30 -0
  279. package/dist/types/mcp.d.ts +3 -0
  280. package/dist/types/mcp.d.ts.map +1 -0
  281. package/dist/types/mcp.js +3 -0
  282. package/dist/types/options.d.ts +112 -0
  283. package/dist/types/options.d.ts.map +1 -0
  284. package/dist/types/options.js +2 -0
  285. package/dist/types/results.d.ts +200 -0
  286. package/dist/types/results.d.ts.map +1 -0
  287. package/dist/types/results.js +2 -0
  288. package/dist/types/workflow-options.d.ts +82 -0
  289. package/dist/types/workflow-options.d.ts.map +1 -0
  290. package/dist/types/workflow-options.js +2 -0
  291. package/dist/types/workflow-results.d.ts +82 -0
  292. package/dist/types/workflow-results.d.ts.map +1 -0
  293. package/dist/types/workflow-results.js +2 -0
  294. package/dist/utils/ai-config-builder.d.ts +14 -0
  295. package/dist/utils/ai-config-builder.d.ts.map +1 -0
  296. package/dist/utils/ai-config-builder.js +22 -0
  297. package/dist/utils/ai-operation-utility.d.ts +142 -0
  298. package/dist/utils/ai-operation-utility.d.ts.map +1 -0
  299. package/dist/utils/ai-operation-utility.js +303 -0
  300. package/dist/utils/ai-service-factory.d.ts +34 -0
  301. package/dist/utils/ai-service-factory.d.ts.map +1 -0
  302. package/dist/utils/ai-service-factory.js +99 -0
  303. package/dist/utils/error-utils.d.ts +70 -0
  304. package/dist/utils/error-utils.d.ts.map +1 -0
  305. package/dist/utils/error-utils.js +104 -0
  306. package/dist/utils/file-utils.d.ts +107 -0
  307. package/dist/utils/file-utils.d.ts.map +1 -0
  308. package/dist/utils/file-utils.js +171 -0
  309. package/dist/utils/id-generator.d.ts +92 -0
  310. package/dist/utils/id-generator.d.ts.map +1 -0
  311. package/dist/utils/id-generator.js +146 -0
  312. package/dist/utils/metadata-utils.d.ts +40 -0
  313. package/dist/utils/metadata-utils.d.ts.map +1 -0
  314. package/dist/utils/metadata-utils.js +43 -0
  315. package/dist/utils/model-executor-parser.d.ts +38 -0
  316. package/dist/utils/model-executor-parser.d.ts.map +1 -0
  317. package/dist/utils/model-executor-parser.js +69 -0
  318. package/dist/utils/model-parser.d.ts +6 -0
  319. package/dist/utils/model-parser.d.ts.map +1 -0
  320. package/dist/utils/model-parser.js +49 -0
  321. package/dist/utils/stack-formatter.d.ts +12 -0
  322. package/dist/utils/stack-formatter.d.ts.map +1 -0
  323. package/dist/utils/stack-formatter.js +36 -0
  324. package/dist/utils/storage-utils.d.ts +49 -0
  325. package/dist/utils/storage-utils.d.ts.map +1 -0
  326. package/dist/utils/storage-utils.js +80 -0
  327. package/dist/utils/streaming-utils.d.ts +38 -0
  328. package/dist/utils/streaming-utils.d.ts.map +1 -0
  329. package/dist/utils/streaming-utils.js +64 -0
  330. package/dist/utils/task-o-matic-error.d.ts +206 -0
  331. package/dist/utils/task-o-matic-error.d.ts.map +1 -0
  332. package/dist/utils/task-o-matic-error.js +304 -0
  333. package/package.json +40 -0
  334. package/src/index.ts +36 -0
  335. package/src/lib/ai-service/ai-operations.ts +310 -0
  336. package/src/lib/ai-service/base-operations.ts +139 -0
  337. package/src/lib/ai-service/documentation-operations.ts +438 -0
  338. package/src/lib/ai-service/filesystem-tools.ts +73 -0
  339. package/src/lib/ai-service/gemini-proxy.ts.bak +52 -0
  340. package/src/lib/ai-service/json-parser.ts +203 -0
  341. package/src/lib/ai-service/mcp-client.ts +54 -0
  342. package/src/lib/ai-service/model-provider.ts +192 -0
  343. package/src/lib/ai-service/prd-operations.ts +854 -0
  344. package/src/lib/ai-service/research-tools.ts +207 -0
  345. package/src/lib/ai-service/retry-handler.ts +89 -0
  346. package/src/lib/ai-service/task-operations.ts +342 -0
  347. package/src/lib/benchmark/registry.ts +307 -0
  348. package/src/lib/benchmark/runner.ts +190 -0
  349. package/src/lib/benchmark/storage.ts +140 -0
  350. package/src/lib/benchmark/types.ts +121 -0
  351. package/src/lib/better-t-stack-cli.ts +524 -0
  352. package/src/lib/bootstrap/cli-bootstrap.ts +397 -0
  353. package/src/lib/bootstrap/index.ts +2 -0
  354. package/src/lib/bootstrap/medusa-bootstrap.ts +261 -0
  355. package/src/lib/config-validation.ts +278 -0
  356. package/src/lib/config.ts +435 -0
  357. package/src/lib/context-builder.ts +383 -0
  358. package/src/lib/executors/claude-code-executor.ts +83 -0
  359. package/src/lib/executors/codex-executor.ts +85 -0
  360. package/src/lib/executors/executor-factory.ts +28 -0
  361. package/src/lib/executors/gemini-executor.ts +80 -0
  362. package/src/lib/executors/kilo-executor.ts +83 -0
  363. package/src/lib/executors/opencode-executor.ts +81 -0
  364. package/src/lib/git-utils.ts +334 -0
  365. package/src/lib/hooks.ts +121 -0
  366. package/src/lib/index.ts +166 -0
  367. package/src/lib/logger.ts +43 -0
  368. package/src/lib/notifications.ts +103 -0
  369. package/src/lib/prompt-builder.ts +471 -0
  370. package/src/lib/prompt-registry.ts +491 -0
  371. package/src/lib/provider-defaults.json +32 -0
  372. package/src/lib/storage/file-system.ts +864 -0
  373. package/src/lib/storage/storage-callbacks.ts +120 -0
  374. package/src/lib/storage/types.ts +58 -0
  375. package/src/lib/task-execution-core.ts +591 -0
  376. package/src/lib/task-execution.ts +59 -0
  377. package/src/lib/task-loop-execution.ts +214 -0
  378. package/src/lib/task-planning.ts +157 -0
  379. package/src/lib/task-review.ts +138 -0
  380. package/src/lib/validation.ts +140 -0
  381. package/src/prompts/documentation-detection.ts +21 -0
  382. package/src/prompts/documentation-recap.ts +11 -0
  383. package/src/prompts/index.ts +14 -0
  384. package/src/prompts/prd-combination.ts +32 -0
  385. package/src/prompts/prd-generation.ts +46 -0
  386. package/src/prompts/prd-parsing.ts +170 -0
  387. package/src/prompts/prd-question-answer.ts +25 -0
  388. package/src/prompts/prd-question.ts +38 -0
  389. package/src/prompts/prd-rework.ts +79 -0
  390. package/src/prompts/prd-suggest-stack.ts +97 -0
  391. package/src/prompts/task-breakdown.ts +149 -0
  392. package/src/prompts/task-enhancement.ts +138 -0
  393. package/src/prompts/task-execution.ts +22 -0
  394. package/src/prompts/task-planning.ts +64 -0
  395. package/src/prompts/workflow-assistance.ts +151 -0
  396. package/src/prompts/workflow-prompts.ts +97 -0
  397. package/src/services/benchmark.ts +433 -0
  398. package/src/services/prd.ts +845 -0
  399. package/src/services/tasks.ts +1515 -0
  400. package/src/services/workflow-ai-assistant.ts +298 -0
  401. package/src/services/workflow-benchmark.ts +339 -0
  402. package/src/services/workflow.ts +779 -0
  403. package/src/test/hooks.test.ts +77 -0
  404. package/src/test/integration/callbacks.test.ts +39 -0
  405. package/src/test/lib/ai-service/task-operations.test.ts +430 -0
  406. package/src/test/lib/config.test.ts +150 -0
  407. package/src/test/lib/git-utils.test.ts +198 -0
  408. package/src/test/mocks/mock-ai-operations.ts +205 -0
  409. package/src/test/mocks/mock-context-builder.ts +84 -0
  410. package/src/test/mocks/mock-model-provider.ts +21 -0
  411. package/src/test/mocks/mock-service-factory.ts +64 -0
  412. package/src/test/mocks/mock-storage.ts +204 -0
  413. package/src/test/model-parsing.test.ts +78 -0
  414. package/src/test/services/task-service.test.ts +551 -0
  415. package/src/test/storage.test.ts +206 -0
  416. package/src/test/task-loop-git.test.ts +142 -0
  417. package/src/test/test-mock-setup.ts +46 -0
  418. package/src/test/test-setup.ts +48 -0
  419. package/src/test/test-utils.ts +45 -0
  420. package/src/test/utils/ai-operation-utility.test.ts +306 -0
  421. package/src/test/utils/error-handling.test.ts +241 -0
  422. package/src/test/utils/file-utils.test.ts +80 -0
  423. package/src/test/utils/id-generator.test.ts +44 -0
  424. package/src/test/utils/model-parser.test.ts +67 -0
  425. package/src/test/validation.test.ts +19 -0
  426. package/src/types/callbacks.ts +14 -0
  427. package/src/types/index.ts +628 -0
  428. package/src/types/mcp.ts +5 -0
  429. package/src/types/options.ts +165 -0
  430. package/src/types/results.ts +216 -0
  431. package/src/types/workflow-options.ts +113 -0
  432. package/src/types/workflow-results.ts +87 -0
  433. package/src/utils/ai-config-builder.ts +33 -0
  434. package/src/utils/ai-operation-utility.ts +380 -0
  435. package/src/utils/ai-service-factory.ts +125 -0
  436. package/src/utils/error-utils.ts +124 -0
  437. package/src/utils/file-utils.ts +197 -0
  438. package/src/utils/id-generator.ts +168 -0
  439. package/src/utils/metadata-utils.ts +48 -0
  440. package/src/utils/model-executor-parser.ts +80 -0
  441. package/src/utils/model-parser.ts +58 -0
  442. package/src/utils/stack-formatter.ts +53 -0
  443. package/src/utils/storage-utils.ts +94 -0
  444. package/src/utils/streaming-utils.ts +91 -0
  445. package/src/utils/task-o-matic-error.ts +393 -0
  446. package/tsconfig.json +20 -0
  447. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,1515 @@
1
+ import {
2
+ getAIOperations,
3
+ getModelProvider,
4
+ getStorage,
5
+ getContextBuilder,
6
+ } from "../utils/ai-service-factory";
7
+ import { formatStackInfo } from "../utils/stack-formatter";
8
+ import { buildAIConfig, AIOptions } from "../utils/ai-config-builder";
9
+ import {
10
+ Task,
11
+ StreamingOptions,
12
+ TaskAIMetadata,
13
+ TaskDocumentation,
14
+ DocumentationDetection,
15
+ } from "../types";
16
+ import { ProgressCallback } from "../types/callbacks";
17
+ import {
18
+ CreateTaskResult,
19
+ EnhanceTaskResult,
20
+ SplitTaskResult,
21
+ PlanTaskResult,
22
+ DocumentTaskResult,
23
+ DeleteTaskResult,
24
+ } from "../types/results";
25
+ import { hooks } from "../lib/hooks";
26
+ import { logger } from "../lib/logger";
27
+ import { createMetricsStreamingOptions } from "../utils/streaming-utils";
28
+ import { getErrorMessage } from "../utils/error-utils";
29
+ import { requireTask } from "../utils/storage-utils";
30
+ import { TaskIDGenerator } from "../utils/id-generator";
31
+ import {
32
+ TaskOMaticError,
33
+ TaskOMaticErrorCodes,
34
+ formatTaskNotFoundError,
35
+ formatInvalidStatusTransitionError,
36
+ formatStorageError,
37
+ createStandardError,
38
+ } from "../utils/task-o-matic-error";
39
+ import { createBaseAIMetadata } from "../utils/metadata-utils";
40
+
41
+ /**
42
+ * Dependencies for TaskService
43
+ */
44
+ export interface TaskServiceDependencies {
45
+ storage?: ReturnType<typeof getStorage>;
46
+ aiOperations?: ReturnType<typeof getAIOperations>;
47
+ modelProvider?: ReturnType<typeof getModelProvider>;
48
+ contextBuilder?: ReturnType<typeof getContextBuilder>;
49
+ hooks?: typeof hooks;
50
+ }
51
+
52
+ /**
53
+ * TaskService - Centralized business logic for all task operations
54
+ *
55
+ * This service provides a comprehensive API for task management with AI-powered features.
56
+ * It's framework-agnostic and can be used by CLI, TUI, or Web applications.
57
+ *
58
+ * @example
59
+ * ```typescript
60
+ * import { TaskService } from "task-o-matic";
61
+ *
62
+ * // Initialize with default configuration
63
+ * const taskService = new TaskService();
64
+ *
65
+ * // Create a task with AI enhancement
66
+ * const result = await taskService.createTask({
67
+ * title: "Implement feature",
68
+ * content: "Feature description",
69
+ * aiEnhance: true,
70
+ * aiOptions: {
71
+ * provider: "anthropic",
72
+ * model: "claude-3-5-sonnet"
73
+ * }
74
+ * });
75
+ *
76
+ * // Or inject dependencies for testing
77
+ * const taskService = new TaskService({
78
+ * storage: mockStorage,
79
+ * aiOperations: mockAI,
80
+ * });
81
+ * ```
82
+ */
83
+ export class TaskService {
84
+ private storage: ReturnType<typeof getStorage>;
85
+ private aiOperations: ReturnType<typeof getAIOperations>;
86
+ private modelProvider: ReturnType<typeof getModelProvider>;
87
+ private contextBuilder: ReturnType<typeof getContextBuilder>;
88
+ private hooks: typeof hooks;
89
+
90
+ /**
91
+ * Create a new TaskService
92
+ *
93
+ * @param dependencies - Optional dependencies to inject (for testing)
94
+ */
95
+ constructor(dependencies: TaskServiceDependencies = {}) {
96
+ // Use injected dependencies or fall back to singletons
97
+ this.storage = dependencies.storage ?? getStorage();
98
+ this.aiOperations = dependencies.aiOperations ?? getAIOperations();
99
+ this.modelProvider = dependencies.modelProvider ?? getModelProvider();
100
+ this.contextBuilder = dependencies.contextBuilder ?? getContextBuilder();
101
+ this.hooks = dependencies.hooks ?? hooks;
102
+ }
103
+ // ============================================================================
104
+ // CORE CRUD OPERATIONS
105
+ // ============================================================================
106
+
107
+ /**
108
+ * Creates a new task with optional AI enhancement
109
+ *
110
+ * @param input - Task creation parameters
111
+ * @param input.title - Task title (required, 1-255 characters)
112
+ * @param input.content - Task content/description (optional)
113
+ * @param input.parentId - Parent task ID for creating subtasks
114
+ * @param input.effort - Estimated effort ("small" | "medium" | "large")
115
+ * @param input.aiEnhance - Enable AI enhancement with Context7 documentation
116
+ * @param input.aiOptions - AI configuration override
117
+ * @param input.streamingOptions - Real-time streaming options
118
+ *
119
+ * @returns Promise resolving to task creation result
120
+ *
121
+ * @throws {TaskOMaticError} If task creation fails (e.g., AI operation errors, storage errors)
122
+ * @throws {Error} If input validation fails
123
+ *
124
+ * @example Basic task creation
125
+ * ```typescript
126
+ * const task = await taskService.createTask({
127
+ * title: "Fix authentication bug",
128
+ * content: "Users cannot login with valid credentials",
129
+ * aiEnhance: false
130
+ * });
131
+ * ```
132
+ *
133
+ * @example Task with AI enhancement
134
+ * ```typescript
135
+ * try {
136
+ * const enhancedTask = await taskService.createTask({
137
+ * title: "Design authentication system",
138
+ * content: "Implement OAuth2 + JWT authentication",
139
+ * aiEnhance: true,
140
+ * streamingOptions: {
141
+ * onChunk: (chunk) => console.log("AI:", chunk)
142
+ * }
143
+ * });
144
+ * console.log("Enhanced content:", enhancedTask.task.content);
145
+ * } catch (error) {
146
+ * if (error instanceof TaskOMaticError) {
147
+ * console.error("AI enhancement failed:", error.getDetails());
148
+ * }
149
+ * }
150
+ * ```
151
+ *
152
+ * @example Creating subtasks
153
+ * ```typescript
154
+ * const subtask = await taskService.createTask({
155
+ * title: "Implement OAuth2 flow",
156
+ * parentId: "1",
157
+ * effort: "medium"
158
+ * });
159
+ * ```
160
+ */
161
+ async createTask(input: {
162
+ title: string;
163
+ content?: string;
164
+ parentId?: string;
165
+ effort?: string;
166
+ aiEnhance?: boolean;
167
+ aiOptions?: AIOptions;
168
+ streamingOptions?: StreamingOptions;
169
+ }): Promise<CreateTaskResult> {
170
+ const startTime = Date.now();
171
+
172
+ let content = input.content;
173
+ let aiMetadata;
174
+
175
+ if (input.aiEnhance) {
176
+ this.hooks.emit("task:progress", {
177
+ message: "Building context for task...",
178
+ type: "progress",
179
+ });
180
+
181
+ // Build context with error handling (Bug fix 2.2)
182
+ let context;
183
+ try {
184
+ context = await this.contextBuilder.buildContextForNewTask(
185
+ input.title,
186
+ input.content
187
+ );
188
+ } catch (error) {
189
+ // Log warning but don't fail task creation
190
+ logger.warn(
191
+ `Warning: Could not build context: ${getErrorMessage(error)}`
192
+ );
193
+ // Continue with empty context
194
+ context = { stack: undefined, existingResearch: {} };
195
+ }
196
+
197
+ const stackInfo = formatStackInfo(context.stack);
198
+
199
+ const enhancementAIConfig = buildAIConfig(input.aiOptions);
200
+
201
+ this.hooks.emit("task:progress", {
202
+ message: "Enhancing task with AI documentation...",
203
+ type: "progress",
204
+ });
205
+
206
+ const taskDescription = input.content ?? "";
207
+ // Generate temporary ID for AI operations (Bug fix 2.6)
208
+ const tempTaskId = TaskIDGenerator.generate();
209
+ content = await this.aiOperations.enhanceTaskWithDocumentation(
210
+ tempTaskId,
211
+ input.title,
212
+ taskDescription,
213
+ stackInfo,
214
+ input.streamingOptions,
215
+ undefined,
216
+ enhancementAIConfig,
217
+ context.existingResearch
218
+ );
219
+
220
+ const aiConfig = this.modelProvider.getAIConfig();
221
+
222
+ aiMetadata = {
223
+ taskId: "",
224
+ aiGenerated: true,
225
+ aiPrompt:
226
+ "Enhance task with relevant documentation using Context7 tools",
227
+ confidence: 0.9,
228
+ aiProvider: aiConfig.provider,
229
+ aiModel: aiConfig.model,
230
+ generatedAt: Date.now(),
231
+ };
232
+ }
233
+
234
+ this.hooks.emit("task:progress", {
235
+ message: "Saving task...",
236
+ type: "progress",
237
+ });
238
+
239
+ const task = await this.storage.createTask(
240
+ {
241
+ title: input.title,
242
+ description: input.content ?? "",
243
+ content,
244
+ parentId: input.parentId,
245
+ estimatedEffort: input.effort as
246
+ | "small"
247
+ | "medium"
248
+ | "large"
249
+ | undefined,
250
+ },
251
+ aiMetadata
252
+ );
253
+
254
+ this.hooks.emit("task:progress", {
255
+ type: "completed",
256
+ message: "Task created successfully",
257
+ });
258
+
259
+ // Emit task:created event
260
+ await this.hooks.emit("task:created", { task });
261
+
262
+ return { success: true, task, aiMetadata };
263
+ }
264
+
265
+ /**
266
+ * List tasks with optional filtering
267
+ *
268
+ * @param filters - Filter criteria
269
+ * @param filters.status - Filter by task status ("todo", "in-progress", "completed")
270
+ * @param filters.tag - Filter by task tag
271
+ *
272
+ * @returns Promise resolving to array of matching tasks
273
+ *
274
+ * @example
275
+ * ```typescript
276
+ * // List all tasks
277
+ * const allTasks = await taskService.listTasks({});
278
+ *
279
+ * // List only completed tasks
280
+ * const completedTasks = await taskService.listTasks({ status: "completed" });
281
+ *
282
+ * // List tasks with specific tag
283
+ * const frontendTasks = await taskService.listTasks({ tag: "frontend" });
284
+ * ```
285
+ */
286
+ async listTasks(filters: { status?: string; tag?: string }): Promise<Task[]> {
287
+ const storage = this.storage;
288
+ const topLevelTasks = await storage.getTopLevelTasks();
289
+
290
+ let filteredTasks = topLevelTasks;
291
+
292
+ if (filters.status) {
293
+ filteredTasks = filteredTasks.filter(
294
+ (task) => task.status === filters.status
295
+ );
296
+ }
297
+
298
+ if (filters.tag) {
299
+ const tagToFilter = filters.tag; // Type narrowing
300
+ filteredTasks = filteredTasks.filter(
301
+ (task) => task.tags && task.tags.includes(tagToFilter)
302
+ );
303
+ }
304
+
305
+ return filteredTasks;
306
+ }
307
+
308
+ async getTask(id: string): Promise<Task | null> {
309
+ return await this.storage.getTask(id);
310
+ }
311
+
312
+ async getTaskContent(id: string): Promise<string | null> {
313
+ return await this.storage.getTaskContent(id);
314
+ }
315
+
316
+ async getTaskAIMetadata(id: string): Promise<TaskAIMetadata | null> {
317
+ return await this.storage.getTaskAIMetadata(id);
318
+ }
319
+
320
+ async getSubtasks(id: string): Promise<Task[]> {
321
+ return await this.storage.getSubtasks(id);
322
+ }
323
+
324
+ async updateTask(
325
+ id: string,
326
+ updates: {
327
+ title?: string;
328
+ description?: string;
329
+ status?: string;
330
+ effort?: string;
331
+ tags?: string | string[];
332
+ }
333
+ ): Promise<Task> {
334
+ const storage = this.storage;
335
+ const existingTask = await storage.getTask(id);
336
+
337
+ if (!existingTask) {
338
+ throw formatTaskNotFoundError(id);
339
+ }
340
+
341
+ // Validate status transitions
342
+ if (updates.status) {
343
+ // Allow same-status transitions as no-ops (needed for retry flows)
344
+ if (updates.status === existingTask.status) {
345
+ // Remove status from updates - it's already correct
346
+ delete updates.status;
347
+ } else {
348
+ const validTransitions: Record<string, string[]> = {
349
+ todo: ["in-progress", "completed"],
350
+ "in-progress": ["completed", "todo"],
351
+ completed: ["todo", "in-progress"],
352
+ };
353
+
354
+ if (!validTransitions[existingTask.status].includes(updates.status)) {
355
+ throw formatInvalidStatusTransitionError(
356
+ existingTask.status,
357
+ updates.status
358
+ );
359
+ }
360
+ }
361
+ }
362
+
363
+ const finalUpdates: { [key: string]: any } = { ...updates };
364
+
365
+ // Handle tags: convert comma-separated string to array and merge
366
+ if (typeof updates.tags === "string") {
367
+ const newTags = updates.tags.split(",").map((tag) => tag.trim());
368
+ const existingTags = existingTask.tags || [];
369
+ finalUpdates.tags = [...new Set([...existingTags, ...newTags])];
370
+ }
371
+
372
+ const updatedTask = await storage.updateTask(id, finalUpdates);
373
+ if (!updatedTask) {
374
+ throw formatStorageError(`updateTask ${id}`);
375
+ }
376
+
377
+ // Emit task:updated event
378
+ await this.hooks.emit("task:updated", {
379
+ task: updatedTask,
380
+ changes: finalUpdates,
381
+ });
382
+
383
+ // Emit task:status-changed event if status changed
384
+ if (updates.status && updates.status !== existingTask.status) {
385
+ await this.hooks.emit("task:status-changed", {
386
+ task: updatedTask,
387
+ oldStatus: existingTask.status,
388
+ newStatus: updates.status,
389
+ });
390
+ }
391
+
392
+ return updatedTask;
393
+ }
394
+
395
+ async setTaskStatus(id: string, status: string): Promise<Task> {
396
+ return await this.updateTask(id, { status });
397
+ }
398
+
399
+ async deleteTask(
400
+ id: string,
401
+ options: { cascade?: boolean; force?: boolean } = {}
402
+ ): Promise<DeleteTaskResult> {
403
+ const { cascade, force } = options;
404
+ const storage = this.storage;
405
+ const taskToDelete = await storage.getTask(id);
406
+
407
+ if (!taskToDelete) {
408
+ throw formatTaskNotFoundError(id);
409
+ }
410
+
411
+ const deleted: Task[] = [];
412
+ const orphanedSubtasks: Task[] = [];
413
+
414
+ // Get subtasks before deletion
415
+ const subtasks = await storage.getSubtasks(id);
416
+
417
+ if (subtasks.length > 0 && !cascade) {
418
+ if (!force) {
419
+ throw createStandardError(
420
+ TaskOMaticErrorCodes.TASK_OPERATION_FAILED,
421
+ `Cannot delete task with ${subtasks.length} subtasks`,
422
+ {
423
+ context: `Task ${id} has ${subtasks.length} subtasks`,
424
+ suggestions: [
425
+ "Use --cascade flag to delete task and all subtasks",
426
+ "Use --force flag to delete task and orphan subtasks",
427
+ "Delete subtasks first, then delete parent task",
428
+ ],
429
+ metadata: { taskId: id, subtaskCount: subtasks.length },
430
+ }
431
+ );
432
+ }
433
+ // Orphan subtasks by removing parent reference
434
+ for (const subtask of subtasks) {
435
+ await storage.updateTask(subtask.id, { parentId: undefined });
436
+ orphanedSubtasks.push(subtask);
437
+ }
438
+ } else if (cascade) {
439
+ // Recursively delete subtasks
440
+ for (const subtask of subtasks) {
441
+ const result = await this.deleteTask(subtask.id, {
442
+ cascade: true,
443
+ force: true,
444
+ });
445
+ deleted.push(...result.deleted);
446
+ orphanedSubtasks.push(...result.orphanedSubtasks);
447
+ }
448
+ }
449
+
450
+ // Delete the main task
451
+ const success = await storage.deleteTask(id);
452
+ if (success) {
453
+ deleted.push(taskToDelete);
454
+ // Emit task:deleted event
455
+ await this.hooks.emit("task:deleted", { taskId: id });
456
+ }
457
+
458
+ return { success: true, deleted, orphanedSubtasks };
459
+ }
460
+
461
+ // ============================================================================
462
+ // TAG OPERATIONS
463
+ // ============================================================================
464
+
465
+ async addTags(id: string, tags: string[]): Promise<Task> {
466
+ const storage = this.storage;
467
+ const task = await storage.getTask(id);
468
+
469
+ if (!task) {
470
+ throw formatTaskNotFoundError(id);
471
+ }
472
+
473
+ const existingTags = task.tags || [];
474
+ const newTags = tags.filter((tag) => !existingTags.includes(tag));
475
+
476
+ if (newTags.length === 0) {
477
+ return task; // No new tags to add
478
+ }
479
+
480
+ const updatedTags = [...existingTags, ...newTags];
481
+ const updatedTask = await storage.updateTask(id, { tags: updatedTags });
482
+
483
+ if (!updatedTask) {
484
+ throw formatStorageError(`add tags to task ${id}`);
485
+ }
486
+
487
+ return updatedTask;
488
+ }
489
+
490
+ async removeTags(id: string, tags: string[]): Promise<Task> {
491
+ const storage = this.storage;
492
+ const task = await storage.getTask(id);
493
+
494
+ if (!task) {
495
+ throw formatTaskNotFoundError(id);
496
+ }
497
+
498
+ const existingTags = task.tags || [];
499
+ const updatedTags = existingTags.filter((tag) => !tags.includes(tag));
500
+
501
+ if (updatedTags.length === existingTags.length) {
502
+ return task; // No tags removed
503
+ }
504
+
505
+ const updatedTask = await storage.updateTask(id, { tags: updatedTags });
506
+
507
+ if (!updatedTask) {
508
+ throw formatStorageError(`remove tags from task ${id}`);
509
+ }
510
+
511
+ return updatedTask;
512
+ }
513
+
514
+ // ============================================================================
515
+ // TASK NAVIGATION
516
+ // ============================================================================
517
+
518
+ /**
519
+ * Get the next task based on priority and filtering criteria
520
+ *
521
+ * @param filters - Filter and priority criteria
522
+ * @param filters.status - Filter by task status
523
+ * @param filters.tag - Filter by task tag
524
+ * @param filters.effort - Filter by estimated effort
525
+ * @param filters.priority - Priority strategy ("newest", "oldest", "effort", or default)
526
+ *
527
+ * @returns Promise resolving to the highest priority task or null if none found
528
+ *
529
+ * @example
530
+ * ```typescript
531
+ * // Get the next task by default priority (task ID order)
532
+ * const nextTask = await taskService.getNextTask({});
533
+ *
534
+ * // Get the newest task with "todo" status
535
+ * const newestTodo = await taskService.getNextTask({
536
+ * status: "todo",
537
+ * priority: "newest"
538
+ * });
539
+ *
540
+ * // Get the highest effort task with specific tag
541
+ * const highEffortTask = await taskService.getNextTask({
542
+ * tag: "backend",
543
+ * priority: "effort"
544
+ * });
545
+ * ```
546
+ */
547
+ async getNextTask(filters: {
548
+ status?: string;
549
+ tag?: string;
550
+ effort?: string;
551
+ priority?: string;
552
+ }): Promise<Task | null> {
553
+ const storage = this.storage;
554
+ const allTasks = await storage.getTasks();
555
+
556
+ // Filter by status and other criteria
557
+ let filteredTasks = allTasks.filter((task) => {
558
+ if (filters.status && task.status !== filters.status) return false;
559
+ if (filters.tag && (!task.tags || !task.tags.includes(filters.tag)))
560
+ return false;
561
+ if (filters.effort && task.estimatedEffort !== filters.effort)
562
+ return false;
563
+ return true;
564
+ });
565
+
566
+ if (filteredTasks.length === 0) return null;
567
+
568
+ // Sort based on priority
569
+ switch (filters.priority) {
570
+ case "newest":
571
+ return filteredTasks.sort((a, b) => b.createdAt - a.createdAt)[0];
572
+ case "oldest":
573
+ return filteredTasks.sort((a, b) => a.createdAt - b.createdAt)[0];
574
+ case "effort":
575
+ const effortOrder = { small: 1, medium: 2, large: 3 };
576
+ return filteredTasks.sort(
577
+ (a, b) =>
578
+ (effortOrder[a.estimatedEffort || "medium"] || 2) -
579
+ (effortOrder[b.estimatedEffort || "medium"] || 2)
580
+ )[0];
581
+ default:
582
+ // Default: task ID order (1, 1.1, 1.2, 2, 2.1, etc.)
583
+ return filteredTasks.sort((a, b) => a.id.localeCompare(b.id))[0];
584
+ }
585
+ }
586
+
587
+ async getTaskTree(rootId?: string): Promise<Task[]> {
588
+ const storage = this.storage;
589
+
590
+ if (rootId) {
591
+ // Return tree starting from specific task
592
+ const rootTask = await storage.getTask(rootId);
593
+ if (!rootTask) {
594
+ throw formatTaskNotFoundError(rootId);
595
+ }
596
+
597
+ // Get all subtasks recursively
598
+ const getAllSubtasks = async (task: Task): Promise<Task[]> => {
599
+ const subtasks = await storage.getSubtasks(task.id);
600
+ const allSubtasks: Task[] = [];
601
+
602
+ for (const subtask of subtasks) {
603
+ allSubtasks.push(subtask);
604
+ const deeperSubtasks = await getAllSubtasks(subtask);
605
+ allSubtasks.push(...deeperSubtasks);
606
+ }
607
+
608
+ return allSubtasks;
609
+ };
610
+
611
+ const subtasks = await getAllSubtasks(rootTask);
612
+ return [rootTask, ...subtasks];
613
+ } else {
614
+ // Return all top-level tasks and their subtasks
615
+ return await storage.getTasks();
616
+ }
617
+ }
618
+
619
+ // ============================================================================
620
+ // AI OPERATIONS
621
+ // ============================================================================
622
+
623
+ /**
624
+ * Enhance a task with AI-generated documentation using Context7
625
+ *
626
+ * Uses AI to enrich the task description with relevant documentation,
627
+ * code examples, and best practices from Context7 documentation sources.
628
+ *
629
+ * @param taskId - ID of the task to enhance
630
+ * @param aiOptions - Optional AI configuration overrides
631
+ * @param streamingOptions - Optional streaming callbacks for real-time feedback
632
+ * @returns Promise resolving to enhancement result with metrics
633
+ *
634
+ * @throws {Error} If task not found
635
+ * @throws {TaskOMaticError} If AI enhancement fails
636
+ *
637
+ * @example Basic enhancement
638
+ * ```typescript
639
+ * const result = await taskService.enhanceTask("1");
640
+ * console.log("Enhanced content:", result.enhancedContent);
641
+ * console.log("Took:", result.stats.duration, "ms");
642
+ * ```
643
+ *
644
+ * @example With streaming
645
+ * ```typescript
646
+ * try {
647
+ * const result = await taskService.enhanceTask("1", undefined, {
648
+ * onChunk: (chunk) => process.stdout.write(chunk)
649
+ * });
650
+ * console.log("\nEnhancement complete!");
651
+ * } catch (error) {
652
+ * if (error instanceof TaskOMaticError) {
653
+ * console.error("Enhancement failed:", error.getDetails());
654
+ * }
655
+ * }
656
+ * ```
657
+ */
658
+ async enhanceTask(
659
+ taskId: string,
660
+ aiOptions?: AIOptions,
661
+ streamingOptions?: StreamingOptions
662
+ ): Promise<EnhanceTaskResult> {
663
+ const startTime = Date.now();
664
+
665
+ this.hooks.emit("task:progress", {
666
+ message: "Starting task enhancement...",
667
+ type: "started",
668
+ });
669
+
670
+ const task = await this.storage.getTask(taskId);
671
+ if (!task) {
672
+ throw formatTaskNotFoundError(taskId);
673
+ }
674
+
675
+ this.hooks.emit("task:progress", {
676
+ message: "Building context...",
677
+ type: "progress",
678
+ });
679
+
680
+ const context = await this.contextBuilder.buildContext(taskId);
681
+ const stackInfo = formatStackInfo(context.stack);
682
+
683
+ const enhancementAIConfig = buildAIConfig(aiOptions);
684
+
685
+ this.hooks.emit("task:progress", {
686
+ message: "Calling AI for enhancement...",
687
+ type: "progress",
688
+ });
689
+
690
+ // Use utility to wrap streaming options and capture metrics (DRY fix 1.1)
691
+ const aiStartTime = Date.now();
692
+ const { options: metricsStreamingOptions, getMetrics } =
693
+ createMetricsStreamingOptions(streamingOptions, aiStartTime);
694
+
695
+ const enhancedContent =
696
+ await this.aiOperations.enhanceTaskWithDocumentation(
697
+ task.id,
698
+ task.title,
699
+ task.description ?? "",
700
+ stackInfo,
701
+ metricsStreamingOptions,
702
+ undefined,
703
+ enhancementAIConfig,
704
+ context.existingResearch
705
+ );
706
+
707
+ // Extract metrics after AI call
708
+ const { tokenUsage, timeToFirstToken } = getMetrics();
709
+
710
+ this.hooks.emit("task:progress", {
711
+ message: "Saving enhanced content...",
712
+ type: "progress",
713
+ });
714
+
715
+ const originalLength = task.description?.length || 0;
716
+
717
+ if (enhancedContent.length > 200) {
718
+ const contentFile = await this.storage.saveEnhancedTaskContent(
719
+ task.id,
720
+ enhancedContent
721
+ );
722
+ await this.storage.updateTask(task.id, {
723
+ contentFile,
724
+ description:
725
+ task.description +
726
+ "\n\n🤖 AI-enhanced with Context7 documentation available.",
727
+ });
728
+ } else {
729
+ await this.storage.updateTask(task.id, { description: enhancedContent });
730
+ }
731
+
732
+ const aiConfig = this.modelProvider.getAIConfig();
733
+
734
+ const aiMetadata = {
735
+ taskId: task.id,
736
+ aiGenerated: true,
737
+ aiPrompt: "Enhance task with Context7 documentation using MCP tools",
738
+ confidence: 0.9,
739
+ aiProvider: aiConfig.provider,
740
+ aiModel: aiConfig.model,
741
+ enhancedAt: Date.now(),
742
+ };
743
+
744
+ await this.storage.saveTaskAIMetadata(aiMetadata);
745
+
746
+ const duration = Date.now() - startTime;
747
+
748
+ this.hooks.emit("task:progress", {
749
+ message: "Task enhancement completed",
750
+ type: "completed",
751
+ });
752
+
753
+ return {
754
+ success: true,
755
+ task,
756
+ enhancedContent,
757
+ stats: {
758
+ originalLength,
759
+ enhancedLength: enhancedContent.length,
760
+ duration,
761
+ tokenUsage,
762
+ timeToFirstToken,
763
+ cost: undefined, // Cost calculation can be added later
764
+ },
765
+ metadata: {
766
+ aiProvider: aiConfig.provider,
767
+ aiModel: aiConfig.model,
768
+ confidence: 0.9,
769
+ },
770
+ };
771
+ }
772
+
773
+ /**
774
+ * Split a task into subtasks using AI
775
+ *
776
+ * Analyzes the task and breaks it down into smaller, actionable subtasks
777
+ * with estimated effort. Can optionally use filesystem tools to understand
778
+ * project structure when creating subtasks.
779
+ *
780
+ * @param taskId - ID of the task to split
781
+ * @param aiOptions - Optional AI configuration overrides
782
+ * @param promptOverride - Optional custom prompt
783
+ * @param messageOverride - Optional custom message
784
+ * @param streamingOptions - Optional streaming callbacks
785
+ * @param enableFilesystemTools - Enable filesystem analysis for context
786
+ * @returns Promise resolving to split result with created subtasks
787
+ *
788
+ * @throws {Error} If task not found or already has subtasks
789
+ * @throws {TaskOMaticError} If AI operation fails
790
+ *
791
+ * @example Basic task splitting
792
+ * ```typescript
793
+ * const result = await taskService.splitTask("1");
794
+ * console.log(`Created ${result.subtasks.length} subtasks`);
795
+ * result.subtasks.forEach(subtask => {
796
+ * console.log(`- ${subtask.title} (${subtask.estimatedEffort})`);
797
+ * });
798
+ * ```
799
+ *
800
+ * @example With filesystem tools for code analysis
801
+ * ```typescript
802
+ * try {
803
+ * const result = await taskService.splitTask(
804
+ * "1",
805
+ * undefined,
806
+ * undefined,
807
+ * undefined,
808
+ * { onChunk: (chunk) => console.log(chunk) },
809
+ * true // Enable filesystem tools
810
+ * );
811
+ * console.log("AI analyzed codebase to create subtasks");
812
+ * } catch (error) {
813
+ * if (error instanceof TaskOMaticError) {
814
+ * console.error("Split failed:", error.suggestions);
815
+ * }
816
+ * }
817
+ * ```
818
+ */
819
+ async splitTask(
820
+ taskId: string,
821
+ aiOptions?: AIOptions,
822
+ promptOverride?: string,
823
+ messageOverride?: string,
824
+ streamingOptions?: StreamingOptions,
825
+ enableFilesystemTools?: boolean
826
+ ): Promise<SplitTaskResult> {
827
+ const startTime = Date.now();
828
+
829
+ this.hooks.emit("task:progress", {
830
+ message: "Starting task breakdown...",
831
+ type: "started",
832
+ });
833
+
834
+ const task = await this.storage.getTask(taskId);
835
+ if (!task) {
836
+ throw formatTaskNotFoundError(taskId);
837
+ }
838
+
839
+ // Check if task already has subtasks
840
+ const existingSubtasks = await this.storage.getSubtasks(taskId);
841
+ if (existingSubtasks.length > 0) {
842
+ throw createStandardError(
843
+ TaskOMaticErrorCodes.TASK_OPERATION_FAILED,
844
+ `Task already has ${existingSubtasks.length} subtasks`,
845
+ {
846
+ context: `Task "${task.title}" (${taskId}) already has subtasks`,
847
+ suggestions: [
848
+ "Use existing subtasks instead of splitting again",
849
+ "Delete existing subtasks first if you want to re-split",
850
+ "Consider editing existing subtasks instead",
851
+ ],
852
+ metadata: { taskId, subtaskCount: existingSubtasks.length },
853
+ }
854
+ );
855
+ }
856
+
857
+ this.hooks.emit("task:progress", {
858
+ message: "Building context...",
859
+ type: "progress",
860
+ });
861
+
862
+ // Build comprehensive context
863
+ const context = await this.contextBuilder.buildContext(taskId);
864
+ const stackInfo = formatStackInfo(context.stack);
865
+
866
+ // Get full task content
867
+ const fullContent = context.task.fullContent || task.description || "";
868
+
869
+ const breakdownAIConfig = buildAIConfig(aiOptions);
870
+
871
+ this.hooks.emit("task:progress", {
872
+ message: "Calling AI to break down task...",
873
+ type: "progress",
874
+ });
875
+
876
+ // Use utility to wrap streaming options and capture metrics (DRY fix 1.1)
877
+ const aiStartTime = Date.now();
878
+ const { options: metricsStreamingOptions, getMetrics } =
879
+ createMetricsStreamingOptions(streamingOptions, aiStartTime);
880
+
881
+ // Use AI service to break down the task with enhanced context
882
+ const subtaskData = await this.aiOperations.breakdownTask(
883
+ task,
884
+ breakdownAIConfig,
885
+ promptOverride,
886
+ messageOverride,
887
+ metricsStreamingOptions,
888
+ undefined,
889
+ fullContent,
890
+ stackInfo,
891
+ existingSubtasks,
892
+ enableFilesystemTools
893
+ );
894
+
895
+ // Extract metrics after AI call
896
+ const { tokenUsage, timeToFirstToken } = getMetrics();
897
+
898
+ this.hooks.emit("task:progress", {
899
+ message: `Creating ${subtaskData.length} subtasks...`,
900
+ type: "progress",
901
+ });
902
+
903
+ // Create subtasks and save AI metadata for each (Bug fix 2.3)
904
+ const createdSubtasks = [];
905
+ const aiConfig = this.modelProvider.getAIConfig();
906
+ const splitTimestamp = Date.now();
907
+
908
+ for (let i = 0; i < subtaskData.length; i++) {
909
+ const subtask = subtaskData[i];
910
+
911
+ this.hooks.emit("task:progress", {
912
+ message: `Creating subtask ${i + 1}/${subtaskData.length}: ${
913
+ subtask.title
914
+ }`,
915
+ type: "progress",
916
+ });
917
+
918
+ // console.log(
919
+ // `[DEBUG] Creating subtask ${i + 1}:`,
920
+ // JSON.stringify(subtask, null, 2)
921
+ // );
922
+
923
+ try {
924
+ const result = await this.createTask({
925
+ title: subtask.title,
926
+ content: subtask.content,
927
+ effort: subtask.estimatedEffort,
928
+ parentId: taskId,
929
+ });
930
+ createdSubtasks.push(result.task);
931
+
932
+ // Save AI metadata for each subtask (Bug fix 2.3)
933
+ const subtaskMetadata = {
934
+ ...createBaseAIMetadata(
935
+ result.task.id,
936
+ aiConfig,
937
+ promptOverride,
938
+ "Split task into meaningful subtasks with full context and existing subtask awareness",
939
+ 0.9
940
+ ),
941
+ splitAt: splitTimestamp,
942
+ parentTaskId: taskId,
943
+ subtaskIndex: i + 1,
944
+ };
945
+ await getStorage().saveTaskAIMetadata(subtaskMetadata);
946
+ } catch (err) {
947
+ logger.error(`[DEBUG] Failed to create subtask ${i + 1}: ${err}`);
948
+ throw err;
949
+ }
950
+ }
951
+
952
+ // Save AI metadata for parent task as well
953
+ const parentMetadata = {
954
+ ...createBaseAIMetadata(
955
+ task.id,
956
+ aiConfig,
957
+ promptOverride,
958
+ "Split task into meaningful subtasks with full context and existing subtask awareness",
959
+ 0.9
960
+ ),
961
+ splitAt: splitTimestamp,
962
+ subtasksCreated: createdSubtasks.length,
963
+ };
964
+ await getStorage().saveTaskAIMetadata(parentMetadata);
965
+
966
+ const duration = Date.now() - startTime;
967
+
968
+ this.hooks.emit("task:progress", {
969
+ message: `Task split into ${createdSubtasks.length} subtasks`,
970
+ type: "completed",
971
+ });
972
+
973
+ return {
974
+ success: true,
975
+ task,
976
+ subtasks: createdSubtasks,
977
+ stats: {
978
+ subtasksCreated: createdSubtasks.length,
979
+ duration,
980
+ tokenUsage,
981
+ timeToFirstToken,
982
+ cost: undefined, // Cost calculation can be added later
983
+ },
984
+ metadata: {
985
+ aiProvider: aiConfig.provider,
986
+ aiModel: aiConfig.model,
987
+ confidence: 0.9,
988
+ },
989
+ };
990
+ }
991
+
992
+ /**
993
+ * Analyze and fetch documentation for a task using Context7
994
+ *
995
+ * Analyzes the task content to identify required libraries and documentation,
996
+ * then fetches relevant documentation from Context7. Caches documentation
997
+ * for future use.
998
+ *
999
+ * @param taskId - ID of the task to document
1000
+ * @param force - Force re-fetch even if documentation exists
1001
+ * @param aiOptions - Optional AI configuration overrides
1002
+ * @param streamingOptions - Optional streaming callbacks
1003
+ * @returns Promise resolving to documentation analysis result
1004
+ *
1005
+ * @throws {Error} If task not found or content is empty
1006
+ * @throws {TaskOMaticError} If AI operation fails
1007
+ *
1008
+ * @example Analyze documentation needs
1009
+ * ```typescript
1010
+ * const result = await taskService.documentTask("1");
1011
+ * if (result.documentation) {
1012
+ * console.log("Documentation fetched:");
1013
+ * console.log(result.documentation.recap);
1014
+ * console.log("Libraries:", result.documentation.libraries);
1015
+ * }
1016
+ * ```
1017
+ *
1018
+ * @example Force refresh documentation
1019
+ * ```typescript
1020
+ * try {
1021
+ * const result = await taskService.documentTask("1", true);
1022
+ * console.log(`Analyzed ${result.analysis.libraries.length} libraries`);
1023
+ * } catch (error) {
1024
+ * if (error instanceof TaskOMaticError) {
1025
+ * console.error("Documentation fetch failed:", error.getDetails());
1026
+ * }
1027
+ * }
1028
+ * ```
1029
+ */
1030
+ async documentTask(
1031
+ taskId: string,
1032
+ force: boolean = false,
1033
+ aiOptions?: AIOptions,
1034
+ streamingOptions?: StreamingOptions
1035
+ ): Promise<DocumentTaskResult> {
1036
+ const startTime = Date.now();
1037
+
1038
+ this.hooks.emit("task:progress", {
1039
+ message: "Analyzing documentation needs...",
1040
+ type: "started",
1041
+ });
1042
+
1043
+ const task = await this.storage.getTask(taskId);
1044
+ if (!task) {
1045
+ throw formatTaskNotFoundError(taskId);
1046
+ }
1047
+
1048
+ if (task.documentation && !force) {
1049
+ if (this.contextBuilder.isDocumentationFresh(task.documentation)) {
1050
+ this.hooks.emit("task:progress", {
1051
+ message: "Documentation is fresh, skipping analysis",
1052
+ type: "info",
1053
+ });
1054
+
1055
+ return {
1056
+ success: true,
1057
+ task,
1058
+ documentation: task.documentation,
1059
+ stats: {
1060
+ duration: Date.now() - startTime,
1061
+ },
1062
+ };
1063
+ }
1064
+ }
1065
+
1066
+ this.hooks.emit("task:progress", {
1067
+ message: "Building context...",
1068
+ type: "progress",
1069
+ });
1070
+
1071
+ const context = await this.contextBuilder.buildContext(taskId);
1072
+ const stackInfo = formatStackInfo(context.stack);
1073
+
1074
+ const analysisAIConfig = buildAIConfig(aiOptions);
1075
+
1076
+ // Get full task content
1077
+ const fullContent = context.task.fullContent || task.description;
1078
+
1079
+ if (!fullContent) {
1080
+ throw createStandardError(
1081
+ TaskOMaticErrorCodes.INVALID_INPUT,
1082
+ "Task content is empty",
1083
+ {
1084
+ context: `Task ${taskId} has no content to enhance`,
1085
+ suggestions: [
1086
+ "Add content to the task before enhancing",
1087
+ "Provide task description or details",
1088
+ ],
1089
+ metadata: { taskId },
1090
+ }
1091
+ );
1092
+ }
1093
+
1094
+ // Get existing documentations from all tasks
1095
+ const tasks = await this.storage.getTasks();
1096
+ const documentations = tasks.map((task) => task.documentation);
1097
+
1098
+ this.hooks.emit("task:progress", {
1099
+ message: "Calling AI to analyze documentation needs...",
1100
+ type: "progress",
1101
+ });
1102
+
1103
+ // First analyze what documentation is needed
1104
+ const analysis = await this.aiOperations.analyzeDocumentationNeeds(
1105
+ task.id,
1106
+ task.title,
1107
+ fullContent,
1108
+ stackInfo,
1109
+ streamingOptions,
1110
+ undefined,
1111
+ analysisAIConfig,
1112
+ documentations
1113
+ );
1114
+
1115
+ let documentation: TaskDocumentation | undefined;
1116
+
1117
+ if (analysis.libraries.length > 0) {
1118
+ this.hooks.emit("task:progress", {
1119
+ message: `Fetching documentation for ${analysis.libraries.length} libraries...`,
1120
+ type: "progress",
1121
+ });
1122
+
1123
+ // Build research object from actual libraries
1124
+ const research: Record<
1125
+ string,
1126
+ Array<{ query: string; doc: string }>
1127
+ > = {};
1128
+ for (const lib of analysis.libraries) {
1129
+ const sanitizedLibrary = this.storage.sanitizeForFilename(lib.name);
1130
+ const sanitizedQuery = this.storage.sanitizeForFilename(
1131
+ lib.searchQuery
1132
+ );
1133
+ const docFile = `docs/${sanitizedLibrary}/${sanitizedQuery}.md`;
1134
+
1135
+ if (!research[lib.name]) {
1136
+ research[lib.name] = [];
1137
+ }
1138
+ research[lib.name].push({
1139
+ query: lib.searchQuery,
1140
+ doc: docFile,
1141
+ });
1142
+ }
1143
+
1144
+ this.hooks.emit("task:progress", {
1145
+ message: "Generating documentation recap...",
1146
+ type: "progress",
1147
+ });
1148
+
1149
+ const recap = await this.aiOperations.generateDocumentationRecap(
1150
+ analysis.libraries,
1151
+ analysis.toolResults?.map((tr) => ({
1152
+ library: tr.toolName,
1153
+ content: JSON.stringify(tr.output),
1154
+ })) || [],
1155
+ streamingOptions
1156
+ );
1157
+
1158
+ documentation = {
1159
+ lastFetched: Date.now(),
1160
+ libraries: analysis.libraries.map(
1161
+ (lib: { context7Id: string }) => lib.context7Id
1162
+ ),
1163
+ recap,
1164
+ files: analysis.files || [],
1165
+ research,
1166
+ };
1167
+
1168
+ this.hooks.emit("task:progress", {
1169
+ message: "Saving documentation...",
1170
+ type: "progress",
1171
+ });
1172
+
1173
+ await this.storage.updateTask(taskId, { documentation });
1174
+ }
1175
+
1176
+ const duration = Date.now() - startTime;
1177
+
1178
+ this.hooks.emit("task:progress", {
1179
+ message: "Documentation analysis completed",
1180
+ type: "completed",
1181
+ });
1182
+
1183
+ return {
1184
+ success: true,
1185
+ task,
1186
+ analysis,
1187
+ documentation,
1188
+ stats: {
1189
+ duration,
1190
+ },
1191
+ };
1192
+ }
1193
+
1194
+ /**
1195
+ * Generate an implementation plan for a task using AI
1196
+ *
1197
+ * Creates a detailed implementation plan with steps, considerations,
1198
+ * and technical approach. Uses filesystem and Context7 tools to understand
1199
+ * the project context and provide relevant suggestions.
1200
+ *
1201
+ * @param taskId - ID of the task to plan
1202
+ * @param aiOptions - Optional AI configuration overrides
1203
+ * @param streamingOptions - Optional streaming callbacks
1204
+ * @returns Promise resolving to plan result with generated plan text
1205
+ *
1206
+ * @throws {Error} If task not found
1207
+ * @throws {TaskOMaticError} If AI operation fails
1208
+ *
1209
+ * @example Basic implementation planning
1210
+ * ```typescript
1211
+ * const result = await taskService.planTask("1");
1212
+ * console.log("Implementation Plan:");
1213
+ * console.log(result.plan);
1214
+ * console.log(`Generated in ${result.stats.duration}ms`);
1215
+ * ```
1216
+ *
1217
+ * @example With streaming for real-time plan generation
1218
+ * ```typescript
1219
+ * try {
1220
+ * const result = await taskService.planTask("1", undefined, {
1221
+ * onChunk: (chunk) => {
1222
+ * // Display plan as it's generated
1223
+ * process.stdout.write(chunk);
1224
+ * }
1225
+ * });
1226
+ * console.log("\n\nPlan saved to:", `plans/${result.task.id}.md`);
1227
+ * } catch (error) {
1228
+ * if (error instanceof TaskOMaticError) {
1229
+ * console.error("Planning failed:", error.getDetails());
1230
+ * }
1231
+ * }
1232
+ * ```
1233
+ */
1234
+ async planTask(
1235
+ taskId: string,
1236
+ aiOptions?: AIOptions,
1237
+ streamingOptions?: StreamingOptions
1238
+ ): Promise<PlanTaskResult> {
1239
+ const startTime = Date.now();
1240
+
1241
+ this.hooks.emit("task:progress", {
1242
+ message: "Creating implementation plan...",
1243
+ type: "started",
1244
+ });
1245
+
1246
+ const task = await this.storage.getTask(taskId);
1247
+ if (!task) {
1248
+ throw formatTaskNotFoundError(taskId);
1249
+ }
1250
+
1251
+ const aiService = this.aiOperations;
1252
+ const planAIConfig = buildAIConfig(aiOptions);
1253
+
1254
+ this.hooks.emit("task:progress", {
1255
+ message: "Building task context...",
1256
+ type: "progress",
1257
+ });
1258
+
1259
+ // Build task context and details
1260
+ let taskContext = `Task ID: ${task.id}\nTitle: ${task.title}\n`;
1261
+ let taskDetails = `Description: ${task.description || "No description"}\n`;
1262
+
1263
+ // If this is a subtask, include parent task context
1264
+ if (task.parentId) {
1265
+ const parentTask = await getStorage().getTask(task.parentId);
1266
+ if (parentTask) {
1267
+ taskContext += `Parent Task ID: ${parentTask.id}\nParent Task Title: ${parentTask.title}\n`;
1268
+ taskDetails += `Parent Task Description: ${
1269
+ parentTask.description || "No description"
1270
+ }\n`;
1271
+ }
1272
+ }
1273
+
1274
+ // If this is a task with subtasks, get subtask details
1275
+ const subtasks = await getStorage().getSubtasks(taskId);
1276
+ if (subtasks.length > 0) {
1277
+ taskDetails += `\nSubtasks:\n`;
1278
+ subtasks.forEach((subtask, index) => {
1279
+ taskDetails += `${index + 1}. ${subtask.title} (${subtask.id})\n`;
1280
+ taskDetails += ` ${subtask.description || "No description"}\n\n`;
1281
+ });
1282
+ }
1283
+
1284
+ this.hooks.emit("task:progress", {
1285
+ message: "Calling AI to create plan...",
1286
+ type: "progress",
1287
+ });
1288
+
1289
+ // Use utility to wrap streaming options and capture metrics (DRY fix 1.1)
1290
+ const aiStartTime = Date.now();
1291
+ const { options: metricsStreamingOptions, getMetrics } =
1292
+ createMetricsStreamingOptions(streamingOptions, aiStartTime);
1293
+
1294
+ const plan = await aiService.planTask(
1295
+ taskContext,
1296
+ taskDetails,
1297
+ planAIConfig,
1298
+ undefined,
1299
+ undefined,
1300
+ metricsStreamingOptions
1301
+ );
1302
+
1303
+ // Extract metrics after AI call
1304
+ const { tokenUsage, timeToFirstToken } = getMetrics();
1305
+
1306
+ this.hooks.emit("task:progress", {
1307
+ message: "Saving plan...",
1308
+ type: "progress",
1309
+ });
1310
+
1311
+ // Save the plan to storage
1312
+ await this.storage.savePlan(taskId, plan);
1313
+
1314
+ const duration = Date.now() - startTime;
1315
+
1316
+ this.hooks.emit("task:progress", {
1317
+ message: "Implementation plan created",
1318
+ type: "completed",
1319
+ });
1320
+
1321
+ const aiConfig = this.modelProvider.getAIConfig();
1322
+
1323
+ return {
1324
+ success: true,
1325
+ task,
1326
+ plan,
1327
+ stats: {
1328
+ duration,
1329
+ tokenUsage,
1330
+ timeToFirstToken,
1331
+ cost: undefined, // Cost calculation can be added later
1332
+ },
1333
+ metadata: {
1334
+ aiProvider: aiConfig.provider,
1335
+ aiModel: aiConfig.model,
1336
+ },
1337
+ };
1338
+ }
1339
+
1340
+ // ============================================================================
1341
+ // DOCUMENTATION OPERATIONS
1342
+ // ============================================================================
1343
+
1344
+ async getTaskDocumentation(taskId: string): Promise<string | null> {
1345
+ return this.storage.getTaskDocumentation(taskId);
1346
+ }
1347
+
1348
+ async addTaskDocumentationFromFile(
1349
+ taskId: string,
1350
+ filePath: string
1351
+ ): Promise<{ filePath: string; task: Task }> {
1352
+ const task = await this.getTask(taskId);
1353
+ if (!task) {
1354
+ throw formatTaskNotFoundError(taskId);
1355
+ }
1356
+
1357
+ try {
1358
+ const { readFileSync, existsSync } = await import("fs");
1359
+ const { resolve } = await import("path");
1360
+
1361
+ const resolvedPath = resolve(filePath);
1362
+ if (!existsSync(resolvedPath)) {
1363
+ throw createStandardError(
1364
+ TaskOMaticErrorCodes.STORAGE_ERROR,
1365
+ `Documentation file not found: ${filePath}`,
1366
+ {
1367
+ context: `Tried to load documentation from ${resolvedPath}`,
1368
+ suggestions: [
1369
+ "Check that the file path is correct",
1370
+ "Ensure the file exists",
1371
+ "Use an absolute path or path relative to current directory",
1372
+ ],
1373
+ metadata: { filePath, resolvedPath },
1374
+ }
1375
+ );
1376
+ }
1377
+
1378
+ const content = readFileSync(resolvedPath, "utf-8");
1379
+ const savedPath = await this.storage.saveTaskDocumentation(
1380
+ taskId,
1381
+ content
1382
+ );
1383
+
1384
+ return {
1385
+ filePath: savedPath,
1386
+ task,
1387
+ };
1388
+ } catch (error) {
1389
+ // Re-throw if already a TaskOMaticError
1390
+ if (error instanceof TaskOMaticError) {
1391
+ throw error;
1392
+ }
1393
+ // Wrap other errors
1394
+ throw formatStorageError(
1395
+ "saveTaskDocumentation",
1396
+ error instanceof Error ? error : undefined
1397
+ );
1398
+ }
1399
+ }
1400
+
1401
+ async setTaskPlan(
1402
+ taskId: string,
1403
+ planText?: string,
1404
+ planFilePath?: string
1405
+ ): Promise<{ planFile: string; task: Task }> {
1406
+ const task = await this.getTask(taskId);
1407
+ if (!task) {
1408
+ throw formatTaskNotFoundError(taskId);
1409
+ }
1410
+
1411
+ let plan: string;
1412
+
1413
+ if (planFilePath) {
1414
+ try {
1415
+ const { readFileSync, existsSync } = await import("fs");
1416
+ const { resolve } = await import("path");
1417
+
1418
+ const resolvedPath = resolve(planFilePath);
1419
+ if (!existsSync(resolvedPath)) {
1420
+ throw createStandardError(
1421
+ TaskOMaticErrorCodes.STORAGE_ERROR,
1422
+ `Plan file not found: ${planFilePath}`,
1423
+ {
1424
+ context: `Tried to load plan from ${resolvedPath}`,
1425
+ suggestions: [
1426
+ "Check that the file path is correct",
1427
+ "Ensure the file exists",
1428
+ "Use an absolute path or path relative to current directory",
1429
+ ],
1430
+ metadata: { planFilePath, resolvedPath },
1431
+ }
1432
+ );
1433
+ }
1434
+
1435
+ plan = readFileSync(resolvedPath, "utf-8");
1436
+ } catch (error) {
1437
+ // Re-throw if already a TaskOMaticError
1438
+ if (error instanceof TaskOMaticError) {
1439
+ throw error;
1440
+ }
1441
+ // Wrap other errors
1442
+ throw formatStorageError(
1443
+ "readFileSync",
1444
+ error instanceof Error ? error : undefined
1445
+ );
1446
+ }
1447
+ } else if (planText) {
1448
+ plan = planText;
1449
+ } else {
1450
+ throw createStandardError(
1451
+ TaskOMaticErrorCodes.INVALID_INPUT,
1452
+ "Either planText or planFilePath must be provided",
1453
+ {
1454
+ context:
1455
+ "setTaskPlan requires either planText or planFilePath parameter",
1456
+ suggestions: [
1457
+ "Provide planText parameter with the plan content",
1458
+ "Provide planFilePath parameter with path to plan file",
1459
+ ],
1460
+ metadata: { taskId },
1461
+ }
1462
+ );
1463
+ }
1464
+
1465
+ await this.storage.savePlan(taskId, plan);
1466
+ const planFile = `plans/${taskId}.md`;
1467
+
1468
+ return {
1469
+ planFile,
1470
+ task,
1471
+ };
1472
+ }
1473
+
1474
+ // ============================================================================
1475
+ // PLAN OPERATIONS
1476
+ // ============================================================================
1477
+
1478
+ async getTaskPlan(
1479
+ taskId: string
1480
+ ): Promise<{ plan: string; createdAt: number; updatedAt: number } | null> {
1481
+ return this.storage.getPlan(taskId);
1482
+ }
1483
+
1484
+ async listTaskPlans(): Promise<
1485
+ Array<{
1486
+ taskId: string;
1487
+ plan: string;
1488
+ createdAt: number;
1489
+ updatedAt: number;
1490
+ }>
1491
+ > {
1492
+ return this.storage.listPlans();
1493
+ }
1494
+
1495
+ async deleteTaskPlan(taskId: string): Promise<boolean> {
1496
+ return this.storage.deletePlan(taskId);
1497
+ }
1498
+ }
1499
+
1500
+ // Lazy singleton instance - only created when first accessed
1501
+ let taskServiceInstance: TaskService | undefined;
1502
+
1503
+ export function getTaskService(): TaskService {
1504
+ if (!taskServiceInstance) {
1505
+ taskServiceInstance = new TaskService();
1506
+ }
1507
+ return taskServiceInstance;
1508
+ }
1509
+
1510
+ // Backward compatibility: export as const but use getter
1511
+ export const taskService = new Proxy({} as TaskService, {
1512
+ get(target, prop) {
1513
+ return (getTaskService() as any)[prop];
1514
+ },
1515
+ });