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.
- package/README.md +646 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +46 -0
- package/dist/lib/ai-service/ai-operations.d.ts +45 -0
- package/dist/lib/ai-service/ai-operations.d.ts.map +1 -0
- package/dist/lib/ai-service/ai-operations.js +60 -0
- package/dist/lib/ai-service/base-operations.d.ts +43 -0
- package/dist/lib/ai-service/base-operations.d.ts.map +1 -0
- package/dist/lib/ai-service/base-operations.js +119 -0
- package/dist/lib/ai-service/documentation-operations.d.ts +18 -0
- package/dist/lib/ai-service/documentation-operations.d.ts.map +1 -0
- package/dist/lib/ai-service/documentation-operations.js +308 -0
- package/dist/lib/ai-service/filesystem-tools.d.ts +69 -0
- package/dist/lib/ai-service/filesystem-tools.d.ts.map +1 -0
- package/dist/lib/ai-service/filesystem-tools.js +70 -0
- package/dist/lib/ai-service/json-parser.d.ts +34 -0
- package/dist/lib/ai-service/json-parser.d.ts.map +1 -0
- package/dist/lib/ai-service/json-parser.js +177 -0
- package/dist/lib/ai-service/mcp-client.d.ts +9 -0
- package/dist/lib/ai-service/mcp-client.d.ts.map +1 -0
- package/dist/lib/ai-service/mcp-client.js +48 -0
- package/dist/lib/ai-service/model-provider.d.ts +12 -0
- package/dist/lib/ai-service/model-provider.d.ts.map +1 -0
- package/dist/lib/ai-service/model-provider.js +146 -0
- package/dist/lib/ai-service/prd-operations.d.ts +25 -0
- package/dist/lib/ai-service/prd-operations.d.ts.map +1 -0
- package/dist/lib/ai-service/prd-operations.js +592 -0
- package/dist/lib/ai-service/research-tools.d.ts +4 -0
- package/dist/lib/ai-service/research-tools.d.ts.map +1 -0
- package/dist/lib/ai-service/research-tools.js +8 -0
- package/dist/lib/ai-service/retry-handler.d.ts +8 -0
- package/dist/lib/ai-service/retry-handler.d.ts.map +1 -0
- package/dist/lib/ai-service/retry-handler.js +63 -0
- package/dist/lib/ai-service/task-operations.d.ts +13 -0
- package/dist/lib/ai-service/task-operations.d.ts.map +1 -0
- package/dist/lib/ai-service/task-operations.js +220 -0
- package/dist/lib/benchmark/registry.d.ts +11 -0
- package/dist/lib/benchmark/registry.d.ts.map +1 -0
- package/dist/lib/benchmark/registry.js +212 -0
- package/dist/lib/benchmark/runner.d.ts +6 -0
- package/dist/lib/benchmark/runner.d.ts.map +1 -0
- package/dist/lib/benchmark/runner.js +150 -0
- package/dist/lib/benchmark/storage.d.ts +13 -0
- package/dist/lib/benchmark/storage.d.ts.map +1 -0
- package/dist/lib/benchmark/storage.js +100 -0
- package/dist/lib/benchmark/types.d.ts +104 -0
- package/dist/lib/benchmark/types.d.ts.map +1 -0
- package/dist/lib/benchmark/types.js +2 -0
- package/dist/lib/better-t-stack-cli.d.ts +50 -0
- package/dist/lib/better-t-stack-cli.d.ts.map +1 -0
- package/dist/lib/better-t-stack-cli.js +428 -0
- package/dist/lib/bootstrap/cli-bootstrap.d.ts +14 -0
- package/dist/lib/bootstrap/cli-bootstrap.d.ts.map +1 -0
- package/dist/lib/bootstrap/cli-bootstrap.js +322 -0
- package/dist/lib/bootstrap/index.d.ts +3 -0
- package/dist/lib/bootstrap/index.d.ts.map +1 -0
- package/dist/lib/bootstrap/index.js +18 -0
- package/dist/lib/bootstrap/medusa-bootstrap.d.ts +14 -0
- package/dist/lib/bootstrap/medusa-bootstrap.d.ts.map +1 -0
- package/dist/lib/bootstrap/medusa-bootstrap.js +215 -0
- package/dist/lib/config-validation.d.ts +215 -0
- package/dist/lib/config-validation.d.ts.map +1 -0
- package/dist/lib/config-validation.js +254 -0
- package/dist/lib/config.d.ts +55 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +351 -0
- package/dist/lib/context-builder.d.ts +66 -0
- package/dist/lib/context-builder.d.ts.map +1 -0
- package/dist/lib/context-builder.js +322 -0
- package/dist/lib/executors/claude-code-executor.d.ts +9 -0
- package/dist/lib/executors/claude-code-executor.d.ts.map +1 -0
- package/dist/lib/executors/claude-code-executor.js +69 -0
- package/dist/lib/executors/codex-executor.d.ts +9 -0
- package/dist/lib/executors/codex-executor.d.ts.map +1 -0
- package/dist/lib/executors/codex-executor.js +73 -0
- package/dist/lib/executors/executor-factory.d.ts +5 -0
- package/dist/lib/executors/executor-factory.d.ts.map +1 -0
- package/dist/lib/executors/executor-factory.js +27 -0
- package/dist/lib/executors/gemini-executor.d.ts +9 -0
- package/dist/lib/executors/gemini-executor.d.ts.map +1 -0
- package/dist/lib/executors/gemini-executor.js +67 -0
- package/dist/lib/executors/kilo-executor.d.ts +9 -0
- package/dist/lib/executors/kilo-executor.d.ts.map +1 -0
- package/dist/lib/executors/kilo-executor.js +69 -0
- package/dist/lib/executors/opencode-executor.d.ts +9 -0
- package/dist/lib/executors/opencode-executor.d.ts.map +1 -0
- package/dist/lib/executors/opencode-executor.js +67 -0
- package/dist/lib/git-utils.d.ts +88 -0
- package/dist/lib/git-utils.d.ts.map +1 -0
- package/dist/lib/git-utils.js +242 -0
- package/dist/lib/hooks.d.ts +73 -0
- package/dist/lib/hooks.d.ts.map +1 -0
- package/dist/lib/hooks.js +62 -0
- package/dist/lib/index.d.ts +100 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +143 -0
- package/dist/lib/logger.d.ts +20 -0
- package/dist/lib/logger.d.ts.map +1 -0
- package/dist/lib/logger.js +32 -0
- package/dist/lib/notifications.d.ts +7 -0
- package/dist/lib/notifications.d.ts.map +1 -0
- package/dist/lib/notifications.js +81 -0
- package/dist/lib/prompt-builder.d.ts +70 -0
- package/dist/lib/prompt-builder.d.ts.map +1 -0
- package/dist/lib/prompt-builder.js +344 -0
- package/dist/lib/prompt-registry.d.ts +22 -0
- package/dist/lib/prompt-registry.d.ts.map +1 -0
- package/dist/lib/prompt-registry.js +409 -0
- package/dist/lib/provider-defaults.json +32 -0
- package/dist/lib/storage/file-system.d.ts +57 -0
- package/dist/lib/storage/file-system.d.ts.map +1 -0
- package/dist/lib/storage/file-system.js +638 -0
- package/dist/lib/storage/storage-callbacks.d.ts +17 -0
- package/dist/lib/storage/storage-callbacks.d.ts.map +1 -0
- package/dist/lib/storage/storage-callbacks.js +94 -0
- package/dist/lib/storage/types.d.ts +43 -0
- package/dist/lib/storage/types.d.ts.map +1 -0
- package/dist/lib/storage/types.js +2 -0
- package/dist/lib/task-execution-core.d.ts +7 -0
- package/dist/lib/task-execution-core.d.ts.map +1 -0
- package/dist/lib/task-execution-core.js +381 -0
- package/dist/lib/task-execution.d.ts +7 -0
- package/dist/lib/task-execution.d.ts.map +1 -0
- package/dist/lib/task-execution.js +40 -0
- package/dist/lib/task-loop-execution.d.ts +7 -0
- package/dist/lib/task-loop-execution.d.ts.map +1 -0
- package/dist/lib/task-loop-execution.js +156 -0
- package/dist/lib/task-planning.d.ts +29 -0
- package/dist/lib/task-planning.d.ts.map +1 -0
- package/dist/lib/task-planning.js +103 -0
- package/dist/lib/task-review.d.ts +27 -0
- package/dist/lib/task-review.d.ts.map +1 -0
- package/dist/lib/task-review.js +103 -0
- package/dist/lib/validation.d.ts +26 -0
- package/dist/lib/validation.d.ts.map +1 -0
- package/dist/lib/validation.js +98 -0
- package/dist/prompts/documentation-detection.d.ts +2 -0
- package/dist/prompts/documentation-detection.d.ts.map +1 -0
- package/dist/prompts/documentation-detection.js +24 -0
- package/dist/prompts/documentation-recap.d.ts +3 -0
- package/dist/prompts/documentation-recap.d.ts.map +1 -0
- package/dist/prompts/documentation-recap.js +13 -0
- package/dist/prompts/index.d.ts +15 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +30 -0
- package/dist/prompts/prd-combination.d.ts +2 -0
- package/dist/prompts/prd-combination.d.ts.map +1 -0
- package/dist/prompts/prd-combination.js +35 -0
- package/dist/prompts/prd-generation.d.ts +2 -0
- package/dist/prompts/prd-generation.d.ts.map +1 -0
- package/dist/prompts/prd-generation.js +49 -0
- package/dist/prompts/prd-parsing.d.ts +3 -0
- package/dist/prompts/prd-parsing.d.ts.map +1 -0
- package/dist/prompts/prd-parsing.js +172 -0
- package/dist/prompts/prd-question-answer.d.ts +3 -0
- package/dist/prompts/prd-question-answer.d.ts.map +1 -0
- package/dist/prompts/prd-question-answer.js +27 -0
- package/dist/prompts/prd-question.d.ts +3 -0
- package/dist/prompts/prd-question.d.ts.map +1 -0
- package/dist/prompts/prd-question.js +40 -0
- package/dist/prompts/prd-rework.d.ts +3 -0
- package/dist/prompts/prd-rework.d.ts.map +1 -0
- package/dist/prompts/prd-rework.js +81 -0
- package/dist/prompts/prd-suggest-stack.d.ts +3 -0
- package/dist/prompts/prd-suggest-stack.d.ts.map +1 -0
- package/dist/prompts/prd-suggest-stack.js +99 -0
- package/dist/prompts/task-breakdown.d.ts +3 -0
- package/dist/prompts/task-breakdown.d.ts.map +1 -0
- package/dist/prompts/task-breakdown.js +151 -0
- package/dist/prompts/task-enhancement.d.ts +3 -0
- package/dist/prompts/task-enhancement.d.ts.map +1 -0
- package/dist/prompts/task-enhancement.js +140 -0
- package/dist/prompts/task-execution.d.ts +3 -0
- package/dist/prompts/task-execution.d.ts.map +1 -0
- package/dist/prompts/task-execution.js +24 -0
- package/dist/prompts/task-planning.d.ts +3 -0
- package/dist/prompts/task-planning.d.ts.map +1 -0
- package/dist/prompts/task-planning.js +66 -0
- package/dist/prompts/workflow-assistance.d.ts +32 -0
- package/dist/prompts/workflow-assistance.d.ts.map +1 -0
- package/dist/prompts/workflow-assistance.js +130 -0
- package/dist/prompts/workflow-prompts.d.ts +9 -0
- package/dist/prompts/workflow-prompts.d.ts.map +1 -0
- package/dist/prompts/workflow-prompts.js +93 -0
- package/dist/services/benchmark.d.ts +26 -0
- package/dist/services/benchmark.d.ts.map +1 -0
- package/dist/services/benchmark.js +343 -0
- package/dist/services/prd.d.ts +136 -0
- package/dist/services/prd.d.ts.map +1 -0
- package/dist/services/prd.js +550 -0
- package/dist/services/tasks.d.ts +388 -0
- package/dist/services/tasks.d.ts.map +1 -0
- package/dist/services/tasks.js +1150 -0
- package/dist/services/workflow-ai-assistant.d.ts +74 -0
- package/dist/services/workflow-ai-assistant.d.ts.map +1 -0
- package/dist/services/workflow-ai-assistant.js +175 -0
- package/dist/services/workflow-benchmark.d.ts +34 -0
- package/dist/services/workflow-benchmark.d.ts.map +1 -0
- package/dist/services/workflow-benchmark.js +318 -0
- package/dist/services/workflow.d.ts +107 -0
- package/dist/services/workflow.d.ts.map +1 -0
- package/dist/services/workflow.js +580 -0
- package/dist/test/hooks.test.d.ts +2 -0
- package/dist/test/hooks.test.d.ts.map +1 -0
- package/dist/test/hooks.test.js +67 -0
- package/dist/test/integration/callbacks.test.d.ts +2 -0
- package/dist/test/integration/callbacks.test.d.ts.map +1 -0
- package/dist/test/integration/callbacks.test.js +64 -0
- package/dist/test/lib/ai-service/task-operations.test.d.ts +2 -0
- package/dist/test/lib/ai-service/task-operations.test.d.ts.map +1 -0
- package/dist/test/lib/ai-service/task-operations.test.js +362 -0
- package/dist/test/lib/config.test.d.ts +2 -0
- package/dist/test/lib/config.test.d.ts.map +1 -0
- package/dist/test/lib/config.test.js +128 -0
- package/dist/test/lib/git-utils.test.d.ts +2 -0
- package/dist/test/lib/git-utils.test.d.ts.map +1 -0
- package/dist/test/lib/git-utils.test.js +168 -0
- package/dist/test/mocks/mock-ai-operations.d.ts +15 -0
- package/dist/test/mocks/mock-ai-operations.d.ts.map +1 -0
- package/dist/test/mocks/mock-ai-operations.js +107 -0
- package/dist/test/mocks/mock-context-builder.d.ts +10 -0
- package/dist/test/mocks/mock-context-builder.d.ts.map +1 -0
- package/dist/test/mocks/mock-context-builder.js +81 -0
- package/dist/test/mocks/mock-model-provider.d.ts +7 -0
- package/dist/test/mocks/mock-model-provider.d.ts.map +1 -0
- package/dist/test/mocks/mock-model-provider.js +21 -0
- package/dist/test/mocks/mock-service-factory.d.ts +11 -0
- package/dist/test/mocks/mock-service-factory.d.ts.map +1 -0
- package/dist/test/mocks/mock-service-factory.js +61 -0
- package/dist/test/mocks/mock-storage.d.ts +50 -0
- package/dist/test/mocks/mock-storage.d.ts.map +1 -0
- package/dist/test/mocks/mock-storage.js +145 -0
- package/dist/test/model-parsing.test.d.ts +2 -0
- package/dist/test/model-parsing.test.d.ts.map +1 -0
- package/dist/test/model-parsing.test.js +73 -0
- package/dist/test/services/task-service.test.d.ts +2 -0
- package/dist/test/services/task-service.test.d.ts.map +1 -0
- package/dist/test/services/task-service.test.js +459 -0
- package/dist/test/storage.test.d.ts +2 -0
- package/dist/test/storage.test.d.ts.map +1 -0
- package/dist/test/storage.test.js +207 -0
- package/dist/test/task-loop-git.test.d.ts +2 -0
- package/dist/test/task-loop-git.test.d.ts.map +1 -0
- package/dist/test/task-loop-git.test.js +95 -0
- package/dist/test/test-mock-setup.d.ts +26 -0
- package/dist/test/test-mock-setup.d.ts.map +1 -0
- package/dist/test/test-mock-setup.js +41 -0
- package/dist/test/test-setup.d.ts +9 -0
- package/dist/test/test-setup.d.ts.map +1 -0
- package/dist/test/test-setup.js +44 -0
- package/dist/test/test-utils.d.ts +22 -0
- package/dist/test/test-utils.d.ts.map +1 -0
- package/dist/test/test-utils.js +37 -0
- package/dist/test/utils/ai-operation-utility.test.d.ts +2 -0
- package/dist/test/utils/ai-operation-utility.test.d.ts.map +1 -0
- package/dist/test/utils/ai-operation-utility.test.js +290 -0
- package/dist/test/utils/error-handling.test.d.ts +2 -0
- package/dist/test/utils/error-handling.test.d.ts.map +1 -0
- package/dist/test/utils/error-handling.test.js +231 -0
- package/dist/test/utils/file-utils.test.d.ts +2 -0
- package/dist/test/utils/file-utils.test.d.ts.map +1 -0
- package/dist/test/utils/file-utils.test.js +76 -0
- package/dist/test/utils/id-generator.test.d.ts +2 -0
- package/dist/test/utils/id-generator.test.d.ts.map +1 -0
- package/dist/test/utils/id-generator.test.js +41 -0
- package/dist/test/utils/model-parser.test.d.ts +2 -0
- package/dist/test/utils/model-parser.test.d.ts.map +1 -0
- package/dist/test/utils/model-parser.test.js +65 -0
- package/dist/test/validation.test.d.ts +2 -0
- package/dist/test/validation.test.d.ts.map +1 -0
- package/dist/test/validation.test.js +22 -0
- package/dist/types/callbacks.d.ts +30 -0
- package/dist/types/callbacks.d.ts.map +1 -0
- package/dist/types/callbacks.js +2 -0
- package/dist/types/index.d.ts +435 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +30 -0
- package/dist/types/mcp.d.ts +3 -0
- package/dist/types/mcp.d.ts.map +1 -0
- package/dist/types/mcp.js +3 -0
- package/dist/types/options.d.ts +112 -0
- package/dist/types/options.d.ts.map +1 -0
- package/dist/types/options.js +2 -0
- package/dist/types/results.d.ts +200 -0
- package/dist/types/results.d.ts.map +1 -0
- package/dist/types/results.js +2 -0
- package/dist/types/workflow-options.d.ts +82 -0
- package/dist/types/workflow-options.d.ts.map +1 -0
- package/dist/types/workflow-options.js +2 -0
- package/dist/types/workflow-results.d.ts +82 -0
- package/dist/types/workflow-results.d.ts.map +1 -0
- package/dist/types/workflow-results.js +2 -0
- package/dist/utils/ai-config-builder.d.ts +14 -0
- package/dist/utils/ai-config-builder.d.ts.map +1 -0
- package/dist/utils/ai-config-builder.js +22 -0
- package/dist/utils/ai-operation-utility.d.ts +142 -0
- package/dist/utils/ai-operation-utility.d.ts.map +1 -0
- package/dist/utils/ai-operation-utility.js +303 -0
- package/dist/utils/ai-service-factory.d.ts +34 -0
- package/dist/utils/ai-service-factory.d.ts.map +1 -0
- package/dist/utils/ai-service-factory.js +99 -0
- package/dist/utils/error-utils.d.ts +70 -0
- package/dist/utils/error-utils.d.ts.map +1 -0
- package/dist/utils/error-utils.js +104 -0
- package/dist/utils/file-utils.d.ts +107 -0
- package/dist/utils/file-utils.d.ts.map +1 -0
- package/dist/utils/file-utils.js +171 -0
- package/dist/utils/id-generator.d.ts +92 -0
- package/dist/utils/id-generator.d.ts.map +1 -0
- package/dist/utils/id-generator.js +146 -0
- package/dist/utils/metadata-utils.d.ts +40 -0
- package/dist/utils/metadata-utils.d.ts.map +1 -0
- package/dist/utils/metadata-utils.js +43 -0
- package/dist/utils/model-executor-parser.d.ts +38 -0
- package/dist/utils/model-executor-parser.d.ts.map +1 -0
- package/dist/utils/model-executor-parser.js +69 -0
- package/dist/utils/model-parser.d.ts +6 -0
- package/dist/utils/model-parser.d.ts.map +1 -0
- package/dist/utils/model-parser.js +49 -0
- package/dist/utils/stack-formatter.d.ts +12 -0
- package/dist/utils/stack-formatter.d.ts.map +1 -0
- package/dist/utils/stack-formatter.js +36 -0
- package/dist/utils/storage-utils.d.ts +49 -0
- package/dist/utils/storage-utils.d.ts.map +1 -0
- package/dist/utils/storage-utils.js +80 -0
- package/dist/utils/streaming-utils.d.ts +38 -0
- package/dist/utils/streaming-utils.d.ts.map +1 -0
- package/dist/utils/streaming-utils.js +64 -0
- package/dist/utils/task-o-matic-error.d.ts +206 -0
- package/dist/utils/task-o-matic-error.d.ts.map +1 -0
- package/dist/utils/task-o-matic-error.js +304 -0
- package/package.json +40 -0
- package/src/index.ts +36 -0
- package/src/lib/ai-service/ai-operations.ts +310 -0
- package/src/lib/ai-service/base-operations.ts +139 -0
- package/src/lib/ai-service/documentation-operations.ts +438 -0
- package/src/lib/ai-service/filesystem-tools.ts +73 -0
- package/src/lib/ai-service/gemini-proxy.ts.bak +52 -0
- package/src/lib/ai-service/json-parser.ts +203 -0
- package/src/lib/ai-service/mcp-client.ts +54 -0
- package/src/lib/ai-service/model-provider.ts +192 -0
- package/src/lib/ai-service/prd-operations.ts +854 -0
- package/src/lib/ai-service/research-tools.ts +207 -0
- package/src/lib/ai-service/retry-handler.ts +89 -0
- package/src/lib/ai-service/task-operations.ts +342 -0
- package/src/lib/benchmark/registry.ts +307 -0
- package/src/lib/benchmark/runner.ts +190 -0
- package/src/lib/benchmark/storage.ts +140 -0
- package/src/lib/benchmark/types.ts +121 -0
- package/src/lib/better-t-stack-cli.ts +524 -0
- package/src/lib/bootstrap/cli-bootstrap.ts +397 -0
- package/src/lib/bootstrap/index.ts +2 -0
- package/src/lib/bootstrap/medusa-bootstrap.ts +261 -0
- package/src/lib/config-validation.ts +278 -0
- package/src/lib/config.ts +435 -0
- package/src/lib/context-builder.ts +383 -0
- package/src/lib/executors/claude-code-executor.ts +83 -0
- package/src/lib/executors/codex-executor.ts +85 -0
- package/src/lib/executors/executor-factory.ts +28 -0
- package/src/lib/executors/gemini-executor.ts +80 -0
- package/src/lib/executors/kilo-executor.ts +83 -0
- package/src/lib/executors/opencode-executor.ts +81 -0
- package/src/lib/git-utils.ts +334 -0
- package/src/lib/hooks.ts +121 -0
- package/src/lib/index.ts +166 -0
- package/src/lib/logger.ts +43 -0
- package/src/lib/notifications.ts +103 -0
- package/src/lib/prompt-builder.ts +471 -0
- package/src/lib/prompt-registry.ts +491 -0
- package/src/lib/provider-defaults.json +32 -0
- package/src/lib/storage/file-system.ts +864 -0
- package/src/lib/storage/storage-callbacks.ts +120 -0
- package/src/lib/storage/types.ts +58 -0
- package/src/lib/task-execution-core.ts +591 -0
- package/src/lib/task-execution.ts +59 -0
- package/src/lib/task-loop-execution.ts +214 -0
- package/src/lib/task-planning.ts +157 -0
- package/src/lib/task-review.ts +138 -0
- package/src/lib/validation.ts +140 -0
- package/src/prompts/documentation-detection.ts +21 -0
- package/src/prompts/documentation-recap.ts +11 -0
- package/src/prompts/index.ts +14 -0
- package/src/prompts/prd-combination.ts +32 -0
- package/src/prompts/prd-generation.ts +46 -0
- package/src/prompts/prd-parsing.ts +170 -0
- package/src/prompts/prd-question-answer.ts +25 -0
- package/src/prompts/prd-question.ts +38 -0
- package/src/prompts/prd-rework.ts +79 -0
- package/src/prompts/prd-suggest-stack.ts +97 -0
- package/src/prompts/task-breakdown.ts +149 -0
- package/src/prompts/task-enhancement.ts +138 -0
- package/src/prompts/task-execution.ts +22 -0
- package/src/prompts/task-planning.ts +64 -0
- package/src/prompts/workflow-assistance.ts +151 -0
- package/src/prompts/workflow-prompts.ts +97 -0
- package/src/services/benchmark.ts +433 -0
- package/src/services/prd.ts +845 -0
- package/src/services/tasks.ts +1515 -0
- package/src/services/workflow-ai-assistant.ts +298 -0
- package/src/services/workflow-benchmark.ts +339 -0
- package/src/services/workflow.ts +779 -0
- package/src/test/hooks.test.ts +77 -0
- package/src/test/integration/callbacks.test.ts +39 -0
- package/src/test/lib/ai-service/task-operations.test.ts +430 -0
- package/src/test/lib/config.test.ts +150 -0
- package/src/test/lib/git-utils.test.ts +198 -0
- package/src/test/mocks/mock-ai-operations.ts +205 -0
- package/src/test/mocks/mock-context-builder.ts +84 -0
- package/src/test/mocks/mock-model-provider.ts +21 -0
- package/src/test/mocks/mock-service-factory.ts +64 -0
- package/src/test/mocks/mock-storage.ts +204 -0
- package/src/test/model-parsing.test.ts +78 -0
- package/src/test/services/task-service.test.ts +551 -0
- package/src/test/storage.test.ts +206 -0
- package/src/test/task-loop-git.test.ts +142 -0
- package/src/test/test-mock-setup.ts +46 -0
- package/src/test/test-setup.ts +48 -0
- package/src/test/test-utils.ts +45 -0
- package/src/test/utils/ai-operation-utility.test.ts +306 -0
- package/src/test/utils/error-handling.test.ts +241 -0
- package/src/test/utils/file-utils.test.ts +80 -0
- package/src/test/utils/id-generator.test.ts +44 -0
- package/src/test/utils/model-parser.test.ts +67 -0
- package/src/test/validation.test.ts +19 -0
- package/src/types/callbacks.ts +14 -0
- package/src/types/index.ts +628 -0
- package/src/types/mcp.ts +5 -0
- package/src/types/options.ts +165 -0
- package/src/types/results.ts +216 -0
- package/src/types/workflow-options.ts +113 -0
- package/src/types/workflow-results.ts +87 -0
- package/src/utils/ai-config-builder.ts +33 -0
- package/src/utils/ai-operation-utility.ts +380 -0
- package/src/utils/ai-service-factory.ts +125 -0
- package/src/utils/error-utils.ts +124 -0
- package/src/utils/file-utils.ts +197 -0
- package/src/utils/id-generator.ts +168 -0
- package/src/utils/metadata-utils.ts +48 -0
- package/src/utils/model-executor-parser.ts +80 -0
- package/src/utils/model-parser.ts +58 -0
- package/src/utils/stack-formatter.ts +53 -0
- package/src/utils/storage-utils.ts +94 -0
- package/src/utils/streaming-utils.ts +91 -0
- package/src/utils/task-o-matic-error.ts +393 -0
- package/tsconfig.json +20 -0
- 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
|
+
});
|