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,864 @@
|
|
|
1
|
+
import { Task, CreateTaskRequest, TaskAIMetadata } from "../../types";
|
|
2
|
+
import { configManager } from "../config";
|
|
3
|
+
import { TaskRepository } from "./types";
|
|
4
|
+
import {
|
|
5
|
+
StorageCallbacks,
|
|
6
|
+
createFileSystemCallbacks,
|
|
7
|
+
} from "./storage-callbacks";
|
|
8
|
+
import {
|
|
9
|
+
TaskOMaticError,
|
|
10
|
+
TaskOMaticErrorCodes,
|
|
11
|
+
formatStorageError,
|
|
12
|
+
createStandardError,
|
|
13
|
+
} from "../../utils/task-o-matic-error";
|
|
14
|
+
import { logger } from "../logger";
|
|
15
|
+
|
|
16
|
+
interface TasksData {
|
|
17
|
+
tasks: Task[];
|
|
18
|
+
nextId: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class FileSystemStorage implements TaskRepository {
|
|
22
|
+
private callbacks: StorageCallbacks;
|
|
23
|
+
|
|
24
|
+
constructor(callbacks?: StorageCallbacks) {
|
|
25
|
+
// If no callbacks provided, use default file system callbacks
|
|
26
|
+
// We use configManager to get the base directory for the default implementation
|
|
27
|
+
this.callbacks =
|
|
28
|
+
callbacks || createFileSystemCallbacks(configManager.getTaskOMaticDir());
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public sanitizeForFilename(name: string): string {
|
|
32
|
+
return name.replace(/[\/\?%*:|"<>]/g, "-");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private validateTaskId(taskId: string): void {
|
|
36
|
+
if (!taskId || typeof taskId !== "string" || taskId.trim() === "") {
|
|
37
|
+
throw createStandardError(
|
|
38
|
+
TaskOMaticErrorCodes.INVALID_INPUT,
|
|
39
|
+
"Task ID must be a non-empty string",
|
|
40
|
+
{
|
|
41
|
+
context: "validateTaskId",
|
|
42
|
+
suggestions: ["Provide a valid task ID string"],
|
|
43
|
+
metadata: { taskId },
|
|
44
|
+
}
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private validateTaskRequest(task: CreateTaskRequest): void {
|
|
50
|
+
if (!task || typeof task !== "object") {
|
|
51
|
+
throw createStandardError(
|
|
52
|
+
TaskOMaticErrorCodes.INVALID_INPUT,
|
|
53
|
+
"Task request must be a valid object",
|
|
54
|
+
{
|
|
55
|
+
context: "validateTaskRequest",
|
|
56
|
+
suggestions: ["Provide a valid task request object"],
|
|
57
|
+
}
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
if (
|
|
61
|
+
!task.title ||
|
|
62
|
+
typeof task.title !== "string" ||
|
|
63
|
+
task.title.trim() === ""
|
|
64
|
+
) {
|
|
65
|
+
throw createStandardError(
|
|
66
|
+
TaskOMaticErrorCodes.INVALID_INPUT,
|
|
67
|
+
"Task title is required and must be a non-empty string",
|
|
68
|
+
{
|
|
69
|
+
context: "validateTaskRequest - title validation",
|
|
70
|
+
suggestions: ["Provide a non-empty title for the task"],
|
|
71
|
+
}
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
if (task.parentId && typeof task.parentId !== "string") {
|
|
75
|
+
throw createStandardError(
|
|
76
|
+
TaskOMaticErrorCodes.INVALID_INPUT,
|
|
77
|
+
"Parent ID must be a string if provided",
|
|
78
|
+
{
|
|
79
|
+
context: "validateTaskRequest - parentId validation",
|
|
80
|
+
suggestions: ["Provide a valid parent ID string or omit the field"],
|
|
81
|
+
metadata: { parentId: task.parentId },
|
|
82
|
+
}
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
if (
|
|
86
|
+
task.estimatedEffort &&
|
|
87
|
+
!["small", "medium", "large"].includes(task.estimatedEffort)
|
|
88
|
+
) {
|
|
89
|
+
throw createStandardError(
|
|
90
|
+
TaskOMaticErrorCodes.INVALID_INPUT,
|
|
91
|
+
"Estimated effort must be 'small', 'medium', or 'large'",
|
|
92
|
+
{
|
|
93
|
+
context: "validateTaskRequest - estimatedEffort validation",
|
|
94
|
+
suggestions: ["Use 'small', 'medium', or 'large' for estimatedEffort"],
|
|
95
|
+
metadata: { estimatedEffort: task.estimatedEffort },
|
|
96
|
+
}
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
if (task.dependencies && !Array.isArray(task.dependencies)) {
|
|
100
|
+
throw createStandardError(
|
|
101
|
+
TaskOMaticErrorCodes.INVALID_INPUT,
|
|
102
|
+
"Dependencies must be an array if provided",
|
|
103
|
+
{
|
|
104
|
+
context: "validateTaskRequest - dependencies validation",
|
|
105
|
+
suggestions: ["Provide dependencies as an array of task IDs"],
|
|
106
|
+
}
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
if (task.tags && !Array.isArray(task.tags)) {
|
|
110
|
+
throw createStandardError(
|
|
111
|
+
TaskOMaticErrorCodes.INVALID_INPUT,
|
|
112
|
+
"Tags must be an array if provided",
|
|
113
|
+
{
|
|
114
|
+
context: "validateTaskRequest - tags validation",
|
|
115
|
+
suggestions: ["Provide tags as an array of strings"],
|
|
116
|
+
}
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private async loadTasksData(): Promise<TasksData> {
|
|
122
|
+
try {
|
|
123
|
+
const content = await this.callbacks.read("tasks.json");
|
|
124
|
+
if (!content) {
|
|
125
|
+
return { tasks: [], nextId: 1 };
|
|
126
|
+
}
|
|
127
|
+
return JSON.parse(content);
|
|
128
|
+
} catch (error) {
|
|
129
|
+
logger.error(`Failed to read tasks file: ${error}`);
|
|
130
|
+
return { tasks: [], nextId: 1 };
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private async saveTasksData(data: TasksData): Promise<void> {
|
|
135
|
+
try {
|
|
136
|
+
await this.callbacks.write("tasks.json", JSON.stringify(data, null, 2));
|
|
137
|
+
} catch (error) {
|
|
138
|
+
throw formatStorageError(
|
|
139
|
+
"write tasks.json",
|
|
140
|
+
error instanceof Error ? error : undefined
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private findTaskInHierarchy(
|
|
146
|
+
tasks: Task[],
|
|
147
|
+
id: string
|
|
148
|
+
): { task: Task | null; parent: Task | null; index: number } {
|
|
149
|
+
for (let i = 0; i < tasks.length; i++) {
|
|
150
|
+
const task = tasks[i];
|
|
151
|
+
if (task.id === id) {
|
|
152
|
+
return { task, parent: null, index: i };
|
|
153
|
+
}
|
|
154
|
+
if (task.subtasks) {
|
|
155
|
+
const result = this.findTaskInHierarchy(task.subtasks, id);
|
|
156
|
+
if (result.task) {
|
|
157
|
+
return { ...result, parent: task };
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return { task: null, parent: null, index: -1 };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private flattenTasks(tasks: Task[]): Task[] {
|
|
165
|
+
const result: Task[] = [];
|
|
166
|
+
for (const task of tasks) {
|
|
167
|
+
const { subtasks, ...taskWithoutSubtasks } = task;
|
|
168
|
+
result.push(taskWithoutSubtasks);
|
|
169
|
+
if (subtasks) {
|
|
170
|
+
result.push(...this.flattenTasks(subtasks));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return result;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Tasks
|
|
177
|
+
async getTasks(): Promise<Task[]> {
|
|
178
|
+
const data = await this.loadTasksData();
|
|
179
|
+
return this.flattenTasks(data.tasks);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async getTopLevelTasks(): Promise<Task[]> {
|
|
183
|
+
const data = await this.loadTasksData();
|
|
184
|
+
return data.tasks;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async getTask(id: string): Promise<Task | null> {
|
|
188
|
+
this.validateTaskId(id);
|
|
189
|
+
|
|
190
|
+
const data = await this.loadTasksData();
|
|
191
|
+
const result = this.findTaskInHierarchy(data.tasks, id);
|
|
192
|
+
return result.task;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async createTask(
|
|
196
|
+
task: CreateTaskRequest,
|
|
197
|
+
aiMetadata?: TaskAIMetadata
|
|
198
|
+
): Promise<Task> {
|
|
199
|
+
this.validateTaskRequest(task);
|
|
200
|
+
const data = await this.loadTasksData();
|
|
201
|
+
let id: string;
|
|
202
|
+
|
|
203
|
+
if (task.parentId) {
|
|
204
|
+
const parentResult = this.findTaskInHierarchy(data.tasks, task.parentId);
|
|
205
|
+
if (!parentResult.task) {
|
|
206
|
+
throw createStandardError(
|
|
207
|
+
TaskOMaticErrorCodes.TASK_NOT_FOUND,
|
|
208
|
+
`Parent task with ID ${task.parentId} not found`,
|
|
209
|
+
{
|
|
210
|
+
context: "createTask - parent validation",
|
|
211
|
+
suggestions: [
|
|
212
|
+
"Create the parent task first",
|
|
213
|
+
"Check that the parent ID is correct",
|
|
214
|
+
"List all tasks to see available parent IDs",
|
|
215
|
+
],
|
|
216
|
+
metadata: { parentId: task.parentId },
|
|
217
|
+
}
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const siblingCount = (parentResult.task.subtasks?.length || 0) + 1;
|
|
222
|
+
id = `${task.parentId}.${siblingCount}`;
|
|
223
|
+
} else {
|
|
224
|
+
if (task.id && typeof task.id === "string") {
|
|
225
|
+
id = task.id;
|
|
226
|
+
} else {
|
|
227
|
+
id = data.nextId.toString();
|
|
228
|
+
data.nextId++;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (task.dependencies && task.dependencies.length > 0) {
|
|
233
|
+
for (const depId of task.dependencies) {
|
|
234
|
+
const depExists = this.taskExists(data.tasks, depId);
|
|
235
|
+
if (!depExists) {
|
|
236
|
+
throw createStandardError(
|
|
237
|
+
TaskOMaticErrorCodes.TASK_NOT_FOUND,
|
|
238
|
+
`Dependency task not found: ${depId}`,
|
|
239
|
+
{
|
|
240
|
+
context: "createTask - dependency validation",
|
|
241
|
+
suggestions: [
|
|
242
|
+
"Create the dependency task first",
|
|
243
|
+
"Check that the dependency ID is correct",
|
|
244
|
+
"Remove the dependency from the task",
|
|
245
|
+
],
|
|
246
|
+
metadata: { dependencyId: depId, taskDependencies: task.dependencies },
|
|
247
|
+
}
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (
|
|
253
|
+
this.wouldCreateCircularDependency(data.tasks, id, task.dependencies)
|
|
254
|
+
) {
|
|
255
|
+
throw createStandardError(
|
|
256
|
+
TaskOMaticErrorCodes.STORAGE_INTEGRITY_ERROR,
|
|
257
|
+
`Circular dependency detected for task ${id}`,
|
|
258
|
+
{
|
|
259
|
+
context: "createTask - circular dependency check",
|
|
260
|
+
suggestions: [
|
|
261
|
+
"Remove circular dependencies from the task",
|
|
262
|
+
"Review the dependency chain",
|
|
263
|
+
],
|
|
264
|
+
metadata: { taskId: id, dependencies: task.dependencies },
|
|
265
|
+
}
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
let contentFile: string | undefined;
|
|
271
|
+
let description = task.description || "";
|
|
272
|
+
|
|
273
|
+
if (task.content && task.content.length > 200) {
|
|
274
|
+
contentFile = await this.saveTaskContent(id, task.content);
|
|
275
|
+
description = task.description || task.content.substring(0, 200) + "...";
|
|
276
|
+
} else if (task.content) {
|
|
277
|
+
description = task.description || task.content;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const newTask: Task = {
|
|
281
|
+
id,
|
|
282
|
+
title: task.title,
|
|
283
|
+
description,
|
|
284
|
+
contentFile,
|
|
285
|
+
status: task.status || "todo",
|
|
286
|
+
estimatedEffort: task.estimatedEffort,
|
|
287
|
+
dependencies: task.dependencies,
|
|
288
|
+
tags: task.tags,
|
|
289
|
+
subtasks: [],
|
|
290
|
+
createdAt: Date.now(),
|
|
291
|
+
updatedAt: Date.now(),
|
|
292
|
+
prdFile: task.prdFile,
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
if (task.parentId) {
|
|
296
|
+
const parentResult = this.findTaskInHierarchy(data.tasks, task.parentId);
|
|
297
|
+
if (parentResult.task) {
|
|
298
|
+
if (!parentResult.task.subtasks) {
|
|
299
|
+
parentResult.task.subtasks = [];
|
|
300
|
+
}
|
|
301
|
+
parentResult.task.subtasks.push(newTask);
|
|
302
|
+
}
|
|
303
|
+
} else {
|
|
304
|
+
data.tasks.push(newTask);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
await this.saveTasksData(data);
|
|
308
|
+
|
|
309
|
+
if (aiMetadata) {
|
|
310
|
+
await this.saveTaskAIMetadata({
|
|
311
|
+
...aiMetadata,
|
|
312
|
+
taskId: id,
|
|
313
|
+
generatedAt: Date.now(),
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return newTask;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
async updateTask(id: string, updates: Partial<Task>): Promise<Task | null> {
|
|
321
|
+
this.validateTaskId(id);
|
|
322
|
+
if (!updates || typeof updates !== "object") {
|
|
323
|
+
throw createStandardError(
|
|
324
|
+
TaskOMaticErrorCodes.INVALID_INPUT,
|
|
325
|
+
"Updates must be a valid object",
|
|
326
|
+
{
|
|
327
|
+
context: "updateTask - updates validation",
|
|
328
|
+
suggestions: ["Provide a valid updates object with task properties"],
|
|
329
|
+
}
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const data = await this.loadTasksData();
|
|
334
|
+
const result = this.findTaskInHierarchy(data.tasks, id);
|
|
335
|
+
|
|
336
|
+
if (!result.task) return null;
|
|
337
|
+
|
|
338
|
+
const updatedTask: Task = {
|
|
339
|
+
...result.task,
|
|
340
|
+
...updates,
|
|
341
|
+
id,
|
|
342
|
+
updatedAt: Date.now(),
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
if (result.parent) {
|
|
346
|
+
const parentIndex = result.parent.subtasks!.findIndex((t) => t.id === id);
|
|
347
|
+
result.parent.subtasks![parentIndex] = updatedTask;
|
|
348
|
+
} else {
|
|
349
|
+
const taskIndex = data.tasks.findIndex((t) => t.id === id);
|
|
350
|
+
data.tasks[taskIndex] = updatedTask;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
await this.saveTasksData(data);
|
|
354
|
+
return updatedTask;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
async deleteTask(id: string): Promise<boolean> {
|
|
358
|
+
this.validateTaskId(id);
|
|
359
|
+
|
|
360
|
+
const data = await this.loadTasksData();
|
|
361
|
+
const result = this.findTaskInHierarchy(data.tasks, id);
|
|
362
|
+
|
|
363
|
+
if (!result.task) return false;
|
|
364
|
+
|
|
365
|
+
if (result.parent) {
|
|
366
|
+
const parentIndex = result.parent.subtasks!.findIndex((t) => t.id === id);
|
|
367
|
+
result.parent.subtasks!.splice(parentIndex, 1);
|
|
368
|
+
} else {
|
|
369
|
+
const taskIndex = data.tasks.findIndex((t) => t.id === id);
|
|
370
|
+
data.tasks.splice(taskIndex, 1);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
await this.saveTasksData(data);
|
|
374
|
+
return true;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
async getSubtasks(parentId: string): Promise<Task[]> {
|
|
378
|
+
const data = await this.loadTasksData();
|
|
379
|
+
const result = this.findTaskInHierarchy(data.tasks, parentId);
|
|
380
|
+
return result.task?.subtasks || [];
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
private taskExists(tasks: Task[], taskId: string): boolean {
|
|
384
|
+
const exactMatch = this.findTaskInHierarchy(tasks, taskId).task;
|
|
385
|
+
if (exactMatch) return true;
|
|
386
|
+
|
|
387
|
+
const taskPrefixedId = taskId.startsWith("task-")
|
|
388
|
+
? taskId.substring(5)
|
|
389
|
+
: `task-${taskId}`;
|
|
390
|
+
const prefixedMatch = this.findTaskInHierarchy(tasks, taskPrefixedId).task;
|
|
391
|
+
if (prefixedMatch) return true;
|
|
392
|
+
|
|
393
|
+
if (taskId.startsWith("task-")) {
|
|
394
|
+
const numericId = taskId.substring(5);
|
|
395
|
+
const numericMatch = this.findTaskInHierarchy(tasks, numericId).task;
|
|
396
|
+
if (numericMatch) return true;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
private wouldCreateCircularDependency(
|
|
403
|
+
tasks: Task[],
|
|
404
|
+
newTaskId: string,
|
|
405
|
+
dependencies: string[]
|
|
406
|
+
): boolean {
|
|
407
|
+
const visited = new Set<string>();
|
|
408
|
+
|
|
409
|
+
const checkCircular = (taskId: string): boolean => {
|
|
410
|
+
if (visited.has(taskId)) {
|
|
411
|
+
return true;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (taskId === newTaskId) {
|
|
415
|
+
return true;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
visited.add(taskId);
|
|
419
|
+
|
|
420
|
+
const task = this.findTaskInHierarchy(tasks, taskId).task;
|
|
421
|
+
if (task && task.dependencies) {
|
|
422
|
+
for (const depId of task.dependencies) {
|
|
423
|
+
if (checkCircular(depId)) {
|
|
424
|
+
return true;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
visited.delete(taskId);
|
|
430
|
+
return false;
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
for (const depId of dependencies) {
|
|
434
|
+
visited.clear();
|
|
435
|
+
if (checkCircular(depId)) {
|
|
436
|
+
return true;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return false;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
private async loadAIMetadata(): Promise<TaskAIMetadata[]> {
|
|
444
|
+
try {
|
|
445
|
+
const content = await this.callbacks.read("ai-metadata.json");
|
|
446
|
+
if (!content) return [];
|
|
447
|
+
return JSON.parse(content);
|
|
448
|
+
} catch (error) {
|
|
449
|
+
logger.error(`Failed to read AI metadata file: ${error}`);
|
|
450
|
+
return [];
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
private async saveAIMetadata(metadata: TaskAIMetadata[]): Promise<void> {
|
|
455
|
+
await this.callbacks.write(
|
|
456
|
+
"ai-metadata.json",
|
|
457
|
+
JSON.stringify(metadata, null, 2)
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
async getTaskAIMetadata(taskId: string): Promise<TaskAIMetadata | null> {
|
|
462
|
+
const metadata = await this.loadAIMetadata();
|
|
463
|
+
return metadata.find((meta) => meta.taskId === taskId) || null;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
async saveTaskAIMetadata(metadata: TaskAIMetadata): Promise<void> {
|
|
467
|
+
const allMetadata = await this.loadAIMetadata();
|
|
468
|
+
const existingIndex = allMetadata.findIndex(
|
|
469
|
+
(meta) => meta.taskId === metadata.taskId
|
|
470
|
+
);
|
|
471
|
+
|
|
472
|
+
if (existingIndex >= 0) {
|
|
473
|
+
allMetadata[existingIndex] = metadata;
|
|
474
|
+
} else {
|
|
475
|
+
allMetadata.push(metadata);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
await this.saveAIMetadata(allMetadata);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
async deleteTaskAIMetadata(taskId: string): Promise<void> {
|
|
482
|
+
const metadata = await this.loadAIMetadata();
|
|
483
|
+
const filtered = metadata.filter((meta) => meta.taskId !== taskId);
|
|
484
|
+
await this.saveAIMetadata(filtered);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
async saveTaskContent(taskId: string, content: string): Promise<string> {
|
|
488
|
+
this.validateTaskId(taskId);
|
|
489
|
+
if (typeof content !== "string") {
|
|
490
|
+
throw createStandardError(
|
|
491
|
+
TaskOMaticErrorCodes.INVALID_INPUT,
|
|
492
|
+
"Content must be a string",
|
|
493
|
+
{
|
|
494
|
+
context: "Content validation",
|
|
495
|
+
suggestions: ["Provide content as a string"],
|
|
496
|
+
}
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
const contentFileName = `tasks/${taskId}.md`;
|
|
501
|
+
|
|
502
|
+
try {
|
|
503
|
+
await this.callbacks.write(contentFileName, content);
|
|
504
|
+
} catch (error) {
|
|
505
|
+
throw formatStorageError(
|
|
506
|
+
"write task content",
|
|
507
|
+
error instanceof Error ? error : undefined
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
return contentFileName;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
async saveEnhancedTaskContent(
|
|
514
|
+
taskId: string,
|
|
515
|
+
content: string
|
|
516
|
+
): Promise<string> {
|
|
517
|
+
this.validateTaskId(taskId);
|
|
518
|
+
if (typeof content !== "string") {
|
|
519
|
+
throw createStandardError(
|
|
520
|
+
TaskOMaticErrorCodes.INVALID_INPUT,
|
|
521
|
+
"Content must be a string",
|
|
522
|
+
{
|
|
523
|
+
context: "Content validation",
|
|
524
|
+
suggestions: ["Provide content as a string"],
|
|
525
|
+
}
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
const contentFileName = `tasks/enhanced/${taskId}.md`;
|
|
530
|
+
|
|
531
|
+
try {
|
|
532
|
+
await this.callbacks.write(contentFileName, content);
|
|
533
|
+
} catch (error) {
|
|
534
|
+
throw formatStorageError(
|
|
535
|
+
"write enhanced task content",
|
|
536
|
+
error instanceof Error ? error : undefined
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
return contentFileName;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
async getTaskContent(taskId: string): Promise<string | null> {
|
|
543
|
+
this.validateTaskId(taskId);
|
|
544
|
+
|
|
545
|
+
const contentFileName = `tasks/${taskId}.md`;
|
|
546
|
+
|
|
547
|
+
try {
|
|
548
|
+
return await this.callbacks.read(contentFileName);
|
|
549
|
+
} catch (error) {
|
|
550
|
+
logger.error(`Failed to read task content for ${taskId}: ${error}`);
|
|
551
|
+
return null;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
async deleteTaskContent(taskId: string): Promise<void> {
|
|
556
|
+
this.validateTaskId(taskId);
|
|
557
|
+
const contentFileName = `tasks/${taskId}.md`;
|
|
558
|
+
await this.callbacks.delete(contentFileName);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
async saveContext7Documentation(
|
|
562
|
+
library: string,
|
|
563
|
+
query: string,
|
|
564
|
+
content: string
|
|
565
|
+
): Promise<string> {
|
|
566
|
+
const sanitizedLibrary = this.sanitizeForFilename(library);
|
|
567
|
+
const sanitizedQuery = this.sanitizeForFilename(query);
|
|
568
|
+
|
|
569
|
+
const filePath = `docs/${sanitizedLibrary}/${sanitizedQuery}.md`;
|
|
570
|
+
await this.callbacks.write(filePath, content);
|
|
571
|
+
|
|
572
|
+
return filePath;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
async getDocumentationFile(fileName: string): Promise<string | null> {
|
|
576
|
+
const filePath = `docs/${fileName}`;
|
|
577
|
+
try {
|
|
578
|
+
return await this.callbacks.read(filePath);
|
|
579
|
+
} catch (error) {
|
|
580
|
+
logger.error(`Failed to read documentation file ${fileName}: ${error}`);
|
|
581
|
+
return null;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
async listDocumentationFiles(): Promise<string[]> {
|
|
586
|
+
try {
|
|
587
|
+
const files = await this.callbacks.list("docs/");
|
|
588
|
+
// Filter for markdown/text files and remove "docs/" prefix for compatibility
|
|
589
|
+
return files
|
|
590
|
+
.filter((f) => f.endsWith(".md") || f.endsWith(".txt"))
|
|
591
|
+
.map((f) => (f.startsWith("docs/") ? f.substring(5) : f));
|
|
592
|
+
} catch (error) {
|
|
593
|
+
logger.error(`Failed to list documentation files: ${error}`);
|
|
594
|
+
return [];
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
async migrateTaskContent(): Promise<number> {
|
|
599
|
+
const data = await this.loadTasksData();
|
|
600
|
+
let migratedCount = 0;
|
|
601
|
+
|
|
602
|
+
const migrateTask = async (task: Task): Promise<Task> => {
|
|
603
|
+
let updatedTask = { ...task };
|
|
604
|
+
|
|
605
|
+
if ("content" in task && (task as any).content && !task.contentFile) {
|
|
606
|
+
const oldContent = (task as any).content;
|
|
607
|
+
|
|
608
|
+
if (oldContent.length > 200) {
|
|
609
|
+
const contentFile = `tasks/${task.id}.md`;
|
|
610
|
+
await this.callbacks.write(contentFile, oldContent);
|
|
611
|
+
|
|
612
|
+
updatedTask.contentFile = contentFile;
|
|
613
|
+
updatedTask.description =
|
|
614
|
+
task.description || oldContent.substring(0, 200) + "...";
|
|
615
|
+
} else {
|
|
616
|
+
updatedTask.description = task.description || oldContent;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
const { content: _, ...taskWithoutContent } = updatedTask as any;
|
|
620
|
+
updatedTask = taskWithoutContent;
|
|
621
|
+
migratedCount++;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
if (task.subtasks) {
|
|
625
|
+
updatedTask.subtasks = await Promise.all(
|
|
626
|
+
task.subtasks.map(migrateTask)
|
|
627
|
+
);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
return updatedTask;
|
|
631
|
+
};
|
|
632
|
+
|
|
633
|
+
data.tasks = await Promise.all(data.tasks.map(migrateTask));
|
|
634
|
+
|
|
635
|
+
if (migratedCount > 0) {
|
|
636
|
+
await this.saveTasksData(data);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
return migratedCount;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
async cleanupOrphanedContent(): Promise<number> {
|
|
643
|
+
const data = await this.loadTasksData();
|
|
644
|
+
const allTasks = this.flattenTasks(data.tasks);
|
|
645
|
+
const validContentFiles = new Set(
|
|
646
|
+
allTasks
|
|
647
|
+
.filter((task) => task.contentFile)
|
|
648
|
+
.map((task) => task.contentFile!)
|
|
649
|
+
);
|
|
650
|
+
|
|
651
|
+
let cleanedCount = 0;
|
|
652
|
+
|
|
653
|
+
try {
|
|
654
|
+
const files = await this.callbacks.list("tasks/");
|
|
655
|
+
|
|
656
|
+
for (const file of files) {
|
|
657
|
+
// file is a full key like "tasks/123.md"
|
|
658
|
+
// validContentFiles contains relative paths like "tasks/123.md"
|
|
659
|
+
// So we can check directly
|
|
660
|
+
|
|
661
|
+
if (file.endsWith(".md")) {
|
|
662
|
+
if (!validContentFiles.has(file)) {
|
|
663
|
+
try {
|
|
664
|
+
await this.callbacks.delete(file);
|
|
665
|
+
cleanedCount++;
|
|
666
|
+
logger.info(`Cleaned up orphaned content file: ${file}`);
|
|
667
|
+
} catch (error) {
|
|
668
|
+
logger.error(`Failed to delete orphaned file ${file}: ${error}`);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
} catch (error) {
|
|
674
|
+
logger.error(`Failed to cleanup orphaned content: ${error}`);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
return cleanedCount;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
async validateStorageIntegrity(): Promise<{
|
|
681
|
+
isValid: boolean;
|
|
682
|
+
issues: string[];
|
|
683
|
+
}> {
|
|
684
|
+
const issues: string[] = [];
|
|
685
|
+
|
|
686
|
+
try {
|
|
687
|
+
const data = await this.loadTasksData();
|
|
688
|
+
if (!Array.isArray(data.tasks)) {
|
|
689
|
+
issues.push("Tasks data is not an array");
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
if (typeof data.nextId !== "number" || data.nextId < 1) {
|
|
693
|
+
issues.push("Invalid nextId in tasks data");
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
const allTasks = this.flattenTasks(data.tasks);
|
|
697
|
+
const taskIds = allTasks.map((task) => task.id);
|
|
698
|
+
const duplicateIds = taskIds.filter(
|
|
699
|
+
(id, index) => taskIds.indexOf(id) !== index
|
|
700
|
+
);
|
|
701
|
+
if (duplicateIds.length > 0) {
|
|
702
|
+
issues.push(`Duplicate task IDs found: ${duplicateIds.join(", ")}`);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
for (const task of allTasks) {
|
|
706
|
+
if (task.dependencies) {
|
|
707
|
+
for (const depId of task.dependencies) {
|
|
708
|
+
if (!taskIds.includes(depId)) {
|
|
709
|
+
issues.push(`Task ${task.id} has invalid dependency: ${depId}`);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
} catch (error) {
|
|
715
|
+
issues.push(
|
|
716
|
+
`Storage validation error: ${
|
|
717
|
+
error instanceof Error ? error.message : "Unknown error"
|
|
718
|
+
}`
|
|
719
|
+
);
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
return {
|
|
723
|
+
isValid: issues.length === 0,
|
|
724
|
+
issues,
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
async savePlan(taskId: string, plan: string): Promise<void> {
|
|
729
|
+
this.validateTaskId(taskId);
|
|
730
|
+
|
|
731
|
+
const planFile = `plans/${taskId}.json`;
|
|
732
|
+
|
|
733
|
+
try {
|
|
734
|
+
const planData = {
|
|
735
|
+
taskId,
|
|
736
|
+
plan,
|
|
737
|
+
createdAt: Date.now(),
|
|
738
|
+
updatedAt: Date.now(),
|
|
739
|
+
};
|
|
740
|
+
|
|
741
|
+
await this.callbacks.write(planFile, JSON.stringify(planData, null, 2));
|
|
742
|
+
} catch (error) {
|
|
743
|
+
throw formatStorageError(
|
|
744
|
+
`write plan for task ${taskId}`,
|
|
745
|
+
error instanceof Error ? error : undefined
|
|
746
|
+
);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
async getPlan(
|
|
751
|
+
taskId: string
|
|
752
|
+
): Promise<{ plan: string; createdAt: number; updatedAt: number } | null> {
|
|
753
|
+
this.validateTaskId(taskId);
|
|
754
|
+
|
|
755
|
+
const planFile = `plans/${taskId}.json`;
|
|
756
|
+
|
|
757
|
+
try {
|
|
758
|
+
const content = await this.callbacks.read(planFile);
|
|
759
|
+
if (!content) return null;
|
|
760
|
+
return JSON.parse(content);
|
|
761
|
+
} catch (error) {
|
|
762
|
+
throw formatStorageError(
|
|
763
|
+
`read plan for task ${taskId}`,
|
|
764
|
+
error instanceof Error ? error : undefined
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
async listPlans(): Promise<
|
|
770
|
+
Array<{
|
|
771
|
+
taskId: string;
|
|
772
|
+
plan: string;
|
|
773
|
+
createdAt: number;
|
|
774
|
+
updatedAt: number;
|
|
775
|
+
}>
|
|
776
|
+
> {
|
|
777
|
+
try {
|
|
778
|
+
const plans: Array<{
|
|
779
|
+
taskId: string;
|
|
780
|
+
plan: string;
|
|
781
|
+
createdAt: number;
|
|
782
|
+
updatedAt: number;
|
|
783
|
+
}> = [];
|
|
784
|
+
|
|
785
|
+
const files = await this.callbacks.list("plans/");
|
|
786
|
+
|
|
787
|
+
for (const file of files) {
|
|
788
|
+
if (file.endsWith(".json")) {
|
|
789
|
+
// file is "plans/123.json"
|
|
790
|
+
const taskId = file.replace("plans/", "").replace(".json", "");
|
|
791
|
+
const planData = await this.getPlan(taskId);
|
|
792
|
+
if (planData) {
|
|
793
|
+
plans.push({
|
|
794
|
+
taskId,
|
|
795
|
+
plan: planData.plan,
|
|
796
|
+
createdAt: planData.createdAt,
|
|
797
|
+
updatedAt: planData.updatedAt,
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
return plans.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
804
|
+
} catch (error) {
|
|
805
|
+
throw formatStorageError(
|
|
806
|
+
"list plans",
|
|
807
|
+
error instanceof Error ? error : undefined
|
|
808
|
+
);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
async deletePlan(taskId: string): Promise<boolean> {
|
|
813
|
+
this.validateTaskId(taskId);
|
|
814
|
+
const planFile = `plans/${taskId}.json`;
|
|
815
|
+
try {
|
|
816
|
+
await this.callbacks.delete(planFile);
|
|
817
|
+
return true;
|
|
818
|
+
} catch (error) {
|
|
819
|
+
return false;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
async saveTaskDocumentation(
|
|
824
|
+
taskId: string,
|
|
825
|
+
documentation: string
|
|
826
|
+
): Promise<string> {
|
|
827
|
+
this.validateTaskId(taskId);
|
|
828
|
+
if (typeof documentation !== "string") {
|
|
829
|
+
throw createStandardError(
|
|
830
|
+
TaskOMaticErrorCodes.INVALID_INPUT,
|
|
831
|
+
"Documentation must be a string",
|
|
832
|
+
{
|
|
833
|
+
context: "Documentation validation",
|
|
834
|
+
suggestions: ["Provide documentation as a string"],
|
|
835
|
+
}
|
|
836
|
+
);
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
const documentationFileName = `docs/tasks/${taskId}.md`;
|
|
840
|
+
|
|
841
|
+
try {
|
|
842
|
+
await this.callbacks.write(documentationFileName, documentation);
|
|
843
|
+
} catch (error) {
|
|
844
|
+
throw formatStorageError(
|
|
845
|
+
"write task documentation",
|
|
846
|
+
error instanceof Error ? error : undefined
|
|
847
|
+
);
|
|
848
|
+
}
|
|
849
|
+
return documentationFileName;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
async getTaskDocumentation(taskId: string): Promise<string | null> {
|
|
853
|
+
this.validateTaskId(taskId);
|
|
854
|
+
|
|
855
|
+
const documentationFileName = `docs/tasks/${taskId}.md`;
|
|
856
|
+
|
|
857
|
+
try {
|
|
858
|
+
return await this.callbacks.read(documentationFileName);
|
|
859
|
+
} catch (error) {
|
|
860
|
+
logger.error(`Failed to read task documentation for ${taskId}: ${error}`);
|
|
861
|
+
return null;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
}
|