@scardis/omnifocus-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/commands/opsx/apply.md +152 -0
- package/.claude/commands/opsx/archive.md +157 -0
- package/.claude/commands/opsx/bulk-archive.md +242 -0
- package/.claude/commands/opsx/continue.md +114 -0
- package/.claude/commands/opsx/explore.md +173 -0
- package/.claude/commands/opsx/ff.md +97 -0
- package/.claude/commands/opsx/new.md +69 -0
- package/.claude/commands/opsx/onboard.md +550 -0
- package/.claude/commands/opsx/propose.md +106 -0
- package/.claude/commands/opsx/sync.md +134 -0
- package/.claude/commands/opsx/verify.md +164 -0
- package/.claude/skills/openspec-apply-change/SKILL.md +156 -0
- package/.claude/skills/openspec-archive-change/SKILL.md +114 -0
- package/.claude/skills/openspec-bulk-archive-change/SKILL.md +246 -0
- package/.claude/skills/openspec-continue-change/SKILL.md +118 -0
- package/.claude/skills/openspec-explore/SKILL.md +288 -0
- package/.claude/skills/openspec-ff-change/SKILL.md +101 -0
- package/.claude/skills/openspec-new-change/SKILL.md +74 -0
- package/.claude/skills/openspec-onboard/SKILL.md +554 -0
- package/.claude/skills/openspec-propose/SKILL.md +110 -0
- package/.claude/skills/openspec-sync-specs/SKILL.md +138 -0
- package/.claude/skills/openspec-verify-change/SKILL.md +168 -0
- package/CONTRIBUTING.md +83 -0
- package/LICENSE +21 -0
- package/README.md +198 -0
- package/dist/runtime/bridge.d.ts +16 -0
- package/dist/runtime/bridge.d.ts.map +1 -0
- package/dist/runtime/bridge.js +76 -0
- package/dist/runtime/bridge.js.map +1 -0
- package/dist/runtime/index.d.ts +5 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +5 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/jxaShim.d.ts +21 -0
- package/dist/runtime/jxaShim.d.ts.map +1 -0
- package/dist/runtime/jxaShim.js +55 -0
- package/dist/runtime/jxaShim.js.map +1 -0
- package/dist/runtime/resultProtocol.d.ts +66 -0
- package/dist/runtime/resultProtocol.d.ts.map +1 -0
- package/dist/runtime/resultProtocol.js +52 -0
- package/dist/runtime/resultProtocol.js.map +1 -0
- package/dist/runtime/snippetLoader.d.ts +4 -0
- package/dist/runtime/snippetLoader.d.ts.map +1 -0
- package/dist/runtime/snippetLoader.js +68 -0
- package/dist/runtime/snippetLoader.js.map +1 -0
- package/dist/schemas/enums.d.ts +9 -0
- package/dist/schemas/enums.d.ts.map +1 -0
- package/dist/schemas/enums.js +26 -0
- package/dist/schemas/enums.js.map +1 -0
- package/dist/schemas/index.d.ts +3 -0
- package/dist/schemas/index.d.ts.map +1 -0
- package/dist/schemas/index.js +3 -0
- package/dist/schemas/index.js.map +1 -0
- package/dist/schemas/shapes.d.ts +726 -0
- package/dist/schemas/shapes.d.ts.map +1 -0
- package/dist/schemas/shapes.js +221 -0
- package/dist/schemas/shapes.js.map +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +50 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/completeProject.d.ts +24 -0
- package/dist/tools/completeProject.d.ts.map +1 -0
- package/dist/tools/completeProject.js +17 -0
- package/dist/tools/completeProject.js.map +1 -0
- package/dist/tools/completeTask.d.ts +25 -0
- package/dist/tools/completeTask.d.ts.map +1 -0
- package/dist/tools/completeTask.js +17 -0
- package/dist/tools/completeTask.js.map +1 -0
- package/dist/tools/createFolder.d.ts +20 -0
- package/dist/tools/createFolder.d.ts.map +1 -0
- package/dist/tools/createFolder.js +13 -0
- package/dist/tools/createFolder.js.map +1 -0
- package/dist/tools/createProject.d.ts +59 -0
- package/dist/tools/createProject.d.ts.map +1 -0
- package/dist/tools/createProject.js +13 -0
- package/dist/tools/createProject.js.map +1 -0
- package/dist/tools/createTag.d.ts +20 -0
- package/dist/tools/createTag.d.ts.map +1 -0
- package/dist/tools/createTag.js +13 -0
- package/dist/tools/createTag.js.map +1 -0
- package/dist/tools/createTask.d.ts +116 -0
- package/dist/tools/createTask.d.ts.map +1 -0
- package/dist/tools/createTask.js +13 -0
- package/dist/tools/createTask.js.map +1 -0
- package/dist/tools/deleteFolder.d.ts +30 -0
- package/dist/tools/deleteFolder.d.ts.map +1 -0
- package/dist/tools/deleteFolder.js +18 -0
- package/dist/tools/deleteFolder.js.map +1 -0
- package/dist/tools/deleteProject.d.ts +30 -0
- package/dist/tools/deleteProject.d.ts.map +1 -0
- package/dist/tools/deleteProject.js +18 -0
- package/dist/tools/deleteProject.js.map +1 -0
- package/dist/tools/deleteTag.d.ts +30 -0
- package/dist/tools/deleteTag.d.ts.map +1 -0
- package/dist/tools/deleteTag.js +18 -0
- package/dist/tools/deleteTag.js.map +1 -0
- package/dist/tools/deleteTask.d.ts +31 -0
- package/dist/tools/deleteTask.d.ts.map +1 -0
- package/dist/tools/deleteTask.js +18 -0
- package/dist/tools/deleteTask.js.map +1 -0
- package/dist/tools/dropProject.d.ts +24 -0
- package/dist/tools/dropProject.d.ts.map +1 -0
- package/dist/tools/dropProject.js +17 -0
- package/dist/tools/dropProject.js.map +1 -0
- package/dist/tools/dropTask.d.ts +25 -0
- package/dist/tools/dropTask.d.ts.map +1 -0
- package/dist/tools/dropTask.js +17 -0
- package/dist/tools/dropTask.js.map +1 -0
- package/dist/tools/editFolder.d.ts +20 -0
- package/dist/tools/editFolder.d.ts.map +1 -0
- package/dist/tools/editFolder.js +13 -0
- package/dist/tools/editFolder.js.map +1 -0
- package/dist/tools/editProject.d.ts +59 -0
- package/dist/tools/editProject.d.ts.map +1 -0
- package/dist/tools/editProject.js +13 -0
- package/dist/tools/editProject.js.map +1 -0
- package/dist/tools/editTag.d.ts +31 -0
- package/dist/tools/editTag.d.ts.map +1 -0
- package/dist/tools/editTag.js +13 -0
- package/dist/tools/editTag.js.map +1 -0
- package/dist/tools/editTask.d.ts +79 -0
- package/dist/tools/editTask.d.ts.map +1 -0
- package/dist/tools/editTask.js +13 -0
- package/dist/tools/editTask.js.map +1 -0
- package/dist/tools/getFolder.d.ts +24 -0
- package/dist/tools/getFolder.d.ts.map +1 -0
- package/dist/tools/getFolder.js +17 -0
- package/dist/tools/getFolder.js.map +1 -0
- package/dist/tools/getProject.d.ts +24 -0
- package/dist/tools/getProject.d.ts.map +1 -0
- package/dist/tools/getProject.js +17 -0
- package/dist/tools/getProject.js.map +1 -0
- package/dist/tools/getTag.d.ts +24 -0
- package/dist/tools/getTag.d.ts.map +1 -0
- package/dist/tools/getTag.js +17 -0
- package/dist/tools/getTag.js.map +1 -0
- package/dist/tools/getTask.d.ts +24 -0
- package/dist/tools/getTask.d.ts.map +1 -0
- package/dist/tools/getTask.js +17 -0
- package/dist/tools/getTask.js.map +1 -0
- package/dist/tools/index.d.ts +732 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +84 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/listFolders.d.ts +50 -0
- package/dist/tools/listFolders.d.ts.map +1 -0
- package/dist/tools/listFolders.js +21 -0
- package/dist/tools/listFolders.js.map +1 -0
- package/dist/tools/listProjects.d.ts +70 -0
- package/dist/tools/listProjects.d.ts.map +1 -0
- package/dist/tools/listProjects.js +21 -0
- package/dist/tools/listProjects.js.map +1 -0
- package/dist/tools/listTags.d.ts +50 -0
- package/dist/tools/listTags.d.ts.map +1 -0
- package/dist/tools/listTags.js +21 -0
- package/dist/tools/listTags.js.map +1 -0
- package/dist/tools/listTasks.d.ts +156 -0
- package/dist/tools/listTasks.d.ts.map +1 -0
- package/dist/tools/listTasks.js +36 -0
- package/dist/tools/listTasks.js.map +1 -0
- package/dist/tools/moveProject.d.ts +20 -0
- package/dist/tools/moveProject.d.ts.map +1 -0
- package/dist/tools/moveProject.js +13 -0
- package/dist/tools/moveProject.js.map +1 -0
- package/dist/tools/moveTask.d.ts +31 -0
- package/dist/tools/moveTask.d.ts.map +1 -0
- package/dist/tools/moveTask.js +13 -0
- package/dist/tools/moveTask.js.map +1 -0
- package/dist/tools/resolveName.d.ts +36 -0
- package/dist/tools/resolveName.d.ts.map +1 -0
- package/dist/tools/resolveName.js +26 -0
- package/dist/tools/resolveName.js.map +1 -0
- package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/.openspec.yaml +2 -0
- package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/design.md +162 -0
- package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/proposal.md +49 -0
- package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/attachments/spec.md +9 -0
- package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/batch-operations/spec.md +9 -0
- package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/database-inspection/spec.md +9 -0
- package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/execution-runtime/spec.md +69 -0
- package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/folder-management/spec.md +25 -0
- package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/forecast/spec.md +9 -0
- package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/identity-resolution/spec.md +45 -0
- package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/perspective-management/spec.md +9 -0
- package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/project-management/spec.md +25 -0
- package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/recurrence/spec.md +9 -0
- package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/settings/spec.md +9 -0
- package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/tag-management/spec.md +25 -0
- package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/task-management/spec.md +29 -0
- package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/url-automation/spec.md +9 -0
- package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/window-state/spec.md +9 -0
- package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/tasks.md +84 -0
- package/openspec/changes/archive/2026-04-09-folder-crud/.openspec.yaml +2 -0
- package/openspec/changes/archive/2026-04-09-folder-crud/design.md +58 -0
- package/openspec/changes/archive/2026-04-09-folder-crud/proposal.md +28 -0
- package/openspec/changes/archive/2026-04-09-folder-crud/specs/folder-write/spec.md +45 -0
- package/openspec/changes/archive/2026-04-09-folder-crud/tasks.md +41 -0
- package/openspec/changes/archive/2026-04-09-folder-tag-list-filtering/.openspec.yaml +2 -0
- package/openspec/changes/archive/2026-04-09-folder-tag-list-filtering/design.md +38 -0
- package/openspec/changes/archive/2026-04-09-folder-tag-list-filtering/proposal.md +30 -0
- package/openspec/changes/archive/2026-04-09-folder-tag-list-filtering/specs/folder-management/spec.md +21 -0
- package/openspec/changes/archive/2026-04-09-folder-tag-list-filtering/specs/tag-management/spec.md +21 -0
- package/openspec/changes/archive/2026-04-09-folder-tag-list-filtering/tasks.md +35 -0
- package/openspec/changes/archive/2026-04-09-move-operations/.openspec.yaml +2 -0
- package/openspec/changes/archive/2026-04-09-move-operations/design.md +43 -0
- package/openspec/changes/archive/2026-04-09-move-operations/proposal.md +25 -0
- package/openspec/changes/archive/2026-04-09-move-operations/specs/move-operations/spec.md +41 -0
- package/openspec/changes/archive/2026-04-09-move-operations/tasks.md +40 -0
- package/openspec/changes/archive/2026-04-09-project-crud/.openspec.yaml +2 -0
- package/openspec/changes/archive/2026-04-09-project-crud/design.md +60 -0
- package/openspec/changes/archive/2026-04-09-project-crud/proposal.md +29 -0
- package/openspec/changes/archive/2026-04-09-project-crud/specs/project-write/spec.md +74 -0
- package/openspec/changes/archive/2026-04-09-project-crud/tasks.md +48 -0
- package/openspec/changes/archive/2026-04-09-project-filtering/.openspec.yaml +2 -0
- package/openspec/changes/archive/2026-04-09-project-filtering/design.md +52 -0
- package/openspec/changes/archive/2026-04-09-project-filtering/proposal.md +26 -0
- package/openspec/changes/archive/2026-04-09-project-filtering/specs/project-filtering/spec.md +66 -0
- package/openspec/changes/archive/2026-04-09-project-filtering/specs/project-management/spec.md +13 -0
- package/openspec/changes/archive/2026-04-09-project-filtering/tasks.md +41 -0
- package/openspec/changes/archive/2026-04-09-tag-crud/.openspec.yaml +2 -0
- package/openspec/changes/archive/2026-04-09-tag-crud/design.md +45 -0
- package/openspec/changes/archive/2026-04-09-tag-crud/proposal.md +28 -0
- package/openspec/changes/archive/2026-04-09-tag-crud/specs/tag-write/spec.md +49 -0
- package/openspec/changes/archive/2026-04-09-tag-crud/tasks.md +41 -0
- package/openspec/changes/archive/2026-04-09-task-crud/.openspec.yaml +2 -0
- package/openspec/changes/archive/2026-04-09-task-crud/design.md +62 -0
- package/openspec/changes/archive/2026-04-09-task-crud/proposal.md +29 -0
- package/openspec/changes/archive/2026-04-09-task-crud/specs/task-management/spec.md +17 -0
- package/openspec/changes/archive/2026-04-09-task-crud/specs/task-write/spec.md +89 -0
- package/openspec/changes/archive/2026-04-09-task-crud/tasks.md +55 -0
- package/openspec/changes/archive/2026-04-09-task-filtering/.openspec.yaml +2 -0
- package/openspec/changes/archive/2026-04-09-task-filtering/design.md +61 -0
- package/openspec/changes/archive/2026-04-09-task-filtering/proposal.md +26 -0
- package/openspec/changes/archive/2026-04-09-task-filtering/specs/task-filtering/spec.md +63 -0
- package/openspec/changes/archive/2026-04-09-task-filtering/specs/task-management/spec.md +17 -0
- package/openspec/changes/archive/2026-04-09-task-filtering/tasks.md +42 -0
- package/openspec/changes/archive/2026-04-10-planned-date/.openspec.yaml +2 -0
- package/openspec/changes/archive/2026-04-10-planned-date/design.md +27 -0
- package/openspec/changes/archive/2026-04-10-planned-date/proposal.md +29 -0
- package/openspec/changes/archive/2026-04-10-planned-date/specs/task-management/spec.md +29 -0
- package/openspec/changes/archive/2026-04-10-planned-date/specs/task-write/spec.md +69 -0
- package/openspec/changes/archive/2026-04-10-planned-date/tasks.md +26 -0
- package/openspec/changes/archive/2026-04-10-task-recurrence/.openspec.yaml +2 -0
- package/openspec/changes/archive/2026-04-10-task-recurrence/design.md +81 -0
- package/openspec/changes/archive/2026-04-10-task-recurrence/proposal.md +28 -0
- package/openspec/changes/archive/2026-04-10-task-recurrence/specs/recurrence/spec.md +47 -0
- package/openspec/changes/archive/2026-04-10-task-recurrence/specs/task-management/spec.md +25 -0
- package/openspec/changes/archive/2026-04-10-task-recurrence/specs/task-write/spec.md +61 -0
- package/openspec/changes/archive/2026-04-10-task-recurrence/tasks.md +39 -0
- package/openspec/config.yaml +20 -0
- package/openspec/specs/attachments/spec.md +15 -0
- package/openspec/specs/batch-operations/spec.md +15 -0
- package/openspec/specs/database-inspection/spec.md +15 -0
- package/openspec/specs/execution-runtime/spec.md +75 -0
- package/openspec/specs/folder-management/spec.md +39 -0
- package/openspec/specs/folder-write/spec.md +45 -0
- package/openspec/specs/forecast/spec.md +15 -0
- package/openspec/specs/identity-resolution/spec.md +51 -0
- package/openspec/specs/move-operations/spec.md +41 -0
- package/openspec/specs/perspective-management/spec.md +15 -0
- package/openspec/specs/project-filtering/spec.md +72 -0
- package/openspec/specs/project-management/spec.md +31 -0
- package/openspec/specs/project-write/spec.md +79 -0
- package/openspec/specs/recurrence/spec.md +51 -0
- package/openspec/specs/settings/spec.md +15 -0
- package/openspec/specs/tag-management/spec.md +39 -0
- package/openspec/specs/tag-write/spec.md +49 -0
- package/openspec/specs/task-filtering/spec.md +63 -0
- package/openspec/specs/task-management/spec.md +51 -0
- package/openspec/specs/task-write/spec.md +115 -0
- package/openspec/specs/url-automation/spec.md +15 -0
- package/openspec/specs/window-state/spec.md +15 -0
- package/package.json +32 -0
- package/scripts/cleanup-fixtures.ts +89 -0
- package/server.json +21 -0
- package/src/runtime/bridge.ts +97 -0
- package/src/runtime/index.ts +4 -0
- package/src/runtime/jxaShim.ts +55 -0
- package/src/runtime/resultProtocol.ts +62 -0
- package/src/runtime/snippetLoader.ts +79 -0
- package/src/schemas/enums.ts +32 -0
- package/src/schemas/index.ts +38 -0
- package/src/schemas/shapes.ts +267 -0
- package/src/server.ts +58 -0
- package/src/snippets/complete_project.js +73 -0
- package/src/snippets/complete_task.js +85 -0
- package/src/snippets/create_folder.js +52 -0
- package/src/snippets/create_project.js +107 -0
- package/src/snippets/create_tag.js +55 -0
- package/src/snippets/create_task.js +159 -0
- package/src/snippets/delete_folder.js +26 -0
- package/src/snippets/delete_project.js +20 -0
- package/src/snippets/delete_tag.js +20 -0
- package/src/snippets/delete_task.js +20 -0
- package/src/snippets/drop_project.js +73 -0
- package/src/snippets/drop_task.js +85 -0
- package/src/snippets/edit_folder.js +46 -0
- package/src/snippets/edit_project.js +106 -0
- package/src/snippets/edit_tag.js +56 -0
- package/src/snippets/edit_task.js +146 -0
- package/src/snippets/get_folder.js +48 -0
- package/src/snippets/get_project.js +77 -0
- package/src/snippets/get_tag.js +51 -0
- package/src/snippets/get_task.js +96 -0
- package/src/snippets/list_folders.js +50 -0
- package/src/snippets/list_projects.js +98 -0
- package/src/snippets/list_tags.js +54 -0
- package/src/snippets/list_tasks.js +127 -0
- package/src/snippets/move_project.js +79 -0
- package/src/snippets/move_task.js +113 -0
- package/src/snippets/resolve_name.js +83 -0
- package/src/tools/completeProject.ts +21 -0
- package/src/tools/completeTask.ts +23 -0
- package/src/tools/createFolder.ts +20 -0
- package/src/tools/createProject.ts +20 -0
- package/src/tools/createTag.ts +20 -0
- package/src/tools/createTask.ts +20 -0
- package/src/tools/deleteFolder.ts +24 -0
- package/src/tools/deleteProject.ts +24 -0
- package/src/tools/deleteTag.ts +24 -0
- package/src/tools/deleteTask.ts +26 -0
- package/src/tools/dropProject.ts +21 -0
- package/src/tools/dropTask.ts +23 -0
- package/src/tools/editFolder.ts +19 -0
- package/src/tools/editProject.ts +20 -0
- package/src/tools/editTag.ts +20 -0
- package/src/tools/editTask.ts +20 -0
- package/src/tools/getFolder.ts +24 -0
- package/src/tools/getProject.ts +24 -0
- package/src/tools/getTag.ts +24 -0
- package/src/tools/getTask.ts +24 -0
- package/src/tools/index.ts +85 -0
- package/src/tools/listFolders.ts +32 -0
- package/src/tools/listProjects.ts +32 -0
- package/src/tools/listTags.ts +32 -0
- package/src/tools/listTasks.ts +56 -0
- package/src/tools/moveProject.ts +20 -0
- package/src/tools/moveTask.ts +20 -0
- package/src/tools/resolveName.ts +37 -0
- package/test/integration/.gitkeep +0 -0
- package/test/integration/completeProject.int.test.ts +25 -0
- package/test/integration/completeTask.int.test.ts +30 -0
- package/test/integration/createFolder.int.test.ts +50 -0
- package/test/integration/createProject.int.test.ts +49 -0
- package/test/integration/createTag.int.test.ts +52 -0
- package/test/integration/createTask.int.test.ts +55 -0
- package/test/integration/deleteFolder.int.test.ts +64 -0
- package/test/integration/deleteProject.int.test.ts +31 -0
- package/test/integration/deleteTag.int.test.ts +61 -0
- package/test/integration/deleteTask.int.test.ts +36 -0
- package/test/integration/dropProject.int.test.ts +24 -0
- package/test/integration/dropTask.int.test.ts +29 -0
- package/test/integration/editFolder.int.test.ts +43 -0
- package/test/integration/editProject.int.test.ts +39 -0
- package/test/integration/editTag.int.test.ts +43 -0
- package/test/integration/editTask.int.test.ts +56 -0
- package/test/integration/fixtures.ts +219 -0
- package/test/integration/getTask.int.test.ts +64 -0
- package/test/integration/listFoldersFiltered.int.test.ts +98 -0
- package/test/integration/listProjects.int.test.ts +73 -0
- package/test/integration/listProjectsFiltered.int.test.ts +96 -0
- package/test/integration/listTagsFiltered.int.test.ts +54 -0
- package/test/integration/listTasksFiltered.int.test.ts +141 -0
- package/test/integration/moveProject.int.test.ts +57 -0
- package/test/integration/moveTask.int.test.ts +61 -0
- package/test/integration/plannedDate.int.test.ts +72 -0
- package/test/integration/preflight.ts +60 -0
- package/test/integration/resolveName.int.test.ts +86 -0
- package/test/integration/taskRecurrence.int.test.ts +106 -0
- package/test/unit/.gitkeep +0 -0
- package/test/unit/bridge.injection.test.ts +66 -0
- package/test/unit/resultProtocol.test.ts +71 -0
- package/test/unit/schemas.createFolder.test.ts +38 -0
- package/test/unit/schemas.createProject.test.ts +115 -0
- package/test/unit/schemas.createTask.test.ts +87 -0
- package/test/unit/schemas.editTag.test.ts +64 -0
- package/test/unit/schemas.folderTagFiltering.test.ts +42 -0
- package/test/unit/schemas.listProjects.test.ts +44 -0
- package/test/unit/schemas.moveOperations.test.ts +60 -0
- package/test/unit/schemas.recurrence.test.ts +120 -0
- package/test/unit/schemas.test.ts +126 -0
- package/test/unit/snippetLoader.test.ts +56 -0
- package/test/unit/tools.deleteTask.test.ts +19 -0
- package/test/unit/tools.listTasks.test.ts +126 -0
- package/tsconfig.json +19 -0
- package/vitest.config.ts +8 -0
- package/vitest.integration.config.ts +18 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
## 1. Snippets (src/snippets/)
|
|
2
|
+
|
|
3
|
+
- [x] 1.1 `create_tag.js`: accept `{name, parentTagId?}`; if `parentTagId` provided resolve parent tag by ID and throw `NotFoundError` if missing; create `new Tag(name)` or `new Tag(name, parentTag)`; return full tag detail envelope
|
|
4
|
+
- [x] 1.2 `edit_tag.js`: accept `{id, name?, status?}`; resolve tag by ID; apply only provided fields; map `status` string to `Tag.Status` enum member; return updated full tag detail envelope
|
|
5
|
+
- [x] 1.3 `delete_tag.js`: accept `{id}`; resolve tag by ID; call `deleteObject(tag)` (OmniFocus handles child tag and association cleanup automatically); return `{ok: true, data: {id}}`
|
|
6
|
+
|
|
7
|
+
## 2. Snippet allowlist
|
|
8
|
+
|
|
9
|
+
- [x] 2.1 Add `create_tag`, `edit_tag`, `delete_tag` to `ALLOWED_SNIPPETS` in `src/runtime/snippetLoader.ts`
|
|
10
|
+
|
|
11
|
+
## 3. Schemas (src/schemas/shapes.ts)
|
|
12
|
+
|
|
13
|
+
- [x] 3.1 Define `CreateTagInput` zod schema: `{name: z.string().min(1), parentTagId: IdSchema.optional()}`
|
|
14
|
+
- [x] 3.2 Define `EditTagInput` zod schema: `{id: IdSchema, name: z.string().min(1).optional(), status: TagStatus.optional()}`; add `.refine()` requiring at least one of `name` or `status`
|
|
15
|
+
|
|
16
|
+
## 4. Tool handlers (src/tools/)
|
|
17
|
+
|
|
18
|
+
- [x] 4.1 `createTag.ts`: validate input with `CreateTagInput`; invoke `runSnippet("create_tag", args)`; validate result against `TagDetail`; return
|
|
19
|
+
- [x] 4.2 `editTag.ts`: validate input with `EditTagInput`; invoke `runSnippet("edit_tag", args)`; validate result against `TagDetail`; return
|
|
20
|
+
- [x] 4.3 `deleteTag.ts`: input `{id: IdSchema}`; tool description MUST state the AI should confirm with the user before invoking and note that child tags are also permanently deleted; invoke `runSnippet("delete_tag", {id})`; return confirmation
|
|
21
|
+
|
|
22
|
+
## 5. Server registration
|
|
23
|
+
|
|
24
|
+
- [x] 5.1 Export all three new tool definitions from `src/tools/index.ts`
|
|
25
|
+
|
|
26
|
+
## 6. Unit tests (test/unit/)
|
|
27
|
+
|
|
28
|
+
- [x] 6.1 `schemas.editTag.test.ts`: valid inputs pass; empty object (neither name nor status) rejected by refine; invalid status enum rejected
|
|
29
|
+
|
|
30
|
+
## 7. Integration tests (test/integration/)
|
|
31
|
+
|
|
32
|
+
- [x] 7.1 `createTag.int.test.ts`: create top-level tag; create child tag; verify `path`, `parentId`, `childTagIds`
|
|
33
|
+
- [x] 7.2 `editTag.int.test.ts`: rename a tag; put tag on hold; verify status and name
|
|
34
|
+
- [x] 7.3 `deleteTag.int.test.ts`: delete a tag; verify subsequent `get_tag` returns not-found; verify child tags also gone
|
|
35
|
+
|
|
36
|
+
## 8. Verification
|
|
37
|
+
|
|
38
|
+
- [x] 8.1 `npm run typecheck` clean
|
|
39
|
+
- [x] 8.2 `npm test` (unit suite) clean
|
|
40
|
+
- [x] 8.3 Manually run integration suite; verify fixture cleanup
|
|
41
|
+
- [ ] 8.4 Connect to Claude Desktop; exercise all three tools against a real database
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
## Context
|
|
2
|
+
|
|
3
|
+
The bootstrap change established the JXA→OmniJS bridge, snippet pattern, and result protocol. All five write tools follow those same patterns. The key question for writes is: what does OmniJS expose for creating and mutating tasks, and what are the edge cases?
|
|
4
|
+
|
|
5
|
+
OmniJS task write operations:
|
|
6
|
+
- `new Task(name, position)` — creates a task; `position` is a `Project` or `Task` (for subtasks)
|
|
7
|
+
- `task.name = "..."` — property assignment for most scalar fields
|
|
8
|
+
- `task.markComplete()` — dedicated method (not property assignment)
|
|
9
|
+
- `task.drop()` — dedicated method
|
|
10
|
+
- `deleteObject(task)` — permanent deletion
|
|
11
|
+
- Tags: `task.addTag(tag)` / `task.removeTag(tag)` — manipulate by Tag object, not ID
|
|
12
|
+
|
|
13
|
+
## Goals / Non-Goals
|
|
14
|
+
|
|
15
|
+
**Goals:**
|
|
16
|
+
- Expose create, edit, complete, drop, and delete for tasks
|
|
17
|
+
- Support inbox tasks (no project), project tasks, and subtasks
|
|
18
|
+
- Edit any subset of fields in one call (fat edit — no single-field tools)
|
|
19
|
+
- Permanently delete with a tool description that instructs the AI to confirm before invoking
|
|
20
|
+
|
|
21
|
+
**Non-Goals:**
|
|
22
|
+
- Moving tasks between projects/containers (separate `move-operations` change)
|
|
23
|
+
- Recurrence rules (separate `recurrence` change)
|
|
24
|
+
- Batch operations (separate change)
|
|
25
|
+
|
|
26
|
+
## Decisions
|
|
27
|
+
|
|
28
|
+
### Decision 1: Tag manipulation by ID, resolved inside the snippet
|
|
29
|
+
|
|
30
|
+
Tags are assigned via `task.addTag(tagObject)` / `task.removeTag(tagObject)`, which require a live `Tag` object. The snippet will receive tag IDs and resolve them via `flattenedTags.find(t => t.id.primaryKey === id)` inline. This keeps the bridge contract clean (IDs only) without requiring a separate resolution round-trip.
|
|
31
|
+
|
|
32
|
+
**Alternative considered:** Accept tag names. Rejected — names are ambiguous; IDs are stable.
|
|
33
|
+
|
|
34
|
+
### Decision 2: `edit_task` replaces tags, not merges
|
|
35
|
+
|
|
36
|
+
When `tagIds` is provided to `edit_task`, the snippet replaces the task's full tag set (remove all, add new). When `tagIds` is omitted, tags are untouched. This is the least surprising contract: callers who want to add one tag still need to pass the full desired set, but they can read it from `get_task` first.
|
|
37
|
+
|
|
38
|
+
**Alternative considered:** Separate `add_tags` / `remove_tags` fields. Rejected — more surface area with no material benefit given that `get_task` is cheap.
|
|
39
|
+
|
|
40
|
+
### Decision 3: `create_task` placement via optional `projectId` and `parentTaskId`
|
|
41
|
+
|
|
42
|
+
- Both omitted → inbox
|
|
43
|
+
- `projectId` only → task added to project root
|
|
44
|
+
- `parentTaskId` only → subtask; inherits project from parent
|
|
45
|
+
- Both provided → error (ambiguous placement; caller must choose)
|
|
46
|
+
|
|
47
|
+
This mirrors how OmniFocus itself treats task placement.
|
|
48
|
+
|
|
49
|
+
### Decision 4: `complete_task` and `drop_task` as separate tools from `edit_task`
|
|
50
|
+
|
|
51
|
+
Status transitions in OmniJS require method calls (`markComplete()`, `drop()`), not property assignment. Exposing them as dedicated tools makes the intent unambiguous and avoids a complex status-to-method dispatch inside `edit_task`. It also makes tool descriptions cleaner for the LLM.
|
|
52
|
+
|
|
53
|
+
### Decision 5: `delete_task` tool description as the confirmation guardrail
|
|
54
|
+
|
|
55
|
+
The server executes `deleteObject(task)` unconditionally when called. The confirmation requirement is enforced through the MCP tool description, which instructs the AI to ask the user explicitly before invoking this tool. This is consistent with how MCP tool descriptions guide AI behavior.
|
|
56
|
+
|
|
57
|
+
## Risks / Trade-offs
|
|
58
|
+
|
|
59
|
+
- **Partial edit with bad field value** → OmniJS will throw; the error envelope propagates back as an `ExecutionError`. No partial writes occur because OmniJS is synchronous within `evaluateJavascript`.
|
|
60
|
+
- **Tag ID not found during edit** → Snippet should throw a descriptive `NotFoundError` for the missing tag ID, not silently skip it.
|
|
61
|
+
- **Inbox placement** → `new Task(name, inbox)` requires passing the `inbox` object. In OmniJS the inbox is accessible as `inbox` (bare global). Verified pattern from the read-side exploration.
|
|
62
|
+
- **deleteObject on a task with subtasks** → OmniJS deletes the task and all subtasks. Tool description should note this.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
## Why
|
|
2
|
+
|
|
3
|
+
The bootstrap change delivered read-only task access. Callers can observe tasks but cannot create, modify, complete, drop, or delete them — making the server unsuitable for any workflow that involves acting on tasks, not just reading them.
|
|
4
|
+
|
|
5
|
+
## What Changes
|
|
6
|
+
|
|
7
|
+
- Add `create_task` tool: create a task in a project, as a subtask, or in the inbox
|
|
8
|
+
- Add `edit_task` tool: modify any subset of a task's scalar fields and tag assignments in a single call
|
|
9
|
+
- Add `complete_task` tool: mark a task complete
|
|
10
|
+
- Add `drop_task` tool: mark a task dropped
|
|
11
|
+
- Add `delete_task` tool: permanently delete a task (tool description instructs the AI to confirm with the user before invoking)
|
|
12
|
+
|
|
13
|
+
## Capabilities
|
|
14
|
+
|
|
15
|
+
### New Capabilities
|
|
16
|
+
|
|
17
|
+
- `task-write`: Create, edit, complete, drop, and permanently delete OmniFocus tasks
|
|
18
|
+
|
|
19
|
+
### Modified Capabilities
|
|
20
|
+
|
|
21
|
+
- `task-management`: Extend existing spec to add write requirements alongside the existing read requirements
|
|
22
|
+
|
|
23
|
+
## Impact
|
|
24
|
+
|
|
25
|
+
- 5 new MCP tools registered in `src/server.ts`
|
|
26
|
+
- 5 new tool handler files in `src/tools/`
|
|
27
|
+
- 5 new OmniJS snippets in `src/snippets/`
|
|
28
|
+
- `ALLOWED_SNIPPETS` allowlist in `src/runtime/snippetLoader.ts` must be extended
|
|
29
|
+
- Integration tests will mutate real OmniFocus data (scoped to fixture folder per existing pattern)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
## MODIFIED Requirements
|
|
2
|
+
|
|
3
|
+
### Requirement: Get task by ID
|
|
4
|
+
|
|
5
|
+
The system SHALL provide a `get_task` tool that accepts `{id: string}` and returns the full detail record of the named task, including `{id, name, note, status, flagged, deferDate, dueDate, completionDate, estimatedMinutes, containerId, containerType, tagIds, parentTaskId}`. The `parentTaskId` field SHALL be `null` for top-level tasks and SHALL contain the parent task's `id.primaryKey` for subtasks. If no task exists with that ID, the tool SHALL return a structured not-found error.
|
|
6
|
+
|
|
7
|
+
#### Scenario: Existing task returns full detail
|
|
8
|
+
- **WHEN** `get_task` is called with the ID of an existing task
|
|
9
|
+
- **THEN** the tool returns the task's full detail record including all scalar fields, the list of tag IDs assigned to it, and `parentTaskId`
|
|
10
|
+
|
|
11
|
+
#### Scenario: Subtask includes parentTaskId
|
|
12
|
+
- **WHEN** `get_task` is called with the ID of a subtask
|
|
13
|
+
- **THEN** the returned record includes `parentTaskId` set to the parent task's stable ID
|
|
14
|
+
|
|
15
|
+
#### Scenario: Missing task returns not-found error
|
|
16
|
+
- **WHEN** `get_task` is called with an ID that does not correspond to any task
|
|
17
|
+
- **THEN** the tool returns a structured error with a not-found code and does not throw an unhandled exception
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
## ADDED Requirements
|
|
2
|
+
|
|
3
|
+
### Requirement: Create task
|
|
4
|
+
|
|
5
|
+
The system SHALL provide a `create_task` tool that creates a new OmniFocus task and returns its full detail record. The tool SHALL accept `{name: string, note?: string, flagged?: boolean, deferDate?: string, dueDate?: string, estimatedMinutes?: number, projectId?: string, parentTaskId?: string, tagIds?: string[]}`. Placement SHALL be determined as follows: if both `projectId` and `parentTaskId` are provided the tool SHALL return an error; if only `projectId` is provided the task is placed at the project root; if only `parentTaskId` is provided the task is created as a subtask inheriting its parent's project; if neither is provided the task is placed in the inbox.
|
|
6
|
+
|
|
7
|
+
#### Scenario: Create inbox task
|
|
8
|
+
- **WHEN** `create_task` is called with `{name: "Buy milk"}` and no `projectId` or `parentTaskId`
|
|
9
|
+
- **THEN** the tool creates the task in the OmniFocus inbox and returns its full detail record including a stable `id`
|
|
10
|
+
|
|
11
|
+
#### Scenario: Create task in a project
|
|
12
|
+
- **WHEN** `create_task` is called with `{name: "Write tests", projectId: "abc123"}`
|
|
13
|
+
- **THEN** the tool creates the task at the root of the specified project and returns its full detail record
|
|
14
|
+
|
|
15
|
+
#### Scenario: Create subtask
|
|
16
|
+
- **WHEN** `create_task` is called with `{name: "Review PR", parentTaskId: "xyz789"}`
|
|
17
|
+
- **THEN** the tool creates the task as a child of the specified parent task and returns its full detail record
|
|
18
|
+
|
|
19
|
+
#### Scenario: Ambiguous placement is rejected
|
|
20
|
+
- **WHEN** `create_task` is called with both `projectId` and `parentTaskId`
|
|
21
|
+
- **THEN** the tool returns a validation error before any snippet executes
|
|
22
|
+
|
|
23
|
+
#### Scenario: Non-existent project returns not-found error
|
|
24
|
+
- **WHEN** `create_task` is called with a `projectId` that does not correspond to any project
|
|
25
|
+
- **THEN** the tool returns a structured not-found error
|
|
26
|
+
|
|
27
|
+
### Requirement: Edit task
|
|
28
|
+
|
|
29
|
+
The system SHALL provide an `edit_task` tool that modifies an existing task and returns its updated full detail record. The tool SHALL accept `{id: string}` plus any subset of `{name?: string, note?: string, flagged?: boolean, deferDate?: string | null, dueDate?: string | null, estimatedMinutes?: number | null, tagIds?: string[]}`. Fields omitted from the call SHALL be left unchanged. When `tagIds` is provided it SHALL replace the task's entire tag set; when omitted, tags SHALL be unchanged. Passing `null` for a date or `estimatedMinutes` SHALL clear the field.
|
|
30
|
+
|
|
31
|
+
#### Scenario: Edit a single field
|
|
32
|
+
- **WHEN** `edit_task` is called with `{id: "abc123", flagged: true}`
|
|
33
|
+
- **THEN** only the `flagged` field is changed; all other fields retain their previous values
|
|
34
|
+
|
|
35
|
+
#### Scenario: Replace tag set
|
|
36
|
+
- **WHEN** `edit_task` is called with `{id: "abc123", tagIds: ["t1", "t2"]}`
|
|
37
|
+
- **THEN** the task's tags are set to exactly `["t1", "t2"]`, replacing any previously assigned tags
|
|
38
|
+
|
|
39
|
+
#### Scenario: Clear a date field
|
|
40
|
+
- **WHEN** `edit_task` is called with `{id: "abc123", dueDate: null}`
|
|
41
|
+
- **THEN** the task's due date is cleared
|
|
42
|
+
|
|
43
|
+
#### Scenario: Non-existent task returns not-found error
|
|
44
|
+
- **WHEN** `edit_task` is called with an ID that does not correspond to any task
|
|
45
|
+
- **THEN** the tool returns a structured not-found error
|
|
46
|
+
|
|
47
|
+
#### Scenario: Non-existent tag ID returns not-found error
|
|
48
|
+
- **WHEN** `edit_task` is called with a `tagIds` array containing an ID that does not correspond to any tag
|
|
49
|
+
- **THEN** the tool returns a structured not-found error and the task is not modified
|
|
50
|
+
|
|
51
|
+
### Requirement: Complete task
|
|
52
|
+
|
|
53
|
+
The system SHALL provide a `complete_task` tool that marks an existing task complete using OmniJS `markComplete()` and returns the task's updated full detail record.
|
|
54
|
+
|
|
55
|
+
#### Scenario: Complete an existing task
|
|
56
|
+
- **WHEN** `complete_task` is called with the ID of an available task
|
|
57
|
+
- **THEN** the task's status becomes `"complete"` and the tool returns the updated detail record
|
|
58
|
+
|
|
59
|
+
#### Scenario: Non-existent task returns not-found error
|
|
60
|
+
- **WHEN** `complete_task` is called with an ID that does not correspond to any task
|
|
61
|
+
- **THEN** the tool returns a structured not-found error
|
|
62
|
+
|
|
63
|
+
### Requirement: Drop task
|
|
64
|
+
|
|
65
|
+
The system SHALL provide a `drop_task` tool that marks an existing task dropped using OmniJS `drop()` and returns the task's updated full detail record.
|
|
66
|
+
|
|
67
|
+
#### Scenario: Drop an existing task
|
|
68
|
+
- **WHEN** `drop_task` is called with the ID of an available task
|
|
69
|
+
- **THEN** the task's status becomes `"dropped"` and the tool returns the updated detail record
|
|
70
|
+
|
|
71
|
+
#### Scenario: Non-existent task returns not-found error
|
|
72
|
+
- **WHEN** `drop_task` is called with an ID that does not correspond to any task
|
|
73
|
+
- **THEN** the tool returns a structured not-found error
|
|
74
|
+
|
|
75
|
+
### Requirement: Delete task
|
|
76
|
+
|
|
77
|
+
The system SHALL provide a `delete_task` tool that permanently deletes a task and all its subtasks using OmniJS `deleteObject()`. The tool description SHALL instruct the AI to confirm with the user before invoking this tool, noting that deletion is permanent and includes all subtasks.
|
|
78
|
+
|
|
79
|
+
#### Scenario: Delete an existing task
|
|
80
|
+
- **WHEN** `delete_task` is called with the ID of an existing task
|
|
81
|
+
- **THEN** the task and all its subtasks are permanently removed from OmniFocus and the tool returns a confirmation envelope
|
|
82
|
+
|
|
83
|
+
#### Scenario: Delete task with subtasks removes all children
|
|
84
|
+
- **WHEN** `delete_task` is called with the ID of a task that has subtasks
|
|
85
|
+
- **THEN** the task and all descendant subtasks are deleted
|
|
86
|
+
|
|
87
|
+
#### Scenario: Non-existent task returns not-found error
|
|
88
|
+
- **WHEN** `delete_task` is called with an ID that does not correspond to any task
|
|
89
|
+
- **THEN** the tool returns a structured not-found error
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
## 1. Snippets (src/snippets/)
|
|
2
|
+
|
|
3
|
+
- [x] 1.1 `create_task.js`: accept `{name, note?, flagged?, deferDate?, dueDate?, estimatedMinutes?, projectId?, parentTaskId?, tagIds?}`; resolve placement (inbox / project root / subtask); throw `ConflictError` if both `projectId` and `parentTaskId` provided; throw `NotFoundError` on missing project, parent task, or tag ID; return full task detail envelope
|
|
4
|
+
- [x] 1.2 `edit_task.js`: accept `{id, name?, note?, flagged?, deferDate?, dueDate?, estimatedMinutes?, tagIds?}`; resolve task by ID; apply only provided fields; when `tagIds` present replace full tag set (remove all, add each by ID); pass `null` dates/estimatedMinutes to clear; return updated full detail envelope
|
|
5
|
+
- [x] 1.3 `complete_task.js`: accept `{id}`; resolve task by ID; call `task.markComplete()`; return updated full detail envelope
|
|
6
|
+
- [x] 1.4 `drop_task.js`: accept `{id}`; resolve task by ID; call `task.drop()`; return updated full detail envelope
|
|
7
|
+
- [x] 1.5 `delete_task.js`: accept `{id}`; resolve task by ID; call `deleteObject(task)`; return `{ok: true, data: {id}}`
|
|
8
|
+
|
|
9
|
+
## 2. Snippet allowlist
|
|
10
|
+
|
|
11
|
+
- [x] 2.1 Add `create_task`, `edit_task`, `complete_task`, `drop_task`, `delete_task` to `ALLOWED_SNIPPETS` in `src/runtime/snippetLoader.ts`
|
|
12
|
+
|
|
13
|
+
## 3. Schemas (src/schemas/shapes.ts)
|
|
14
|
+
|
|
15
|
+
- [x] 3.1 Add `parentTaskId: z.string().nullable()` to `TaskDetail` schema
|
|
16
|
+
- [x] 3.2 Define `CreateTaskInput` zod schema with all optional fields and the mutual-exclusion refinement for `projectId` + `parentTaskId`
|
|
17
|
+
- [x] 3.3 Define `EditTaskInput` zod schema — `id` required, all other fields optional; date fields accept `string | null`; `estimatedMinutes` accepts `number | null`
|
|
18
|
+
|
|
19
|
+
## 4. Tool handlers (src/tools/)
|
|
20
|
+
|
|
21
|
+
- [x] 4.1 `createTask.ts`: validate input with `CreateTaskInput`; invoke `runSnippet("create_task", args)`; validate result against `TaskDetail`; return
|
|
22
|
+
- [x] 4.2 `editTask.ts`: validate input with `EditTaskInput`; invoke `runSnippet("edit_task", args)`; validate result against `TaskDetail`; return
|
|
23
|
+
- [x] 4.3 `completeTask.ts`: input `{id: IdSchema}`; invoke `runSnippet("complete_task", {id})`; validate result against `TaskDetail`; return
|
|
24
|
+
- [x] 4.4 `dropTask.ts`: input `{id: IdSchema}`; invoke `runSnippet("drop_task", {id})`; validate result against `TaskDetail`; return
|
|
25
|
+
- [x] 4.5 `deleteTask.ts`: input `{id: IdSchema}`; tool description MUST state the AI should confirm with the user before invoking and note that deletion is permanent and includes all subtasks; invoke `runSnippet("delete_task", {id})`; return confirmation
|
|
26
|
+
|
|
27
|
+
## 5. Server registration
|
|
28
|
+
|
|
29
|
+
- [x] 5.1 Export all five new tool definitions from `src/tools/index.ts`
|
|
30
|
+
- [x] 5.2 Verify `src/server.ts` picks them up via the `allTools` barrel (no change needed if barrel is already dynamic)
|
|
31
|
+
|
|
32
|
+
## 6. get_task update
|
|
33
|
+
|
|
34
|
+
- [x] 6.1 Update `src/snippets/get_task.js` to include `parentTaskId` in the returned detail (`t.parentTask ? t.parentTask.id.primaryKey : null`)
|
|
35
|
+
- [x] 6.2 Update `src/tools/getTask.ts` to validate against the updated `TaskDetail` schema
|
|
36
|
+
|
|
37
|
+
## 7. Unit tests (test/unit/)
|
|
38
|
+
|
|
39
|
+
- [x] 7.1 `schemas.createTask.test.ts`: valid inputs pass; both `projectId` + `parentTaskId` rejected; missing name rejected; null dates accepted in edit schema
|
|
40
|
+
- [x] 7.2 `tools.deleteTask.test.ts`: verify tool description contains confirmation language
|
|
41
|
+
|
|
42
|
+
## 8. Integration tests (test/integration/)
|
|
43
|
+
|
|
44
|
+
- [x] 8.1 `createTask.int.test.ts`: create inbox task, verify returned ID; create project task, verify containerId; create subtask, verify parentTaskId
|
|
45
|
+
- [x] 8.2 `editTask.int.test.ts`: edit name; edit flags; replace tag set; clear due date
|
|
46
|
+
- [x] 8.3 `completeTask.int.test.ts`: complete a task, verify status = `"complete"`
|
|
47
|
+
- [x] 8.4 `dropTask.int.test.ts`: drop a task, verify status = `"dropped"`
|
|
48
|
+
- [x] 8.5 `deleteTask.int.test.ts`: delete a task, verify subsequent `get_task` returns not-found
|
|
49
|
+
|
|
50
|
+
## 9. Verification
|
|
51
|
+
|
|
52
|
+
- [x] 9.1 `npm run typecheck` clean
|
|
53
|
+
- [x] 9.2 `npm test` (unit suite) clean
|
|
54
|
+
- [x] 9.3 Manually run integration suite; verify fixture cleanup
|
|
55
|
+
- [ ] 9.4 Connect to Claude Desktop; exercise all five tools against a real database
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
## Context
|
|
2
|
+
|
|
3
|
+
`list_tasks` currently fetches all tasks in scope and returns a flat `TaskSummary` array with no server-side filtering. For large databases, `scope: { all: true }` can return thousands of tasks — far too many for an LLM to reason over. Filtering must happen inside the OmniJS snippet before data crosses the JXA bridge; post-hoc filtering in TypeScript would still pay the full serialization cost.
|
|
4
|
+
|
|
5
|
+
`TaskSummary` currently omits `dueDate` and `tagIds`. Without these, filtered results are opaque: if the LLM asks "what's due today?" it can't present due dates in its response without issuing a `get_task` call per result.
|
|
6
|
+
|
|
7
|
+
## Goals / Non-Goals
|
|
8
|
+
|
|
9
|
+
**Goals:**
|
|
10
|
+
- Filter tasks by `flagged`, `status[]`, `tagId`, and `dueBeforeDate` inside the snippet
|
|
11
|
+
- Enrich `TaskSummary` with `dueDate` and `tagIds`
|
|
12
|
+
- Change default behavior: exclude `complete` and `dropped` tasks when no `status` filter is provided
|
|
13
|
+
- Cap results with a configurable `limit` (default 200)
|
|
14
|
+
|
|
15
|
+
**Non-Goals:**
|
|
16
|
+
- Multi-tag filtering with AND/OR semantics (future work)
|
|
17
|
+
- Full-text search on task name or note
|
|
18
|
+
- Pagination / cursor-based continuation (hard cap only)
|
|
19
|
+
- Filtering `list_projects` (separate change)
|
|
20
|
+
|
|
21
|
+
## Decisions
|
|
22
|
+
|
|
23
|
+
### Decision 1: Filter in snippet, not TypeScript
|
|
24
|
+
|
|
25
|
+
All filter logic runs inside `evaluateJavascript`. The TypeScript layer passes filter args through and validates the schema. This avoids deserializing thousands of tasks only to discard most of them.
|
|
26
|
+
|
|
27
|
+
Alternative considered: filter in TS. Rejected because the JXA serialization cost is paid per-task regardless — a 5000-task DB would serialize all 5000 even if only 10 match.
|
|
28
|
+
|
|
29
|
+
### Decision 2: Enrich TaskSummary with dueDate and tagIds
|
|
30
|
+
|
|
31
|
+
`TaskSummary` gains two fields:
|
|
32
|
+
- `dueDate: string | null` — ISO datetime
|
|
33
|
+
- `tagIds: string[]`
|
|
34
|
+
|
|
35
|
+
These are the fields most likely to appear in filtering queries and most useful to display in results. Other detail fields (`note`, `deferDate`, `estimatedMinutes`) remain in `TaskDetail` only.
|
|
36
|
+
|
|
37
|
+
This is a non-breaking addition to the shape — existing consumers receive new optional fields.
|
|
38
|
+
|
|
39
|
+
### Decision 3: Default excludes complete and dropped
|
|
40
|
+
|
|
41
|
+
When `filter.status` is omitted, the snippet filters out `Task.Status.Completed` and `Task.Status.Dropped`. This is a breaking behavior change but the correct default for LLM-driven queries. The previous default (return everything) was only useful for bulk inspection, which can still be achieved with `status: ["available", "blocked", "dueSoon", "next", "overdue", "complete", "dropped"]`.
|
|
42
|
+
|
|
43
|
+
### Decision 4: Status filter as array, tag filter as single ID
|
|
44
|
+
|
|
45
|
+
`status` accepts an array of `TaskStatus` values. This lets callers express compound queries ("available OR overdue") in a single call.
|
|
46
|
+
|
|
47
|
+
`tagId` accepts a single ID — the 80% use case. Multi-tag AND/OR filtering adds significant complexity for uncertain benefit in v1.
|
|
48
|
+
|
|
49
|
+
### Decision 5: dueBeforeDate is inclusive
|
|
50
|
+
|
|
51
|
+
`dueBeforeDate` matches tasks where `task.dueDate <= dueBeforeDate`. "Due before end of today" is expressed as the end-of-day ISO timestamp. Null due dates never match.
|
|
52
|
+
|
|
53
|
+
### Decision 6: limit default 200, passed as snippet arg
|
|
54
|
+
|
|
55
|
+
The limit is enforced inside the snippet (slice after filtering) so the cap applies before serialization. Default 200. Callers may increase it up to any value — there is no server-side maximum, just the JXA timeout.
|
|
56
|
+
|
|
57
|
+
## Risks / Trade-offs
|
|
58
|
+
|
|
59
|
+
- **Breaking default behavior** — consumers that relied on `list_tasks` returning complete tasks will see a behavior change. Risk is low: this is a local MCP server with a single consumer (Claude Desktop), and the new default is strictly more useful.
|
|
60
|
+
- **tagId filter requires flattenedTags lookup** — to check `task.tags`, OmniJS traverses the tag tree. On databases with thousands of tags this is fast in practice but not free.
|
|
61
|
+
- **limit does not paginate** — if a user has 500 overdue tasks and limit is 200, they see the first 200 with no way to retrieve the rest. Acceptable for v1; pagination is future work.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
## Why
|
|
2
|
+
|
|
3
|
+
`list_tasks` currently returns all tasks in scope with no filtering — an LLM asking "what's overdue?" on a large database receives thousands of tasks and must reason over the entire set. Filtering in the snippet before the JXA bridge means the LLM only sees the relevant tasks, making planning queries fast and practical.
|
|
4
|
+
|
|
5
|
+
## What Changes
|
|
6
|
+
|
|
7
|
+
- `list_tasks` accepts an optional `filter` object: `flagged`, `status` (array), `tagId`, `dueBeforeDate`
|
|
8
|
+
- `list_tasks` accepts an optional `limit` (default 200)
|
|
9
|
+
- **BREAKING**: Default behavior changes — when no `status` filter is given, complete and dropped tasks are excluded. Pass `status: ["complete"]` explicitly to retrieve completed tasks.
|
|
10
|
+
- `TaskSummary` is enriched with `dueDate` and `tagIds` fields so the LLM can reason about results without fetching full task detail for each match
|
|
11
|
+
|
|
12
|
+
## Capabilities
|
|
13
|
+
|
|
14
|
+
### New Capabilities
|
|
15
|
+
- `task-filtering`: Filter parameters and enriched summary for `list_tasks`
|
|
16
|
+
|
|
17
|
+
### Modified Capabilities
|
|
18
|
+
- `task-management`: `list_tasks` tool signature changes (new filter/limit params, enriched return shape, changed default behavior)
|
|
19
|
+
|
|
20
|
+
## Impact
|
|
21
|
+
|
|
22
|
+
- `src/snippets/list_tasks.js` — rewritten to apply filters and return enriched fields
|
|
23
|
+
- `src/schemas/shapes.ts` — `TaskSummary` gains `dueDate` and `tagIds`; new `ListTasksFilter` schema
|
|
24
|
+
- `src/tools/listTasks.ts` — input schema updated with filter and limit
|
|
25
|
+
- `test/unit/tools.listTasks.test.ts` — existing unit tests may need updating for new schema shape
|
|
26
|
+
- `test/integration/listTasks.int.test.ts` — new integration tests for filter combinations
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
## ADDED Requirements
|
|
2
|
+
|
|
3
|
+
### Requirement: Filter list_tasks results
|
|
4
|
+
|
|
5
|
+
The system SHALL allow `list_tasks` to accept an optional `filter` object that restricts which tasks are returned. All filter fields are optional and combine as AND conditions. When `filter.status` is omitted, the tool SHALL exclude tasks with status `complete` or `dropped` by default.
|
|
6
|
+
|
|
7
|
+
Filter fields:
|
|
8
|
+
- `flagged` (boolean): when `true`, return only flagged tasks
|
|
9
|
+
- `status` (array of TaskStatus): return only tasks whose status is in the array; overrides the default exclusion of complete/dropped
|
|
10
|
+
- `tagId` (string): return only tasks that have this tag assigned
|
|
11
|
+
- `dueBeforeDate` (ISO datetime string): return only tasks where `dueDate` is non-null and on or before this date
|
|
12
|
+
|
|
13
|
+
#### Scenario: Filter by flagged
|
|
14
|
+
- **WHEN** `list_tasks` is called with `{ scope: { all: true }, filter: { flagged: true } }`
|
|
15
|
+
- **THEN** the tool returns only tasks where `flagged` is `true`, excluding complete and dropped tasks
|
|
16
|
+
|
|
17
|
+
#### Scenario: Filter by status array
|
|
18
|
+
- **WHEN** `list_tasks` is called with `{ scope: { all: true }, filter: { status: ["overdue", "dueSoon"] } }`
|
|
19
|
+
- **THEN** the tool returns only tasks with status `overdue` or `dueSoon`
|
|
20
|
+
|
|
21
|
+
#### Scenario: Filter by tagId
|
|
22
|
+
- **WHEN** `list_tasks` is called with `{ scope: { projectId: "abc123" }, filter: { tagId: "tag456" } }`
|
|
23
|
+
- **THEN** the tool returns only tasks in that project that have the specified tag assigned
|
|
24
|
+
|
|
25
|
+
#### Scenario: Filter by dueBeforeDate
|
|
26
|
+
- **WHEN** `list_tasks` is called with `{ scope: { all: true }, filter: { dueBeforeDate: "2026-04-09T23:59:59Z" } }`
|
|
27
|
+
- **THEN** the tool returns only tasks with a non-null dueDate on or before the specified date, excluding complete and dropped tasks
|
|
28
|
+
|
|
29
|
+
#### Scenario: Default excludes complete and dropped
|
|
30
|
+
- **WHEN** `list_tasks` is called with no filter
|
|
31
|
+
- **THEN** the tool returns tasks with any status except `complete` and `dropped`
|
|
32
|
+
|
|
33
|
+
#### Scenario: Explicit status filter overrides default exclusion
|
|
34
|
+
- **WHEN** `list_tasks` is called with `{ filter: { status: ["complete"] } }`
|
|
35
|
+
- **THEN** the tool returns completed tasks (the default exclusion does not apply)
|
|
36
|
+
|
|
37
|
+
#### Scenario: Combined filters act as AND
|
|
38
|
+
- **WHEN** `list_tasks` is called with `{ filter: { flagged: true, tagId: "tag456" } }`
|
|
39
|
+
- **THEN** the tool returns only tasks that are both flagged AND have that tag
|
|
40
|
+
|
|
41
|
+
### Requirement: Limit list_tasks results
|
|
42
|
+
|
|
43
|
+
The system SHALL allow `list_tasks` to accept an optional `limit` integer that caps the number of tasks returned. When `limit` is omitted, a default of 200 SHALL apply.
|
|
44
|
+
|
|
45
|
+
#### Scenario: Default limit of 200
|
|
46
|
+
- **WHEN** `list_tasks` is called without a `limit` and more than 200 tasks match
|
|
47
|
+
- **THEN** the tool returns at most 200 tasks
|
|
48
|
+
|
|
49
|
+
#### Scenario: Custom limit
|
|
50
|
+
- **WHEN** `list_tasks` is called with `{ limit: 50 }`
|
|
51
|
+
- **THEN** the tool returns at most 50 tasks
|
|
52
|
+
|
|
53
|
+
### Requirement: Enriched task summary includes dueDate and tagIds
|
|
54
|
+
|
|
55
|
+
The `TaskSummary` returned by `list_tasks` SHALL include `dueDate` (ISO datetime or null) and `tagIds` (array of tag ID strings) in addition to existing fields.
|
|
56
|
+
|
|
57
|
+
#### Scenario: Summary includes dueDate
|
|
58
|
+
- **WHEN** `list_tasks` returns a task that has a due date set
|
|
59
|
+
- **THEN** the task summary includes `dueDate` as an ISO datetime string
|
|
60
|
+
|
|
61
|
+
#### Scenario: Summary includes tagIds
|
|
62
|
+
- **WHEN** `list_tasks` returns a task that has tags assigned
|
|
63
|
+
- **THEN** the task summary includes `tagIds` as an array of tag ID strings
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
## MODIFIED Requirements
|
|
2
|
+
|
|
3
|
+
### Requirement: List tasks with scope filter
|
|
4
|
+
|
|
5
|
+
The system SHALL provide a `list_tasks` tool that returns tasks within a caller-specified scope. The scope SHALL be one of: `{projectId: string}`, `{folderId: string}` (all tasks in projects under that folder, recursively), `{inbox: true}`, or `{all: true}` (the full flattened task list). The tool SHALL accept an optional `filter` object (see task-filtering spec) and an optional `limit` integer (default 200). The tool SHALL return an array of task summaries, each containing `{id, name, status, flagged, containerId, containerType, dueDate, tagIds}`. When no `filter.status` is provided, tasks with status `complete` or `dropped` SHALL be excluded by default.
|
|
6
|
+
|
|
7
|
+
#### Scenario: List tasks in a specific project
|
|
8
|
+
- **WHEN** `list_tasks` is called with `{projectId: "abc123"}`
|
|
9
|
+
- **THEN** the tool returns actionable tasks (non-complete, non-dropped) directly or transitively contained in that project, each carrying the project id as `containerId` and `"project"` as `containerType`
|
|
10
|
+
|
|
11
|
+
#### Scenario: List inbox tasks
|
|
12
|
+
- **WHEN** `list_tasks` is called with `{inbox: true}`
|
|
13
|
+
- **THEN** the tool returns actionable inbox tasks with `containerType` set to `"inbox"`
|
|
14
|
+
|
|
15
|
+
#### Scenario: Invalid scope is rejected at the TS boundary
|
|
16
|
+
- **WHEN** `list_tasks` is called with `{projectId: "abc", inbox: true}` (mutually exclusive scopes)
|
|
17
|
+
- **THEN** the tool returns a validation error before any snippet executes
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
## 1. Schema changes (src/schemas/)
|
|
2
|
+
|
|
3
|
+
- [x] 1.1 Add `dueDate: z.string().datetime().nullable()` and `tagIds: z.array(IdSchema)` to `TaskSummary` in `shapes.ts`
|
|
4
|
+
- [x] 1.2 Define `ListTasksFilter` zod schema: `{ flagged: z.literal(true).optional(), status: z.array(TaskStatus).optional(), tagId: IdSchema.optional(), dueBeforeDate: z.string().datetime().optional() }`
|
|
5
|
+
- [x] 1.3 Export `ListTasksFilter` from `src/schemas/index.ts`
|
|
6
|
+
|
|
7
|
+
## 2. Snippet rewrite (src/snippets/list_tasks.js)
|
|
8
|
+
|
|
9
|
+
- [x] 2.1 Add `dueDate` and `tagIds` to the `mapTask` helper
|
|
10
|
+
- [x] 2.2 Implement default status exclusion: when `args.filter?.status` is absent, exclude `Task.Status.Completed` and `Task.Status.Dropped`
|
|
11
|
+
- [x] 2.3 Implement `flagged` filter: when `args.filter?.flagged` is `true`, exclude non-flagged tasks
|
|
12
|
+
- [x] 2.4 Implement `status` array filter: when `args.filter?.status` is provided, keep only tasks whose status maps to one of the given strings
|
|
13
|
+
- [x] 2.5 Implement `tagId` filter: when `args.filter?.tagId` is provided, keep only tasks that have a tag whose `id.primaryKey` matches
|
|
14
|
+
- [x] 2.6 Implement `dueBeforeDate` filter: when provided, keep only tasks where `task.dueDate` is non-null and `<= new Date(args.filter.dueBeforeDate)`
|
|
15
|
+
- [x] 2.7 Apply `args.limit` (default 200) as a slice after all filters
|
|
16
|
+
|
|
17
|
+
## 3. Tool handler update (src/tools/listTasks.ts)
|
|
18
|
+
|
|
19
|
+
- [x] 3.1 Add `filter: ListTasksFilter.optional()` and `limit: z.number().int().positive().optional()` to `listTasksSchema`
|
|
20
|
+
- [x] 3.2 Pass `filter` and `limit` through to `runSnippet`
|
|
21
|
+
- [x] 3.3 Update tool description to document the filter params and the new default-exclusion behavior
|
|
22
|
+
|
|
23
|
+
## 4. Unit tests (test/unit/)
|
|
24
|
+
|
|
25
|
+
- [x] 4.1 Update `tools.listTasks.test.ts`: add `dueDate` and `tagIds` to any TaskSummary fixtures that are now required fields
|
|
26
|
+
- [x] 4.2 Add schema tests for `ListTasksFilter`: valid filter passes; invalid status enum rejected; dueBeforeDate must be ISO datetime
|
|
27
|
+
|
|
28
|
+
## 5. Integration tests (test/integration/)
|
|
29
|
+
|
|
30
|
+
- [x] 5.1 `listTasksFiltered.int.test.ts`: create tasks with known properties in a fixture folder; verify `flagged: true` filter returns only flagged tasks
|
|
31
|
+
- [x] 5.2 Verify `status` filter: create a completed task; confirm it is absent by default and present when `status: ["complete"]` is passed
|
|
32
|
+
- [x] 5.3 Verify `tagId` filter: assign a tag to one task; confirm only that task is returned
|
|
33
|
+
- [x] 5.4 Verify `dueBeforeDate` filter: create a task with a due date in the past; confirm it is returned; create one due far in the future; confirm it is not
|
|
34
|
+
- [x] 5.5 Verify `limit`: create more tasks than the limit; confirm result is capped
|
|
35
|
+
- [x] 5.6 Verify enriched summary: returned tasks include non-null `dueDate` and `tagIds` when applicable
|
|
36
|
+
|
|
37
|
+
## 6. Verification
|
|
38
|
+
|
|
39
|
+
- [x] 6.1 `npm run typecheck` clean
|
|
40
|
+
- [x] 6.2 `npm test` (unit suite) clean
|
|
41
|
+
- [x] 6.3 Manually run integration suite; verify fixture cleanup
|
|
42
|
+
- [ ] 6.4 Connect to Claude Desktop; exercise filter queries against a real database
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
## Context
|
|
2
|
+
|
|
3
|
+
OmniFocus 4 distinguishes three date types on tasks: `deferDate` (hides until), `plannedDate` (Forecast target), and `dueDate` (hard deadline). The MCP server already handles defer and due with the pattern `isoOrNull(task.deferDate)` for reading and `task.deferDate = args.deferDate ? new Date(args.deferDate) : null` for writing. `plannedDate` follows the identical pattern — confirmed writable via OmniJS.
|
|
4
|
+
|
|
5
|
+
## Goals / Non-Goals
|
|
6
|
+
|
|
7
|
+
**Goals:**
|
|
8
|
+
- Add `plannedDate` to `create_task`, `edit_task`, `get_task`, and all snippets returning `TaskDetail`
|
|
9
|
+
- Follow the exact same pattern as `deferDate` and `dueDate`
|
|
10
|
+
|
|
11
|
+
**Non-Goals:**
|
|
12
|
+
- `effectiveDeferDate` / `effectiveDueDate` (computed fields inherited from projects — read-only, not actionable)
|
|
13
|
+
- Planned date on projects (projects don't have `plannedDate` in OmniJS)
|
|
14
|
+
|
|
15
|
+
## Decisions
|
|
16
|
+
|
|
17
|
+
### Decision 1: Same pattern as existing dates
|
|
18
|
+
|
|
19
|
+
`plannedDate` is added as `z.string().datetime().optional()` in `CreateTaskInput`, `z.string().datetime().nullable().optional()` in `EditTaskInput`, and `z.string().datetime().nullable()` in `TaskDetail`. No new abstractions needed.
|
|
20
|
+
|
|
21
|
+
### Decision 2: All TaskDetail-returning snippets updated
|
|
22
|
+
|
|
23
|
+
Five snippets return `TaskDetail`: `create_task`, `edit_task`, `get_task`, `complete_task`, `drop_task`. All five get `plannedDate: isoOrNull(task.plannedDate)` added to their return object.
|
|
24
|
+
|
|
25
|
+
## Risks / Trade-offs
|
|
26
|
+
|
|
27
|
+
- **Minimal risk** — purely additive, follows established patterns exactly. No behavioral change for existing callers.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
## Why
|
|
2
|
+
|
|
3
|
+
OmniFocus 4 has three distinct date concepts — defer, planned, and due — but the MCP server only exposes `deferDate` and `dueDate`. The missing `plannedDate` is the soft "when I intend to work on this" date that shows up in Forecast view without hiding the task or triggering overdue status. This is the right date type for many recurring tasks (e.g., cleaning, reviews) where you want Forecast visibility without hard-deadline pressure.
|
|
4
|
+
|
|
5
|
+
The OmniJS API already exposes `task.plannedDate` as a fully readable, writable, and clearable property — no API barrier.
|
|
6
|
+
|
|
7
|
+
## What Changes
|
|
8
|
+
|
|
9
|
+
- `create_task`: add optional `plannedDate` field
|
|
10
|
+
- `edit_task`: add optional `plannedDate` field (nullable to clear)
|
|
11
|
+
- `get_task` / `TaskDetail`: return `plannedDate` in the detail record
|
|
12
|
+
- All snippets returning `TaskDetail` (`complete_task`, `drop_task`) include `plannedDate`
|
|
13
|
+
|
|
14
|
+
## Capabilities
|
|
15
|
+
|
|
16
|
+
### New Capabilities
|
|
17
|
+
|
|
18
|
+
(none)
|
|
19
|
+
|
|
20
|
+
### Modified Capabilities
|
|
21
|
+
|
|
22
|
+
- `task-write`: `create_task` and `edit_task` gain a `plannedDate` parameter
|
|
23
|
+
- `task-management`: `get_task` returns `plannedDate` in `TaskDetail`
|
|
24
|
+
|
|
25
|
+
## Impact
|
|
26
|
+
|
|
27
|
+
- `src/schemas/shapes.ts`: `CreateTaskInput`, `EditTaskInput`, `TaskDetail` extended
|
|
28
|
+
- `src/snippets/create_task.js`, `edit_task.js`, `get_task.js`, `complete_task.js`, `drop_task.js`: add `plannedDate` field
|
|
29
|
+
- No breaking changes — all new fields are optional; existing callers unaffected
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
## MODIFIED Requirements
|
|
2
|
+
|
|
3
|
+
### Requirement: Get task by ID
|
|
4
|
+
|
|
5
|
+
The system SHALL provide a `get_task` tool that accepts `{id: string}` and returns the full detail record of the named task, including `{id, name, note, status, flagged, deferDate, plannedDate, dueDate, completionDate, estimatedMinutes, containerId, containerType, tagIds, parentTaskId, repetitionRule}`. The `parentTaskId` field SHALL be `null` for top-level tasks and SHALL contain the parent task's `id.primaryKey` for subtasks. The `repetitionRule` field SHALL be `null` when no repetition rule is set, and SHALL be a structured object with `{frequency, interval, daysOfWeek?, method}` when a rule exists. The `plannedDate` field SHALL be `null` when no planned date is set. If no task exists with that ID, the tool SHALL return a structured not-found error.
|
|
6
|
+
|
|
7
|
+
#### Scenario: Existing task returns full detail
|
|
8
|
+
- **WHEN** `get_task` is called with the ID of an existing task
|
|
9
|
+
- **THEN** the tool returns the task's full detail record including all scalar fields, the list of tag IDs assigned to it, `parentTaskId`, `repetitionRule`, and `plannedDate`
|
|
10
|
+
|
|
11
|
+
#### Scenario: Subtask includes parentTaskId
|
|
12
|
+
- **WHEN** `get_task` is called with the ID of a subtask
|
|
13
|
+
- **THEN** the returned record includes `parentTaskId` set to the parent task's stable ID
|
|
14
|
+
|
|
15
|
+
#### Scenario: Task without repetition returns null repetitionRule
|
|
16
|
+
- **WHEN** `get_task` is called for a task with no repetition rule
|
|
17
|
+
- **THEN** the returned TaskDetail includes `repetitionRule: null`
|
|
18
|
+
|
|
19
|
+
#### Scenario: Task with repetition returns structured repetitionRule
|
|
20
|
+
- **WHEN** `get_task` is called for a task that has a repetition rule
|
|
21
|
+
- **THEN** the returned TaskDetail includes `repetitionRule` with `frequency`, `interval`, `method`, and `daysOfWeek` (if applicable)
|
|
22
|
+
|
|
23
|
+
#### Scenario: Task with planned date returns plannedDate
|
|
24
|
+
- **WHEN** `get_task` is called for a task that has a planned date set
|
|
25
|
+
- **THEN** the returned TaskDetail includes `plannedDate` as an ISO datetime string
|
|
26
|
+
|
|
27
|
+
#### Scenario: Missing task returns not-found error
|
|
28
|
+
- **WHEN** `get_task` is called with an ID that does not correspond to any task
|
|
29
|
+
- **THEN** the tool returns a structured error with a not-found code and does not throw an unhandled exception
|