sqlew 3.7.3 → 3.8.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/CHANGELOG.md +427 -1
- package/README.md +71 -2
- package/assets/kanban-visualizer.png +0 -0
- package/assets/sample-agents/sqlew-scrum-master.md +4 -4
- package/dist/adapters/postgresql-adapter.d.ts +197 -5
- package/dist/adapters/postgresql-adapter.d.ts.map +1 -1
- package/dist/adapters/postgresql-adapter.js +300 -36
- package/dist/adapters/postgresql-adapter.js.map +1 -1
- package/dist/cli/db-export.d.ts +29 -0
- package/dist/cli/db-export.d.ts.map +1 -0
- package/dist/cli/db-export.js +251 -0
- package/dist/cli/db-export.js.map +1 -0
- package/dist/cli/db-import.d.ts +31 -0
- package/dist/cli/db-import.d.ts.map +1 -0
- package/dist/cli/db-import.js +258 -0
- package/dist/cli/db-import.js.map +1 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +32 -73
- package/dist/cli.js.map +1 -1
- package/dist/config/knex/bootstrap/20251025021416_seed_master_data.d.ts.map +1 -1
- package/dist/config/knex/bootstrap/20251025021416_seed_master_data.js +158 -45
- package/dist/config/knex/bootstrap/20251025021416_seed_master_data.js.map +1 -1
- package/dist/config/knex/bootstrap/20251025070349_create_views.d.ts.map +1 -1
- package/dist/config/knex/bootstrap/20251025070349_create_views.js +60 -8
- package/dist/config/knex/bootstrap/20251025070349_create_views.js.map +1 -1
- package/dist/config/knex/enhancements/20251025100000_seed_help_metadata.js +1 -1
- package/dist/config/knex/enhancements/20251025100100_seed_remaining_use_cases.js +5 -5
- package/dist/config/knex/enhancements/20251028000000_simplify_agent_system.d.ts.map +1 -1
- package/dist/config/knex/enhancements/20251028000000_simplify_agent_system.js +9 -1
- package/dist/config/knex/enhancements/20251028000000_simplify_agent_system.js.map +1 -1
- package/dist/config/knex/enhancements/20251105000000_add_token_usage_table.d.ts.map +1 -1
- package/dist/config/knex/enhancements/20251105000000_add_token_usage_table.js +8 -1
- package/dist/config/knex/enhancements/20251105000000_add_token_usage_table.js.map +1 -1
- package/dist/config/knex/enhancements/20251108000000_add_planning_layers_v3_8_0.d.ts +21 -0
- package/dist/config/knex/enhancements/20251108000000_add_planning_layers_v3_8_0.d.ts.map +1 -0
- package/dist/config/knex/enhancements/20251108000000_add_planning_layers_v3_8_0.js +73 -0
- package/dist/config/knex/enhancements/20251108000000_add_planning_layers_v3_8_0.js.map +1 -0
- package/dist/config/knex/enhancements/20251109000000_fix_task_file_links_unique_constraint_v3_8_0.d.ts +19 -0
- package/dist/config/knex/enhancements/20251109000000_fix_task_file_links_unique_constraint_v3_8_0.d.ts.map +1 -0
- package/dist/config/knex/enhancements/20251109000000_fix_task_file_links_unique_constraint_v3_8_0.js +88 -0
- package/dist/config/knex/enhancements/20251109000000_fix_task_file_links_unique_constraint_v3_8_0.js.map +1 -0
- package/dist/config/knex/enhancements/20251109000003_token_usage_cross_db_compat_v3_7_5.d.ts +24 -0
- package/dist/config/knex/enhancements/20251109000003_token_usage_cross_db_compat_v3_7_5.d.ts.map +1 -0
- package/dist/config/knex/enhancements/20251109000003_token_usage_cross_db_compat_v3_7_5.js +79 -0
- package/dist/config/knex/enhancements/20251109000003_token_usage_cross_db_compat_v3_7_5.js.map +1 -0
- package/dist/config/knex/enhancements/20251109010000_tool_cleanup_v3_8_0.d.ts +27 -0
- package/dist/config/knex/enhancements/20251109010000_tool_cleanup_v3_8_0.d.ts.map +1 -0
- package/dist/config/knex/enhancements/20251109010000_tool_cleanup_v3_8_0.js +347 -0
- package/dist/config/knex/enhancements/20251109010000_tool_cleanup_v3_8_0.js.map +1 -0
- package/dist/config/knex/enhancements/20251109020000_fix_missing_help_actions_v3_8_0.d.ts +30 -0
- package/dist/config/knex/enhancements/20251109020000_fix_missing_help_actions_v3_8_0.d.ts.map +1 -0
- package/dist/config/knex/enhancements/20251109020000_fix_missing_help_actions_v3_8_0.js +232 -0
- package/dist/config/knex/enhancements/20251109020000_fix_missing_help_actions_v3_8_0.js.map +1 -0
- package/dist/config/knex/upgrades/20251104000000_add_multi_project_v3_7_0.d.ts.map +1 -1
- package/dist/config/knex/upgrades/20251104000000_add_multi_project_v3_7_0.js +9 -0
- package/dist/config/knex/upgrades/20251104000000_add_multi_project_v3_7_0.js.map +1 -1
- package/dist/config/knex/upgrades/20251108000000_hotfix_v_tagged_constraints_project_id.d.ts +14 -0
- package/dist/config/knex/upgrades/20251108000000_hotfix_v_tagged_constraints_project_id.d.ts.map +1 -0
- package/dist/config/knex/upgrades/20251108000000_hotfix_v_tagged_constraints_project_id.js +97 -0
- package/dist/config/knex/upgrades/20251108000000_hotfix_v_tagged_constraints_project_id.js.map +1 -0
- package/dist/config/knex/upgrades/20251109000002_multi_project_cross_db_compat_v3_7_5.d.ts +24 -0
- package/dist/config/knex/upgrades/20251109000002_multi_project_cross_db_compat_v3_7_5.d.ts.map +1 -0
- package/dist/config/knex/upgrades/20251109000002_multi_project_cross_db_compat_v3_7_5.js +303 -0
- package/dist/config/knex/upgrades/20251109000002_multi_project_cross_db_compat_v3_7_5.js.map +1 -0
- package/dist/constants.d.ts +14 -2
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +28 -1
- package/dist/constants.js.map +1 -1
- package/dist/database/config/adapter-factory.d.ts +13 -0
- package/dist/database/config/adapter-factory.d.ts.map +1 -0
- package/dist/database/config/adapter-factory.js +21 -0
- package/dist/database/config/adapter-factory.js.map +1 -0
- package/dist/database/config/config-ops.d.ts +34 -0
- package/dist/database/config/config-ops.d.ts.map +1 -0
- package/dist/database/config/config-ops.js +76 -0
- package/dist/database/config/config-ops.js.map +1 -0
- package/dist/database/index.d.ts +21 -0
- package/dist/database/index.d.ts.map +1 -0
- package/dist/database/index.js +31 -0
- package/dist/database/index.js.map +1 -0
- package/dist/database/initialization/cleanup.d.ts +8 -0
- package/dist/database/initialization/cleanup.d.ts.map +1 -0
- package/dist/database/initialization/cleanup.js +17 -0
- package/dist/database/initialization/cleanup.js.map +1 -0
- package/dist/database/initialization/init.d.ts +21 -0
- package/dist/database/initialization/init.d.ts.map +1 -0
- package/dist/database/initialization/init.js +83 -0
- package/dist/database/initialization/init.js.map +1 -0
- package/dist/database/operations/deletes.d.ts +10 -0
- package/dist/database/operations/deletes.d.ts.map +1 -0
- package/dist/database/operations/deletes.js +14 -0
- package/dist/database/operations/deletes.js.map +1 -0
- package/dist/database/operations/inserts.d.ts +40 -0
- package/dist/database/operations/inserts.d.ts.map +1 -0
- package/dist/database/operations/inserts.js +202 -0
- package/dist/database/operations/inserts.js.map +1 -0
- package/dist/database/operations/queries.d.ts +57 -0
- package/dist/database/operations/queries.d.ts.map +1 -0
- package/dist/database/operations/queries.js +77 -0
- package/dist/database/operations/queries.js.map +1 -0
- package/dist/database/operations/updates.d.ts +10 -0
- package/dist/database/operations/updates.d.ts.map +1 -0
- package/dist/database/operations/updates.js +14 -0
- package/dist/database/operations/updates.js.map +1 -0
- package/dist/database/types.d.ts +5 -0
- package/dist/database/types.d.ts.map +1 -0
- package/dist/database/types.js +5 -0
- package/dist/database/types.js.map +1 -0
- package/dist/database.d.ts +4 -144
- package/dist/database.d.ts.map +1 -1
- package/dist/database.js +5 -461
- package/dist/database.js.map +1 -1
- package/dist/index.js +29 -777
- package/dist/index.js.map +1 -1
- package/dist/server/arg-parser.d.ts +40 -0
- package/dist/server/arg-parser.d.ts.map +1 -0
- package/dist/server/arg-parser.js +137 -0
- package/dist/server/arg-parser.js.map +1 -0
- package/dist/server/setup.d.ts +31 -0
- package/dist/server/setup.d.ts.map +1 -0
- package/dist/server/setup.js +186 -0
- package/dist/server/setup.js.map +1 -0
- package/dist/server/shutdown.d.ts +15 -0
- package/dist/server/shutdown.d.ts.map +1 -0
- package/dist/server/shutdown.js +42 -0
- package/dist/server/shutdown.js.map +1 -0
- package/dist/server/tool-handlers.d.ts +10 -0
- package/dist/server/tool-handlers.d.ts.map +1 -0
- package/dist/server/tool-handlers.js +478 -0
- package/dist/server/tool-handlers.js.map +1 -0
- package/dist/server/tool-registry.d.ts +11 -0
- package/dist/server/tool-registry.d.ts.map +1 -0
- package/dist/server/tool-registry.js +225 -0
- package/dist/server/tool-registry.js.map +1 -0
- package/dist/tests/all-features.test.js +6 -38
- package/dist/tests/all-features.test.js.map +1 -1
- package/dist/tests/batch-validation-comprehensive.test.d.ts +12 -0
- package/dist/tests/batch-validation-comprehensive.test.d.ts.map +1 -0
- package/dist/tests/batch-validation-comprehensive.test.js +472 -0
- package/dist/tests/batch-validation-comprehensive.test.js.map +1 -0
- package/dist/tests/batch-validation-integration.test.d.ts +6 -0
- package/dist/tests/batch-validation-integration.test.d.ts.map +1 -0
- package/dist/tests/batch-validation-integration.test.js +193 -0
- package/dist/tests/batch-validation-integration.test.js.map +1 -0
- package/dist/tests/batch-validation.test.d.ts +6 -0
- package/dist/tests/batch-validation.test.d.ts.map +1 -0
- package/dist/tests/batch-validation.test.js +298 -0
- package/dist/tests/batch-validation.test.js.map +1 -0
- package/dist/tests/context-modular.test.d.ts +7 -0
- package/dist/tests/context-modular.test.d.ts.map +1 -0
- package/dist/tests/context-modular.test.js +271 -0
- package/dist/tests/context-modular.test.js.map +1 -0
- package/dist/tests/decision-batch-validation.test.d.ts +6 -0
- package/dist/tests/decision-batch-validation.test.d.ts.map +1 -0
- package/dist/tests/decision-batch-validation.test.js +253 -0
- package/dist/tests/decision-batch-validation.test.js.map +1 -0
- package/dist/tests/help-system.test.js +1 -1
- package/dist/tests/help-system.test.js.map +1 -1
- package/dist/tests/migrations/test-all-versions-real.js +3 -2
- package/dist/tests/migrations/test-all-versions-real.js.map +1 -1
- package/dist/tests/multi-project.test.js +2 -2
- package/dist/tests/multi-project.test.js.map +1 -1
- package/dist/tests/parameter-validation.test.js +2 -16
- package/dist/tests/parameter-validation.test.js.map +1 -1
- package/dist/tests/tasks.auto-pruning-decision-link.test.js +1 -1
- package/dist/tests/tasks.auto-pruning-decision-link.test.js.map +1 -1
- package/dist/tests/tasks.file-actions-integration.test.d.ts +10 -0
- package/dist/tests/tasks.file-actions-integration.test.d.ts.map +1 -0
- package/dist/tests/tasks.file-actions-integration.test.js +162 -0
- package/dist/tests/tasks.file-actions-integration.test.js.map +1 -0
- package/dist/tests/tasks.file-actions-validation.test.d.ts +6 -0
- package/dist/tests/tasks.file-actions-validation.test.d.ts.map +1 -0
- package/dist/tests/tasks.file-actions-validation.test.js +221 -0
- package/dist/tests/tasks.file-actions-validation.test.js.map +1 -0
- package/dist/tools/constraints/actions/add.d.ts +15 -0
- package/dist/tools/constraints/actions/add.d.ts.map +1 -0
- package/dist/tools/constraints/actions/add.js +101 -0
- package/dist/tools/constraints/actions/add.js.map +1 -0
- package/dist/tools/constraints/actions/deactivate.d.ts +16 -0
- package/dist/tools/constraints/actions/deactivate.d.ts.map +1 -0
- package/dist/tools/constraints/actions/deactivate.js +49 -0
- package/dist/tools/constraints/actions/deactivate.js.map +1 -0
- package/dist/tools/constraints/actions/get.d.ts +16 -0
- package/dist/tools/constraints/actions/get.d.ts.map +1 -0
- package/dist/tools/constraints/actions/get.js +76 -0
- package/dist/tools/constraints/actions/get.js.map +1 -0
- package/dist/tools/constraints/help/example.d.ts +9 -0
- package/dist/tools/constraints/help/example.d.ts.map +1 -0
- package/dist/tools/constraints/help/example.js +144 -0
- package/dist/tools/constraints/help/example.js.map +1 -0
- package/dist/tools/constraints/help/help.d.ts +9 -0
- package/dist/tools/constraints/help/help.d.ts.map +1 -0
- package/dist/tools/constraints/help/help.js +31 -0
- package/dist/tools/constraints/help/help.js.map +1 -0
- package/dist/tools/constraints/index.d.ts +12 -0
- package/dist/tools/constraints/index.d.ts.map +1 -0
- package/dist/tools/constraints/index.js +13 -0
- package/dist/tools/constraints/index.js.map +1 -0
- package/dist/tools/constraints/internal/validation.d.ts +11 -0
- package/dist/tools/constraints/internal/validation.d.ts.map +1 -0
- package/dist/tools/constraints/internal/validation.js +17 -0
- package/dist/tools/constraints/internal/validation.js.map +1 -0
- package/dist/tools/constraints/types.d.ts +6 -0
- package/dist/tools/constraints/types.d.ts.map +1 -0
- package/dist/tools/constraints/types.js +6 -0
- package/dist/tools/constraints/types.js.map +1 -0
- package/dist/tools/context/actions/add-context.d.ts +14 -0
- package/dist/tools/context/actions/add-context.d.ts.map +1 -0
- package/dist/tools/context/actions/add-context.js +62 -0
- package/dist/tools/context/actions/add-context.js.map +1 -0
- package/dist/tools/context/actions/batch-set.d.ts +16 -0
- package/dist/tools/context/actions/batch-set.d.ts.map +1 -0
- package/dist/tools/context/actions/batch-set.js +126 -0
- package/dist/tools/context/actions/batch-set.js.map +1 -0
- package/dist/tools/context/actions/create-template.d.ts +15 -0
- package/dist/tools/context/actions/create-template.d.ts.map +1 -0
- package/dist/tools/context/actions/create-template.js +68 -0
- package/dist/tools/context/actions/create-template.js.map +1 -0
- package/dist/tools/context/actions/get.d.ts +18 -0
- package/dist/tools/context/actions/get.d.ts.map +1 -0
- package/dist/tools/context/actions/get.js +80 -0
- package/dist/tools/context/actions/get.js.map +1 -0
- package/dist/tools/context/actions/hard-delete.d.ts +18 -0
- package/dist/tools/context/actions/hard-delete.d.ts.map +1 -0
- package/dist/tools/context/actions/hard-delete.js +85 -0
- package/dist/tools/context/actions/hard-delete.js.map +1 -0
- package/dist/tools/context/actions/has-updates.d.ts +16 -0
- package/dist/tools/context/actions/has-updates.d.ts.map +1 -0
- package/dist/tools/context/actions/has-updates.js +83 -0
- package/dist/tools/context/actions/has-updates.js.map +1 -0
- package/dist/tools/context/actions/list-contexts.d.ts +14 -0
- package/dist/tools/context/actions/list-contexts.d.ts.map +1 -0
- package/dist/tools/context/actions/list-contexts.js +43 -0
- package/dist/tools/context/actions/list-contexts.js.map +1 -0
- package/dist/tools/context/actions/list-templates.d.ts +15 -0
- package/dist/tools/context/actions/list-templates.d.ts.map +1 -0
- package/dist/tools/context/actions/list-templates.js +47 -0
- package/dist/tools/context/actions/list-templates.js.map +1 -0
- package/dist/tools/context/actions/list.d.ts +16 -0
- package/dist/tools/context/actions/list.d.ts.map +1 -0
- package/dist/tools/context/actions/list.js +100 -0
- package/dist/tools/context/actions/list.js.map +1 -0
- package/dist/tools/context/actions/quick-set.d.ts +21 -0
- package/dist/tools/context/actions/quick-set.d.ts.map +1 -0
- package/dist/tools/context/actions/quick-set.js +104 -0
- package/dist/tools/context/actions/quick-set.js.map +1 -0
- package/dist/tools/context/actions/search-advanced.d.ts +15 -0
- package/dist/tools/context/actions/search-advanced.d.ts.map +1 -0
- package/dist/tools/context/actions/search-advanced.js +140 -0
- package/dist/tools/context/actions/search-advanced.js.map +1 -0
- package/dist/tools/context/actions/search-layer.d.ts +15 -0
- package/dist/tools/context/actions/search-layer.d.ts.map +1 -0
- package/dist/tools/context/actions/search-layer.js +106 -0
- package/dist/tools/context/actions/search-layer.js.map +1 -0
- package/dist/tools/context/actions/search-tags.d.ts +15 -0
- package/dist/tools/context/actions/search-tags.d.ts.map +1 -0
- package/dist/tools/context/actions/search-tags.js +85 -0
- package/dist/tools/context/actions/search-tags.js.map +1 -0
- package/dist/tools/context/actions/set-from-template.d.ts +16 -0
- package/dist/tools/context/actions/set-from-template.d.ts.map +1 -0
- package/dist/tools/context/actions/set-from-template.js +83 -0
- package/dist/tools/context/actions/set-from-template.js.map +1 -0
- package/dist/tools/context/actions/set.d.ts +16 -0
- package/dist/tools/context/actions/set.d.ts.map +1 -0
- package/dist/tools/context/actions/set.js +56 -0
- package/dist/tools/context/actions/set.js.map +1 -0
- package/dist/tools/context/actions/versions.d.ts +15 -0
- package/dist/tools/context/actions/versions.d.ts.map +1 -0
- package/dist/tools/context/actions/versions.js +69 -0
- package/dist/tools/context/actions/versions.js.map +1 -0
- package/dist/tools/context/help/example.d.ts +5 -0
- package/dist/tools/context/help/example.d.ts.map +1 -0
- package/dist/tools/context/help/example.js +96 -0
- package/dist/tools/context/help/example.js.map +1 -0
- package/dist/tools/context/help/help.d.ts +5 -0
- package/dist/tools/context/help/help.d.ts.map +1 -0
- package/dist/tools/context/help/help.js +65 -0
- package/dist/tools/context/help/help.js.map +1 -0
- package/dist/tools/context/index.d.ts +25 -0
- package/dist/tools/context/index.d.ts.map +1 -0
- package/dist/tools/context/index.js +26 -0
- package/dist/tools/context/index.js.map +1 -0
- package/dist/tools/context/internal/queries.d.ts +18 -0
- package/dist/tools/context/internal/queries.d.ts.map +1 -0
- package/dist/tools/context/internal/queries.js +160 -0
- package/dist/tools/context/internal/queries.js.map +1 -0
- package/dist/tools/context/internal/validation.d.ts +39 -0
- package/dist/tools/context/internal/validation.d.ts.map +1 -0
- package/dist/tools/context/internal/validation.js +125 -0
- package/dist/tools/context/internal/validation.js.map +1 -0
- package/dist/tools/context/types.d.ts +6 -0
- package/dist/tools/context/types.d.ts.map +1 -0
- package/dist/tools/context/types.js +6 -0
- package/dist/tools/context/types.js.map +1 -0
- package/dist/tools/example/actions/get.d.ts +14 -0
- package/dist/tools/example/actions/get.d.ts.map +1 -0
- package/dist/tools/example/actions/get.js +40 -0
- package/dist/tools/example/actions/get.js.map +1 -0
- package/dist/tools/example/actions/list-all.d.ts +13 -0
- package/dist/tools/example/actions/list-all.d.ts.map +1 -0
- package/dist/tools/example/actions/list-all.js +39 -0
- package/dist/tools/example/actions/list-all.js.map +1 -0
- package/dist/tools/example/actions/search.d.ts +14 -0
- package/dist/tools/example/actions/search.d.ts.map +1 -0
- package/dist/tools/example/actions/search.js +52 -0
- package/dist/tools/example/actions/search.js.map +1 -0
- package/dist/tools/example/help/example.d.ts +6 -0
- package/dist/tools/example/help/example.d.ts.map +1 -0
- package/dist/tools/example/help/example.js +69 -0
- package/dist/tools/example/help/example.js.map +1 -0
- package/dist/tools/example/help/help.d.ts +6 -0
- package/dist/tools/example/help/help.d.ts.map +1 -0
- package/dist/tools/example/help/help.js +133 -0
- package/dist/tools/example/help/help.js.map +1 -0
- package/dist/tools/example/index.d.ts +11 -0
- package/dist/tools/example/index.d.ts.map +1 -0
- package/dist/tools/example/index.js +12 -0
- package/dist/tools/example/index.js.map +1 -0
- package/dist/tools/example/types.d.ts +59 -0
- package/dist/tools/example/types.d.ts.map +1 -0
- package/dist/tools/example/types.js +6 -0
- package/dist/tools/example/types.js.map +1 -0
- package/dist/tools/files/actions/check-lock.d.ts +16 -0
- package/dist/tools/files/actions/check-lock.d.ts.map +1 -0
- package/dist/tools/files/actions/check-lock.js +70 -0
- package/dist/tools/files/actions/check-lock.js.map +1 -0
- package/dist/tools/files/actions/get.d.ts +16 -0
- package/dist/tools/files/actions/get.d.ts.map +1 -0
- package/dist/tools/files/actions/get.js +113 -0
- package/dist/tools/files/actions/get.js.map +1 -0
- package/dist/tools/files/actions/record-batch.d.ts +18 -0
- package/dist/tools/files/actions/record-batch.d.ts.map +1 -0
- package/dist/tools/files/actions/record-batch.js +114 -0
- package/dist/tools/files/actions/record-batch.js.map +1 -0
- package/dist/tools/files/actions/record.d.ts +16 -0
- package/dist/tools/files/actions/record.d.ts.map +1 -0
- package/dist/tools/files/actions/record.js +37 -0
- package/dist/tools/files/actions/record.js.map +1 -0
- package/dist/tools/files/actions/sqlite-flush.d.ts +27 -0
- package/dist/tools/files/actions/sqlite-flush.d.ts.map +1 -0
- package/dist/tools/files/actions/sqlite-flush.js +66 -0
- package/dist/tools/files/actions/sqlite-flush.js.map +1 -0
- package/dist/tools/files/help/example.d.ts +5 -0
- package/dist/tools/files/help/example.d.ts.map +1 -0
- package/dist/tools/files/help/example.js +98 -0
- package/dist/tools/files/help/example.js.map +1 -0
- package/dist/tools/files/help/help.d.ts +5 -0
- package/dist/tools/files/help/help.d.ts.map +1 -0
- package/dist/tools/files/help/help.js +29 -0
- package/dist/tools/files/help/help.js.map +1 -0
- package/dist/tools/files/index.d.ts +14 -0
- package/dist/tools/files/index.d.ts.map +1 -0
- package/dist/tools/files/index.js +15 -0
- package/dist/tools/files/index.js.map +1 -0
- package/dist/tools/files/internal/queries.d.ts +18 -0
- package/dist/tools/files/internal/queries.d.ts.map +1 -0
- package/dist/tools/files/internal/queries.js +63 -0
- package/dist/tools/files/internal/queries.js.map +1 -0
- package/dist/tools/files/internal/validation.d.ts +18 -0
- package/dist/tools/files/internal/validation.d.ts.map +1 -0
- package/dist/tools/files/internal/validation.js +40 -0
- package/dist/tools/files/internal/validation.js.map +1 -0
- package/dist/tools/files/types.d.ts +6 -0
- package/dist/tools/files/types.d.ts.map +1 -0
- package/dist/tools/files/types.js +6 -0
- package/dist/tools/files/types.js.map +1 -0
- package/dist/tools/help/actions/batch-guide.d.ts +14 -0
- package/dist/tools/help/actions/batch-guide.d.ts.map +1 -0
- package/dist/tools/help/actions/batch-guide.js +59 -0
- package/dist/tools/help/actions/batch-guide.js.map +1 -0
- package/dist/tools/help/actions/error-recovery.d.ts +12 -0
- package/dist/tools/help/actions/error-recovery.d.ts.map +1 -0
- package/dist/tools/help/actions/error-recovery.js +107 -0
- package/dist/tools/help/actions/error-recovery.js.map +1 -0
- package/dist/tools/help/actions/query-action.d.ts +15 -0
- package/dist/tools/help/actions/query-action.d.ts.map +1 -0
- package/dist/tools/help/actions/query-action.js +15 -0
- package/dist/tools/help/actions/query-action.js.map +1 -0
- package/dist/tools/help/actions/query-params.d.ts +15 -0
- package/dist/tools/help/actions/query-params.d.ts.map +1 -0
- package/dist/tools/help/actions/query-params.js +15 -0
- package/dist/tools/help/actions/query-params.js.map +1 -0
- package/dist/tools/help/actions/query-tool.d.ts +15 -0
- package/dist/tools/help/actions/query-tool.d.ts.map +1 -0
- package/dist/tools/help/actions/query-tool.js +15 -0
- package/dist/tools/help/actions/query-tool.js.map +1 -0
- package/dist/tools/help/actions/workflow-hints.d.ts +14 -0
- package/dist/tools/help/actions/workflow-hints.d.ts.map +1 -0
- package/dist/tools/help/actions/workflow-hints.js +15 -0
- package/dist/tools/help/actions/workflow-hints.js.map +1 -0
- package/dist/tools/help/help/example.d.ts +6 -0
- package/dist/tools/help/help/example.d.ts.map +1 -0
- package/dist/tools/help/help/example.js +70 -0
- package/dist/tools/help/help/example.js.map +1 -0
- package/dist/tools/help/help/help.d.ts +6 -0
- package/dist/tools/help/help/help.d.ts.map +1 -0
- package/dist/tools/help/help/help.js +67 -0
- package/dist/tools/help/help/help.js.map +1 -0
- package/dist/tools/help/index.d.ts +14 -0
- package/dist/tools/help/index.d.ts.map +1 -0
- package/dist/tools/help/index.js +15 -0
- package/dist/tools/help/index.js.map +1 -0
- package/dist/tools/help/types.d.ts +92 -0
- package/dist/tools/help/types.d.ts.map +1 -0
- package/dist/tools/help/types.js +6 -0
- package/dist/tools/help/types.js.map +1 -0
- package/dist/tools/help-queries.d.ts.map +1 -1
- package/dist/tools/help-queries.js +1 -0
- package/dist/tools/help-queries.js.map +1 -1
- package/dist/tools/tasks/actions/add-dependency.d.ts +12 -0
- package/dist/tools/tasks/actions/add-dependency.d.ts.map +1 -0
- package/dist/tools/tasks/actions/add-dependency.js +124 -0
- package/dist/tools/tasks/actions/add-dependency.js.map +1 -0
- package/dist/tools/tasks/actions/archive.d.ts +11 -0
- package/dist/tools/tasks/actions/archive.d.ts.map +1 -0
- package/dist/tools/tasks/actions/archive.js +67 -0
- package/dist/tools/tasks/actions/archive.js.map +1 -0
- package/dist/tools/tasks/actions/create-batch.d.ts +19 -0
- package/dist/tools/tasks/actions/create-batch.d.ts.map +1 -0
- package/dist/tools/tasks/actions/create-batch.js +103 -0
- package/dist/tools/tasks/actions/create-batch.js.map +1 -0
- package/dist/tools/tasks/actions/create.d.ts +16 -0
- package/dist/tools/tasks/actions/create.d.ts.map +1 -0
- package/dist/tools/tasks/actions/create.js +180 -0
- package/dist/tools/tasks/actions/create.js.map +1 -0
- package/dist/tools/tasks/actions/get-dependencies.d.ts +12 -0
- package/dist/tools/tasks/actions/get-dependencies.d.ts.map +1 -0
- package/dist/tools/tasks/actions/get-dependencies.js +41 -0
- package/dist/tools/tasks/actions/get-dependencies.js.map +1 -0
- package/dist/tools/tasks/actions/get-pruned-files.d.ts +13 -0
- package/dist/tools/tasks/actions/get-pruned-files.d.ts.map +1 -0
- package/dist/tools/tasks/actions/get-pruned-files.js +45 -0
- package/dist/tools/tasks/actions/get-pruned-files.js.map +1 -0
- package/dist/tools/tasks/actions/get.d.ts +12 -0
- package/dist/tools/tasks/actions/get.d.ts.map +1 -0
- package/dist/tools/tasks/actions/get.js +88 -0
- package/dist/tools/tasks/actions/get.js.map +1 -0
- package/dist/tools/tasks/actions/link-pruned-file.d.ts +13 -0
- package/dist/tools/tasks/actions/link-pruned-file.d.ts.map +1 -0
- package/dist/tools/tasks/actions/link-pruned-file.js +63 -0
- package/dist/tools/tasks/actions/link-pruned-file.js.map +1 -0
- package/dist/tools/tasks/actions/link.d.ts +14 -0
- package/dist/tools/tasks/actions/link.d.ts.map +1 -0
- package/dist/tools/tasks/actions/link.js +118 -0
- package/dist/tools/tasks/actions/link.js.map +1 -0
- package/dist/tools/tasks/actions/list.d.ts +17 -0
- package/dist/tools/tasks/actions/list.d.ts.map +1 -0
- package/dist/tools/tasks/actions/list.js +98 -0
- package/dist/tools/tasks/actions/list.js.map +1 -0
- package/dist/tools/tasks/actions/move.d.ts +12 -0
- package/dist/tools/tasks/actions/move.d.ts.map +1 -0
- package/dist/tools/tasks/actions/move.js +91 -0
- package/dist/tools/tasks/actions/move.js.map +1 -0
- package/dist/tools/tasks/actions/remove-dependency.d.ts +12 -0
- package/dist/tools/tasks/actions/remove-dependency.d.ts.map +1 -0
- package/dist/tools/tasks/actions/remove-dependency.js +36 -0
- package/dist/tools/tasks/actions/remove-dependency.js.map +1 -0
- package/dist/tools/tasks/actions/update.d.ts +10 -0
- package/dist/tools/tasks/actions/update.d.ts.map +1 -0
- package/dist/tools/tasks/actions/update.js +186 -0
- package/dist/tools/tasks/actions/update.js.map +1 -0
- package/dist/tools/tasks/actions/watch-files.d.ts +14 -0
- package/dist/tools/tasks/actions/watch-files.d.ts.map +1 -0
- package/dist/tools/tasks/actions/watch-files.js +127 -0
- package/dist/tools/tasks/actions/watch-files.js.map +1 -0
- package/dist/tools/tasks/help/example.d.ts +8 -0
- package/dist/tools/tasks/help/example.d.ts.map +1 -0
- package/dist/tools/tasks/help/example.js +215 -0
- package/dist/tools/tasks/help/example.js.map +1 -0
- package/dist/tools/tasks/help/help.d.ts +8 -0
- package/dist/tools/tasks/help/help.d.ts.map +1 -0
- package/dist/tools/tasks/help/help.js +293 -0
- package/dist/tools/tasks/help/help.js.map +1 -0
- package/dist/tools/tasks/help/use-case.d.ts +11 -0
- package/dist/tools/tasks/help/use-case.d.ts.map +1 -0
- package/dist/tools/tasks/help/use-case.js +768 -0
- package/dist/tools/tasks/help/use-case.js.map +1 -0
- package/dist/tools/tasks/index.d.ts +28 -0
- package/dist/tools/tasks/index.d.ts.map +1 -0
- package/dist/tools/tasks/index.js +33 -0
- package/dist/tools/tasks/index.js.map +1 -0
- package/dist/tools/tasks/internal/state-machine.d.ts +16 -0
- package/dist/tools/tasks/internal/state-machine.d.ts.map +1 -0
- package/dist/tools/tasks/internal/state-machine.js +36 -0
- package/dist/tools/tasks/internal/state-machine.js.map +1 -0
- package/dist/tools/tasks/internal/task-queries.d.ts +12 -0
- package/dist/tools/tasks/internal/task-queries.d.ts.map +1 -0
- package/dist/tools/tasks/internal/task-queries.js +61 -0
- package/dist/tools/tasks/internal/task-queries.js.map +1 -0
- package/dist/tools/tasks/internal/validation.d.ts +47 -0
- package/dist/tools/tasks/internal/validation.d.ts.map +1 -0
- package/dist/tools/tasks/internal/validation.js +261 -0
- package/dist/tools/tasks/internal/validation.js.map +1 -0
- package/dist/tools/tasks/types.d.ts +68 -0
- package/dist/tools/tasks/types.d.ts.map +1 -0
- package/dist/tools/tasks/types.js +45 -0
- package/dist/tools/tasks/types.js.map +1 -0
- package/dist/tools/tasks/watcher/status.d.ts +9 -0
- package/dist/tools/tasks/watcher/status.d.ts.map +1 -0
- package/dist/tools/tasks/watcher/status.js +126 -0
- package/dist/tools/tasks/watcher/status.js.map +1 -0
- package/dist/tools/tasks.d.ts +12 -148
- package/dist/tools/tasks.d.ts.map +1 -1
- package/dist/tools/tasks.js +12 -2037
- package/dist/tools/tasks.js.map +1 -1
- package/dist/tools/use_case/actions/get.d.ts +14 -0
- package/dist/tools/use_case/actions/get.d.ts.map +1 -0
- package/dist/tools/use_case/actions/get.js +15 -0
- package/dist/tools/use_case/actions/get.js.map +1 -0
- package/dist/tools/use_case/actions/list-all.d.ts +15 -0
- package/dist/tools/use_case/actions/list-all.d.ts.map +1 -0
- package/dist/tools/use_case/actions/list-all.js +20 -0
- package/dist/tools/use_case/actions/list-all.js.map +1 -0
- package/dist/tools/use_case/actions/search.d.ts +14 -0
- package/dist/tools/use_case/actions/search.d.ts.map +1 -0
- package/dist/tools/use_case/actions/search.js +87 -0
- package/dist/tools/use_case/actions/search.js.map +1 -0
- package/dist/tools/use_case/help/example.d.ts +105 -0
- package/dist/tools/use_case/help/example.d.ts.map +1 -0
- package/dist/tools/use_case/help/example.js +138 -0
- package/dist/tools/use_case/help/example.js.map +1 -0
- package/dist/tools/use_case/help/help.d.ts +33 -0
- package/dist/tools/use_case/help/help.d.ts.map +1 -0
- package/dist/tools/use_case/help/help.js +109 -0
- package/dist/tools/use_case/help/help.js.map +1 -0
- package/dist/tools/use_case/index.d.ts +11 -0
- package/dist/tools/use_case/index.d.ts.map +1 -0
- package/dist/tools/use_case/index.js +12 -0
- package/dist/tools/use_case/index.js.map +1 -0
- package/dist/tools/use_case/types.d.ts +58 -0
- package/dist/tools/use_case/types.d.ts.map +1 -0
- package/dist/tools/use_case/types.js +6 -0
- package/dist/tools/use_case/types.js.map +1 -0
- package/dist/types/actions.d.ts +37 -0
- package/dist/types/actions.d.ts.map +1 -0
- package/dist/types/actions.js +6 -0
- package/dist/types/actions.js.map +1 -0
- package/dist/types/constraint/params.d.ts +2 -0
- package/dist/types/constraint/params.d.ts.map +1 -0
- package/dist/types/constraint/params.js +2 -0
- package/dist/types/constraint/params.js.map +1 -0
- package/dist/types/constraint/responses.d.ts +2 -0
- package/dist/types/constraint/responses.d.ts.map +1 -0
- package/dist/types/constraint/responses.js +2 -0
- package/dist/types/constraint/responses.js.map +1 -0
- package/dist/types/decision/batch.d.ts +24 -0
- package/dist/types/decision/batch.d.ts.map +1 -0
- package/dist/types/decision/batch.js +5 -0
- package/dist/types/decision/batch.js.map +1 -0
- package/dist/types/decision/params.d.ts +73 -0
- package/dist/types/decision/params.d.ts.map +1 -0
- package/dist/types/decision/params.js +5 -0
- package/dist/types/decision/params.js.map +1 -0
- package/dist/types/decision/responses.d.ts +79 -0
- package/dist/types/decision/responses.d.ts.map +1 -0
- package/dist/types/decision/responses.js +5 -0
- package/dist/types/decision/responses.js.map +1 -0
- package/dist/types/decision/templates.d.ts +74 -0
- package/dist/types/decision/templates.d.ts.map +1 -0
- package/dist/types/decision/templates.js +5 -0
- package/dist/types/decision/templates.js.map +1 -0
- package/dist/types/enums.d.ts +43 -0
- package/dist/types/enums.d.ts.map +1 -0
- package/dist/types/enums.js +47 -0
- package/dist/types/enums.js.map +1 -0
- package/dist/types/file/params.d.ts +40 -0
- package/dist/types/file/params.d.ts.map +1 -0
- package/dist/types/file/params.js +6 -0
- package/dist/types/file/params.js.map +1 -0
- package/dist/types/file/responses.d.ts +2 -0
- package/dist/types/file/responses.d.ts.map +1 -0
- package/dist/types/file/responses.js +2 -0
- package/dist/types/file/responses.js.map +1 -0
- package/dist/types/import-export.d.ts +126 -0
- package/dist/types/import-export.d.ts.map +1 -0
- package/dist/types/import-export.js +6 -0
- package/dist/types/import-export.js.map +1 -0
- package/dist/types/index.d.ts +25 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +35 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/master-entities.d.ts +33 -0
- package/dist/types/master-entities.d.ts.map +1 -0
- package/dist/types/master-entities.js +6 -0
- package/dist/types/master-entities.js.map +1 -0
- package/dist/types/task/params.d.ts +172 -0
- package/dist/types/task/params.d.ts.map +1 -0
- package/dist/types/task/params.js +8 -0
- package/dist/types/task/params.js.map +1 -0
- package/dist/types/task/responses.d.ts +334 -0
- package/dist/types/task/responses.d.ts.map +1 -0
- package/dist/types/task/responses.js +8 -0
- package/dist/types/task/responses.js.map +1 -0
- package/dist/types/transaction-entities.d.ts +89 -0
- package/dist/types/transaction-entities.d.ts.map +1 -0
- package/dist/types/transaction-entities.js +5 -0
- package/dist/types/transaction-entities.js.map +1 -0
- package/dist/types/validation.d.ts +44 -0
- package/dist/types/validation.d.ts.map +1 -0
- package/dist/types/validation.js +6 -0
- package/dist/types/validation.js.map +1 -0
- package/dist/types/view-entities.d.ts +61 -0
- package/dist/types/view-entities.d.ts.map +1 -0
- package/dist/types/view-entities.js +5 -0
- package/dist/types/view-entities.js.map +1 -0
- package/dist/types.d.ts +133 -10
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/action-specs/config-specs.d.ts +10 -0
- package/dist/utils/action-specs/config-specs.d.ts.map +1 -0
- package/dist/utils/action-specs/config-specs.js +12 -0
- package/dist/utils/action-specs/config-specs.js.map +1 -0
- package/dist/utils/action-specs/constraint-specs.d.ts +9 -0
- package/dist/utils/action-specs/constraint-specs.d.ts.map +1 -0
- package/dist/utils/action-specs/constraint-specs.js +42 -0
- package/dist/utils/action-specs/constraint-specs.js.map +1 -0
- package/dist/utils/action-specs/decision-specs.d.ts +9 -0
- package/dist/utils/action-specs/decision-specs.d.ts.map +1 -0
- package/dist/utils/action-specs/decision-specs.js +194 -0
- package/dist/utils/action-specs/decision-specs.js.map +1 -0
- package/dist/utils/action-specs/file-specs.d.ts +9 -0
- package/dist/utils/action-specs/file-specs.d.ts.map +1 -0
- package/dist/utils/action-specs/file-specs.js +56 -0
- package/dist/utils/action-specs/file-specs.js.map +1 -0
- package/dist/utils/action-specs/index.d.ts +38 -0
- package/dist/utils/action-specs/index.d.ts.map +1 -0
- package/dist/utils/action-specs/index.js +63 -0
- package/dist/utils/action-specs/index.js.map +1 -0
- package/dist/utils/action-specs/task-specs.d.ts +9 -0
- package/dist/utils/action-specs/task-specs.d.ts.map +1 -0
- package/dist/utils/action-specs/task-specs.js +143 -0
- package/dist/utils/action-specs/task-specs.js.map +1 -0
- package/dist/utils/action-specs/types.d.ts +14 -0
- package/dist/utils/action-specs/types.d.ts.map +1 -0
- package/dist/utils/action-specs/types.js +9 -0
- package/dist/utils/action-specs/types.js.map +1 -0
- package/dist/utils/batch-validation.d.ts +156 -0
- package/dist/utils/batch-validation.d.ts.map +1 -0
- package/dist/utils/batch-validation.example.d.ts +48 -0
- package/dist/utils/batch-validation.example.d.ts.map +1 -0
- package/dist/utils/batch-validation.example.js +180 -0
- package/dist/utils/batch-validation.example.js.map +1 -0
- package/dist/utils/batch-validation.js +345 -0
- package/dist/utils/batch-validation.js.map +1 -0
- package/dist/utils/exporter/export.d.ts +100 -0
- package/dist/utils/exporter/export.d.ts.map +1 -0
- package/dist/utils/exporter/export.js +363 -0
- package/dist/utils/exporter/export.js.map +1 -0
- package/dist/utils/importer/import.d.ts +29 -0
- package/dist/utils/importer/import.d.ts.map +1 -0
- package/dist/utils/importer/import.js +514 -0
- package/dist/utils/importer/import.js.map +1 -0
- package/dist/utils/importer/master-tables.d.ts +18 -0
- package/dist/utils/importer/master-tables.d.ts.map +1 -0
- package/dist/utils/importer/master-tables.js +255 -0
- package/dist/utils/importer/master-tables.js.map +1 -0
- package/dist/utils/importer/topological-sort.d.ts +61 -0
- package/dist/utils/importer/topological-sort.d.ts.map +1 -0
- package/dist/utils/importer/topological-sort.js +143 -0
- package/dist/utils/importer/topological-sort.js.map +1 -0
- package/dist/utils/levenshtein.d.ts +18 -0
- package/dist/utils/levenshtein.d.ts.map +1 -0
- package/dist/utils/levenshtein.js +46 -0
- package/dist/utils/levenshtein.js.map +1 -0
- package/dist/utils/parameter-validator.d.ts +3 -3
- package/dist/utils/parameter-validator.d.ts.map +1 -1
- package/dist/utils/parameter-validator.js +3 -39
- package/dist/utils/parameter-validator.js.map +1 -1
- package/dist/utils/sql-dump/core/dependency-sort.d.ts +16 -0
- package/dist/utils/sql-dump/core/dependency-sort.d.ts.map +1 -0
- package/dist/utils/sql-dump/core/dependency-sort.js +105 -0
- package/dist/utils/sql-dump/core/dependency-sort.js.map +1 -0
- package/dist/utils/sql-dump/core/generate-dump.d.ts +13 -0
- package/dist/utils/sql-dump/core/generate-dump.d.ts.map +1 -0
- package/dist/utils/sql-dump/core/generate-dump.js +181 -0
- package/dist/utils/sql-dump/core/generate-dump.js.map +1 -0
- package/dist/utils/sql-dump/core/index-export.d.ts +9 -0
- package/dist/utils/sql-dump/core/index-export.d.ts.map +1 -0
- package/dist/utils/sql-dump/core/index-export.js +173 -0
- package/dist/utils/sql-dump/core/index-export.js.map +1 -0
- package/dist/utils/sql-dump/core/sequence-reset.d.ts +6 -0
- package/dist/utils/sql-dump/core/sequence-reset.d.ts.map +1 -0
- package/dist/utils/sql-dump/core/sequence-reset.js +28 -0
- package/dist/utils/sql-dump/core/sequence-reset.js.map +1 -0
- package/dist/utils/sql-dump/core/table-export.d.ts +2 -0
- package/dist/utils/sql-dump/core/table-export.d.ts.map +1 -0
- package/dist/utils/sql-dump/core/table-export.js +4 -0
- package/dist/utils/sql-dump/core/table-export.js.map +1 -0
- package/dist/utils/sql-dump/core/view-export.d.ts +2 -0
- package/dist/utils/sql-dump/core/view-export.d.ts.map +1 -0
- package/dist/utils/sql-dump/core/view-export.js +4 -0
- package/dist/utils/sql-dump/core/view-export.js.map +1 -0
- package/dist/utils/sql-dump/formatters/bulk-insert.d.ts +14 -0
- package/dist/utils/sql-dump/formatters/bulk-insert.d.ts.map +1 -0
- package/dist/utils/sql-dump/formatters/bulk-insert.js +177 -0
- package/dist/utils/sql-dump/formatters/bulk-insert.js.map +1 -0
- package/dist/utils/sql-dump/formatters/identifiers.d.ts +6 -0
- package/dist/utils/sql-dump/formatters/identifiers.d.ts.map +1 -0
- package/dist/utils/sql-dump/formatters/identifiers.js +16 -0
- package/dist/utils/sql-dump/formatters/identifiers.js.map +1 -0
- package/dist/utils/sql-dump/formatters/value-formatter.d.ts +14 -0
- package/dist/utils/sql-dump/formatters/value-formatter.d.ts.map +1 -0
- package/dist/utils/sql-dump/formatters/value-formatter.js +281 -0
- package/dist/utils/sql-dump/formatters/value-formatter.js.map +1 -0
- package/dist/utils/sql-dump/generators/controls.d.ts +10 -0
- package/dist/utils/sql-dump/generators/controls.d.ts.map +1 -0
- package/dist/utils/sql-dump/generators/controls.js +36 -0
- package/dist/utils/sql-dump/generators/controls.js.map +1 -0
- package/dist/utils/sql-dump/generators/headers.d.ts +6 -0
- package/dist/utils/sql-dump/generators/headers.d.ts.map +1 -0
- package/dist/utils/sql-dump/generators/headers.js +19 -0
- package/dist/utils/sql-dump/generators/headers.js.map +1 -0
- package/dist/utils/sql-dump/index.d.ts +14 -0
- package/dist/utils/sql-dump/index.d.ts.map +1 -0
- package/dist/utils/sql-dump/index.js +16 -0
- package/dist/utils/sql-dump/index.js.map +1 -0
- package/dist/utils/sql-dump/schema/indexes.d.ts +6 -0
- package/dist/utils/sql-dump/schema/indexes.d.ts.map +1 -0
- package/dist/utils/sql-dump/schema/indexes.js +42 -0
- package/dist/utils/sql-dump/schema/indexes.js.map +1 -0
- package/dist/utils/sql-dump/schema/primary-keys.d.ts +6 -0
- package/dist/utils/sql-dump/schema/primary-keys.d.ts.map +1 -0
- package/dist/utils/sql-dump/schema/primary-keys.js +41 -0
- package/dist/utils/sql-dump/schema/primary-keys.js.map +1 -0
- package/dist/utils/sql-dump/schema/tables.d.ts +12 -0
- package/dist/utils/sql-dump/schema/tables.d.ts.map +1 -0
- package/dist/utils/sql-dump/schema/tables.js +370 -0
- package/dist/utils/sql-dump/schema/tables.js.map +1 -0
- package/dist/utils/sql-dump/schema/views.d.ts +11 -0
- package/dist/utils/sql-dump/schema/views.d.ts.map +1 -0
- package/dist/utils/sql-dump/schema/views.js +110 -0
- package/dist/utils/sql-dump/schema/views.js.map +1 -0
- package/dist/utils/sql-dump/types.d.ts +10 -0
- package/dist/utils/sql-dump/types.d.ts.map +1 -0
- package/dist/utils/sql-dump/types.js +3 -0
- package/dist/utils/sql-dump/types.js.map +1 -0
- package/docs/AI_AGENT_GUIDE.md +2 -2
- package/docs/AUTO_FILE_TRACKING.md +0 -1
- package/docs/BATCH_VALIDATION.md +617 -0
- package/docs/DATABASE_AUTH.md +1 -2
- package/docs/DECISION_TO_TASK_MIGRATION_GUIDE.md +2 -2
- package/docs/SHARED_CONCEPTS.md +2 -2
- package/docs/SPECIALIZED_AGENTS.md +1 -1
- package/docs/TASK_ACTIONS.md +7 -7
- package/docs/TASK_MIGRATION.md +5 -5
- package/docs/TASK_SYSTEM.md +5 -5
- package/docs/TOOL_REFERENCE.md +1 -3
- package/docs/WORKFLOWS.md +1 -1
- package/docs/{DATABASE_MIGRATION.md → cli/DATABASE_MIGRATION.md} +71 -32
- package/docs/cli/DATA_EXPORT_IMPORT.md +400 -0
- package/docs/cli/README.md +227 -0
- package/package.json +7 -4
- package/dist/tools/config.d.ts +0 -58
- package/dist/tools/config.d.ts.map +0 -1
- package/dist/tools/config.js +0 -281
- package/dist/tools/config.js.map +0 -1
- package/dist/tools/constraints.d.ts +0 -49
- package/dist/tools/constraints.d.ts.map +0 -1
- package/dist/tools/constraints.js +0 -378
- package/dist/tools/constraints.js.map +0 -1
- package/dist/tools/context.d.ts +0 -208
- package/dist/tools/context.d.ts.map +0 -1
- package/dist/tools/context.js +0 -1661
- package/dist/tools/context.js.map +0 -1
- package/dist/tools/files.d.ts +0 -54
- package/dist/tools/files.d.ts.map +0 -1
- package/dist/tools/files.js +0 -478
- package/dist/tools/files.js.map +0 -1
- package/dist/tools/messaging.d.ts +0 -71
- package/dist/tools/messaging.d.ts.map +0 -1
- package/dist/tools/messaging.js +0 -483
- package/dist/tools/messaging.js.map +0 -1
- package/dist/tools/utils.d.ts +0 -70
- package/dist/tools/utils.d.ts.map +0 -1
- package/dist/tools/utils.js +0 -483
- package/dist/tools/utils.js.map +0 -1
- package/dist/utils/action-specs.d.ts +0 -46
- package/dist/utils/action-specs.d.ts.map +0 -1
- package/dist/utils/action-specs.js +0 -527
- package/dist/utils/action-specs.js.map +0 -1
- package/docs/BASEADAPTER_IMPLEMENTATION.md +0 -399
- package/docs/HELP_PREVIEW_COMPARISON.md +0 -259
- package/docs/MIGRATION_CHAIN.md +0 -293
- package/docs/MIGRATION_v2.md +0 -538
- package/docs/MIGRATION_v3.3.md +0 -602
- package/docs/MIGRATION_v3.6.0.md +0 -170
- package/docs/MULTI_PROJECT_ARCHITECTURE.md +0 -497
package/dist/tools/tasks.js
CHANGED
|
@@ -1,2042 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Task management tools for Kanban Task Watcher
|
|
3
|
-
* Implements create, update, get, list, move, link, archive, batch_create actions
|
|
4
3
|
*
|
|
5
|
-
*
|
|
6
|
-
|
|
7
|
-
import { getAdapter, getOrCreateAgent, getOrCreateTag, getOrCreateContextKey, getLayerId, getOrCreateFile } from '../database.js';
|
|
8
|
-
import { getProjectContext } from '../utils/project-context.js';
|
|
9
|
-
import { detectAndTransitionStaleTasks, autoArchiveOldDoneTasks, detectAndCompleteReviewedTasks, detectAndArchiveOnCommit } from '../utils/task-stale-detection.js';
|
|
10
|
-
import { FileWatcher } from '../watcher/index.js';
|
|
11
|
-
import { validatePriorityRange, validateLength, validateRange } from '../utils/validators.js';
|
|
12
|
-
import { logTaskCreate, logTaskStatusChange } from '../utils/activity-logging.js';
|
|
13
|
-
import { parseStringArray } from '../utils/param-parser.js';
|
|
14
|
-
import { validateActionParams, validateBatchParams } from '../utils/parameter-validator.js';
|
|
15
|
-
import { debugLog } from '../utils/debug-logger.js';
|
|
16
|
-
import connectionManager from '../utils/connection-manager.js';
|
|
17
|
-
/**
|
|
18
|
-
* Task status enum (matches m_task_statuses)
|
|
19
|
-
*/
|
|
20
|
-
const TASK_STATUS = {
|
|
21
|
-
TODO: 1,
|
|
22
|
-
IN_PROGRESS: 2,
|
|
23
|
-
WAITING_REVIEW: 3,
|
|
24
|
-
BLOCKED: 4,
|
|
25
|
-
DONE: 5,
|
|
26
|
-
ARCHIVED: 6,
|
|
27
|
-
};
|
|
28
|
-
/**
|
|
29
|
-
* Task status name mapping
|
|
30
|
-
*/
|
|
31
|
-
const STATUS_TO_ID = {
|
|
32
|
-
'todo': TASK_STATUS.TODO,
|
|
33
|
-
'in_progress': TASK_STATUS.IN_PROGRESS,
|
|
34
|
-
'waiting_review': TASK_STATUS.WAITING_REVIEW,
|
|
35
|
-
'blocked': TASK_STATUS.BLOCKED,
|
|
36
|
-
'done': TASK_STATUS.DONE,
|
|
37
|
-
'archived': TASK_STATUS.ARCHIVED,
|
|
38
|
-
};
|
|
39
|
-
const ID_TO_STATUS = {
|
|
40
|
-
[TASK_STATUS.TODO]: 'todo',
|
|
41
|
-
[TASK_STATUS.IN_PROGRESS]: 'in_progress',
|
|
42
|
-
[TASK_STATUS.WAITING_REVIEW]: 'waiting_review',
|
|
43
|
-
[TASK_STATUS.BLOCKED]: 'blocked',
|
|
44
|
-
[TASK_STATUS.DONE]: 'done',
|
|
45
|
-
[TASK_STATUS.ARCHIVED]: 'archived',
|
|
46
|
-
};
|
|
47
|
-
/**
|
|
48
|
-
* Valid status transitions
|
|
49
|
-
*/
|
|
50
|
-
const VALID_TRANSITIONS = {
|
|
51
|
-
[TASK_STATUS.TODO]: [TASK_STATUS.IN_PROGRESS, TASK_STATUS.BLOCKED],
|
|
52
|
-
[TASK_STATUS.IN_PROGRESS]: [TASK_STATUS.WAITING_REVIEW, TASK_STATUS.BLOCKED, TASK_STATUS.DONE],
|
|
53
|
-
[TASK_STATUS.WAITING_REVIEW]: [TASK_STATUS.IN_PROGRESS, TASK_STATUS.TODO, TASK_STATUS.DONE],
|
|
54
|
-
[TASK_STATUS.BLOCKED]: [TASK_STATUS.TODO, TASK_STATUS.IN_PROGRESS],
|
|
55
|
-
[TASK_STATUS.DONE]: [TASK_STATUS.ARCHIVED],
|
|
56
|
-
[TASK_STATUS.ARCHIVED]: [], // No transitions from archived
|
|
57
|
-
};
|
|
58
|
-
/**
|
|
59
|
-
* Internal helper: Create task without wrapping in transaction
|
|
60
|
-
* Used by createTask (with transaction) and batchCreateTasks (manages its own transaction)
|
|
4
|
+
* REFACTORED: This file now re-exports from the modular tasks/ directory structure.
|
|
5
|
+
* The original 2,362-line monolithic file has been split into 23 action-based files.
|
|
61
6
|
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const priority = params.priority !== undefined ? params.priority : 2;
|
|
73
|
-
validatePriorityRange(priority);
|
|
74
|
-
// Get status_id
|
|
75
|
-
const status = params.status || 'todo';
|
|
76
|
-
const statusId = STATUS_TO_ID[status];
|
|
77
|
-
if (!statusId) {
|
|
78
|
-
throw new Error(`Invalid status: ${status}. Must be one of: todo, in_progress, waiting_review, blocked, done, archived`);
|
|
79
|
-
}
|
|
80
|
-
// Validate layer if provided
|
|
81
|
-
let layerId = null;
|
|
82
|
-
if (params.layer) {
|
|
83
|
-
layerId = await getLayerId(adapter, params.layer, trx);
|
|
84
|
-
if (layerId === null) {
|
|
85
|
-
throw new Error(`Invalid layer: ${params.layer}. Must be one of: presentation, business, data, infrastructure, cross-cutting`);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
// Get or create agents
|
|
89
|
-
let assignedAgentId = null;
|
|
90
|
-
if (params.assigned_agent) {
|
|
91
|
-
assignedAgentId = await getOrCreateAgent(adapter, params.assigned_agent, trx);
|
|
92
|
-
}
|
|
93
|
-
// Default to generic agent pool if no created_by_agent provided
|
|
94
|
-
// Empty string triggers allocation from generic-N pool
|
|
95
|
-
const createdBy = params.created_by_agent || '';
|
|
96
|
-
const createdByAgentId = await getOrCreateAgent(adapter, createdBy, trx);
|
|
97
|
-
// Insert task
|
|
98
|
-
const now = Math.floor(Date.now() / 1000);
|
|
99
|
-
const [taskId] = await knex('t_tasks').insert({
|
|
100
|
-
project_id: projectId,
|
|
101
|
-
title: params.title,
|
|
102
|
-
status_id: statusId,
|
|
103
|
-
priority: priority,
|
|
104
|
-
assigned_agent_id: assignedAgentId,
|
|
105
|
-
created_by_agent_id: createdByAgentId,
|
|
106
|
-
layer_id: layerId,
|
|
107
|
-
created_ts: now,
|
|
108
|
-
updated_ts: now
|
|
109
|
-
});
|
|
110
|
-
// Process acceptance_criteria (can be string, JSON string, or array)
|
|
111
|
-
let acceptanceCriteriaString = null;
|
|
112
|
-
let acceptanceCriteriaJson = null;
|
|
113
|
-
if (params.acceptance_criteria) {
|
|
114
|
-
if (Array.isArray(params.acceptance_criteria)) {
|
|
115
|
-
// Array format - store as JSON in acceptance_criteria_json
|
|
116
|
-
acceptanceCriteriaJson = JSON.stringify(params.acceptance_criteria);
|
|
117
|
-
// Also create human-readable summary in acceptance_criteria
|
|
118
|
-
acceptanceCriteriaString = params.acceptance_criteria
|
|
119
|
-
.map((check, i) => `${i + 1}. ${check.type}: ${check.command || check.file || check.pattern || ''}`)
|
|
120
|
-
.join('\n');
|
|
121
|
-
}
|
|
122
|
-
else if (typeof params.acceptance_criteria === 'string') {
|
|
123
|
-
// Try to parse as JSON first
|
|
124
|
-
try {
|
|
125
|
-
const parsed = JSON.parse(params.acceptance_criteria);
|
|
126
|
-
if (Array.isArray(parsed)) {
|
|
127
|
-
// It's a JSON array string - store in JSON field
|
|
128
|
-
acceptanceCriteriaJson = params.acceptance_criteria;
|
|
129
|
-
// Also create human-readable summary
|
|
130
|
-
acceptanceCriteriaString = parsed
|
|
131
|
-
.map((check, i) => `${i + 1}. ${check.type}: ${check.command || check.file || check.pattern || ''}`)
|
|
132
|
-
.join('\n');
|
|
133
|
-
}
|
|
134
|
-
else {
|
|
135
|
-
// Valid JSON but not an array - store as plain text
|
|
136
|
-
acceptanceCriteriaString = params.acceptance_criteria;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
catch {
|
|
140
|
-
// Not valid JSON - store as plain text
|
|
141
|
-
acceptanceCriteriaString = params.acceptance_criteria;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
// Insert task details if provided
|
|
146
|
-
if (params.description || acceptanceCriteriaString || acceptanceCriteriaJson || params.notes) {
|
|
147
|
-
await knex('t_task_details').insert({
|
|
148
|
-
project_id: projectId,
|
|
149
|
-
task_id: Number(taskId),
|
|
150
|
-
description: params.description || null,
|
|
151
|
-
acceptance_criteria: acceptanceCriteriaString,
|
|
152
|
-
acceptance_criteria_json: acceptanceCriteriaJson,
|
|
153
|
-
notes: params.notes || null
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
// Insert tags if provided
|
|
157
|
-
if (params.tags && params.tags.length > 0) {
|
|
158
|
-
// Parse tags - handle MCP SDK converting JSON string to char array
|
|
159
|
-
let tagsParsed;
|
|
160
|
-
if (typeof params.tags === 'string') {
|
|
161
|
-
// String - try to parse as JSON
|
|
162
|
-
try {
|
|
163
|
-
tagsParsed = JSON.parse(params.tags);
|
|
164
|
-
}
|
|
165
|
-
catch {
|
|
166
|
-
// If not valid JSON, treat as single tag name
|
|
167
|
-
tagsParsed = [params.tags];
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
else if (Array.isArray(params.tags)) {
|
|
171
|
-
// Check if it's an array of single characters (MCP SDK bug)
|
|
172
|
-
// Example: ['[', '"', 't', 'e', 's', 't', 'i', 'n', 'g', '"', ']']
|
|
173
|
-
if (params.tags.every((item) => typeof item === 'string' && item.length === 1)) {
|
|
174
|
-
// Join characters back into string and parse JSON
|
|
175
|
-
const jsonString = params.tags.join('');
|
|
176
|
-
try {
|
|
177
|
-
tagsParsed = JSON.parse(jsonString);
|
|
178
|
-
}
|
|
179
|
-
catch (e) {
|
|
180
|
-
const errMsg = e instanceof Error ? e.message : String(e);
|
|
181
|
-
throw new Error(`Invalid tags format: ${jsonString}. ${errMsg}`);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
else {
|
|
185
|
-
// Normal array of tag names
|
|
186
|
-
tagsParsed = params.tags;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
else {
|
|
190
|
-
throw new Error('Parameter "tags" must be a string or array');
|
|
191
|
-
}
|
|
192
|
-
for (const tagName of tagsParsed) {
|
|
193
|
-
const tagId = await getOrCreateTag(adapter, projectId, tagName, trx); // v3.7.3: pass projectId
|
|
194
|
-
await knex('t_task_tags').insert({
|
|
195
|
-
project_id: projectId,
|
|
196
|
-
task_id: Number(taskId),
|
|
197
|
-
tag_id: tagId
|
|
198
|
-
}).onConflict(['project_id', 'task_id', 'tag_id']).ignore();
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
// Activity logging (replaces triggers)
|
|
202
|
-
await logTaskCreate(knex, {
|
|
203
|
-
task_id: Number(taskId),
|
|
204
|
-
title: params.title,
|
|
205
|
-
agent_id: createdByAgentId,
|
|
206
|
-
layer_id: layerId || undefined
|
|
207
|
-
});
|
|
208
|
-
// Link files and register with watcher if watch_files provided (v3.4.1)
|
|
209
|
-
if (params.watch_files && params.watch_files.length > 0) {
|
|
210
|
-
// Parse watch_files - handle MCP SDK converting JSON string to char array
|
|
211
|
-
let watchFilesParsed;
|
|
212
|
-
if (typeof params.watch_files === 'string') {
|
|
213
|
-
// String - try to parse as JSON
|
|
214
|
-
try {
|
|
215
|
-
watchFilesParsed = JSON.parse(params.watch_files);
|
|
216
|
-
}
|
|
217
|
-
catch {
|
|
218
|
-
// If not valid JSON, treat as single file path
|
|
219
|
-
watchFilesParsed = [params.watch_files];
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
else if (Array.isArray(params.watch_files)) {
|
|
223
|
-
// Check if it's an array of single characters (MCP SDK bug)
|
|
224
|
-
// Example: ['[', '"', 'f', 'i', 'l', 'e', '.', 't', 'x', 't', '"', ']']
|
|
225
|
-
if (params.watch_files.every((item) => typeof item === 'string' && item.length === 1)) {
|
|
226
|
-
// Join characters back into string and parse JSON
|
|
227
|
-
const jsonString = params.watch_files.join('');
|
|
228
|
-
try {
|
|
229
|
-
watchFilesParsed = JSON.parse(jsonString);
|
|
230
|
-
}
|
|
231
|
-
catch (e) {
|
|
232
|
-
const errMsg = e instanceof Error ? e.message : String(e);
|
|
233
|
-
throw new Error(`Invalid watch_files format: ${jsonString}. ${errMsg}`);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
else {
|
|
237
|
-
// Normal array of file paths
|
|
238
|
-
watchFilesParsed = params.watch_files;
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
else {
|
|
242
|
-
throw new Error('Parameter "watch_files" must be a string or array');
|
|
243
|
-
}
|
|
244
|
-
for (const filePath of watchFilesParsed) {
|
|
245
|
-
const fileId = await getOrCreateFile(adapter, projectId, filePath, trx); // v3.7.3: pass projectId
|
|
246
|
-
await knex('t_task_file_links').insert({
|
|
247
|
-
project_id: projectId,
|
|
248
|
-
task_id: Number(taskId),
|
|
249
|
-
file_id: fileId
|
|
250
|
-
}).onConflict(['project_id', 'task_id', 'file_id']).ignore();
|
|
251
|
-
}
|
|
252
|
-
// Register files with watcher for auto-tracking
|
|
253
|
-
try {
|
|
254
|
-
const watcher = FileWatcher.getInstance();
|
|
255
|
-
for (const filePath of watchFilesParsed) {
|
|
256
|
-
watcher.registerFile(filePath, Number(taskId), params.title, status);
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
catch (error) {
|
|
260
|
-
// Watcher may not be initialized yet, ignore
|
|
261
|
-
debugLog('WARN', 'Could not register files with watcher', { error });
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
return {
|
|
265
|
-
success: true,
|
|
266
|
-
task_id: Number(taskId),
|
|
267
|
-
title: params.title,
|
|
268
|
-
status: status,
|
|
269
|
-
message: `Task "${params.title}" created successfully`
|
|
270
|
-
};
|
|
271
|
-
}
|
|
272
|
-
/**
|
|
273
|
-
* Create a new task
|
|
274
|
-
*/
|
|
275
|
-
export async function createTask(params, adapter) {
|
|
276
|
-
validateActionParams('task', 'create', params);
|
|
277
|
-
const actualAdapter = adapter ?? getAdapter();
|
|
278
|
-
// Validate required parameters
|
|
279
|
-
if (!params.title || params.title.trim() === '') {
|
|
280
|
-
throw new Error('Parameter "title" is required and cannot be empty');
|
|
281
|
-
}
|
|
282
|
-
validateLength(params.title, 'Parameter "title"', 200);
|
|
283
|
-
try {
|
|
284
|
-
return await connectionManager.executeWithRetry(async () => {
|
|
285
|
-
return await actualAdapter.transaction(async (trx) => {
|
|
286
|
-
return await createTaskInternal(params, actualAdapter, trx);
|
|
287
|
-
});
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
catch (error) {
|
|
291
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
292
|
-
// Preserve validation errors (they already contain helpful information)
|
|
293
|
-
if (message.startsWith('{') && message.includes('"error"')) {
|
|
294
|
-
throw error;
|
|
295
|
-
}
|
|
296
|
-
throw new Error(`Failed to create task: ${message}`);
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
/**
|
|
300
|
-
* Update task metadata
|
|
301
|
-
*/
|
|
302
|
-
export async function updateTask(params, adapter) {
|
|
303
|
-
validateActionParams('task', 'update', params);
|
|
304
|
-
const actualAdapter = adapter ?? getAdapter();
|
|
305
|
-
// Validate required parameters
|
|
306
|
-
if (!params.task_id) {
|
|
307
|
-
throw new Error('Parameter "task_id" is required');
|
|
308
|
-
}
|
|
309
|
-
// Fail-fast project_id validation (Constraint #29)
|
|
310
|
-
const projectId = getProjectContext().getProjectId();
|
|
311
|
-
try {
|
|
312
|
-
return await connectionManager.executeWithRetry(async () => {
|
|
313
|
-
return await actualAdapter.transaction(async (trx) => {
|
|
314
|
-
const knex = actualAdapter.getKnex();
|
|
315
|
-
// Check if task exists with project_id isolation
|
|
316
|
-
const taskExists = await trx('t_tasks')
|
|
317
|
-
.where({ id: params.task_id, project_id: projectId })
|
|
318
|
-
.first();
|
|
319
|
-
if (!taskExists) {
|
|
320
|
-
throw new Error(`Task with id ${params.task_id} not found`);
|
|
321
|
-
}
|
|
322
|
-
// Build update data dynamically
|
|
323
|
-
const updateData = {};
|
|
324
|
-
if (params.title !== undefined) {
|
|
325
|
-
if (params.title.trim() === '') {
|
|
326
|
-
throw new Error('Parameter "title" cannot be empty');
|
|
327
|
-
}
|
|
328
|
-
validateLength(params.title, 'Parameter "title"', 200);
|
|
329
|
-
updateData.title = params.title;
|
|
330
|
-
}
|
|
331
|
-
if (params.priority !== undefined) {
|
|
332
|
-
validatePriorityRange(params.priority);
|
|
333
|
-
updateData.priority = params.priority;
|
|
334
|
-
}
|
|
335
|
-
if (params.assigned_agent !== undefined) {
|
|
336
|
-
const agentId = await getOrCreateAgent(actualAdapter, params.assigned_agent, trx);
|
|
337
|
-
updateData.assigned_agent_id = agentId;
|
|
338
|
-
}
|
|
339
|
-
if (params.layer !== undefined) {
|
|
340
|
-
const layerId = await getLayerId(actualAdapter, params.layer, trx);
|
|
341
|
-
if (layerId === null) {
|
|
342
|
-
throw new Error(`Invalid layer: ${params.layer}. Must be one of: presentation, business, data, infrastructure, cross-cutting`);
|
|
343
|
-
}
|
|
344
|
-
updateData.layer_id = layerId;
|
|
345
|
-
}
|
|
346
|
-
// Update t_tasks if any updates (with project_id isolation)
|
|
347
|
-
if (Object.keys(updateData).length > 0) {
|
|
348
|
-
await trx('t_tasks')
|
|
349
|
-
.where({ id: params.task_id, project_id: projectId })
|
|
350
|
-
.update(updateData);
|
|
351
|
-
// TODO: Add activity logging for updates if needed
|
|
352
|
-
}
|
|
353
|
-
// Update t_task_details if any detail fields provided
|
|
354
|
-
if (params.description !== undefined || params.acceptance_criteria !== undefined || params.notes !== undefined) {
|
|
355
|
-
// Process acceptance_criteria (can be string or array)
|
|
356
|
-
let acceptanceCriteriaString = undefined;
|
|
357
|
-
let acceptanceCriteriaJson = undefined;
|
|
358
|
-
if (params.acceptance_criteria !== undefined) {
|
|
359
|
-
if (Array.isArray(params.acceptance_criteria)) {
|
|
360
|
-
// Array format - store as JSON in acceptance_criteria_json
|
|
361
|
-
acceptanceCriteriaJson = JSON.stringify(params.acceptance_criteria);
|
|
362
|
-
// Also create human-readable summary in acceptance_criteria
|
|
363
|
-
acceptanceCriteriaString = params.acceptance_criteria
|
|
364
|
-
.map((check, i) => `${i + 1}. ${check.type}: ${check.command || check.file || check.pattern || ''}`)
|
|
365
|
-
.join('\n');
|
|
366
|
-
}
|
|
367
|
-
else if (typeof params.acceptance_criteria === 'string') {
|
|
368
|
-
// Try to parse as JSON first
|
|
369
|
-
try {
|
|
370
|
-
const parsed = JSON.parse(params.acceptance_criteria);
|
|
371
|
-
if (Array.isArray(parsed)) {
|
|
372
|
-
// It's a JSON array string - store in JSON field
|
|
373
|
-
acceptanceCriteriaJson = params.acceptance_criteria;
|
|
374
|
-
// Also create human-readable summary
|
|
375
|
-
acceptanceCriteriaString = parsed
|
|
376
|
-
.map((check, i) => `${i + 1}. ${check.type}: ${check.command || check.file || check.pattern || ''}`)
|
|
377
|
-
.join('\n');
|
|
378
|
-
}
|
|
379
|
-
else {
|
|
380
|
-
// Valid JSON but not an array - store as plain text
|
|
381
|
-
acceptanceCriteriaString = params.acceptance_criteria || null;
|
|
382
|
-
acceptanceCriteriaJson = null;
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
catch {
|
|
386
|
-
// Not valid JSON - store as plain text
|
|
387
|
-
acceptanceCriteriaString = params.acceptance_criteria || null;
|
|
388
|
-
acceptanceCriteriaJson = null;
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
// Check if details exist (with project_id isolation)
|
|
393
|
-
const detailsExist = await trx('t_task_details')
|
|
394
|
-
.where({ task_id: params.task_id, project_id: projectId })
|
|
395
|
-
.first();
|
|
396
|
-
const detailsUpdate = {};
|
|
397
|
-
if (params.description !== undefined) {
|
|
398
|
-
detailsUpdate.description = params.description || null;
|
|
399
|
-
}
|
|
400
|
-
if (acceptanceCriteriaString !== undefined) {
|
|
401
|
-
detailsUpdate.acceptance_criteria = acceptanceCriteriaString;
|
|
402
|
-
}
|
|
403
|
-
if (acceptanceCriteriaJson !== undefined) {
|
|
404
|
-
detailsUpdate.acceptance_criteria_json = acceptanceCriteriaJson;
|
|
405
|
-
}
|
|
406
|
-
if (params.notes !== undefined) {
|
|
407
|
-
detailsUpdate.notes = params.notes || null;
|
|
408
|
-
}
|
|
409
|
-
if (detailsExist && Object.keys(detailsUpdate).length > 0) {
|
|
410
|
-
// Update existing details (with project_id isolation)
|
|
411
|
-
await trx('t_task_details')
|
|
412
|
-
.where({ task_id: params.task_id, project_id: projectId })
|
|
413
|
-
.update(detailsUpdate);
|
|
414
|
-
}
|
|
415
|
-
else if (!detailsExist) {
|
|
416
|
-
// Insert new details
|
|
417
|
-
await trx('t_task_details').insert({
|
|
418
|
-
project_id: projectId,
|
|
419
|
-
task_id: params.task_id,
|
|
420
|
-
description: params.description || null,
|
|
421
|
-
acceptance_criteria: acceptanceCriteriaString !== undefined ? acceptanceCriteriaString : null,
|
|
422
|
-
acceptance_criteria_json: acceptanceCriteriaJson !== undefined ? acceptanceCriteriaJson : null,
|
|
423
|
-
notes: params.notes || null
|
|
424
|
-
});
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
// Handle watch_files if provided (v3.4.1)
|
|
428
|
-
if (params.watch_files && params.watch_files.length > 0) {
|
|
429
|
-
// Parse watch_files - handle MCP SDK converting JSON string to char array
|
|
430
|
-
let watchFilesParsed;
|
|
431
|
-
if (typeof params.watch_files === 'string') {
|
|
432
|
-
// String - try to parse as JSON
|
|
433
|
-
try {
|
|
434
|
-
watchFilesParsed = JSON.parse(params.watch_files);
|
|
435
|
-
}
|
|
436
|
-
catch {
|
|
437
|
-
// If not valid JSON, treat as single file path
|
|
438
|
-
watchFilesParsed = [params.watch_files];
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
else if (Array.isArray(params.watch_files)) {
|
|
442
|
-
// Check if it's an array of single characters (MCP SDK bug)
|
|
443
|
-
if (params.watch_files.every((item) => typeof item === 'string' && item.length === 1)) {
|
|
444
|
-
// Join characters back into string and parse JSON
|
|
445
|
-
const jsonString = params.watch_files.join('');
|
|
446
|
-
try {
|
|
447
|
-
watchFilesParsed = JSON.parse(jsonString);
|
|
448
|
-
}
|
|
449
|
-
catch {
|
|
450
|
-
throw new Error(`Invalid watch_files format: ${jsonString}`);
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
else {
|
|
454
|
-
// Normal array of file paths
|
|
455
|
-
watchFilesParsed = params.watch_files;
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
else {
|
|
459
|
-
throw new Error('Parameter "watch_files" must be a string or array');
|
|
460
|
-
}
|
|
461
|
-
for (const filePath of watchFilesParsed) {
|
|
462
|
-
const fileId = await getOrCreateFile(actualAdapter, projectId, filePath, trx); // v3.7.3: pass projectId
|
|
463
|
-
await trx('t_task_file_links').insert({
|
|
464
|
-
project_id: projectId,
|
|
465
|
-
task_id: params.task_id,
|
|
466
|
-
file_id: fileId
|
|
467
|
-
}).onConflict(['project_id', 'task_id', 'file_id']).ignore();
|
|
468
|
-
}
|
|
469
|
-
// Register files with watcher for auto-tracking
|
|
470
|
-
try {
|
|
471
|
-
const taskData = await trx('t_tasks as t')
|
|
472
|
-
.join('m_task_statuses as s', 't.status_id', 's.id')
|
|
473
|
-
.where({ 't.id': params.task_id, 't.project_id': projectId })
|
|
474
|
-
.select('t.title', 's.name as status')
|
|
475
|
-
.first();
|
|
476
|
-
if (taskData) {
|
|
477
|
-
const watcher = FileWatcher.getInstance();
|
|
478
|
-
for (const filePath of watchFilesParsed) {
|
|
479
|
-
watcher.registerFile(filePath, params.task_id, taskData.title, taskData.status);
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
catch (error) {
|
|
484
|
-
// Watcher may not be initialized yet, ignore
|
|
485
|
-
debugLog('WARN', 'Could not register files with watcher', { error });
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
return {
|
|
489
|
-
success: true,
|
|
490
|
-
task_id: params.task_id,
|
|
491
|
-
message: `Task ${params.task_id} updated successfully`
|
|
492
|
-
};
|
|
493
|
-
});
|
|
494
|
-
});
|
|
495
|
-
}
|
|
496
|
-
catch (error) {
|
|
497
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
498
|
-
// Preserve validation errors (they already contain helpful information)
|
|
499
|
-
if (message.startsWith('{') && message.includes('"error"')) {
|
|
500
|
-
throw error; // Re-throw validation error as-is
|
|
501
|
-
}
|
|
502
|
-
throw new Error(`Failed to update task: ${message}`);
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
/**
|
|
506
|
-
* Internal helper: Query task dependencies (used by getTask and getDependencies)
|
|
507
|
-
*/
|
|
508
|
-
async function queryTaskDependencies(adapter, taskId, includeDetails = false) {
|
|
509
|
-
const knex = adapter.getKnex();
|
|
510
|
-
const projectId = getProjectContext().getProjectId();
|
|
511
|
-
// Build query based on include_details flag
|
|
512
|
-
const selectFields = includeDetails
|
|
513
|
-
? [
|
|
514
|
-
't.id',
|
|
515
|
-
't.title',
|
|
516
|
-
's.name as status',
|
|
517
|
-
't.priority',
|
|
518
|
-
'aa.name as assigned_to',
|
|
519
|
-
't.created_ts',
|
|
520
|
-
't.updated_ts',
|
|
521
|
-
'td.description'
|
|
522
|
-
]
|
|
523
|
-
: [
|
|
524
|
-
't.id',
|
|
525
|
-
't.title',
|
|
526
|
-
's.name as status',
|
|
527
|
-
't.priority'
|
|
528
|
-
];
|
|
529
|
-
// Get blockers (tasks that block this task) - with project_id isolation
|
|
530
|
-
let blockersQuery = knex('t_tasks as t')
|
|
531
|
-
.join('t_task_dependencies as d', 't.id', 'd.blocker_task_id')
|
|
532
|
-
.leftJoin('m_task_statuses as s', 't.status_id', 's.id')
|
|
533
|
-
.leftJoin('m_agents as aa', 't.assigned_agent_id', 'aa.id')
|
|
534
|
-
.where({ 'd.blocked_task_id': taskId, 'd.project_id': projectId, 't.project_id': projectId })
|
|
535
|
-
.select(selectFields);
|
|
536
|
-
if (includeDetails) {
|
|
537
|
-
blockersQuery = blockersQuery
|
|
538
|
-
.leftJoin('t_task_details as td', function () {
|
|
539
|
-
this.on('t.id', '=', 'td.task_id')
|
|
540
|
-
.andOn('t.project_id', '=', 'td.project_id');
|
|
541
|
-
});
|
|
542
|
-
}
|
|
543
|
-
const blockers = await blockersQuery;
|
|
544
|
-
// Get blocking (tasks this task blocks) - with project_id isolation
|
|
545
|
-
let blockingQuery = knex('t_tasks as t')
|
|
546
|
-
.join('t_task_dependencies as d', 't.id', 'd.blocked_task_id')
|
|
547
|
-
.leftJoin('m_task_statuses as s', 't.status_id', 's.id')
|
|
548
|
-
.leftJoin('m_agents as aa', 't.assigned_agent_id', 'aa.id')
|
|
549
|
-
.where({ 'd.blocker_task_id': taskId, 'd.project_id': projectId, 't.project_id': projectId })
|
|
550
|
-
.select(selectFields);
|
|
551
|
-
if (includeDetails) {
|
|
552
|
-
blockingQuery = blockingQuery
|
|
553
|
-
.leftJoin('t_task_details as td', function () {
|
|
554
|
-
this.on('t.id', '=', 'td.task_id')
|
|
555
|
-
.andOn('t.project_id', '=', 'td.project_id');
|
|
556
|
-
});
|
|
557
|
-
}
|
|
558
|
-
const blocking = await blockingQuery;
|
|
559
|
-
return { blockers, blocking };
|
|
560
|
-
}
|
|
561
|
-
/**
|
|
562
|
-
* Get full task details
|
|
563
|
-
*/
|
|
564
|
-
export async function getTask(params, adapter) {
|
|
565
|
-
validateActionParams('task', 'get', params);
|
|
566
|
-
const actualAdapter = adapter ?? getAdapter();
|
|
567
|
-
const knex = actualAdapter.getKnex();
|
|
568
|
-
if (!params.task_id) {
|
|
569
|
-
throw new Error('Parameter "task_id" is required');
|
|
570
|
-
}
|
|
571
|
-
// Fail-fast project_id validation (Constraint #29)
|
|
572
|
-
const projectId = getProjectContext().getProjectId();
|
|
573
|
-
try {
|
|
574
|
-
// Get task with details (with project_id isolation)
|
|
575
|
-
const task = await knex('t_tasks as t')
|
|
576
|
-
.leftJoin('m_task_statuses as s', 't.status_id', 's.id')
|
|
577
|
-
.leftJoin('m_agents as aa', 't.assigned_agent_id', 'aa.id')
|
|
578
|
-
.leftJoin('m_agents as ca', 't.created_by_agent_id', 'ca.id')
|
|
579
|
-
.leftJoin('m_layers as l', 't.layer_id', 'l.id')
|
|
580
|
-
.leftJoin('t_task_details as td', function () {
|
|
581
|
-
this.on('t.id', '=', 'td.task_id')
|
|
582
|
-
.andOn('t.project_id', '=', 'td.project_id');
|
|
583
|
-
})
|
|
584
|
-
.where({ 't.id': params.task_id, 't.project_id': projectId })
|
|
585
|
-
.select('t.id', 't.title', 's.name as status', 't.priority', 'aa.name as assigned_to', 'ca.name as created_by', 'l.name as layer', 't.created_ts', 't.updated_ts', 't.completed_ts', 'td.description', 'td.acceptance_criteria', 'td.notes')
|
|
586
|
-
.first();
|
|
587
|
-
if (!task) {
|
|
588
|
-
return {
|
|
589
|
-
found: false,
|
|
590
|
-
task_id: params.task_id
|
|
591
|
-
};
|
|
592
|
-
}
|
|
593
|
-
// Get tags
|
|
594
|
-
const tags = await knex('t_task_tags as tt')
|
|
595
|
-
.join('m_tags as tg', 'tt.tag_id', 'tg.id')
|
|
596
|
-
.where('tt.task_id', params.task_id)
|
|
597
|
-
.select('tg.name')
|
|
598
|
-
.then(rows => rows.map((row) => row.name));
|
|
599
|
-
// Get decision links
|
|
600
|
-
const decisions = await knex('t_task_decision_links as tdl')
|
|
601
|
-
.join('m_context_keys as ck', 'tdl.decision_key_id', 'ck.id')
|
|
602
|
-
.where('tdl.task_id', params.task_id)
|
|
603
|
-
.select('ck.key', 'tdl.link_type');
|
|
604
|
-
// Get constraint links
|
|
605
|
-
const constraints = await knex('t_task_constraint_links as tcl')
|
|
606
|
-
.join('t_constraints as c', 'tcl.constraint_id', 'c.id')
|
|
607
|
-
.where('tcl.task_id', params.task_id)
|
|
608
|
-
.select('c.id', 'c.constraint_text');
|
|
609
|
-
// Get file links
|
|
610
|
-
const files = await knex('t_task_file_links as tfl')
|
|
611
|
-
.join('m_files as f', 'tfl.file_id', 'f.id')
|
|
612
|
-
.where('tfl.task_id', params.task_id)
|
|
613
|
-
.select('f.path')
|
|
614
|
-
.then(rows => rows.map((row) => row.path));
|
|
615
|
-
// Build result
|
|
616
|
-
const result = {
|
|
617
|
-
found: true,
|
|
618
|
-
task: {
|
|
619
|
-
...task,
|
|
620
|
-
tags: tags,
|
|
621
|
-
linked_decisions: decisions,
|
|
622
|
-
linked_constraints: constraints,
|
|
623
|
-
linked_files: files
|
|
624
|
-
}
|
|
625
|
-
};
|
|
626
|
-
// Include dependencies if requested (token-efficient, metadata-only)
|
|
627
|
-
if (params.include_dependencies) {
|
|
628
|
-
const deps = await queryTaskDependencies(actualAdapter, params.task_id, false);
|
|
629
|
-
result.task.dependencies = {
|
|
630
|
-
blockers: deps.blockers,
|
|
631
|
-
blocking: deps.blocking
|
|
632
|
-
};
|
|
633
|
-
}
|
|
634
|
-
return result;
|
|
635
|
-
}
|
|
636
|
-
catch (error) {
|
|
637
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
638
|
-
throw new Error(`Failed to get task: ${message}`);
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
/**
|
|
642
|
-
* List tasks (token-efficient, no descriptions)
|
|
643
|
-
*/
|
|
644
|
-
export async function listTasks(params = {}, adapter) {
|
|
645
|
-
validateActionParams('task', 'list', params);
|
|
646
|
-
const actualAdapter = adapter ?? getAdapter();
|
|
647
|
-
const knex = actualAdapter.getKnex();
|
|
648
|
-
// Get current project ID for filtering (Constraint #22)
|
|
649
|
-
const projectId = getProjectContext().getProjectId();
|
|
650
|
-
try {
|
|
651
|
-
// Run auto-stale detection, git-aware completion, and auto-archive before listing
|
|
652
|
-
const transitionCount = await detectAndTransitionStaleTasks(actualAdapter);
|
|
653
|
-
const gitCompletedCount = await detectAndCompleteReviewedTasks(actualAdapter);
|
|
654
|
-
const gitArchivedCount = await detectAndArchiveOnCommit(actualAdapter);
|
|
655
|
-
const archiveCount = await autoArchiveOldDoneTasks(actualAdapter);
|
|
656
|
-
// Build query with optional dependency counts
|
|
657
|
-
let query;
|
|
658
|
-
if (params.include_dependency_counts) {
|
|
659
|
-
// Include dependency counts with LEFT JOINs
|
|
660
|
-
const blockersCTE = knex('t_task_dependencies')
|
|
661
|
-
.select('blocked_task_id')
|
|
662
|
-
.count('* as blocked_by_count')
|
|
663
|
-
.groupBy('blocked_task_id')
|
|
664
|
-
.as('blockers');
|
|
665
|
-
const blockingCTE = knex('t_task_dependencies')
|
|
666
|
-
.select('blocker_task_id')
|
|
667
|
-
.count('* as blocking_count')
|
|
668
|
-
.groupBy('blocker_task_id')
|
|
669
|
-
.as('blocking');
|
|
670
|
-
query = knex('v_task_board as vt')
|
|
671
|
-
.leftJoin(blockersCTE, 'vt.id', 'blockers.blocked_task_id')
|
|
672
|
-
.leftJoin(blockingCTE, 'vt.id', 'blocking.blocker_task_id')
|
|
673
|
-
.select('vt.*', knex.raw('COALESCE(blockers.blocked_by_count, 0) as blocked_by_count'), knex.raw('COALESCE(blocking.blocking_count, 0) as blocking_count'));
|
|
674
|
-
}
|
|
675
|
-
else {
|
|
676
|
-
// Standard query without dependency counts
|
|
677
|
-
query = knex('v_task_board');
|
|
678
|
-
}
|
|
679
|
-
// Filter by project_id (Constraint #22: Multi-project isolation)
|
|
680
|
-
query = query.where(params.include_dependency_counts ? 'vt.project_id' : 'project_id', projectId);
|
|
681
|
-
// Filter by status
|
|
682
|
-
if (params.status) {
|
|
683
|
-
if (!STATUS_TO_ID[params.status]) {
|
|
684
|
-
throw new Error(`Invalid status: ${params.status}. Must be one of: todo, in_progress, waiting_review, blocked, done, archived`);
|
|
685
|
-
}
|
|
686
|
-
query = query.where(params.include_dependency_counts ? 'vt.status' : 'status', params.status);
|
|
687
|
-
}
|
|
688
|
-
// Filter by assigned agent
|
|
689
|
-
if (params.assigned_agent) {
|
|
690
|
-
query = query.where(params.include_dependency_counts ? 'vt.assigned_to' : 'assigned_to', params.assigned_agent);
|
|
691
|
-
}
|
|
692
|
-
// Filter by layer
|
|
693
|
-
if (params.layer) {
|
|
694
|
-
query = query.where(params.include_dependency_counts ? 'vt.layer' : 'layer', params.layer);
|
|
695
|
-
}
|
|
696
|
-
// Filter by tags
|
|
697
|
-
if (params.tags && params.tags.length > 0) {
|
|
698
|
-
// Parse tags (handles both arrays and JSON strings from MCP)
|
|
699
|
-
const tags = parseStringArray(params.tags);
|
|
700
|
-
for (const tag of tags) {
|
|
701
|
-
query = query.where(params.include_dependency_counts ? 'vt.tags' : 'tags', 'like', `%${tag}%`);
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
// Order by updated timestamp (most recent first)
|
|
705
|
-
query = query.orderBy(params.include_dependency_counts ? 'vt.updated_ts' : 'updated_ts', 'desc');
|
|
706
|
-
// Pagination
|
|
707
|
-
const limit = params.limit !== undefined ? params.limit : 50;
|
|
708
|
-
const offset = params.offset || 0;
|
|
709
|
-
validateRange(limit, 'Parameter "limit"', 0, 100);
|
|
710
|
-
validateRange(offset, 'Parameter "offset"', 0, Number.MAX_SAFE_INTEGER);
|
|
711
|
-
query = query.limit(limit).offset(offset);
|
|
712
|
-
// Execute query
|
|
713
|
-
const rows = await query;
|
|
714
|
-
return {
|
|
715
|
-
tasks: rows,
|
|
716
|
-
count: rows.length,
|
|
717
|
-
stale_tasks_transitioned: transitionCount,
|
|
718
|
-
git_auto_completed: gitCompletedCount,
|
|
719
|
-
git_archived: gitArchivedCount,
|
|
720
|
-
archived_tasks: archiveCount
|
|
721
|
-
};
|
|
722
|
-
}
|
|
723
|
-
catch (error) {
|
|
724
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
725
|
-
throw new Error(`Failed to list tasks: ${message}`);
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
/**
|
|
729
|
-
* Move task to different status
|
|
730
|
-
*/
|
|
731
|
-
export async function moveTask(params, adapter) {
|
|
732
|
-
validateActionParams('task', 'move', params);
|
|
733
|
-
const actualAdapter = adapter ?? getAdapter();
|
|
734
|
-
const knex = actualAdapter.getKnex();
|
|
735
|
-
if (!params.task_id) {
|
|
736
|
-
throw new Error('Parameter "task_id" is required');
|
|
737
|
-
}
|
|
738
|
-
if (!params.new_status) {
|
|
739
|
-
throw new Error('Parameter "new_status" is required');
|
|
740
|
-
}
|
|
741
|
-
try {
|
|
742
|
-
// Run auto-stale detection and auto-archive before move
|
|
743
|
-
await detectAndTransitionStaleTasks(actualAdapter);
|
|
744
|
-
await autoArchiveOldDoneTasks(actualAdapter);
|
|
745
|
-
return await connectionManager.executeWithRetry(async () => {
|
|
746
|
-
return await actualAdapter.transaction(async (trx) => {
|
|
747
|
-
// Get current status
|
|
748
|
-
const taskRow = await trx('t_tasks')
|
|
749
|
-
.where({ id: params.task_id })
|
|
750
|
-
.select('status_id')
|
|
751
|
-
.first();
|
|
752
|
-
if (!taskRow) {
|
|
753
|
-
throw new Error(`Task with id ${params.task_id} not found`);
|
|
754
|
-
}
|
|
755
|
-
const currentStatusId = taskRow.status_id;
|
|
756
|
-
const newStatusId = STATUS_TO_ID[params.new_status];
|
|
757
|
-
if (!newStatusId) {
|
|
758
|
-
throw new Error(`Invalid new_status: ${params.new_status}. Must be one of: todo, in_progress, waiting_review, blocked, done, archived`);
|
|
759
|
-
}
|
|
760
|
-
// Check if transition is valid
|
|
761
|
-
const validNextStatuses = VALID_TRANSITIONS[currentStatusId] || [];
|
|
762
|
-
if (!validNextStatuses.includes(newStatusId)) {
|
|
763
|
-
throw new Error(`Invalid transition from ${ID_TO_STATUS[currentStatusId]} to ${params.new_status}. ` +
|
|
764
|
-
`Valid transitions: ${validNextStatuses.map(id => ID_TO_STATUS[id]).join(', ')}`);
|
|
765
|
-
}
|
|
766
|
-
// Update status
|
|
767
|
-
const updateData = {
|
|
768
|
-
status_id: newStatusId
|
|
769
|
-
};
|
|
770
|
-
// Set completed_ts when moving to done
|
|
771
|
-
if (newStatusId === TASK_STATUS.DONE) {
|
|
772
|
-
updateData.completed_ts = Math.floor(Date.now() / 1000);
|
|
773
|
-
}
|
|
774
|
-
await trx('t_tasks')
|
|
775
|
-
.where({ id: params.task_id })
|
|
776
|
-
.update(updateData);
|
|
777
|
-
// Activity logging (replaces trigger)
|
|
778
|
-
// Note: Using system agent (id=1) for status changes
|
|
779
|
-
// In a real implementation, you'd pass the actual agent_id who made the change
|
|
780
|
-
const systemAgentId = 1;
|
|
781
|
-
await logTaskStatusChange(trx, {
|
|
782
|
-
task_id: params.task_id,
|
|
783
|
-
old_status: currentStatusId,
|
|
784
|
-
new_status: newStatusId,
|
|
785
|
-
agent_id: systemAgentId
|
|
786
|
-
});
|
|
787
|
-
// Update watcher if moving to done or archived (stop watching)
|
|
788
|
-
if (params.new_status === 'done' || params.new_status === 'archived') {
|
|
789
|
-
try {
|
|
790
|
-
const watcher = FileWatcher.getInstance();
|
|
791
|
-
watcher.unregisterTask(params.task_id);
|
|
792
|
-
}
|
|
793
|
-
catch (error) {
|
|
794
|
-
// Watcher may not be initialized, ignore
|
|
795
|
-
}
|
|
796
|
-
}
|
|
797
|
-
return {
|
|
798
|
-
success: true,
|
|
799
|
-
task_id: params.task_id,
|
|
800
|
-
old_status: ID_TO_STATUS[currentStatusId],
|
|
801
|
-
new_status: params.new_status,
|
|
802
|
-
message: `Task ${params.task_id} moved from ${ID_TO_STATUS[currentStatusId]} to ${params.new_status}`
|
|
803
|
-
};
|
|
804
|
-
});
|
|
805
|
-
});
|
|
806
|
-
}
|
|
807
|
-
catch (error) {
|
|
808
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
809
|
-
// Preserve validation errors (they already contain helpful information)
|
|
810
|
-
if (message.startsWith('{') && message.includes('"error"')) {
|
|
811
|
-
throw error;
|
|
812
|
-
}
|
|
813
|
-
throw new Error(`Failed to move task: ${message}`);
|
|
814
|
-
}
|
|
815
|
-
}
|
|
816
|
-
/**
|
|
817
|
-
* Link task to decision/constraint/file
|
|
818
|
-
*/
|
|
819
|
-
export async function linkTask(params, adapter) {
|
|
820
|
-
validateActionParams('task', 'link', params);
|
|
821
|
-
const actualAdapter = adapter ?? getAdapter();
|
|
822
|
-
const knex = actualAdapter.getKnex();
|
|
823
|
-
// Get project context (v3.7.3)
|
|
824
|
-
const projectId = getProjectContext().getProjectId();
|
|
825
|
-
if (!params.task_id) {
|
|
826
|
-
throw new Error('Parameter "task_id" is required');
|
|
827
|
-
}
|
|
828
|
-
if (!params.link_type) {
|
|
829
|
-
throw new Error('Parameter "link_type" is required');
|
|
830
|
-
}
|
|
831
|
-
if (params.target_id === undefined || params.target_id === null) {
|
|
832
|
-
throw new Error('Parameter "target_id" is required');
|
|
833
|
-
}
|
|
834
|
-
try {
|
|
835
|
-
return await connectionManager.executeWithRetry(async () => {
|
|
836
|
-
return await actualAdapter.transaction(async (trx) => {
|
|
837
|
-
// Check if task exists
|
|
838
|
-
const taskExists = await trx('t_tasks').where({ id: params.task_id }).first();
|
|
839
|
-
if (!taskExists) {
|
|
840
|
-
throw new Error(`Task with id ${params.task_id} not found`);
|
|
841
|
-
}
|
|
842
|
-
if (params.link_type === 'decision') {
|
|
843
|
-
const decisionKey = String(params.target_id);
|
|
844
|
-
const keyId = await getOrCreateContextKey(actualAdapter, decisionKey, trx);
|
|
845
|
-
const linkRelation = params.link_relation || 'implements';
|
|
846
|
-
await trx('t_task_decision_links').insert({
|
|
847
|
-
task_id: params.task_id,
|
|
848
|
-
decision_key_id: keyId,
|
|
849
|
-
link_type: linkRelation
|
|
850
|
-
}).onConflict(['task_id', 'decision_key_id']).merge();
|
|
851
|
-
return {
|
|
852
|
-
success: true,
|
|
853
|
-
task_id: params.task_id,
|
|
854
|
-
linked_to: 'decision',
|
|
855
|
-
target: decisionKey,
|
|
856
|
-
relation: linkRelation,
|
|
857
|
-
message: `Task ${params.task_id} linked to decision "${decisionKey}"`
|
|
858
|
-
};
|
|
859
|
-
}
|
|
860
|
-
else if (params.link_type === 'constraint') {
|
|
861
|
-
const constraintId = Number(params.target_id);
|
|
862
|
-
// Check if constraint exists
|
|
863
|
-
const constraintExists = await trx('t_constraints').where({ id: constraintId }).first();
|
|
864
|
-
if (!constraintExists) {
|
|
865
|
-
throw new Error(`Constraint with id ${constraintId} not found`);
|
|
866
|
-
}
|
|
867
|
-
await trx('t_task_constraint_links').insert({
|
|
868
|
-
task_id: params.task_id,
|
|
869
|
-
constraint_id: constraintId
|
|
870
|
-
}).onConflict(['task_id', 'constraint_id']).ignore();
|
|
871
|
-
return {
|
|
872
|
-
success: true,
|
|
873
|
-
task_id: params.task_id,
|
|
874
|
-
linked_to: 'constraint',
|
|
875
|
-
target: constraintId,
|
|
876
|
-
message: `Task ${params.task_id} linked to constraint ${constraintId}`
|
|
877
|
-
};
|
|
878
|
-
}
|
|
879
|
-
else if (params.link_type === 'file') {
|
|
880
|
-
// Deprecation warning (v3.4.1)
|
|
881
|
-
debugLog('WARN', `DEPRECATION: task.link(link_type="file") is deprecated as of v3.4.1. Use task.create(watch_files=[...]) or task.update(watch_files=[...]) instead. Or use the new watch_files action: { action: "watch_files", task_id: ${params.task_id}, file_paths: ["..."] }`);
|
|
882
|
-
const filePath = String(params.target_id);
|
|
883
|
-
const fileId = await getOrCreateFile(actualAdapter, projectId, filePath, trx); // v3.7.3: pass projectId
|
|
884
|
-
await trx('t_task_file_links').insert({
|
|
885
|
-
task_id: params.task_id,
|
|
886
|
-
file_id: fileId
|
|
887
|
-
}).onConflict(['task_id', 'file_id']).ignore();
|
|
888
|
-
// Register file with watcher for auto-tracking
|
|
889
|
-
try {
|
|
890
|
-
const taskData = await trx('t_tasks as t')
|
|
891
|
-
.join('m_task_statuses as s', 't.status_id', 's.id')
|
|
892
|
-
.where('t.id', params.task_id)
|
|
893
|
-
.select('t.title', 's.name as status')
|
|
894
|
-
.first();
|
|
895
|
-
if (taskData) {
|
|
896
|
-
const watcher = FileWatcher.getInstance();
|
|
897
|
-
watcher.registerFile(filePath, params.task_id, taskData.title, taskData.status);
|
|
898
|
-
}
|
|
899
|
-
}
|
|
900
|
-
catch (error) {
|
|
901
|
-
// Watcher may not be initialized yet, ignore
|
|
902
|
-
debugLog('WARN', 'Could not register file with watcher', { error });
|
|
903
|
-
}
|
|
904
|
-
return {
|
|
905
|
-
success: true,
|
|
906
|
-
task_id: params.task_id,
|
|
907
|
-
linked_to: 'file',
|
|
908
|
-
target: filePath,
|
|
909
|
-
deprecation_warning: 'task.link(link_type="file") is deprecated. Use task.create/update(watch_files) or watch_files action instead.',
|
|
910
|
-
message: `Task ${params.task_id} linked to file "${filePath}" (DEPRECATED API - use watch_files instead)`
|
|
911
|
-
};
|
|
912
|
-
}
|
|
913
|
-
else {
|
|
914
|
-
throw new Error(`Invalid link_type: ${params.link_type}. Must be one of: decision, constraint, file`);
|
|
915
|
-
}
|
|
916
|
-
});
|
|
917
|
-
});
|
|
918
|
-
}
|
|
919
|
-
catch (error) {
|
|
920
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
921
|
-
throw new Error(`Failed to link task: ${message}`);
|
|
922
|
-
}
|
|
923
|
-
}
|
|
924
|
-
/**
|
|
925
|
-
* Archive completed task
|
|
926
|
-
*/
|
|
927
|
-
export async function archiveTask(params, adapter) {
|
|
928
|
-
validateActionParams('task', 'archive', params);
|
|
929
|
-
const actualAdapter = adapter ?? getAdapter();
|
|
930
|
-
const knex = actualAdapter.getKnex();
|
|
931
|
-
if (!params.task_id) {
|
|
932
|
-
throw new Error('Parameter "task_id" is required');
|
|
933
|
-
}
|
|
934
|
-
try {
|
|
935
|
-
return await connectionManager.executeWithRetry(async () => {
|
|
936
|
-
return await actualAdapter.transaction(async (trx) => {
|
|
937
|
-
// Check if task is in 'done' status
|
|
938
|
-
const taskRow = await trx('t_tasks')
|
|
939
|
-
.where({ id: params.task_id })
|
|
940
|
-
.select('status_id')
|
|
941
|
-
.first();
|
|
942
|
-
if (!taskRow) {
|
|
943
|
-
throw new Error(`Task with id ${params.task_id} not found`);
|
|
944
|
-
}
|
|
945
|
-
if (taskRow.status_id !== TASK_STATUS.DONE) {
|
|
946
|
-
throw new Error(`Task ${params.task_id} must be in 'done' status to archive (current: ${ID_TO_STATUS[taskRow.status_id]})`);
|
|
947
|
-
}
|
|
948
|
-
// Update to archived
|
|
949
|
-
await trx('t_tasks')
|
|
950
|
-
.where({ id: params.task_id })
|
|
951
|
-
.update({ status_id: TASK_STATUS.ARCHIVED });
|
|
952
|
-
// Activity logging
|
|
953
|
-
// Note: Using system agent (id=1) for status changes
|
|
954
|
-
const systemAgentId = 1;
|
|
955
|
-
await logTaskStatusChange(trx, {
|
|
956
|
-
task_id: params.task_id,
|
|
957
|
-
old_status: TASK_STATUS.DONE,
|
|
958
|
-
new_status: TASK_STATUS.ARCHIVED,
|
|
959
|
-
agent_id: systemAgentId
|
|
960
|
-
});
|
|
961
|
-
// Unregister from file watcher (archived tasks don't need tracking)
|
|
962
|
-
try {
|
|
963
|
-
const watcher = FileWatcher.getInstance();
|
|
964
|
-
watcher.unregisterTask(params.task_id);
|
|
965
|
-
}
|
|
966
|
-
catch (error) {
|
|
967
|
-
// Watcher may not be initialized, ignore
|
|
968
|
-
}
|
|
969
|
-
return {
|
|
970
|
-
success: true,
|
|
971
|
-
task_id: params.task_id,
|
|
972
|
-
message: `Task ${params.task_id} archived successfully`
|
|
973
|
-
};
|
|
974
|
-
});
|
|
975
|
-
});
|
|
976
|
-
}
|
|
977
|
-
catch (error) {
|
|
978
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
979
|
-
throw new Error(`Failed to archive task: ${message}`);
|
|
980
|
-
}
|
|
981
|
-
}
|
|
982
|
-
/**
|
|
983
|
-
* Add dependency (blocking relationship) between tasks
|
|
984
|
-
*/
|
|
985
|
-
export async function addDependency(params, adapter) {
|
|
986
|
-
validateActionParams('task', 'add_dependency', params);
|
|
987
|
-
const actualAdapter = adapter ?? getAdapter();
|
|
988
|
-
const knex = actualAdapter.getKnex();
|
|
989
|
-
if (!params.blocker_task_id) {
|
|
990
|
-
throw new Error('Parameter "blocker_task_id" is required');
|
|
991
|
-
}
|
|
992
|
-
if (!params.blocked_task_id) {
|
|
993
|
-
throw new Error('Parameter "blocked_task_id" is required');
|
|
994
|
-
}
|
|
995
|
-
try {
|
|
996
|
-
return await connectionManager.executeWithRetry(async () => {
|
|
997
|
-
return await actualAdapter.transaction(async (trx) => {
|
|
998
|
-
// Validation 1: No self-dependencies
|
|
999
|
-
if (params.blocker_task_id === params.blocked_task_id) {
|
|
1000
|
-
throw new Error('Self-dependency not allowed');
|
|
1001
|
-
}
|
|
1002
|
-
// Validation 2: Both tasks must exist and check if archived
|
|
1003
|
-
const blockerTask = await trx('t_tasks')
|
|
1004
|
-
.where({ id: params.blocker_task_id })
|
|
1005
|
-
.select('id', 'status_id')
|
|
1006
|
-
.first();
|
|
1007
|
-
const blockedTask = await trx('t_tasks')
|
|
1008
|
-
.where({ id: params.blocked_task_id })
|
|
1009
|
-
.select('id', 'status_id')
|
|
1010
|
-
.first();
|
|
1011
|
-
if (!blockerTask) {
|
|
1012
|
-
throw new Error(`Blocker task #${params.blocker_task_id} not found`);
|
|
1013
|
-
}
|
|
1014
|
-
if (!blockedTask) {
|
|
1015
|
-
throw new Error(`Blocked task #${params.blocked_task_id} not found`);
|
|
1016
|
-
}
|
|
1017
|
-
// Validation 3: Neither task is archived
|
|
1018
|
-
if (blockerTask.status_id === TASK_STATUS.ARCHIVED) {
|
|
1019
|
-
throw new Error(`Cannot add dependency: Task #${params.blocker_task_id} is archived`);
|
|
1020
|
-
}
|
|
1021
|
-
if (blockedTask.status_id === TASK_STATUS.ARCHIVED) {
|
|
1022
|
-
throw new Error(`Cannot add dependency: Task #${params.blocked_task_id} is archived`);
|
|
1023
|
-
}
|
|
1024
|
-
// Validation 4: No direct circular (reverse relationship)
|
|
1025
|
-
const reverseExists = await trx('t_task_dependencies')
|
|
1026
|
-
.where({
|
|
1027
|
-
blocker_task_id: params.blocked_task_id,
|
|
1028
|
-
blocked_task_id: params.blocker_task_id
|
|
1029
|
-
})
|
|
1030
|
-
.first();
|
|
1031
|
-
if (reverseExists) {
|
|
1032
|
-
throw new Error(`Circular dependency detected: Task #${params.blocked_task_id} already blocks Task #${params.blocker_task_id}`);
|
|
1033
|
-
}
|
|
1034
|
-
// Validation 5: No transitive circular (check if adding this would create a cycle)
|
|
1035
|
-
const cycleCheck = await trx.raw(`
|
|
1036
|
-
WITH RECURSIVE dependency_chain AS (
|
|
1037
|
-
-- Start from the task that would be blocked
|
|
1038
|
-
SELECT blocked_task_id as task_id, 1 as depth
|
|
1039
|
-
FROM t_task_dependencies
|
|
1040
|
-
WHERE blocker_task_id = ?
|
|
1041
|
-
|
|
1042
|
-
UNION ALL
|
|
1043
|
-
|
|
1044
|
-
-- Follow the chain of dependencies
|
|
1045
|
-
SELECT d.blocked_task_id, dc.depth + 1
|
|
1046
|
-
FROM t_task_dependencies d
|
|
1047
|
-
JOIN dependency_chain dc ON d.blocker_task_id = dc.task_id
|
|
1048
|
-
WHERE dc.depth < 100
|
|
1049
|
-
)
|
|
1050
|
-
SELECT task_id FROM dependency_chain WHERE task_id = ?
|
|
1051
|
-
`, [params.blocked_task_id, params.blocker_task_id])
|
|
1052
|
-
.then((result) => result[0]);
|
|
1053
|
-
if (cycleCheck) {
|
|
1054
|
-
// Build cycle path for error message
|
|
1055
|
-
const cyclePathResult = await trx.raw(`
|
|
1056
|
-
WITH RECURSIVE dependency_chain AS (
|
|
1057
|
-
SELECT blocked_task_id as task_id, 1 as depth,
|
|
1058
|
-
CAST(blocked_task_id AS TEXT) as path
|
|
1059
|
-
FROM t_task_dependencies
|
|
1060
|
-
WHERE blocker_task_id = ?
|
|
1061
|
-
|
|
1062
|
-
UNION ALL
|
|
1063
|
-
|
|
1064
|
-
SELECT d.blocked_task_id, dc.depth + 1,
|
|
1065
|
-
dc.path || ' → ' || d.blocked_task_id
|
|
1066
|
-
FROM t_task_dependencies d
|
|
1067
|
-
JOIN dependency_chain dc ON d.blocker_task_id = dc.task_id
|
|
1068
|
-
WHERE dc.depth < 100
|
|
1069
|
-
)
|
|
1070
|
-
SELECT path FROM dependency_chain WHERE task_id = ? ORDER BY depth DESC LIMIT 1
|
|
1071
|
-
`, [params.blocked_task_id, params.blocker_task_id])
|
|
1072
|
-
.then((result) => result[0]);
|
|
1073
|
-
const cyclePath = cyclePathResult?.path || `#${params.blocked_task_id} → ... → #${params.blocker_task_id}`;
|
|
1074
|
-
throw new Error(`Circular dependency detected: Task #${params.blocker_task_id} → #${cyclePath} → #${params.blocker_task_id}`);
|
|
1075
|
-
}
|
|
1076
|
-
// All validations passed - insert dependency
|
|
1077
|
-
await trx('t_task_dependencies').insert({
|
|
1078
|
-
blocker_task_id: params.blocker_task_id,
|
|
1079
|
-
blocked_task_id: params.blocked_task_id,
|
|
1080
|
-
created_ts: Math.floor(Date.now() / 1000)
|
|
1081
|
-
});
|
|
1082
|
-
return {
|
|
1083
|
-
success: true,
|
|
1084
|
-
message: `Dependency added: Task #${params.blocker_task_id} blocks Task #${params.blocked_task_id}`
|
|
1085
|
-
};
|
|
1086
|
-
});
|
|
1087
|
-
});
|
|
1088
|
-
}
|
|
1089
|
-
catch (error) {
|
|
1090
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1091
|
-
// Don't wrap error messages that are already descriptive
|
|
1092
|
-
if (message.includes('not found') || message.includes('not allowed') || message.includes('Circular dependency') || message.includes('Cannot add dependency')) {
|
|
1093
|
-
throw new Error(message);
|
|
1094
|
-
}
|
|
1095
|
-
throw new Error(`Failed to add dependency: ${message}`);
|
|
1096
|
-
}
|
|
1097
|
-
}
|
|
1098
|
-
/**
|
|
1099
|
-
* Remove dependency between tasks
|
|
1100
|
-
*/
|
|
1101
|
-
export async function removeDependency(params, adapter) {
|
|
1102
|
-
validateActionParams('task', 'remove_dependency', params);
|
|
1103
|
-
const actualAdapter = adapter ?? getAdapter();
|
|
1104
|
-
const knex = actualAdapter.getKnex();
|
|
1105
|
-
if (!params.blocker_task_id) {
|
|
1106
|
-
throw new Error('Parameter "blocker_task_id" is required');
|
|
1107
|
-
}
|
|
1108
|
-
if (!params.blocked_task_id) {
|
|
1109
|
-
throw new Error('Parameter "blocked_task_id" is required');
|
|
1110
|
-
}
|
|
1111
|
-
try {
|
|
1112
|
-
await knex('t_task_dependencies')
|
|
1113
|
-
.where({
|
|
1114
|
-
blocker_task_id: params.blocker_task_id,
|
|
1115
|
-
blocked_task_id: params.blocked_task_id
|
|
1116
|
-
})
|
|
1117
|
-
.delete();
|
|
1118
|
-
return {
|
|
1119
|
-
success: true,
|
|
1120
|
-
message: `Dependency removed: Task #${params.blocker_task_id} no longer blocks Task #${params.blocked_task_id}`
|
|
1121
|
-
};
|
|
1122
|
-
}
|
|
1123
|
-
catch (error) {
|
|
1124
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1125
|
-
throw new Error(`Failed to remove dependency: ${message}`);
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
1128
|
-
/**
|
|
1129
|
-
* Get dependencies for a task (bidirectional: what blocks this task, what this task blocks)
|
|
1130
|
-
*/
|
|
1131
|
-
export async function getDependencies(params, adapter) {
|
|
1132
|
-
validateActionParams('task', 'get_dependencies', params);
|
|
1133
|
-
const actualAdapter = adapter ?? getAdapter();
|
|
1134
|
-
const knex = actualAdapter.getKnex();
|
|
1135
|
-
if (!params.task_id) {
|
|
1136
|
-
throw new Error('Parameter "task_id" is required');
|
|
1137
|
-
}
|
|
1138
|
-
const includeDetails = params.include_details || false;
|
|
1139
|
-
try {
|
|
1140
|
-
// Check if task exists
|
|
1141
|
-
const taskExists = await knex('t_tasks').where({ id: params.task_id }).first();
|
|
1142
|
-
if (!taskExists) {
|
|
1143
|
-
throw new Error(`Task with id ${params.task_id} not found`);
|
|
1144
|
-
}
|
|
1145
|
-
// Use the shared helper function
|
|
1146
|
-
const deps = await queryTaskDependencies(actualAdapter, params.task_id, includeDetails);
|
|
1147
|
-
return {
|
|
1148
|
-
task_id: params.task_id,
|
|
1149
|
-
blockers: deps.blockers,
|
|
1150
|
-
blocking: deps.blocking
|
|
1151
|
-
};
|
|
1152
|
-
}
|
|
1153
|
-
catch (error) {
|
|
1154
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1155
|
-
// Don't wrap error messages that are already descriptive
|
|
1156
|
-
if (message.includes('not found')) {
|
|
1157
|
-
throw new Error(message);
|
|
1158
|
-
}
|
|
1159
|
-
throw new Error(`Failed to get dependencies: ${message}`);
|
|
1160
|
-
}
|
|
1161
|
-
}
|
|
1162
|
-
/**
|
|
1163
|
-
* Create multiple tasks atomically
|
|
1164
|
-
*/
|
|
1165
|
-
export async function batchCreateTasks(params, adapter) {
|
|
1166
|
-
validateBatchParams('task', 'tasks', params.tasks, 'create', 50);
|
|
1167
|
-
const actualAdapter = adapter ?? getAdapter();
|
|
1168
|
-
if (!params.tasks || !Array.isArray(params.tasks)) {
|
|
1169
|
-
throw new Error('Parameter "tasks" is required and must be an array');
|
|
1170
|
-
}
|
|
1171
|
-
if (params.tasks.length > 50) {
|
|
1172
|
-
throw new Error('Parameter "tasks" must contain at most 50 items');
|
|
1173
|
-
}
|
|
1174
|
-
const atomic = params.atomic !== undefined ? params.atomic : true;
|
|
1175
|
-
try {
|
|
1176
|
-
if (atomic) {
|
|
1177
|
-
// Atomic mode: All or nothing
|
|
1178
|
-
const results = await connectionManager.executeWithRetry(async () => {
|
|
1179
|
-
return await actualAdapter.transaction(async (trx) => {
|
|
1180
|
-
const processedResults = [];
|
|
1181
|
-
for (const task of params.tasks) {
|
|
1182
|
-
try {
|
|
1183
|
-
const result = await createTaskInternal(task, actualAdapter, trx);
|
|
1184
|
-
processedResults.push({
|
|
1185
|
-
title: task.title,
|
|
1186
|
-
task_id: result.task_id,
|
|
1187
|
-
success: true,
|
|
1188
|
-
error: undefined
|
|
1189
|
-
});
|
|
1190
|
-
}
|
|
1191
|
-
catch (error) {
|
|
1192
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1193
|
-
throw new Error(`Batch failed at task "${task.title}": ${errorMessage}`);
|
|
1194
|
-
}
|
|
1195
|
-
}
|
|
1196
|
-
return processedResults;
|
|
1197
|
-
});
|
|
1198
|
-
});
|
|
1199
|
-
return {
|
|
1200
|
-
success: true,
|
|
1201
|
-
created: results.length,
|
|
1202
|
-
failed: 0,
|
|
1203
|
-
results: results
|
|
1204
|
-
};
|
|
1205
|
-
}
|
|
1206
|
-
else {
|
|
1207
|
-
// Non-atomic mode: Process each independently
|
|
1208
|
-
const results = [];
|
|
1209
|
-
let created = 0;
|
|
1210
|
-
let failed = 0;
|
|
1211
|
-
for (const task of params.tasks) {
|
|
1212
|
-
try {
|
|
1213
|
-
const result = await connectionManager.executeWithRetry(async () => {
|
|
1214
|
-
return await actualAdapter.transaction(async (trx) => {
|
|
1215
|
-
return await createTaskInternal(task, actualAdapter, trx);
|
|
1216
|
-
});
|
|
1217
|
-
});
|
|
1218
|
-
results.push({
|
|
1219
|
-
title: task.title,
|
|
1220
|
-
task_id: result.task_id,
|
|
1221
|
-
success: true,
|
|
1222
|
-
error: undefined
|
|
1223
|
-
});
|
|
1224
|
-
created++;
|
|
1225
|
-
}
|
|
1226
|
-
catch (error) {
|
|
1227
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1228
|
-
results.push({
|
|
1229
|
-
title: task.title,
|
|
1230
|
-
task_id: undefined,
|
|
1231
|
-
success: false,
|
|
1232
|
-
error: errorMessage
|
|
1233
|
-
});
|
|
1234
|
-
failed++;
|
|
1235
|
-
}
|
|
1236
|
-
}
|
|
1237
|
-
return {
|
|
1238
|
-
success: failed === 0,
|
|
1239
|
-
created: created,
|
|
1240
|
-
failed: failed,
|
|
1241
|
-
results: results
|
|
1242
|
-
};
|
|
1243
|
-
}
|
|
1244
|
-
}
|
|
1245
|
-
catch (error) {
|
|
1246
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1247
|
-
throw new Error(`Failed to execute batch operation: ${message}`);
|
|
1248
|
-
}
|
|
1249
|
-
}
|
|
1250
|
-
/**
|
|
1251
|
-
* Watch/unwatch files for a task (v3.4.1)
|
|
1252
|
-
* Replaces the need to use task.link(file) for file watching
|
|
1253
|
-
*/
|
|
1254
|
-
export async function watchFiles(params, adapter) {
|
|
1255
|
-
validateActionParams('task', 'watch_files', params);
|
|
1256
|
-
const actualAdapter = adapter ?? getAdapter();
|
|
1257
|
-
const knex = actualAdapter.getKnex();
|
|
1258
|
-
const projectId = getProjectContext().getProjectId();
|
|
1259
|
-
if (!params.task_id) {
|
|
1260
|
-
throw new Error('Parameter "task_id" is required');
|
|
1261
|
-
}
|
|
1262
|
-
if (!params.action) {
|
|
1263
|
-
throw new Error('Parameter "action" is required (watch, unwatch, or list)');
|
|
1264
|
-
}
|
|
1265
|
-
try {
|
|
1266
|
-
return await connectionManager.executeWithRetry(async () => {
|
|
1267
|
-
return await actualAdapter.transaction(async (trx) => {
|
|
1268
|
-
// Check if task exists (project-scoped)
|
|
1269
|
-
const taskData = await trx('t_tasks as t')
|
|
1270
|
-
.join('m_task_statuses as s', 't.status_id', 's.id')
|
|
1271
|
-
.where({ 't.id': params.task_id, 't.project_id': projectId })
|
|
1272
|
-
.select('t.id', 't.title', 's.name as status')
|
|
1273
|
-
.first();
|
|
1274
|
-
if (!taskData) {
|
|
1275
|
-
throw new Error(`Task with id ${params.task_id} not found`);
|
|
1276
|
-
}
|
|
1277
|
-
if (params.action === 'watch') {
|
|
1278
|
-
if (!params.file_paths || params.file_paths.length === 0) {
|
|
1279
|
-
throw new Error('Parameter "file_paths" is required for watch action');
|
|
1280
|
-
}
|
|
1281
|
-
const addedFiles = [];
|
|
1282
|
-
for (const filePath of params.file_paths) {
|
|
1283
|
-
const fileId = await getOrCreateFile(actualAdapter, projectId, filePath, trx); // v3.7.3: pass projectId
|
|
1284
|
-
// Check if already exists
|
|
1285
|
-
const existing = await trx('t_task_file_links')
|
|
1286
|
-
.where({ task_id: params.task_id, file_id: fileId })
|
|
1287
|
-
.first();
|
|
1288
|
-
if (!existing) {
|
|
1289
|
-
await trx('t_task_file_links').insert({
|
|
1290
|
-
task_id: params.task_id,
|
|
1291
|
-
file_id: fileId
|
|
1292
|
-
});
|
|
1293
|
-
addedFiles.push(filePath);
|
|
1294
|
-
}
|
|
1295
|
-
}
|
|
1296
|
-
// Register files with watcher
|
|
1297
|
-
try {
|
|
1298
|
-
const watcher = FileWatcher.getInstance();
|
|
1299
|
-
for (const filePath of addedFiles) {
|
|
1300
|
-
watcher.registerFile(filePath, params.task_id, taskData.title, taskData.status);
|
|
1301
|
-
}
|
|
1302
|
-
}
|
|
1303
|
-
catch (error) {
|
|
1304
|
-
// Watcher may not be initialized yet, ignore
|
|
1305
|
-
debugLog('WARN', 'Could not register files with watcher', { error });
|
|
1306
|
-
}
|
|
1307
|
-
return {
|
|
1308
|
-
success: true,
|
|
1309
|
-
task_id: params.task_id,
|
|
1310
|
-
action: 'watch',
|
|
1311
|
-
files_added: addedFiles.length,
|
|
1312
|
-
files: addedFiles,
|
|
1313
|
-
message: `Watching ${addedFiles.length} file(s) for task ${params.task_id}`
|
|
1314
|
-
};
|
|
1315
|
-
}
|
|
1316
|
-
else if (params.action === 'unwatch') {
|
|
1317
|
-
if (!params.file_paths || params.file_paths.length === 0) {
|
|
1318
|
-
throw new Error('Parameter "file_paths" is required for unwatch action');
|
|
1319
|
-
}
|
|
1320
|
-
const removedFiles = [];
|
|
1321
|
-
for (const filePath of params.file_paths) {
|
|
1322
|
-
const deleted = await trx('t_task_file_links')
|
|
1323
|
-
.where('task_id', params.task_id)
|
|
1324
|
-
.whereIn('file_id', function () {
|
|
1325
|
-
this.select('id').from('m_files').where({ path: filePath });
|
|
1326
|
-
})
|
|
1327
|
-
.delete();
|
|
1328
|
-
if (deleted > 0) {
|
|
1329
|
-
removedFiles.push(filePath);
|
|
1330
|
-
}
|
|
1331
|
-
}
|
|
1332
|
-
return {
|
|
1333
|
-
success: true,
|
|
1334
|
-
task_id: params.task_id,
|
|
1335
|
-
action: 'unwatch',
|
|
1336
|
-
files_removed: removedFiles.length,
|
|
1337
|
-
files: removedFiles,
|
|
1338
|
-
message: `Stopped watching ${removedFiles.length} file(s) for task ${params.task_id}`
|
|
1339
|
-
};
|
|
1340
|
-
}
|
|
1341
|
-
else if (params.action === 'list') {
|
|
1342
|
-
const files = await trx('t_task_file_links as tfl')
|
|
1343
|
-
.join('m_files as f', 'tfl.file_id', 'f.id')
|
|
1344
|
-
.where('tfl.task_id', params.task_id)
|
|
1345
|
-
.select('f.path')
|
|
1346
|
-
.then(rows => rows.map((row) => row.path));
|
|
1347
|
-
return {
|
|
1348
|
-
success: true,
|
|
1349
|
-
task_id: params.task_id,
|
|
1350
|
-
action: 'list',
|
|
1351
|
-
files_count: files.length,
|
|
1352
|
-
files: files,
|
|
1353
|
-
message: `Task ${params.task_id} is watching ${files.length} file(s)`
|
|
1354
|
-
};
|
|
1355
|
-
}
|
|
1356
|
-
else {
|
|
1357
|
-
throw new Error(`Invalid action: ${params.action}. Must be one of: watch, unwatch, list`);
|
|
1358
|
-
}
|
|
1359
|
-
});
|
|
1360
|
-
});
|
|
1361
|
-
}
|
|
1362
|
-
catch (error) {
|
|
1363
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1364
|
-
throw new Error(`Failed to ${params.action} files: ${message}`);
|
|
1365
|
-
}
|
|
1366
|
-
}
|
|
1367
|
-
/**
|
|
1368
|
-
* Get pruned files for a task (v3.5.0 Auto-Pruning)
|
|
1369
|
-
* Returns audit trail of files that were auto-pruned as non-existent
|
|
1370
|
-
*/
|
|
1371
|
-
export async function getPrunedFiles(params, adapter) {
|
|
1372
|
-
const actualAdapter = adapter ?? getAdapter();
|
|
1373
|
-
const knex = actualAdapter.getKnex();
|
|
1374
|
-
try {
|
|
1375
|
-
// Validate task_id
|
|
1376
|
-
if (!params.task_id || typeof params.task_id !== 'number') {
|
|
1377
|
-
throw new Error('task_id is required and must be a number');
|
|
1378
|
-
}
|
|
1379
|
-
// Validate task exists
|
|
1380
|
-
const task = await knex('t_tasks').where({ id: params.task_id }).first();
|
|
1381
|
-
if (!task) {
|
|
1382
|
-
throw new Error(`Task not found: ${params.task_id}`);
|
|
1383
|
-
}
|
|
1384
|
-
// Get pruned files
|
|
1385
|
-
const limit = params.limit || 100;
|
|
1386
|
-
const rows = await knex('t_task_pruned_files as tpf')
|
|
1387
|
-
.leftJoin('m_context_keys as k', 'tpf.linked_decision_key_id', 'k.id')
|
|
1388
|
-
.where('tpf.task_id', params.task_id)
|
|
1389
|
-
.select('tpf.id', 'tpf.file_path', knex.raw(`datetime(tpf.pruned_ts, 'unixepoch') as pruned_at`), 'k.key as linked_decision')
|
|
1390
|
-
.orderBy('tpf.pruned_ts', 'desc')
|
|
1391
|
-
.limit(limit);
|
|
1392
|
-
return {
|
|
1393
|
-
success: true,
|
|
1394
|
-
task_id: params.task_id,
|
|
1395
|
-
pruned_files: rows,
|
|
1396
|
-
count: rows.length,
|
|
1397
|
-
message: rows.length > 0
|
|
1398
|
-
? `Found ${rows.length} pruned file(s) for task ${params.task_id}`
|
|
1399
|
-
: `No pruned files for task ${params.task_id}`
|
|
1400
|
-
};
|
|
1401
|
-
}
|
|
1402
|
-
catch (error) {
|
|
1403
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1404
|
-
throw new Error(`Failed to get pruned files: ${message}`);
|
|
1405
|
-
}
|
|
1406
|
-
}
|
|
1407
|
-
/**
|
|
1408
|
-
* Link a pruned file to a decision (v3.5.0 Auto-Pruning)
|
|
1409
|
-
* Attaches WHY reasoning to pruned files for project archaeology
|
|
1410
|
-
*/
|
|
1411
|
-
export async function linkPrunedFile(params, adapter) {
|
|
1412
|
-
const actualAdapter = adapter ?? getAdapter();
|
|
1413
|
-
const knex = actualAdapter.getKnex();
|
|
1414
|
-
try {
|
|
1415
|
-
// Validate pruned_file_id
|
|
1416
|
-
if (!params.pruned_file_id || typeof params.pruned_file_id !== 'number') {
|
|
1417
|
-
throw new Error('pruned_file_id is required and must be a number');
|
|
1418
|
-
}
|
|
1419
|
-
// Validate decision_key
|
|
1420
|
-
if (!params.decision_key || typeof params.decision_key !== 'string') {
|
|
1421
|
-
throw new Error('decision_key is required and must be a string');
|
|
1422
|
-
}
|
|
1423
|
-
// Get decision key_id
|
|
1424
|
-
const decision = await knex('m_context_keys as k')
|
|
1425
|
-
.whereExists(function () {
|
|
1426
|
-
this.select('*')
|
|
1427
|
-
.from('t_decisions as d')
|
|
1428
|
-
.whereRaw('d.key_id = k.id');
|
|
1429
|
-
})
|
|
1430
|
-
.where('k.key', params.decision_key)
|
|
1431
|
-
.select('k.id as key_id')
|
|
1432
|
-
.first();
|
|
1433
|
-
if (!decision) {
|
|
1434
|
-
throw new Error(`Decision not found: ${params.decision_key}`);
|
|
1435
|
-
}
|
|
1436
|
-
// Check if pruned file exists
|
|
1437
|
-
const prunedFile = await knex('t_task_pruned_files')
|
|
1438
|
-
.where({ id: params.pruned_file_id })
|
|
1439
|
-
.select('id', 'task_id', 'file_path')
|
|
1440
|
-
.first();
|
|
1441
|
-
if (!prunedFile) {
|
|
1442
|
-
throw new Error(`Pruned file record not found: ${params.pruned_file_id}`);
|
|
1443
|
-
}
|
|
1444
|
-
// Update the link
|
|
1445
|
-
const updated = await knex('t_task_pruned_files')
|
|
1446
|
-
.where({ id: params.pruned_file_id })
|
|
1447
|
-
.update({ linked_decision_key_id: decision.key_id });
|
|
1448
|
-
if (updated === 0) {
|
|
1449
|
-
throw new Error(`Failed to link pruned file #${params.pruned_file_id} to decision ${params.decision_key}`);
|
|
1450
|
-
}
|
|
1451
|
-
return {
|
|
1452
|
-
success: true,
|
|
1453
|
-
pruned_file_id: params.pruned_file_id,
|
|
1454
|
-
decision_key: params.decision_key,
|
|
1455
|
-
task_id: prunedFile.task_id,
|
|
1456
|
-
file_path: prunedFile.file_path,
|
|
1457
|
-
message: `Linked pruned file "${prunedFile.file_path}" to decision "${params.decision_key}"`
|
|
1458
|
-
};
|
|
1459
|
-
}
|
|
1460
|
-
catch (error) {
|
|
1461
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1462
|
-
throw new Error(`Failed to link pruned file: ${message}`);
|
|
1463
|
-
}
|
|
1464
|
-
}
|
|
1465
|
-
/**
|
|
1466
|
-
* Return comprehensive help documentation
|
|
1467
|
-
*/
|
|
1468
|
-
export function taskHelp() {
|
|
1469
|
-
return {
|
|
1470
|
-
tool: 'task',
|
|
1471
|
-
description: 'Kanban Task Watcher for managing tasks with AI-optimized lifecycle states',
|
|
1472
|
-
note: '💡 TIP: Use action: "example" to see comprehensive usage scenarios and real-world examples for all task actions.',
|
|
1473
|
-
important: '🚨 AUTOMATIC FILE WATCHING: Linking files to tasks activates automatic file change monitoring and acceptance criteria validation. You can save 300 tokens per file compared to registering watchers manually. See auto_file_tracking section below.',
|
|
1474
|
-
actions: {
|
|
1475
|
-
create: {
|
|
1476
|
-
description: 'Create a new task',
|
|
1477
|
-
required_params: ['title'],
|
|
1478
|
-
optional_params: ['description', 'acceptance_criteria', 'notes', 'priority', 'assigned_agent', 'created_by_agent', 'layer', 'tags', 'status', 'watch_files'],
|
|
1479
|
-
watch_files_param: '⭐ NEW in v3.4.1: Pass watch_files array to automatically link and watch files (replaces task.link(file))',
|
|
1480
|
-
example: {
|
|
1481
|
-
action: 'create',
|
|
1482
|
-
title: 'Implement authentication endpoint',
|
|
1483
|
-
description: 'Add JWT-based authentication to /api/login',
|
|
1484
|
-
priority: 3,
|
|
1485
|
-
assigned_agent: 'backend-agent',
|
|
1486
|
-
layer: 'presentation',
|
|
1487
|
-
tags: ['api', 'authentication'],
|
|
1488
|
-
watch_files: ['src/api/auth.ts', 'src/middleware/jwt.ts']
|
|
1489
|
-
}
|
|
1490
|
-
},
|
|
1491
|
-
update: {
|
|
1492
|
-
description: 'Update task metadata',
|
|
1493
|
-
required_params: ['task_id'],
|
|
1494
|
-
optional_params: ['title', 'priority', 'assigned_agent', 'layer', 'description', 'acceptance_criteria', 'notes', 'watch_files'],
|
|
1495
|
-
watch_files_param: '⭐ NEW in v3.4.1: Pass watch_files array to add files to watch list',
|
|
1496
|
-
example: {
|
|
1497
|
-
action: 'update',
|
|
1498
|
-
task_id: 5,
|
|
1499
|
-
priority: 4,
|
|
1500
|
-
assigned_agent: 'senior-backend-agent',
|
|
1501
|
-
watch_files: ['src/api/users.ts']
|
|
1502
|
-
}
|
|
1503
|
-
},
|
|
1504
|
-
get: {
|
|
1505
|
-
description: 'Get full task details including descriptions and links',
|
|
1506
|
-
required_params: ['task_id'],
|
|
1507
|
-
example: {
|
|
1508
|
-
action: 'get',
|
|
1509
|
-
task_id: 5
|
|
1510
|
-
}
|
|
1511
|
-
},
|
|
1512
|
-
list: {
|
|
1513
|
-
description: 'List tasks (token-efficient, no descriptions)',
|
|
1514
|
-
required_params: [],
|
|
1515
|
-
optional_params: ['status', 'assigned_agent', 'layer', 'tags', 'limit', 'offset'],
|
|
1516
|
-
example: {
|
|
1517
|
-
action: 'list',
|
|
1518
|
-
status: 'in_progress',
|
|
1519
|
-
assigned_agent: 'backend-agent',
|
|
1520
|
-
limit: 20
|
|
1521
|
-
}
|
|
1522
|
-
},
|
|
1523
|
-
move: {
|
|
1524
|
-
description: 'Move task to different status with validation',
|
|
1525
|
-
required_params: ['task_id', 'new_status'],
|
|
1526
|
-
valid_statuses: ['todo', 'in_progress', 'waiting_review', 'blocked', 'done', 'archived'],
|
|
1527
|
-
transitions: {
|
|
1528
|
-
todo: ['in_progress', 'blocked'],
|
|
1529
|
-
in_progress: ['waiting_review', 'blocked', 'done'],
|
|
1530
|
-
waiting_review: ['in_progress', 'todo', 'done'],
|
|
1531
|
-
blocked: ['todo', 'in_progress'],
|
|
1532
|
-
done: ['archived'],
|
|
1533
|
-
archived: []
|
|
1534
|
-
},
|
|
1535
|
-
example: {
|
|
1536
|
-
action: 'move',
|
|
1537
|
-
task_id: 5,
|
|
1538
|
-
new_status: 'in_progress'
|
|
1539
|
-
}
|
|
1540
|
-
},
|
|
1541
|
-
link: {
|
|
1542
|
-
description: 'Link task to decision/constraint/file',
|
|
1543
|
-
required_params: ['task_id', 'link_type', 'target_id'],
|
|
1544
|
-
optional_params: ['link_relation'],
|
|
1545
|
-
link_types: ['decision', 'constraint', 'file'],
|
|
1546
|
-
file_linking_behavior: '⚠️ DEPRECATED in v3.4.1: link_type="file" is deprecated. Use watch_files action or watch_files parameter instead.',
|
|
1547
|
-
deprecation_note: 'For file watching, use: (1) watch_files parameter in create/update, or (2) watch_files action with watch/unwatch/list',
|
|
1548
|
-
example: {
|
|
1549
|
-
action: 'link',
|
|
1550
|
-
task_id: 5,
|
|
1551
|
-
link_type: 'decision',
|
|
1552
|
-
target_id: 'auth_method',
|
|
1553
|
-
link_relation: 'implements'
|
|
1554
|
-
}
|
|
1555
|
-
},
|
|
1556
|
-
watch_files: {
|
|
1557
|
-
description: '⭐ NEW in v3.4.1: Watch/unwatch files for a task (replaces task.link(file))',
|
|
1558
|
-
required_params: ['task_id', 'action'],
|
|
1559
|
-
optional_params: ['file_paths'],
|
|
1560
|
-
actions: ['watch', 'unwatch', 'list'],
|
|
1561
|
-
behavior: {
|
|
1562
|
-
watch: 'Add files to watch list and activate file monitoring',
|
|
1563
|
-
unwatch: 'Remove files from watch list',
|
|
1564
|
-
list: 'List all files currently watched by this task'
|
|
1565
|
-
},
|
|
1566
|
-
examples: {
|
|
1567
|
-
watch: {
|
|
1568
|
-
task_id: 5,
|
|
1569
|
-
action: 'watch',
|
|
1570
|
-
file_paths: ['src/api/auth.ts', 'src/middleware/jwt.ts']
|
|
1571
|
-
},
|
|
1572
|
-
unwatch: {
|
|
1573
|
-
task_id: 5,
|
|
1574
|
-
action: 'unwatch',
|
|
1575
|
-
file_paths: ['src/middleware/jwt.ts']
|
|
1576
|
-
},
|
|
1577
|
-
list: {
|
|
1578
|
-
task_id: 5,
|
|
1579
|
-
action: 'list'
|
|
1580
|
-
}
|
|
1581
|
-
},
|
|
1582
|
-
note: 'Preferred over task.link(file) for better clarity and batch operations'
|
|
1583
|
-
},
|
|
1584
|
-
archive: {
|
|
1585
|
-
description: 'Archive completed task (must be in done status)',
|
|
1586
|
-
required_params: ['task_id'],
|
|
1587
|
-
example: {
|
|
1588
|
-
action: 'archive',
|
|
1589
|
-
task_id: 5
|
|
1590
|
-
}
|
|
1591
|
-
},
|
|
1592
|
-
batch_create: {
|
|
1593
|
-
description: 'Create multiple tasks atomically',
|
|
1594
|
-
required_params: ['tasks'],
|
|
1595
|
-
optional_params: ['atomic'],
|
|
1596
|
-
limits: {
|
|
1597
|
-
max_items: 50
|
|
1598
|
-
},
|
|
1599
|
-
note: '⚠️ IMPORTANT: The "tasks" parameter must be a JavaScript array, not a JSON string. MCP tools require pre-parsed objects.',
|
|
1600
|
-
example: {
|
|
1601
|
-
action: 'batch_create',
|
|
1602
|
-
tasks: [
|
|
1603
|
-
{ title: 'Task 1', priority: 2 },
|
|
1604
|
-
{ title: 'Task 2', priority: 3, layer: 'business' }
|
|
1605
|
-
],
|
|
1606
|
-
atomic: true
|
|
1607
|
-
}
|
|
1608
|
-
},
|
|
1609
|
-
add_dependency: {
|
|
1610
|
-
description: 'Add blocking relationship between tasks',
|
|
1611
|
-
required_params: ['blocker_task_id', 'blocked_task_id'],
|
|
1612
|
-
validations: [
|
|
1613
|
-
'No self-dependencies',
|
|
1614
|
-
'No circular dependencies (direct or transitive)',
|
|
1615
|
-
'Both tasks must exist',
|
|
1616
|
-
'Neither task can be archived'
|
|
1617
|
-
],
|
|
1618
|
-
example: {
|
|
1619
|
-
action: 'add_dependency',
|
|
1620
|
-
blocker_task_id: 1,
|
|
1621
|
-
blocked_task_id: 2
|
|
1622
|
-
},
|
|
1623
|
-
note: 'Task #1 must be completed before Task #2 can start'
|
|
1624
|
-
},
|
|
1625
|
-
remove_dependency: {
|
|
1626
|
-
description: 'Remove blocking relationship between tasks',
|
|
1627
|
-
required_params: ['blocker_task_id', 'blocked_task_id'],
|
|
1628
|
-
example: {
|
|
1629
|
-
action: 'remove_dependency',
|
|
1630
|
-
blocker_task_id: 1,
|
|
1631
|
-
blocked_task_id: 2
|
|
1632
|
-
},
|
|
1633
|
-
note: 'Silently succeeds even if dependency does not exist'
|
|
1634
|
-
},
|
|
1635
|
-
get_dependencies: {
|
|
1636
|
-
description: 'Query task dependencies (bidirectional)',
|
|
1637
|
-
required_params: ['task_id'],
|
|
1638
|
-
optional_params: ['include_details'],
|
|
1639
|
-
returns: {
|
|
1640
|
-
blockers: 'Array of tasks that block this task',
|
|
1641
|
-
blocking: 'Array of tasks this task blocks'
|
|
1642
|
-
},
|
|
1643
|
-
example: {
|
|
1644
|
-
action: 'get_dependencies',
|
|
1645
|
-
task_id: 2,
|
|
1646
|
-
include_details: true
|
|
1647
|
-
},
|
|
1648
|
-
note: 'Defaults to metadata-only (token-efficient). Set include_details=true for full task details.'
|
|
1649
|
-
},
|
|
1650
|
-
watcher: {
|
|
1651
|
-
description: 'Query file watcher status and monitored files/tasks',
|
|
1652
|
-
required_params: [],
|
|
1653
|
-
optional_params: ['subaction'],
|
|
1654
|
-
subactions: ['status', 'list_files', 'list_tasks', 'help'],
|
|
1655
|
-
default_subaction: 'status',
|
|
1656
|
-
examples: {
|
|
1657
|
-
status: {
|
|
1658
|
-
action: 'watcher',
|
|
1659
|
-
subaction: 'status'
|
|
1660
|
-
},
|
|
1661
|
-
list_files: {
|
|
1662
|
-
action: 'watcher',
|
|
1663
|
-
subaction: 'list_files'
|
|
1664
|
-
},
|
|
1665
|
-
list_tasks: {
|
|
1666
|
-
action: 'watcher',
|
|
1667
|
-
subaction: 'list_tasks'
|
|
1668
|
-
}
|
|
1669
|
-
},
|
|
1670
|
-
note: 'Use to monitor which files/tasks are being watched. File watching activates automatically when you link files to tasks.'
|
|
1671
|
-
},
|
|
1672
|
-
help: {
|
|
1673
|
-
description: 'Return this help documentation',
|
|
1674
|
-
example: { action: 'help' }
|
|
1675
|
-
}
|
|
1676
|
-
},
|
|
1677
|
-
auto_stale_detection: {
|
|
1678
|
-
description: 'Tasks automatically transition when abandoned',
|
|
1679
|
-
behavior: {
|
|
1680
|
-
in_progress: 'Untouched for >2 hours → waiting_review',
|
|
1681
|
-
waiting_review: 'Untouched for >24 hours → todo'
|
|
1682
|
-
},
|
|
1683
|
-
config_keys: {
|
|
1684
|
-
task_stale_hours_in_progress: 'Hours before in_progress tasks go stale (default: 2)',
|
|
1685
|
-
task_stale_hours_waiting_review: 'Hours before waiting_review tasks go stale (default: 24)',
|
|
1686
|
-
task_auto_stale_enabled: 'Enable/disable auto-stale detection (default: true)'
|
|
1687
|
-
}
|
|
1688
|
-
},
|
|
1689
|
-
priority_levels: {
|
|
1690
|
-
1: 'low',
|
|
1691
|
-
2: 'medium (default)',
|
|
1692
|
-
3: 'high',
|
|
1693
|
-
4: 'critical'
|
|
1694
|
-
},
|
|
1695
|
-
auto_file_tracking: {
|
|
1696
|
-
description: 'Automatic file watching and acceptance criteria validation - save 300 tokens per file vs manual registration',
|
|
1697
|
-
recommendation: '⭐ BEST PRACTICE: Except in exceptional cases, it is recommended to set up file watchers for all tasks that involve code changes. This provides automatic status tracking with zero token overhead.',
|
|
1698
|
-
how_it_works: [
|
|
1699
|
-
'1. Link files to tasks using the link action with link_type="file"',
|
|
1700
|
-
'2. File watcher automatically activates and monitors linked files',
|
|
1701
|
-
'3. When files are saved, watcher detects changes',
|
|
1702
|
-
'4. If task has acceptance_criteria, watcher validates criteria against changes',
|
|
1703
|
-
'5. Results appear in terminal output with pass/fail status'
|
|
1704
|
-
],
|
|
1705
|
-
requirements: [
|
|
1706
|
-
'Task must have files linked via link action',
|
|
1707
|
-
'File paths must be relative to project root (e.g., "src/api/auth.ts")',
|
|
1708
|
-
'Watcher only monitors files explicitly linked to tasks'
|
|
1709
|
-
],
|
|
1710
|
-
token_efficiency: 'File watching happens in background. No MCP tokens consumed until you query status. Manual file tracking would cost ~500-1000 tokens per file check.',
|
|
1711
|
-
documentation_reference: 'docs/AUTO_FILE_TRACKING.md - Complete guide with examples'
|
|
1712
|
-
},
|
|
1713
|
-
documentation: {
|
|
1714
|
-
task_overview: 'docs/TASK_OVERVIEW.md - Lifecycle, status transitions, auto-stale detection (363 lines, ~10k tokens)',
|
|
1715
|
-
task_actions: 'docs/TASK_ACTIONS.md - All action references with examples (854 lines, ~21k tokens)',
|
|
1716
|
-
task_linking: 'docs/TASK_LINKING.md - Link tasks to decisions/constraints/files (729 lines, ~18k tokens)',
|
|
1717
|
-
task_migration: 'docs/TASK_MIGRATION.md - Migrate from decision-based tracking (701 lines, ~18k tokens)',
|
|
1718
|
-
tool_selection: 'docs/TOOL_SELECTION.md - Task vs decision vs constraint comparison (236 lines, ~12k tokens)',
|
|
1719
|
-
workflows: 'docs/WORKFLOWS.md - Multi-agent task coordination workflows (602 lines, ~30k tokens)',
|
|
1720
|
-
shared_concepts: 'docs/SHARED_CONCEPTS.md - Layer definitions, enum values (status/priority), atomic mode (339 lines, ~17k tokens)'
|
|
1721
|
-
}
|
|
1722
|
-
};
|
|
1723
|
-
}
|
|
1724
|
-
/**
|
|
1725
|
-
* Query file watcher status and monitored files/tasks
|
|
1726
|
-
*/
|
|
1727
|
-
export async function watcherStatus(args, adapter) {
|
|
1728
|
-
const actualAdapter = adapter ?? getAdapter();
|
|
1729
|
-
const knex = actualAdapter.getKnex();
|
|
1730
|
-
const projectId = getProjectContext().getProjectId();
|
|
1731
|
-
const subaction = args.subaction || 'status';
|
|
1732
|
-
const watcher = FileWatcher.getInstance();
|
|
1733
|
-
if (subaction === 'help') {
|
|
1734
|
-
return {
|
|
1735
|
-
action: 'watcher',
|
|
1736
|
-
description: 'Query file watcher status and monitored files/tasks',
|
|
1737
|
-
subactions: {
|
|
1738
|
-
status: {
|
|
1739
|
-
description: 'Get overall watcher status (running, files watched, tasks monitored)',
|
|
1740
|
-
example: { action: 'watcher', subaction: 'status' }
|
|
1741
|
-
},
|
|
1742
|
-
list_files: {
|
|
1743
|
-
description: 'List all files being watched with their associated tasks',
|
|
1744
|
-
example: { action: 'watcher', subaction: 'list_files' }
|
|
1745
|
-
},
|
|
1746
|
-
list_tasks: {
|
|
1747
|
-
description: 'List all tasks that have active file watchers',
|
|
1748
|
-
example: { action: 'watcher', subaction: 'list_tasks' }
|
|
1749
|
-
},
|
|
1750
|
-
help: {
|
|
1751
|
-
description: 'Show this help documentation',
|
|
1752
|
-
example: { action: 'watcher', subaction: 'help' }
|
|
1753
|
-
}
|
|
1754
|
-
},
|
|
1755
|
-
note: 'File watching activates automatically when you link files to tasks using the link action with link_type="file". The watcher monitors linked files for changes and validates acceptance criteria.'
|
|
1756
|
-
};
|
|
1757
|
-
}
|
|
1758
|
-
if (subaction === 'status') {
|
|
1759
|
-
const status = watcher.getStatus();
|
|
1760
|
-
return {
|
|
1761
|
-
success: true,
|
|
1762
|
-
watcher_status: {
|
|
1763
|
-
running: status.running,
|
|
1764
|
-
files_watched: status.filesWatched,
|
|
1765
|
-
tasks_monitored: status.tasksWatched
|
|
1766
|
-
},
|
|
1767
|
-
message: status.running
|
|
1768
|
-
? `File watcher is running. Monitoring ${status.filesWatched} file(s) across ${status.tasksWatched} task(s).`
|
|
1769
|
-
: 'File watcher is not running. Link files to tasks to activate automatic file watching.'
|
|
1770
|
-
};
|
|
1771
|
-
}
|
|
1772
|
-
if (subaction === 'list_files') {
|
|
1773
|
-
const fileLinks = await knex('t_task_file_links as tfl')
|
|
1774
|
-
.join('t_tasks as t', function () {
|
|
1775
|
-
this.on('tfl.task_id', '=', 't.id')
|
|
1776
|
-
.andOn('tfl.project_id', '=', 't.project_id');
|
|
1777
|
-
})
|
|
1778
|
-
.join('m_task_statuses as ts', 't.status_id', 'ts.id')
|
|
1779
|
-
.join('m_files as f', 'tfl.file_id', 'f.id')
|
|
1780
|
-
.where('t.project_id', projectId)
|
|
1781
|
-
.whereNot('t.status_id', STATUS_TO_ID['archived']) // Exclude archived tasks
|
|
1782
|
-
.select('f.path as file_path', 't.id', 't.title', 'ts.name as status_name')
|
|
1783
|
-
.orderBy(['f.path', 't.id']);
|
|
1784
|
-
// Group by file
|
|
1785
|
-
const fileMap = new Map();
|
|
1786
|
-
for (const link of fileLinks) {
|
|
1787
|
-
if (!fileMap.has(link.file_path)) {
|
|
1788
|
-
fileMap.set(link.file_path, []);
|
|
1789
|
-
}
|
|
1790
|
-
fileMap.get(link.file_path).push({
|
|
1791
|
-
task_id: link.id,
|
|
1792
|
-
task_title: link.title,
|
|
1793
|
-
status: link.status_name
|
|
1794
|
-
});
|
|
1795
|
-
}
|
|
1796
|
-
const files = Array.from(fileMap.entries()).map(([path, tasks]) => ({
|
|
1797
|
-
file_path: path,
|
|
1798
|
-
tasks: tasks
|
|
1799
|
-
}));
|
|
1800
|
-
return {
|
|
1801
|
-
success: true,
|
|
1802
|
-
files_watched: files.length,
|
|
1803
|
-
files: files,
|
|
1804
|
-
message: files.length > 0
|
|
1805
|
-
? `Watching ${files.length} file(s) linked to tasks.`
|
|
1806
|
-
: 'No files currently linked to tasks. Use link action with link_type="file" to activate file watching.'
|
|
1807
|
-
};
|
|
1808
|
-
}
|
|
1809
|
-
if (subaction === 'list_tasks') {
|
|
1810
|
-
const taskLinks = await knex('t_tasks as t')
|
|
1811
|
-
.join('m_task_statuses as ts', 't.status_id', 'ts.id')
|
|
1812
|
-
.join('t_task_file_links as tfl', function () {
|
|
1813
|
-
this.on('t.id', '=', 'tfl.task_id')
|
|
1814
|
-
.andOn('t.project_id', '=', 'tfl.project_id');
|
|
1815
|
-
})
|
|
1816
|
-
.join('m_files as f', 'tfl.file_id', 'f.id')
|
|
1817
|
-
.where('t.project_id', projectId)
|
|
1818
|
-
.whereNot('t.status_id', STATUS_TO_ID['archived']) // Exclude archived tasks
|
|
1819
|
-
.groupBy('t.id', 't.title', 'ts.name')
|
|
1820
|
-
.select('t.id', 't.title', 'ts.name as status_name', knex.raw('COUNT(DISTINCT tfl.file_id) as file_count'), knex.raw('GROUP_CONCAT(DISTINCT f.path, \', \') as files'))
|
|
1821
|
-
.orderBy('t.id');
|
|
1822
|
-
const tasks = taskLinks.map(task => ({
|
|
1823
|
-
task_id: task.id,
|
|
1824
|
-
task_title: task.title,
|
|
1825
|
-
status: task.status_name,
|
|
1826
|
-
files_count: task.file_count,
|
|
1827
|
-
files: task.files.split(', ')
|
|
1828
|
-
}));
|
|
1829
|
-
return {
|
|
1830
|
-
success: true,
|
|
1831
|
-
tasks_monitored: tasks.length,
|
|
1832
|
-
tasks: tasks,
|
|
1833
|
-
message: tasks.length > 0
|
|
1834
|
-
? `Monitoring ${tasks.length} task(s) with linked files.`
|
|
1835
|
-
: 'No tasks currently have linked files. Use link action with link_type="file" to activate file watching.'
|
|
1836
|
-
};
|
|
1837
|
-
}
|
|
1838
|
-
return {
|
|
1839
|
-
error: `Invalid subaction: ${subaction}. Valid subactions: status, list_files, list_tasks, help`
|
|
1840
|
-
};
|
|
1841
|
-
}
|
|
1842
|
-
/**
|
|
1843
|
-
* Get comprehensive examples for task tool
|
|
1844
|
-
* @returns Examples documentation object
|
|
1845
|
-
*/
|
|
1846
|
-
export function taskExample() {
|
|
1847
|
-
return {
|
|
1848
|
-
tool: 'task',
|
|
1849
|
-
description: 'Comprehensive task management examples for Kanban-style workflow',
|
|
1850
|
-
scenarios: {
|
|
1851
|
-
basic_task_management: {
|
|
1852
|
-
title: 'Creating and Managing Tasks',
|
|
1853
|
-
examples: [
|
|
1854
|
-
{
|
|
1855
|
-
scenario: 'Create a new task',
|
|
1856
|
-
request: '{ action: "create", title: "Implement user authentication", description: "Add JWT-based auth to API", priority: 3, assigned_agent: "backend-agent", layer: "business", tags: ["authentication", "security"] }',
|
|
1857
|
-
explanation: 'Creates task in todo status with high priority'
|
|
1858
|
-
},
|
|
1859
|
-
{
|
|
1860
|
-
scenario: 'Get task details',
|
|
1861
|
-
request: '{ action: "get", task_id: 5 }',
|
|
1862
|
-
response: 'Full task details including metadata, links, and timestamps'
|
|
1863
|
-
},
|
|
1864
|
-
{
|
|
1865
|
-
scenario: 'List tasks by status',
|
|
1866
|
-
request: '{ action: "list", status: "in_progress", limit: 20 }',
|
|
1867
|
-
explanation: 'View all in-progress tasks'
|
|
1868
|
-
}
|
|
1869
|
-
]
|
|
1870
|
-
},
|
|
1871
|
-
status_workflow: {
|
|
1872
|
-
title: 'Task Lifecycle (Status Transitions)',
|
|
1873
|
-
workflow: [
|
|
1874
|
-
{
|
|
1875
|
-
step: 1,
|
|
1876
|
-
status: 'todo',
|
|
1877
|
-
action: '{ action: "create", title: "...", status: "todo" }',
|
|
1878
|
-
description: 'Task created and waiting to be started'
|
|
1879
|
-
},
|
|
1880
|
-
{
|
|
1881
|
-
step: 2,
|
|
1882
|
-
status: 'in_progress',
|
|
1883
|
-
action: '{ action: "move", task_id: 1, new_status: "in_progress" }',
|
|
1884
|
-
description: 'Agent starts working on task'
|
|
1885
|
-
},
|
|
1886
|
-
{
|
|
1887
|
-
step: 3,
|
|
1888
|
-
status: 'waiting_review',
|
|
1889
|
-
action: '{ action: "move", task_id: 1, new_status: "waiting_review" }',
|
|
1890
|
-
description: 'Work complete, awaiting review/approval'
|
|
1891
|
-
},
|
|
1892
|
-
{
|
|
1893
|
-
step: 4,
|
|
1894
|
-
status: 'done',
|
|
1895
|
-
action: '{ action: "move", task_id: 1, new_status: "done" }',
|
|
1896
|
-
description: 'Task reviewed and completed'
|
|
1897
|
-
},
|
|
1898
|
-
{
|
|
1899
|
-
step: 5,
|
|
1900
|
-
status: 'archived',
|
|
1901
|
-
action: '{ action: "archive", task_id: 1 }',
|
|
1902
|
-
description: 'Task archived for historical record'
|
|
1903
|
-
}
|
|
1904
|
-
],
|
|
1905
|
-
blocked_status: {
|
|
1906
|
-
description: 'Use "blocked" when task cannot proceed due to dependencies',
|
|
1907
|
-
example: '{ action: "move", task_id: 1, new_status: "blocked" }'
|
|
1908
|
-
}
|
|
1909
|
-
},
|
|
1910
|
-
auto_stale_detection: {
|
|
1911
|
-
title: 'Automatic Stale Task Management',
|
|
1912
|
-
behavior: [
|
|
1913
|
-
{
|
|
1914
|
-
rule: 'in_progress > 2 hours → waiting_review',
|
|
1915
|
-
explanation: 'Tasks stuck in progress auto-move to waiting_review',
|
|
1916
|
-
rationale: 'Prevents tasks from being forgotten while in progress'
|
|
1917
|
-
},
|
|
1918
|
-
{
|
|
1919
|
-
rule: 'waiting_review > 24 hours → todo',
|
|
1920
|
-
explanation: 'Unreviewed tasks return to todo queue',
|
|
1921
|
-
rationale: 'Ensures waiting tasks dont accumulate indefinitely'
|
|
1922
|
-
}
|
|
1923
|
-
],
|
|
1924
|
-
configuration: {
|
|
1925
|
-
keys: ['task_stale_hours_in_progress', 'task_stale_hours_waiting_review', 'task_auto_stale_enabled'],
|
|
1926
|
-
note: 'Configure via config table in database'
|
|
1927
|
-
}
|
|
1928
|
-
},
|
|
1929
|
-
task_linking: {
|
|
1930
|
-
title: 'Linking Tasks to Context',
|
|
1931
|
-
examples: [
|
|
1932
|
-
{
|
|
1933
|
-
scenario: 'Link task to decision',
|
|
1934
|
-
request: '{ action: "link", task_id: 5, link_type: "decision", target_id: "api_auth_method", link_relation: "implements" }',
|
|
1935
|
-
explanation: 'Track which tasks implement specific decisions'
|
|
1936
|
-
},
|
|
1937
|
-
{
|
|
1938
|
-
scenario: 'Link task to constraint',
|
|
1939
|
-
request: '{ action: "link", task_id: 5, link_type: "constraint", target_id: 3, link_relation: "addresses" }',
|
|
1940
|
-
explanation: 'Show task addresses a performance/architecture/security constraint'
|
|
1941
|
-
},
|
|
1942
|
-
{
|
|
1943
|
-
scenario: 'Link task to file',
|
|
1944
|
-
request: '{ action: "link", task_id: 5, link_type: "file", target_id: "src/api/auth.ts", link_relation: "modifies" }',
|
|
1945
|
-
explanation: 'Activates automatic file watching for the task - saves 300 tokens per file vs manual registration',
|
|
1946
|
-
behavior: 'File watcher monitors linked files and validates acceptance criteria when files change'
|
|
1947
|
-
}
|
|
1948
|
-
]
|
|
1949
|
-
},
|
|
1950
|
-
batch_operations: {
|
|
1951
|
-
title: 'Batch Task Creation',
|
|
1952
|
-
examples: [
|
|
1953
|
-
{
|
|
1954
|
-
scenario: 'Create multiple related tasks',
|
|
1955
|
-
request: '{ action: "batch_create", tasks: [{"title": "Design API", "priority": 3}, {"title": "Implement API", "priority": 3}, {"title": "Write tests", "priority": 2}], atomic: false }',
|
|
1956
|
-
explanation: 'Create task breakdown - use atomic:false for best-effort'
|
|
1957
|
-
}
|
|
1958
|
-
]
|
|
1959
|
-
},
|
|
1960
|
-
filtering_queries: {
|
|
1961
|
-
title: 'Advanced Task Queries',
|
|
1962
|
-
examples: [
|
|
1963
|
-
{
|
|
1964
|
-
scenario: 'Find high-priority tasks for agent',
|
|
1965
|
-
request: '{ action: "list", assigned_agent: "backend-agent", priority: 3, status: "todo" }',
|
|
1966
|
-
note: 'Priority is numeric: 1=low, 2=medium, 3=high, 4=critical'
|
|
1967
|
-
},
|
|
1968
|
-
{
|
|
1969
|
-
scenario: 'Get all security-related tasks',
|
|
1970
|
-
request: '{ action: "list", tags: ["security"], limit: 50 }',
|
|
1971
|
-
explanation: 'Filter by tags for topic-based views'
|
|
1972
|
-
},
|
|
1973
|
-
{
|
|
1974
|
-
scenario: 'View infrastructure layer tasks',
|
|
1975
|
-
request: '{ action: "list", layer: "infrastructure" }',
|
|
1976
|
-
explanation: 'See all DevOps/config related tasks'
|
|
1977
|
-
}
|
|
1978
|
-
]
|
|
1979
|
-
},
|
|
1980
|
-
file_watcher_status: {
|
|
1981
|
-
title: 'File Watcher Status Queries',
|
|
1982
|
-
examples: [
|
|
1983
|
-
{
|
|
1984
|
-
scenario: 'Check if file watcher is running',
|
|
1985
|
-
request: '{ action: "watcher", subaction: "status" }',
|
|
1986
|
-
explanation: 'Returns running status, files watched count, tasks monitored count',
|
|
1987
|
-
response: '{ running: true, files_watched: 5, tasks_monitored: 3 }'
|
|
1988
|
-
},
|
|
1989
|
-
{
|
|
1990
|
-
scenario: 'List all files being watched',
|
|
1991
|
-
request: '{ action: "watcher", subaction: "list_files" }',
|
|
1992
|
-
explanation: 'Shows file paths and which tasks are watching them',
|
|
1993
|
-
response: '{ files: [{ file_path: "src/api/auth.ts", tasks: [{ task_id: 5, title: "...", status: "in_progress" }] }] }'
|
|
1994
|
-
},
|
|
1995
|
-
{
|
|
1996
|
-
scenario: 'List tasks with active file watchers',
|
|
1997
|
-
request: '{ action: "watcher", subaction: "list_tasks" }',
|
|
1998
|
-
explanation: 'Shows tasks and which files they are watching',
|
|
1999
|
-
response: '{ tasks: [{ task_id: 5, title: "...", files: ["src/api/auth.ts", "src/api/middleware.ts"] }] }'
|
|
2000
|
-
}
|
|
2001
|
-
]
|
|
2002
|
-
}
|
|
2003
|
-
},
|
|
2004
|
-
valid_transitions: {
|
|
2005
|
-
from_todo: ['in_progress', 'blocked', 'done', 'archived'],
|
|
2006
|
-
from_in_progress: ['waiting_review', 'blocked', 'todo'],
|
|
2007
|
-
from_waiting_review: ['done', 'in_progress', 'todo'],
|
|
2008
|
-
from_blocked: ['todo', 'in_progress'],
|
|
2009
|
-
from_done: ['archived', 'todo'],
|
|
2010
|
-
from_archived: []
|
|
2011
|
-
},
|
|
2012
|
-
best_practices: {
|
|
2013
|
-
task_creation: [
|
|
2014
|
-
'Use descriptive titles (200 char max)',
|
|
2015
|
-
'Set appropriate priority: 1=low, 2=medium (default), 3=high, 4=critical',
|
|
2016
|
-
'Assign to layer where work will be done',
|
|
2017
|
-
'Tag comprehensively for easy filtering',
|
|
2018
|
-
'Include acceptance_criteria for complex tasks'
|
|
2019
|
-
],
|
|
2020
|
-
status_management: [
|
|
2021
|
-
'Move to in_progress when starting work',
|
|
2022
|
-
'Use waiting_review for completed but unverified work',
|
|
2023
|
-
'Set to blocked with notes explaining dependency',
|
|
2024
|
-
'Archive done tasks periodically for cleaner views'
|
|
2025
|
-
],
|
|
2026
|
-
linking: [
|
|
2027
|
-
'⭐ RECOMMENDED: Set up file watchers for all tasks involving code changes (except exceptional cases)',
|
|
2028
|
-
'Link tasks to decisions they implement',
|
|
2029
|
-
'Link to constraints they address',
|
|
2030
|
-
'Link files to activate automatic file watching (save 300 tokens per file vs manual registration)',
|
|
2031
|
-
'Use descriptive link_relation values'
|
|
2032
|
-
],
|
|
2033
|
-
coordination: [
|
|
2034
|
-
'Use assigned_agent for clear ownership',
|
|
2035
|
-
'Filter by status for Kanban board views',
|
|
2036
|
-
'Monitor auto-stale transitions for stuck work',
|
|
2037
|
-
'Use tags for cross-cutting concerns (security, performance, etc.)'
|
|
2038
|
-
]
|
|
2039
|
-
}
|
|
2040
|
-
};
|
|
2041
|
-
}
|
|
7
|
+
* Directory structure:
|
|
8
|
+
* - actions/ (14 action files - one per action)
|
|
9
|
+
* - internal/ (5 utility files - validation, state machine, queries)
|
|
10
|
+
* - watcher/ (2 watcher files - status queries)
|
|
11
|
+
* - help/ (2 help files - help and examples)
|
|
12
|
+
* - types.ts (Task-specific types and constants)
|
|
13
|
+
* - index.ts (Barrel export for all actions)
|
|
14
|
+
*/
|
|
15
|
+
// Re-export everything from the modular implementation
|
|
16
|
+
export * from './tasks/index.js';
|
|
2042
17
|
//# sourceMappingURL=tasks.js.map
|