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,278 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import {
|
|
3
|
+
TaskOMaticError,
|
|
4
|
+
TaskOMaticErrorCodes,
|
|
5
|
+
} from "../utils/task-o-matic-error";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Zod schema for AI Provider
|
|
9
|
+
*/
|
|
10
|
+
import { AI_PROVIDERS_LIST } from "../types";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Zod schema for AI Provider
|
|
14
|
+
*/
|
|
15
|
+
export const AIProviderSchema = z.enum(AI_PROVIDERS_LIST, {
|
|
16
|
+
errorMap: () => ({
|
|
17
|
+
message: `Provider must be one of: ${AI_PROVIDERS_LIST.join(", ")}`,
|
|
18
|
+
}),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Zod schema for AI Configuration
|
|
23
|
+
*/
|
|
24
|
+
export const AIConfigSchema = z.object({
|
|
25
|
+
provider: AIProviderSchema,
|
|
26
|
+
model: z.string().min(1, "Model name cannot be empty"),
|
|
27
|
+
apiKey: z.string().optional(),
|
|
28
|
+
baseURL: z
|
|
29
|
+
.string()
|
|
30
|
+
.url("Base URL must be a valid URL")
|
|
31
|
+
.optional()
|
|
32
|
+
.or(z.literal("")),
|
|
33
|
+
maxTokens: z
|
|
34
|
+
.number()
|
|
35
|
+
.int("Max tokens must be an integer")
|
|
36
|
+
.positive("Max tokens must be positive")
|
|
37
|
+
.max(1000000, "Max tokens cannot exceed 1,000,000")
|
|
38
|
+
.optional(),
|
|
39
|
+
temperature: z
|
|
40
|
+
.number()
|
|
41
|
+
.min(0, "Temperature must be between 0 and 2")
|
|
42
|
+
.max(2, "Temperature must be between 0 and 2")
|
|
43
|
+
.optional(),
|
|
44
|
+
context7Enabled: z.boolean().optional(),
|
|
45
|
+
reasoning: z
|
|
46
|
+
.object({
|
|
47
|
+
maxTokens: z
|
|
48
|
+
.number()
|
|
49
|
+
.int("Reasoning max tokens must be an integer")
|
|
50
|
+
.positive("Reasoning max tokens must be positive")
|
|
51
|
+
.max(100000, "Reasoning max tokens cannot exceed 100,000")
|
|
52
|
+
.optional(),
|
|
53
|
+
})
|
|
54
|
+
.optional(),
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Zod schema for full Config object
|
|
59
|
+
*/
|
|
60
|
+
export const ConfigSchema = z.object({
|
|
61
|
+
ai: AIConfigSchema,
|
|
62
|
+
workingDirectory: z.string().optional(),
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Validation result type
|
|
67
|
+
*/
|
|
68
|
+
export interface ValidationResult<T> {
|
|
69
|
+
success: boolean;
|
|
70
|
+
data?: T;
|
|
71
|
+
errors?: Array<{
|
|
72
|
+
path: string;
|
|
73
|
+
message: string;
|
|
74
|
+
}>;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Validate AI configuration
|
|
79
|
+
*
|
|
80
|
+
* @param config - AI configuration to validate
|
|
81
|
+
* @returns Validated and typed configuration
|
|
82
|
+
* @throws {TaskOMaticError} If validation fails
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```typescript
|
|
86
|
+
* const aiConfig = {
|
|
87
|
+
* provider: "openai",
|
|
88
|
+
* model: "gpt-4",
|
|
89
|
+
* temperature: 0.7,
|
|
90
|
+
* maxTokens: 4000
|
|
91
|
+
* };
|
|
92
|
+
*
|
|
93
|
+
* const validated = validateAIConfig(aiConfig);
|
|
94
|
+
* // validated is now type-safe and guaranteed valid
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
export function validateAIConfig(
|
|
98
|
+
config: unknown
|
|
99
|
+
): z.infer<typeof AIConfigSchema> {
|
|
100
|
+
try {
|
|
101
|
+
return AIConfigSchema.parse(config);
|
|
102
|
+
} catch (error) {
|
|
103
|
+
if (error instanceof z.ZodError) {
|
|
104
|
+
const errors = error.errors.map((err) => ({
|
|
105
|
+
path: err.path.join("."),
|
|
106
|
+
message: err.message,
|
|
107
|
+
}));
|
|
108
|
+
|
|
109
|
+
throw new TaskOMaticError("AI configuration validation failed", {
|
|
110
|
+
code: TaskOMaticErrorCodes.CONFIGURATION_ERROR,
|
|
111
|
+
context: JSON.stringify(config, null, 2),
|
|
112
|
+
suggestions: [
|
|
113
|
+
"Check that all required fields are present",
|
|
114
|
+
"Verify provider is one of: openai, anthropic, openrouter, custom",
|
|
115
|
+
"Ensure model name is not empty",
|
|
116
|
+
"Check temperature is between 0 and 2",
|
|
117
|
+
"Verify maxTokens is a positive integer",
|
|
118
|
+
...errors.map((e) => `Fix ${e.path}: ${e.message}`),
|
|
119
|
+
],
|
|
120
|
+
metadata: { errors },
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Validate full configuration object
|
|
129
|
+
*
|
|
130
|
+
* @param config - Configuration object to validate
|
|
131
|
+
* @returns Validated and typed configuration
|
|
132
|
+
* @throws {TaskOMaticError} If validation fails
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* ```typescript
|
|
136
|
+
* const config = {
|
|
137
|
+
* ai: {
|
|
138
|
+
* provider: "anthropic",
|
|
139
|
+
* model: "claude-sonnet-4.5",
|
|
140
|
+
* temperature: 0.5
|
|
141
|
+
* },
|
|
142
|
+
* workingDirectory: "/path/to/project"
|
|
143
|
+
* };
|
|
144
|
+
*
|
|
145
|
+
* const validated = validateConfig(config);
|
|
146
|
+
* ```
|
|
147
|
+
*/
|
|
148
|
+
export function validateConfig(config: unknown): z.infer<typeof ConfigSchema> {
|
|
149
|
+
try {
|
|
150
|
+
return ConfigSchema.parse(config);
|
|
151
|
+
} catch (error) {
|
|
152
|
+
if (error instanceof z.ZodError) {
|
|
153
|
+
const errors = error.errors.map((err) => ({
|
|
154
|
+
path: err.path.join("."),
|
|
155
|
+
message: err.message,
|
|
156
|
+
}));
|
|
157
|
+
|
|
158
|
+
throw new TaskOMaticError("Configuration validation failed", {
|
|
159
|
+
code: TaskOMaticErrorCodes.CONFIGURATION_ERROR,
|
|
160
|
+
context: JSON.stringify(config, null, 2),
|
|
161
|
+
suggestions: [
|
|
162
|
+
"Check that all required fields are present",
|
|
163
|
+
"Verify the configuration structure is correct",
|
|
164
|
+
...errors.map((e) => `Fix ${e.path}: ${e.message}`),
|
|
165
|
+
],
|
|
166
|
+
metadata: { errors },
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
throw error;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Safe validation that returns a result object instead of throwing
|
|
175
|
+
*
|
|
176
|
+
* @param config - Configuration to validate
|
|
177
|
+
* @returns Validation result with success flag and data or errors
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* ```typescript
|
|
181
|
+
* const result = safeValidateConfig(userInput);
|
|
182
|
+
* if (result.success) {
|
|
183
|
+
* console.log("Valid config:", result.data);
|
|
184
|
+
* } else {
|
|
185
|
+
* console.error("Validation errors:", result.errors);
|
|
186
|
+
* }
|
|
187
|
+
* ```
|
|
188
|
+
*/
|
|
189
|
+
export function safeValidateConfig(
|
|
190
|
+
config: unknown
|
|
191
|
+
): ValidationResult<z.infer<typeof ConfigSchema>> {
|
|
192
|
+
const result = ConfigSchema.safeParse(config);
|
|
193
|
+
|
|
194
|
+
if (result.success) {
|
|
195
|
+
return {
|
|
196
|
+
success: true,
|
|
197
|
+
data: result.data,
|
|
198
|
+
};
|
|
199
|
+
} else {
|
|
200
|
+
return {
|
|
201
|
+
success: false,
|
|
202
|
+
errors: result.error.errors.map((err) => ({
|
|
203
|
+
path: err.path.join("."),
|
|
204
|
+
message: err.message,
|
|
205
|
+
})),
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Safe AI config validation
|
|
212
|
+
*
|
|
213
|
+
* @param config - AI configuration to validate
|
|
214
|
+
* @returns Validation result with success flag and data or errors
|
|
215
|
+
*/
|
|
216
|
+
export function safeValidateAIConfig(
|
|
217
|
+
config: unknown
|
|
218
|
+
): ValidationResult<z.infer<typeof AIConfigSchema>> {
|
|
219
|
+
const result = AIConfigSchema.safeParse(config);
|
|
220
|
+
|
|
221
|
+
if (result.success) {
|
|
222
|
+
return {
|
|
223
|
+
success: true,
|
|
224
|
+
data: result.data,
|
|
225
|
+
};
|
|
226
|
+
} else {
|
|
227
|
+
return {
|
|
228
|
+
success: false,
|
|
229
|
+
errors: result.error.errors.map((err) => ({
|
|
230
|
+
path: err.path.join("."),
|
|
231
|
+
message: err.message,
|
|
232
|
+
})),
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Validate partial AI config (for updates)
|
|
239
|
+
*
|
|
240
|
+
* @param config - Partial AI configuration to validate
|
|
241
|
+
* @returns Validated partial configuration
|
|
242
|
+
* @throws {TaskOMaticError} If validation fails
|
|
243
|
+
*
|
|
244
|
+
* @example
|
|
245
|
+
* ```typescript
|
|
246
|
+
* // Only validating fields that are present
|
|
247
|
+
* const updates = {
|
|
248
|
+
* temperature: 0.8
|
|
249
|
+
* };
|
|
250
|
+
*
|
|
251
|
+
* const validated = validatePartialAIConfig(updates);
|
|
252
|
+
* ```
|
|
253
|
+
*/
|
|
254
|
+
export function validatePartialAIConfig(
|
|
255
|
+
config: unknown
|
|
256
|
+
): Partial<z.infer<typeof AIConfigSchema>> {
|
|
257
|
+
try {
|
|
258
|
+
return AIConfigSchema.partial().parse(config);
|
|
259
|
+
} catch (error) {
|
|
260
|
+
if (error instanceof z.ZodError) {
|
|
261
|
+
const errors = error.errors.map((err) => ({
|
|
262
|
+
path: err.path.join("."),
|
|
263
|
+
message: err.message,
|
|
264
|
+
}));
|
|
265
|
+
|
|
266
|
+
throw new TaskOMaticError("AI configuration update validation failed", {
|
|
267
|
+
code: TaskOMaticErrorCodes.CONFIGURATION_ERROR,
|
|
268
|
+
context: JSON.stringify(config, null, 2),
|
|
269
|
+
suggestions: [
|
|
270
|
+
"Check that field values are valid",
|
|
271
|
+
...errors.map((e) => `Fix ${e.path}: ${e.message}`),
|
|
272
|
+
],
|
|
273
|
+
metadata: { errors },
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
throw error;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
import { join, resolve } from "path";
|
|
2
|
+
import { cwd } from "process";
|
|
3
|
+
import { AIConfig, EnvAIConfig, ProviderDefaults, AIProvider } from "../types";
|
|
4
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
5
|
+
import { config as dotenvConfig } from "dotenv";
|
|
6
|
+
import { validateConfig, validatePartialAIConfig } from "./config-validation";
|
|
7
|
+
import {
|
|
8
|
+
createStandardError,
|
|
9
|
+
TaskOMaticErrorCodes,
|
|
10
|
+
} from "../utils/task-o-matic-error";
|
|
11
|
+
import { logger } from "./logger";
|
|
12
|
+
|
|
13
|
+
export interface Config {
|
|
14
|
+
ai: AIConfig;
|
|
15
|
+
workingDirectory?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ConfigCallbacks {
|
|
19
|
+
read: (key: string) => Promise<string | null>;
|
|
20
|
+
write: (key: string, value: string) => Promise<void>;
|
|
21
|
+
getEnv: (key: string) => string | undefined;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Provider-specific sensible defaults for 2025
|
|
25
|
+
// Externalized to JSON for easy updates
|
|
26
|
+
import PROVIDER_DEFAULTS from "./provider-defaults.json";
|
|
27
|
+
export { PROVIDER_DEFAULTS as providerDefaults };
|
|
28
|
+
|
|
29
|
+
function getApiKeyFromEnv(
|
|
30
|
+
provider: AIProvider,
|
|
31
|
+
getEnv: (key: string) => string | undefined
|
|
32
|
+
): string | undefined {
|
|
33
|
+
switch (provider) {
|
|
34
|
+
case "openrouter":
|
|
35
|
+
return getEnv("OPENROUTER_API_KEY");
|
|
36
|
+
case "anthropic":
|
|
37
|
+
return getEnv("ANTHROPIC_API_KEY");
|
|
38
|
+
case "openai":
|
|
39
|
+
return getEnv("OPENAI_API_KEY");
|
|
40
|
+
case "custom":
|
|
41
|
+
return getEnv("CUSTOM_API_KEY");
|
|
42
|
+
case "zai":
|
|
43
|
+
return getEnv("ZAI_API_KEY");
|
|
44
|
+
default:
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function createDefaultConfigCallbacks(
|
|
50
|
+
workingDir: string = cwd()
|
|
51
|
+
): ConfigCallbacks {
|
|
52
|
+
// Ensure dotenv is loaded for the default callbacks
|
|
53
|
+
const envPath = join(workingDir, ".env");
|
|
54
|
+
if (existsSync(envPath)) {
|
|
55
|
+
dotenvConfig({ path: envPath });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
read: async (key: string) => {
|
|
60
|
+
// If key is relative, assume it's in .task-o-matic dir relative to workingDir
|
|
61
|
+
// But ConfigManager usually passes "config.json"
|
|
62
|
+
// We need to resolve it.
|
|
63
|
+
// For default callbacks, we replicate the original behavior:
|
|
64
|
+
// .task-o-matic/config.json in workingDir
|
|
65
|
+
|
|
66
|
+
const configPath = join(workingDir, ".task-o-matic", key);
|
|
67
|
+
if (existsSync(configPath)) {
|
|
68
|
+
return readFileSync(configPath, "utf-8");
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
},
|
|
72
|
+
write: async (key: string, value: string) => {
|
|
73
|
+
const taskOMaticDir = join(workingDir, ".task-o-matic");
|
|
74
|
+
if (!existsSync(taskOMaticDir)) {
|
|
75
|
+
mkdirSync(taskOMaticDir, { recursive: true });
|
|
76
|
+
}
|
|
77
|
+
const configPath = join(taskOMaticDir, key);
|
|
78
|
+
writeFileSync(configPath, value, "utf-8");
|
|
79
|
+
},
|
|
80
|
+
getEnv: (key: string) => process.env[key],
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export class ConfigManager {
|
|
85
|
+
private config: Config | null = null;
|
|
86
|
+
private customWorkingDir: string | null = null;
|
|
87
|
+
private callbacks: ConfigCallbacks;
|
|
88
|
+
|
|
89
|
+
constructor(callbacks?: ConfigCallbacks, workingDirectory?: string) {
|
|
90
|
+
if (workingDirectory) {
|
|
91
|
+
this.customWorkingDir = resolve(workingDirectory);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// If callbacks not provided, create defaults using the working directory
|
|
95
|
+
this.callbacks =
|
|
96
|
+
callbacks || createDefaultConfigCallbacks(this.getWorkingDirectory());
|
|
97
|
+
|
|
98
|
+
// We cannot await in constructor.
|
|
99
|
+
// Consumers MUST call load() or ensure it's loaded before accessing config.
|
|
100
|
+
// For backward compatibility in CLI (where sync access was common),
|
|
101
|
+
// we might need a way to force sync load if using default callbacks?
|
|
102
|
+
// But we want to support async callbacks.
|
|
103
|
+
// So we enforce async initialization pattern.
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
setWorkingDirectory(dir: string): void {
|
|
107
|
+
this.customWorkingDir = resolve(dir);
|
|
108
|
+
// Re-create default callbacks to point to new dir
|
|
109
|
+
this.callbacks = createDefaultConfigCallbacks(this.getWorkingDirectory());
|
|
110
|
+
// Invalidate config so next access requires reload
|
|
111
|
+
this.config = null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
setCallbacks(callbacks: ConfigCallbacks): void {
|
|
115
|
+
this.callbacks = callbacks;
|
|
116
|
+
this.config = null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
getWorkingDirectory(): string {
|
|
120
|
+
return this.customWorkingDir || cwd();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
getTaskOMaticDir(): string {
|
|
124
|
+
return join(this.getWorkingDirectory(), ".task-o-matic");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
private loadEnvConfig(): Partial<AIConfig> {
|
|
128
|
+
const providerStr = this.callbacks.getEnv("AI_PROVIDER")?.toLowerCase();
|
|
129
|
+
const provider = providerStr ? (providerStr as AIProvider) : undefined;
|
|
130
|
+
|
|
131
|
+
// Check if provider is valid if set
|
|
132
|
+
// (This validation is loose here, strict validation happens in validateConfig)
|
|
133
|
+
|
|
134
|
+
const maxTokensStr = this.callbacks.getEnv("AI_MAX_TOKENS");
|
|
135
|
+
const tempStr = this.callbacks.getEnv("AI_TEMPERATURE");
|
|
136
|
+
const modelStr = this.callbacks.getEnv("AI_MODEL");
|
|
137
|
+
|
|
138
|
+
const config: Partial<AIConfig> = {};
|
|
139
|
+
if (provider) config.provider = provider;
|
|
140
|
+
if (modelStr) config.model = modelStr;
|
|
141
|
+
if (maxTokensStr) config.maxTokens = parseInt(maxTokensStr);
|
|
142
|
+
if (tempStr) config.temperature = parseFloat(tempStr);
|
|
143
|
+
|
|
144
|
+
// API Key logic needs to know the provider.
|
|
145
|
+
// If provider is not in env, we should check the resolved provider...
|
|
146
|
+
// But here we only return partial env overrides.
|
|
147
|
+
// The API key fetching logic in getApiKeyFromEnv depends on the provider.
|
|
148
|
+
// If the provider comes from file, we can't know it here easily if we are just returning overrides.
|
|
149
|
+
// However, getApiKeyFromEnv is a helper.
|
|
150
|
+
|
|
151
|
+
// We can fetch ALL potential API keys? No, that's messy.
|
|
152
|
+
// Or we just fetch the API key if AI_PROVIDER is set?
|
|
153
|
+
// If AI_PROVIDER is NOT set, we don't know which env var to read for the key yet
|
|
154
|
+
// (unless we assume the default or the file one).
|
|
155
|
+
|
|
156
|
+
// If we return undefined for apiKey here, it might overwrite the file one?
|
|
157
|
+
// No, undefined properties don't overwrite if we use spread correctly?
|
|
158
|
+
// { ...a, ...b } where b has undefined properties...
|
|
159
|
+
// In JS/TS spread { ...{a:1}, ...{a: undefined} } results in {a: undefined}.
|
|
160
|
+
// So we must NOT include undefined keys in the returned object.
|
|
161
|
+
|
|
162
|
+
if (provider) {
|
|
163
|
+
const key = getApiKeyFromEnv(provider, this.callbacks.getEnv);
|
|
164
|
+
if (key) config.apiKey = key;
|
|
165
|
+
} else {
|
|
166
|
+
// If provider not in env, we might miss the API key from env if the user
|
|
167
|
+
// intended to use file-provider + env-key.
|
|
168
|
+
// This is a bit tricky with the current structure.
|
|
169
|
+
// But let's stick to the fix for provider override first.
|
|
170
|
+
// We will deal with API key resolution merging later or assume user sets both in env.
|
|
171
|
+
|
|
172
|
+
// Actually, we can check all possible keys?
|
|
173
|
+
// Or better: The merge logic in load() is complex.
|
|
174
|
+
|
|
175
|
+
// Let's defer API key resolution to after we know the final provider?
|
|
176
|
+
// But load() does: ai: { ...defaultConfig.ai, ...fileConfig.ai, ...envConfig }
|
|
177
|
+
|
|
178
|
+
// If I change loadEnvConfig to NOT return apiKey if provider is missing,
|
|
179
|
+
// then apiKey will come from fileConfig or defaultConfig.
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return config;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async load(): Promise<Config> {
|
|
186
|
+
const envConfig = this.loadEnvConfig();
|
|
187
|
+
|
|
188
|
+
// Default provider is openrouter
|
|
189
|
+
const defaultProvider = envConfig.provider || "openrouter";
|
|
190
|
+
// We can try to get API key for default provider if not in envConfig
|
|
191
|
+
let defaultApiKey = envConfig.apiKey;
|
|
192
|
+
if (!defaultApiKey) {
|
|
193
|
+
defaultApiKey = getApiKeyFromEnv(defaultProvider, this.callbacks.getEnv);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const defaultConfig: Config = {
|
|
197
|
+
ai: {
|
|
198
|
+
provider: "openrouter",
|
|
199
|
+
model: "z-ai/glm-4.6",
|
|
200
|
+
maxTokens: 32768,
|
|
201
|
+
temperature: 0.5,
|
|
202
|
+
apiKey: defaultApiKey, // This might be overwritten by file or envConfig later
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
try {
|
|
207
|
+
// Use relative path "config.json" - callbacks handle resolution
|
|
208
|
+
const configData = await this.callbacks.read("config.json");
|
|
209
|
+
|
|
210
|
+
if (configData) {
|
|
211
|
+
const fileConfig = JSON.parse(configData);
|
|
212
|
+
|
|
213
|
+
// We need to resolve the final provider to get the correct API key from env
|
|
214
|
+
// if it wasn't provided in envConfig.
|
|
215
|
+
const finalProvider = envConfig.provider || fileConfig.ai?.provider || defaultConfig.ai.provider;
|
|
216
|
+
|
|
217
|
+
// If envConfig didn't provide a key, check if we can get it based on finalProvider
|
|
218
|
+
let finalApiKey = envConfig.apiKey;
|
|
219
|
+
if (!finalApiKey) {
|
|
220
|
+
finalApiKey = getApiKeyFromEnv(finalProvider, this.callbacks.getEnv);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// We construct a refined envConfig that includes the correct API key
|
|
224
|
+
const refinedEnvConfig = { ...envConfig };
|
|
225
|
+
if (finalApiKey) {
|
|
226
|
+
refinedEnvConfig.apiKey = finalApiKey;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const mergedConfig = {
|
|
230
|
+
...defaultConfig,
|
|
231
|
+
...fileConfig,
|
|
232
|
+
ai: { ...defaultConfig.ai, ...fileConfig.ai, ...refinedEnvConfig },
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
// Validate the merged configuration
|
|
236
|
+
const validatedConfig = validateConfig(mergedConfig);
|
|
237
|
+
this.config = validatedConfig;
|
|
238
|
+
|
|
239
|
+
if (this.config && this.config.workingDirectory) {
|
|
240
|
+
this.customWorkingDir = this.config.workingDirectory;
|
|
241
|
+
}
|
|
242
|
+
} else {
|
|
243
|
+
// Apply envConfig to defaults
|
|
244
|
+
const mergedConfig = {
|
|
245
|
+
...defaultConfig,
|
|
246
|
+
ai: { ...defaultConfig.ai, ...envConfig }
|
|
247
|
+
};
|
|
248
|
+
this.config = validateConfig(mergedConfig);
|
|
249
|
+
}
|
|
250
|
+
} catch (error) {
|
|
251
|
+
logger.warn(`Failed to read or validate config, using defaults: ${error}`);
|
|
252
|
+
// Even defaults should be validated
|
|
253
|
+
this.config = validateConfig(defaultConfig);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return this.config!;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async save(): Promise<void> {
|
|
260
|
+
if (!this.config) {
|
|
261
|
+
throw createStandardError(
|
|
262
|
+
TaskOMaticErrorCodes.CONFIGURATION_ERROR,
|
|
263
|
+
"Config not loaded, cannot save.",
|
|
264
|
+
{
|
|
265
|
+
suggestions: ["Call await configManager.load() before saving"],
|
|
266
|
+
}
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
try {
|
|
270
|
+
await this.callbacks.write(
|
|
271
|
+
"config.json",
|
|
272
|
+
JSON.stringify(this.config, null, 2)
|
|
273
|
+
);
|
|
274
|
+
} catch (error) {
|
|
275
|
+
logger.error(`Failed to save config: ${error}`);
|
|
276
|
+
throw error;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
getConfig(): Config {
|
|
281
|
+
if (!this.config) {
|
|
282
|
+
// If we are here, it means load() wasn't called or hasn't finished.
|
|
283
|
+
// Since we can't be async here, we must throw or return defaults.
|
|
284
|
+
// Returning defaults might hide issues.
|
|
285
|
+
// Throwing forces users to await load().
|
|
286
|
+
throw createStandardError(
|
|
287
|
+
TaskOMaticErrorCodes.CONFIGURATION_ERROR,
|
|
288
|
+
"Config not loaded. Call await configManager.load() first.",
|
|
289
|
+
{
|
|
290
|
+
context: "Configuration must be loaded before access",
|
|
291
|
+
suggestions: [
|
|
292
|
+
"Call await configManager.load() first",
|
|
293
|
+
"Check initialization order",
|
|
294
|
+
],
|
|
295
|
+
}
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
return this.config;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
getAIConfig(): AIConfig {
|
|
302
|
+
return this.getConfig().ai;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
async setAIConfig(aiConfig: Partial<AIConfig>): Promise<void> {
|
|
306
|
+
if (!this.config) {
|
|
307
|
+
await this.load();
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Validate the partial config before merging
|
|
311
|
+
const validatedPartial = validatePartialAIConfig(aiConfig);
|
|
312
|
+
|
|
313
|
+
// Merge and validate the full config
|
|
314
|
+
const mergedAIConfig = { ...this.config!.ai, ...validatedPartial };
|
|
315
|
+
const validatedConfig = validateConfig({
|
|
316
|
+
...this.config!,
|
|
317
|
+
ai: mergedAIConfig,
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
this.config = validatedConfig;
|
|
321
|
+
await this.save();
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
setConfig(config: Config): void {
|
|
325
|
+
this.config = config;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Helper for legacy code that might need path (deprecated for direct use)
|
|
329
|
+
getConfigFilePath(): string {
|
|
330
|
+
return join(this.getTaskOMaticDir(), "config.json");
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Validate configuration independently of load().
|
|
335
|
+
* Can be used to validate config before applying changes.
|
|
336
|
+
*/
|
|
337
|
+
validate(configToValidate?: Partial<Config>): {
|
|
338
|
+
valid: boolean;
|
|
339
|
+
errors: string[];
|
|
340
|
+
} {
|
|
341
|
+
const errors: string[] = [];
|
|
342
|
+
|
|
343
|
+
const config = configToValidate || this.config;
|
|
344
|
+
if (!config) {
|
|
345
|
+
return {
|
|
346
|
+
valid: false,
|
|
347
|
+
errors: [
|
|
348
|
+
"No configuration to validate. Either provide a config or call load() first.",
|
|
349
|
+
],
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Validate AI config
|
|
354
|
+
if (config.ai) {
|
|
355
|
+
const { provider, model, apiKey, maxTokens, temperature } = config.ai;
|
|
356
|
+
|
|
357
|
+
// Validate provider
|
|
358
|
+
if (
|
|
359
|
+
provider &&
|
|
360
|
+
![
|
|
361
|
+
"openrouter",
|
|
362
|
+
"anthropic",
|
|
363
|
+
"openai",
|
|
364
|
+
"custom",
|
|
365
|
+
"gemini",
|
|
366
|
+
"zai",
|
|
367
|
+
].includes(provider)
|
|
368
|
+
) {
|
|
369
|
+
errors.push(
|
|
370
|
+
`Invalid provider: ${provider}. Must be one of: openrouter, anthropic, openai, custom, gemini, zai`
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Validate model
|
|
375
|
+
if (model !== undefined && typeof model !== "string") {
|
|
376
|
+
errors.push("Model must be a string");
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Validate maxTokens
|
|
380
|
+
if (maxTokens !== undefined) {
|
|
381
|
+
if (
|
|
382
|
+
typeof maxTokens !== "number" ||
|
|
383
|
+
maxTokens < 1 ||
|
|
384
|
+
maxTokens > 200000
|
|
385
|
+
) {
|
|
386
|
+
errors.push("maxTokens must be a number between 1 and 200000");
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Validate temperature
|
|
391
|
+
if (temperature !== undefined) {
|
|
392
|
+
if (
|
|
393
|
+
typeof temperature !== "number" ||
|
|
394
|
+
temperature < 0 ||
|
|
395
|
+
temperature > 2
|
|
396
|
+
) {
|
|
397
|
+
errors.push("temperature must be a number between 0 and 2");
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Warn about missing API key (not an error, just a warning)
|
|
402
|
+
if (!apiKey && provider !== "custom") {
|
|
403
|
+
// This is a soft validation - API key can be set via env vars
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
return {
|
|
408
|
+
valid: errors.length === 0,
|
|
409
|
+
errors,
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
export const configManager = new ConfigManager();
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Helper function to set working directory and reload config.
|
|
418
|
+
* Combines the common pattern of setWorkingDirectory + load.
|
|
419
|
+
*
|
|
420
|
+
* @param dir - Working directory path
|
|
421
|
+
* @param manager - ConfigManager instance (defaults to singleton)
|
|
422
|
+
* @returns Loaded config
|
|
423
|
+
*
|
|
424
|
+
* @example
|
|
425
|
+
* ```typescript
|
|
426
|
+
* await setupWorkingDirectory("/path/to/project");
|
|
427
|
+
* ```
|
|
428
|
+
*/
|
|
429
|
+
export async function setupWorkingDirectory(
|
|
430
|
+
dir: string,
|
|
431
|
+
manager: ConfigManager = configManager
|
|
432
|
+
): Promise<Config> {
|
|
433
|
+
manager.setWorkingDirectory(dir);
|
|
434
|
+
return await manager.load();
|
|
435
|
+
}
|