@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
package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/tag-management/spec.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
## ADDED Requirements
|
|
2
|
+
|
|
3
|
+
### Requirement: List tags
|
|
4
|
+
|
|
5
|
+
The system SHALL provide a `list_tags` tool that returns every tag in the OmniFocus database as an array of summaries, each containing at minimum `{id, name, path, parentId, status}`. Tags form a tree in OmniFocus; the `path` field SHALL use the canonical ` ▸ ` separator and represent the full ancestor chain. The `parentId` field SHALL be `null` for top-level tags. The `status` field SHALL be one of `"active" | "onHold" | "dropped"`.
|
|
6
|
+
|
|
7
|
+
#### Scenario: All tags are returned including nested tags
|
|
8
|
+
- **WHEN** `list_tags` is called with no arguments
|
|
9
|
+
- **THEN** the tool returns every tag in the database, top-level and nested, each with its full path and correct parentId
|
|
10
|
+
|
|
11
|
+
#### Scenario: On-hold tag is reported with correct status
|
|
12
|
+
- **WHEN** the database contains a tag that has been placed on hold
|
|
13
|
+
- **THEN** that tag appears in the result with `status: "onHold"`
|
|
14
|
+
|
|
15
|
+
### Requirement: Get tag by ID
|
|
16
|
+
|
|
17
|
+
The system SHALL provide a `get_tag` tool that accepts `{id: string}` and returns the full detail record of the named tag, including `{id, name, path, parentId, status, childTagIds}`. If no tag exists with that ID, the tool SHALL return a structured not-found error.
|
|
18
|
+
|
|
19
|
+
#### Scenario: Existing tag returns full detail
|
|
20
|
+
- **WHEN** `get_tag` is called with the ID of an existing tag
|
|
21
|
+
- **THEN** the tool returns the tag's full detail including the IDs of its immediate child tags
|
|
22
|
+
|
|
23
|
+
#### Scenario: Missing tag returns not-found error
|
|
24
|
+
- **WHEN** `get_tag` is called with an ID that does not correspond to any tag
|
|
25
|
+
- **THEN** the tool returns a structured error with a not-found code
|
package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/task-management/spec.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
## ADDED 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 return an array of task summaries, each containing at minimum `{id, name, status, flagged, containerId, containerType}`.
|
|
6
|
+
|
|
7
|
+
#### Scenario: List tasks in a specific project
|
|
8
|
+
- **WHEN** `list_tasks` is called with `{projectId: "abc123"}`
|
|
9
|
+
- **THEN** the tool returns every task directly or transitively contained in that project, with each element 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 every task in the OmniFocus inbox 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
|
|
18
|
+
|
|
19
|
+
### Requirement: Get task by ID
|
|
20
|
+
|
|
21
|
+
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}`. If no task exists with that ID, the tool SHALL return a structured not-found error.
|
|
22
|
+
|
|
23
|
+
#### Scenario: Existing task returns full detail
|
|
24
|
+
- **WHEN** `get_task` is called with the ID of an existing task
|
|
25
|
+
- **THEN** the tool returns the task's full detail record including all scalar fields and the list of tag IDs assigned to it
|
|
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
|
package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/url-automation/spec.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
## ADDED Requirements
|
|
2
|
+
|
|
3
|
+
### Requirement: Capability declared
|
|
4
|
+
|
|
5
|
+
The `url-automation` capability SHALL cover `omnifocus://` URL construction (including task-paste format for bulk creation, add-to-inbox URLs, and deep links to entities by ID) and parsing of incoming `omnifocus://` URLs into structured form. Requirements for individual tools SHALL be added by the `url-automation` change.
|
|
6
|
+
|
|
7
|
+
#### Scenario: Capability is named and scoped
|
|
8
|
+
- **WHEN** a future change proposes adding URL-automation tools
|
|
9
|
+
- **THEN** it lands requirements under this capability rather than inventing a new capability name
|
package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/window-state/spec.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
## ADDED Requirements
|
|
2
|
+
|
|
3
|
+
### Requirement: Capability declared
|
|
4
|
+
|
|
5
|
+
The `window-state` capability SHALL cover read-only inspection of OmniFocus document window state, including the currently active window, active perspective, sidebar selection, and content selection. Window *mutation* (resize, close, focus, open) is an explicit non-goal. Requirements for individual tools SHALL be added by the `perspectives-and-windows` change.
|
|
6
|
+
|
|
7
|
+
#### Scenario: Capability is named and read-only scope is fixed
|
|
8
|
+
- **WHEN** a future change proposes adding window-state tools
|
|
9
|
+
- **THEN** it lands read-only requirements under this capability; any mutation proposal requires first revisiting the non-goal in design
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
## 1. Repository scaffold
|
|
2
|
+
|
|
3
|
+
- [x] 1.1 Create `package.json` with dependencies: `@modelcontextprotocol/sdk`, `zod`; devDependencies: `typescript`, `vitest`, `@types/node`, `tsx`
|
|
4
|
+
- [x] 1.2 Create `tsconfig.json` targeting Node 20, strict mode, `moduleResolution: "bundler"`, `outDir: "dist"`, sourcemaps on
|
|
5
|
+
- [x] 1.3 Create directory structure: `src/runtime/`, `src/snippets/`, `src/tools/`, `src/schemas/`, `test/unit/`, `test/integration/`
|
|
6
|
+
- [x] 1.4 Add `.gitignore` covering `node_modules`, `dist`, `*.log`, macOS cruft
|
|
7
|
+
- [x] 1.5 Add root `README.md` stub with macOS-only note and integration-test warning about sync
|
|
8
|
+
|
|
9
|
+
## 2. Execution runtime (src/runtime/)
|
|
10
|
+
|
|
11
|
+
- [x] 2.1 Implement `snippetLoader.ts`: reads `src/snippets/<name>.js` from the resolved snippet root, caches in memory, validates exactly one `__ARGS__` token, throws on zero or multiple
|
|
12
|
+
- [x] 2.2 Implement `resultProtocol.ts`: zod schema for the `{ok, data} | {ok: false, error}` envelope, `parseResultLine(stdout: string)` that selects the first JSON-parseable line and validates the envelope, `ExecutionError` class carrying name/message/stack from the error branch
|
|
13
|
+
- [x] 2.3 Implement `jxaShim.ts`: a template string containing the JXA wrapper that calls `Application('OmniFocus').evaluateJavascript(snippet)`, wraps in try/catch, prints the envelope as one JSON line
|
|
14
|
+
- [x] 2.4 Implement `bridge.ts`: `runSnippet(name: string, args: unknown, opts?: {timeoutMs?: number}): Promise<unknown>` — loads the snippet via the loader, injects args via `template.replace("__ARGS__", JSON.stringify(args))`, wraps the result in the JXA shim, spawns `osascript -l JavaScript`, enforces timeout via `AbortController` + SIGTERM, parses stdout via `parseResultLine`, throws `ExecutionError` on `ok: false`, returns `data` on success
|
|
15
|
+
- [x] 2.5 Export a single `runtime` barrel from `src/runtime/index.ts`
|
|
16
|
+
|
|
17
|
+
## 3. Shared schemas (src/schemas/)
|
|
18
|
+
|
|
19
|
+
- [x] 3.1 Define zod schemas: `IdSchema` (non-empty string), `EntityType` enum (`"task" | "project" | "folder" | "tag" | "perspective"`), `ProjectType` enum, `ProjectStatus` enum, `TaskStatus` enum, `TagStatus` enum, `FolderStatus` enum
|
|
20
|
+
- [x] 3.2 Define shared return-shape schemas: `TaskSummary`, `TaskDetail`, `ProjectSummary`, `ProjectDetail`, `FolderSummary`, `FolderDetail`, `TagSummary`, `TagDetail`, `ResolveCandidate`
|
|
21
|
+
- [x] 3.3 Export a `schemas` barrel from `src/schemas/index.ts`
|
|
22
|
+
|
|
23
|
+
## 4. Snippets (src/snippets/*.js) — read-only set for this change
|
|
24
|
+
|
|
25
|
+
- [x] 4.1 `list_projects.js`: iterate `flattenedProjects`, map to summary shape with `id.primaryKey`, canonical `status`/`type` enum values, folder path via ` ▸ ` separator; return `JSON.stringify({ok: true, data: [...]})`
|
|
26
|
+
- [x] 4.2 `get_project.js`: resolve project by `args.id` via `Project.byIdentifier` (or equivalent), build detail shape including review metadata and tag IDs, return envelope; throw on not-found with a `NotFoundError` constructor defined at the top of the snippet
|
|
27
|
+
- [x] 4.3 `list_folders.js`: iterate `flattenedFolders`, map to summary with full path; return envelope
|
|
28
|
+
- [x] 4.4 `get_folder.js`: resolve folder by ID, return detail including child folder IDs and immediate project IDs
|
|
29
|
+
- [x] 4.5 `list_tasks.js`: accept `args.scope` discriminated union (`projectId` / `folderId` / `inbox` / `all`), iterate the correct source, map to summary; return envelope
|
|
30
|
+
- [x] 4.6 `get_task.js`: resolve task by ID, return full detail including tag IDs
|
|
31
|
+
- [x] 4.7 `list_tags.js`: iterate `flattenedTags`, map to summary with full path, parentId, status
|
|
32
|
+
- [x] 4.8 `get_tag.js`: resolve tag by ID, return detail including child tag IDs
|
|
33
|
+
- [x] 4.9 `resolve_name.js`: accept `args.type`, `args.query`, optional `args.scope`; walk the relevant flat list, match by exact name (and path suffix if scope given), return array of candidates with `{id, name, path, type}`; never throw on zero matches
|
|
34
|
+
- [x] 4.10 Add a shared helper comment block at the top of each snippet documenting the paste-to-console procedure
|
|
35
|
+
|
|
36
|
+
## 5. MCP tools (src/tools/)
|
|
37
|
+
|
|
38
|
+
- [x] 5.1 `listProjects.ts`: zod input schema (empty object), handler invokes `runSnippet("list_projects", {})`, validates output against `z.array(ProjectSummary)`, returns
|
|
39
|
+
- [x] 5.2 `getProject.ts`: input `{id}`, handler invokes `runSnippet("get_project", {id})`, validates `ProjectDetail`
|
|
40
|
+
- [x] 5.3 `listFolders.ts`: empty input, returns `z.array(FolderSummary)`
|
|
41
|
+
- [x] 5.4 `getFolder.ts`: input `{id}`, returns `FolderDetail`
|
|
42
|
+
- [x] 5.5 `listTasks.ts`: input discriminated union (`{projectId} | {folderId} | {inbox: true} | {all: true}`) with zod `discriminatedUnion` or refinement rejecting mutual exclusivity, returns `z.array(TaskSummary)`
|
|
43
|
+
- [x] 5.6 `getTask.ts`: input `{id}`, returns `TaskDetail`
|
|
44
|
+
- [x] 5.7 `listTags.ts`: empty input, returns `z.array(TagSummary)`
|
|
45
|
+
- [x] 5.8 `getTag.ts`: input `{id}`, returns `TagDetail`
|
|
46
|
+
- [x] 5.9 `resolveName.ts`: input `{type, query, scope?}`, returns `z.array(ResolveCandidate)`
|
|
47
|
+
- [x] 5.10 Export a `tools` barrel from `src/tools/index.ts` listing all nine tool definitions
|
|
48
|
+
|
|
49
|
+
## 6. MCP server entrypoint
|
|
50
|
+
|
|
51
|
+
- [x] 6.1 `src/server.ts`: construct an `@modelcontextprotocol/sdk` server, register all nine tools from the barrel, wire stdio transport, start listening
|
|
52
|
+
- [x] 6.2 Add `bin` field to `package.json` pointing at the compiled entrypoint and a `scripts.start` for development via `tsx`
|
|
53
|
+
|
|
54
|
+
## 7. Unit tests (test/unit/)
|
|
55
|
+
|
|
56
|
+
- [x] 7.1 `snippetLoader.test.ts`: golden-file test that loading each snippet in `src/snippets/` succeeds and contains exactly one `__ARGS__`; contract-violation tests for zero/multiple placeholders (using fixture files)
|
|
57
|
+
- [x] 7.2 `resultProtocol.test.ts`: parses well-formed success envelope, parses well-formed error envelope, rejects malformed JSON, selects first parseable line when preceded by chatter
|
|
58
|
+
- [x] 7.3 `bridge.injection.test.ts`: given a snippet template and args containing apostrophes, quotes, backslashes, newlines, and emoji, assert the generated script source is valid JavaScript (parse via `new Function`) and that evaluating it recovers the original args object unchanged — proves Decision 2 structurally
|
|
59
|
+
- [x] 7.4 `schemas.test.ts`: round-trip validation for each shared schema; rejection tests for the `sequential`-as-string and `items`-as-JSON-string regression patterns at the boundary
|
|
60
|
+
- [x] 7.5 `tools.listTasks.test.ts`: rejects mutually exclusive scope combinations at the zod boundary before any bridge call
|
|
61
|
+
|
|
62
|
+
## 8. Integration test harness (test/integration/)
|
|
63
|
+
|
|
64
|
+
- [x] 8.1 `fixtures.ts`: `createTestFolder()` generates `__MCP_TEST_<uuid>__` top-level folder via the bridge; `cleanupTestFolder(id)` removes it; `withTestFolder(fn)` helper wraps a test body
|
|
65
|
+
- [x] 8.2 `preflight.ts`: detects OmniFocus sync status (via a read-only snippet that inspects the relevant setting); throws unless `MCP_TEST_ALLOW_SYNC=1` is set; invoked once in the vitest global setup
|
|
66
|
+
- [x] 8.3 `vitest.integration.config.ts`: separate vitest config for integration with `pool: "forks"`, `maxConcurrency: 1`, `globalSetup: ./preflight.ts`, `testMatch: test/integration/**`
|
|
67
|
+
- [x] 8.4 Integration test: `listProjects.int.test.ts` creates a fixture folder with a child project, calls the real bridge, asserts the fixture project appears in the result with the correct folder path
|
|
68
|
+
- [x] 8.5 Integration test: `getTask.int.test.ts` creates a fixture task with an apostrophe and unicode in its name, retrieves by ID, asserts name survives round-trip — proves Decision 2 end-to-end
|
|
69
|
+
- [x] 8.6 Integration test: `resolveName.int.test.ts` creates two fixture projects with identical names under different folders, asserts `resolve_name` returns both candidates with distinct paths
|
|
70
|
+
- [x] 8.7 Add a `scripts.test:integration` in `package.json` guarded by macOS check
|
|
71
|
+
- [x] 8.8 Add a `scripts.test:cleanup-fixtures` that scans for stale `__MCP_TEST_*` folders and removes them
|
|
72
|
+
|
|
73
|
+
## 9. Documentation
|
|
74
|
+
|
|
75
|
+
- [x] 9.1 Expand `README.md` with: prerequisites (macOS, OmniFocus running), install, MCP client configuration snippet, the list of tools delivered in this change, the loud warning about integration tests mutating real data and the sync preflight
|
|
76
|
+
- [x] 9.2 Add `CONTRIBUTING.md` documenting the snippet authoring rules (the `__ARGS__` placeholder, no other interpolation, paste-to-console workflow) and the forbidden-scripting-dictionary rule from execution-runtime spec
|
|
77
|
+
|
|
78
|
+
## 10. Verification
|
|
79
|
+
|
|
80
|
+
- [x] 10.1 Run `npm run typecheck` (or `tsc --noEmit`) clean
|
|
81
|
+
- [x] 10.2 Run `npm run test` (unit suite) clean
|
|
82
|
+
- [x] 10.3 Manually run integration suite against a real OmniFocus with sync disabled; verify fixture folder is created and torn down
|
|
83
|
+
- [x] 10.4 Manually connect the server to an MCP client (Claude Desktop or equivalent) and invoke each of the nine tools against a real database; verify results match the spec scenarios
|
|
84
|
+
- [x] 10.5 Confirm all spec scenarios in `specs/execution-runtime/spec.md` and `specs/identity-resolution/spec.md` have corresponding test coverage (unit + integration)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
## Context
|
|
2
|
+
|
|
3
|
+
Folders in OmniJS are simpler than tasks or projects — they have only `name` and `status` as meaningful writable properties, and they nest via parent/child relationships. The main complexity is deletion: `deleteObject(folder)` leaves an empty shell (as discovered during bootstrap integration testing); the snippet must recursively delete contents first.
|
|
4
|
+
|
|
5
|
+
OmniJS folder write operations:
|
|
6
|
+
- `new Folder(name)` — creates a top-level folder (auto-inserted at root)
|
|
7
|
+
- `new Folder(name, folderObject)` — creates a nested folder inside `folderObject`
|
|
8
|
+
- `folder.name = "..."` — rename
|
|
9
|
+
- `deleteObject(folder)` — removes folder shell but NOT contents; must delete contents first
|
|
10
|
+
- Recursive deletion pattern (established in bootstrap fixtures): delete all projects in folder, recurse child folders, then delete the folder itself
|
|
11
|
+
|
|
12
|
+
## Goals / Non-Goals
|
|
13
|
+
|
|
14
|
+
**Goals:**
|
|
15
|
+
- Create top-level and nested folders
|
|
16
|
+
- Rename folders
|
|
17
|
+
- Delete folders with full recursive cascade (projects, tasks, child folders)
|
|
18
|
+
|
|
19
|
+
**Non-Goals:**
|
|
20
|
+
- Moving a folder to a different parent (separate `move-operations` change)
|
|
21
|
+
- Changing folder status directly (OmniFocus manages active/dropped based on contents)
|
|
22
|
+
|
|
23
|
+
## Decisions
|
|
24
|
+
|
|
25
|
+
### Decision 1: Recursive deletion in the snippet
|
|
26
|
+
|
|
27
|
+
The snippet for `delete_folder` must implement recursive deletion rather than relying on `deleteObject` to cascade. The pattern is:
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
function deleteFolder(f) {
|
|
31
|
+
f.flattenedProjects.forEach(p => deleteObject(p));
|
|
32
|
+
f.folders.forEach(child => deleteFolder(child));
|
|
33
|
+
deleteObject(f);
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
This was validated during bootstrap integration testing (`fixtures.ts` uses this exact pattern).
|
|
38
|
+
|
|
39
|
+
### Decision 2: `create_folder` placement via optional `parentFolderId`
|
|
40
|
+
|
|
41
|
+
- `parentFolderId` omitted → top-level folder
|
|
42
|
+
- `parentFolderId` provided → nested inside that folder
|
|
43
|
+
|
|
44
|
+
`new Folder(name)` for top-level; `new Folder(name, parentFolder)` for nested. Snippet resolves the parent by ID.
|
|
45
|
+
|
|
46
|
+
### Decision 3: No `edit_folder` status field
|
|
47
|
+
|
|
48
|
+
Folder status (`active` / `dropped`) in OmniFocus reflects whether the folder has active contents — it is not directly user-settable in the same way as project status. `edit_folder` exposes only `name`. If this proves insufficient in practice it can be extended.
|
|
49
|
+
|
|
50
|
+
### Decision 4: `delete_folder` tool description is the strongest warning in the server
|
|
51
|
+
|
|
52
|
+
This is the most destructive single operation: one call can delete an entire project hierarchy. The tool description must explicitly state that all child folders, projects, and tasks are permanently deleted, and that the AI must confirm with the user before calling it.
|
|
53
|
+
|
|
54
|
+
## Risks / Trade-offs
|
|
55
|
+
|
|
56
|
+
- **`deleteObject` on folder leaves shell** — confirmed behavior; recursive snippet pattern mitigates this fully.
|
|
57
|
+
- **Deleting a folder containing hundreds of projects** — could be slow inside `evaluateJavascript`; the 30s timeout applies. Not worth special-casing in v1.
|
|
58
|
+
- **Race between read and delete** — if the user reads a folder ID then calls delete, OmniFocus has no transaction isolation. Acceptable for a single-user desktop app.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
## Why
|
|
2
|
+
|
|
3
|
+
The bootstrap change delivered read-only folder access. Callers can traverse the folder hierarchy but cannot create, rename, or delete folders — blocking any workflow that involves organizing projects into folders.
|
|
4
|
+
|
|
5
|
+
## What Changes
|
|
6
|
+
|
|
7
|
+
- Add `create_folder` tool: create a folder at the top level or nested inside an existing folder
|
|
8
|
+
- Add `edit_folder` tool: rename a folder
|
|
9
|
+
- Add `delete_folder` tool: permanently delete a folder and all its contents — projects, tasks, and child folders (tool description instructs the AI to confirm with the user before invoking and calls out the destructive cascade)
|
|
10
|
+
|
|
11
|
+
## Capabilities
|
|
12
|
+
|
|
13
|
+
### New Capabilities
|
|
14
|
+
|
|
15
|
+
- `folder-write`: Create, rename, and permanently delete OmniFocus folders
|
|
16
|
+
|
|
17
|
+
### Modified Capabilities
|
|
18
|
+
|
|
19
|
+
_(none — existing `folder-management` read requirements are unchanged)_
|
|
20
|
+
|
|
21
|
+
## Impact
|
|
22
|
+
|
|
23
|
+
- 3 new MCP tools registered in `src/server.ts`
|
|
24
|
+
- 3 new tool handler files in `src/tools/`
|
|
25
|
+
- 3 new OmniJS snippets in `src/snippets/`
|
|
26
|
+
- `ALLOWED_SNIPPETS` allowlist in `src/runtime/snippetLoader.ts` must be extended
|
|
27
|
+
- Integration tests will mutate real OmniFocus data (scoped to fixture folder per existing pattern)
|
|
28
|
+
- `delete_folder` is the most destructive single operation in the server — the tool description warrants an explicit warning that the entire subtree (child folders, projects, and all tasks) is permanently removed
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
## ADDED Requirements
|
|
2
|
+
|
|
3
|
+
### Requirement: Create folder
|
|
4
|
+
|
|
5
|
+
The system SHALL provide a `create_folder` tool that creates a new OmniFocus folder and returns its full detail record. The tool SHALL accept `{name: string, parentFolderId?: string}`. If `parentFolderId` is provided the folder SHALL be created nested inside that folder; otherwise it SHALL be created at the top level.
|
|
6
|
+
|
|
7
|
+
#### Scenario: Create top-level folder
|
|
8
|
+
- **WHEN** `create_folder` is called with `{name: "Work"}` and no `parentFolderId`
|
|
9
|
+
- **THEN** the tool creates the folder at the top level and returns its full detail record including a stable `id`
|
|
10
|
+
|
|
11
|
+
#### Scenario: Create nested folder
|
|
12
|
+
- **WHEN** `create_folder` is called with `{name: "Active", parentFolderId: "abc123"}`
|
|
13
|
+
- **THEN** the tool creates the folder inside the specified parent folder and returns its full detail record with `path` reflecting the full ancestor chain
|
|
14
|
+
|
|
15
|
+
#### Scenario: Non-existent parent folder returns not-found error
|
|
16
|
+
- **WHEN** `create_folder` is called with a `parentFolderId` that does not correspond to any folder
|
|
17
|
+
- **THEN** the tool returns a structured not-found error
|
|
18
|
+
|
|
19
|
+
### Requirement: Edit folder
|
|
20
|
+
|
|
21
|
+
The system SHALL provide an `edit_folder` tool that renames an existing folder and returns its updated full detail record. The tool SHALL accept `{id: string, name: string}`.
|
|
22
|
+
|
|
23
|
+
#### Scenario: Rename a folder
|
|
24
|
+
- **WHEN** `edit_folder` is called with `{id: "abc123", name: "Personal"}`
|
|
25
|
+
- **THEN** the folder's name is updated and the tool returns the updated detail record with the new name reflected in `path`
|
|
26
|
+
|
|
27
|
+
#### Scenario: Non-existent folder returns not-found error
|
|
28
|
+
- **WHEN** `edit_folder` is called with an ID that does not correspond to any folder
|
|
29
|
+
- **THEN** the tool returns a structured not-found error
|
|
30
|
+
|
|
31
|
+
### Requirement: Delete folder
|
|
32
|
+
|
|
33
|
+
The system SHALL provide a `delete_folder` tool that permanently and recursively deletes a folder, all child folders, all projects within those folders, and all tasks within those projects. The tool description SHALL instruct the AI to confirm with the user before invoking, explicitly stating that the entire subtree — child folders, projects, and all tasks — is permanently deleted and cannot be undone.
|
|
34
|
+
|
|
35
|
+
#### Scenario: Delete a folder and all its contents
|
|
36
|
+
- **WHEN** `delete_folder` is called with the ID of an existing folder
|
|
37
|
+
- **THEN** the folder, all descendant folders, all projects within those folders, and all tasks within those projects are permanently removed from OmniFocus and the tool returns a confirmation envelope
|
|
38
|
+
|
|
39
|
+
#### Scenario: Delete empty folder
|
|
40
|
+
- **WHEN** `delete_folder` is called with the ID of a folder that contains no projects or child folders
|
|
41
|
+
- **THEN** the folder is removed and the tool returns a confirmation envelope
|
|
42
|
+
|
|
43
|
+
#### Scenario: Non-existent folder returns not-found error
|
|
44
|
+
- **WHEN** `delete_folder` is called with an ID that does not correspond to any folder
|
|
45
|
+
- **THEN** the tool returns a structured not-found error
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
## 1. Snippets (src/snippets/)
|
|
2
|
+
|
|
3
|
+
- [x] 1.1 `create_folder.js`: accept `{name, parentFolderId?}`; if `parentFolderId` provided resolve parent by ID and throw `NotFoundError` if missing; create `new Folder(name)` or `new Folder(name, parentFolder)`; return full folder detail envelope
|
|
4
|
+
- [x] 1.2 `edit_folder.js`: accept `{id, name}`; resolve folder by ID; assign `folder.name = name`; return updated full folder detail envelope
|
|
5
|
+
- [x] 1.3 `delete_folder.js`: accept `{id}`; resolve folder by ID; recursively delete contents (projects then child folders) then `deleteObject(folder)`; return `{ok: true, data: {id}}`
|
|
6
|
+
|
|
7
|
+
## 2. Snippet allowlist
|
|
8
|
+
|
|
9
|
+
- [x] 2.1 Add `create_folder`, `edit_folder`, `delete_folder` to `ALLOWED_SNIPPETS` in `src/runtime/snippetLoader.ts`
|
|
10
|
+
|
|
11
|
+
## 3. Schemas (src/schemas/shapes.ts)
|
|
12
|
+
|
|
13
|
+
- [x] 3.1 Define `CreateFolderInput` zod schema: `{name: z.string().min(1), parentFolderId: IdSchema.optional()}`
|
|
14
|
+
- [x] 3.2 Define `EditFolderInput` zod schema: `{id: IdSchema, name: z.string().min(1)}`
|
|
15
|
+
|
|
16
|
+
## 4. Tool handlers (src/tools/)
|
|
17
|
+
|
|
18
|
+
- [x] 4.1 `createFolder.ts`: validate input with `CreateFolderInput`; invoke `runSnippet("create_folder", args)`; validate result against `FolderDetail`; return
|
|
19
|
+
- [x] 4.2 `editFolder.ts`: validate input with `EditFolderInput`; invoke `runSnippet("edit_folder", args)`; validate result against `FolderDetail`; return
|
|
20
|
+
- [x] 4.3 `deleteFolder.ts`: input `{id: IdSchema}`; tool description MUST state the AI should confirm with the user before invoking and explicitly note that the entire subtree — child folders, projects, and all tasks — is permanently deleted; invoke `runSnippet("delete_folder", {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.createFolder.test.ts`: valid inputs pass; empty name rejected
|
|
29
|
+
|
|
30
|
+
## 7. Integration tests (test/integration/)
|
|
31
|
+
|
|
32
|
+
- [x] 7.1 `createFolder.int.test.ts`: create top-level folder; create nested folder; verify `path` and `parentId`
|
|
33
|
+
- [x] 7.2 `editFolder.int.test.ts`: rename a folder; verify `name` and `path` updated
|
|
34
|
+
- [x] 7.3 `deleteFolder.int.test.ts`: create folder with child project and tasks; delete folder; verify `get_folder` returns not-found and projects are 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,38 @@
|
|
|
1
|
+
## Context
|
|
2
|
+
|
|
3
|
+
`list_folders` and `list_tags` are the last two list tools without filtering or limit support. The pattern established by `list_tasks` and `list_projects` — filter inside the OmniJS snippet, expose filter object and limit from the TypeScript handler — applies directly.
|
|
4
|
+
|
|
5
|
+
Unlike tasks and projects, there is no universally agreed "terminal" status for folders (dropped folders are unusual but valid to show) or tags (onHold tags are actively used). Therefore the default behavior includes all statuses; filtering is opt-in.
|
|
6
|
+
|
|
7
|
+
## Goals / Non-Goals
|
|
8
|
+
|
|
9
|
+
**Goals:**
|
|
10
|
+
- `list_folders`: add `limit` (default 200) and optional `status` filter (`active` | `dropped`)
|
|
11
|
+
- `list_tags`: add `limit` (default 200) and optional `status` filter (`active` | `onHold` | `dropped`)
|
|
12
|
+
|
|
13
|
+
**Non-Goals:**
|
|
14
|
+
- `parentId` / subtree scoping for folders or tags (flat list is sufficient in practice)
|
|
15
|
+
- Filtering by name or path pattern
|
|
16
|
+
- Pagination
|
|
17
|
+
|
|
18
|
+
## Decisions
|
|
19
|
+
|
|
20
|
+
### Decision 1: No default status exclusion
|
|
21
|
+
|
|
22
|
+
Unlike `list_tasks` and `list_projects`, the default returns all statuses. Dropped folders/tags are uncommon enough that including them doesn't overwhelm results, and an LLM may need to see them. Callers can pass `status: ["active"]` to filter.
|
|
23
|
+
|
|
24
|
+
### Decision 2: Single status value, not array
|
|
25
|
+
|
|
26
|
+
For `list_folders` and `list_tags`, a single `status` string (not an array) is sufficient — users rarely need "active OR onHold" for tags. Simpler API. If multi-status becomes needed, it's a non-breaking addition later.
|
|
27
|
+
|
|
28
|
+
### Decision 3: Limit default 200
|
|
29
|
+
|
|
30
|
+
Folders and tags are fewer than tasks; 200 is generous. Consistent with the spirit of existing defaults (tasks: 200, projects: 100).
|
|
31
|
+
|
|
32
|
+
### Decision 4: Same filter-in-snippet pattern
|
|
33
|
+
|
|
34
|
+
All filtering in the OmniJS snippet before data crosses the JXA bridge. TypeScript layer validates and passes through. Consistent with list_tasks and list_projects.
|
|
35
|
+
|
|
36
|
+
## Risks / Trade-offs
|
|
37
|
+
|
|
38
|
+
- **Minimal risk** — purely additive. Existing callers passing no args get all items (same as before but now capped at 200, which is a minor behavior change). Acceptable.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
## Why
|
|
2
|
+
|
|
3
|
+
`list_folders` and `list_tags` return all items with no way to cap results or filter by status. While folders and tags are typically fewer than tasks or projects, users with large databases may have hundreds of tags (including dropped ones). Adding `limit` and optional `status` filtering completes the filtering pattern applied to `list_tasks` and `list_projects`.
|
|
4
|
+
|
|
5
|
+
## What Changes
|
|
6
|
+
|
|
7
|
+
- Add `limit` parameter to `list_folders` (default 200)
|
|
8
|
+
- Add `status` filter to `list_folders`: when provided, restrict to folders with that status (`active` or `dropped`); when omitted, return all folders
|
|
9
|
+
- Add `limit` parameter to `list_tags` (default 200)
|
|
10
|
+
- Add `status` filter to `list_tags`: when provided, restrict to tags with that status (`active`, `onHold`, or `dropped`); when omitted, return all tags
|
|
11
|
+
- All filtering happens inside the OmniJS snippet
|
|
12
|
+
|
|
13
|
+
## Capabilities
|
|
14
|
+
|
|
15
|
+
### New Capabilities
|
|
16
|
+
|
|
17
|
+
_(none)_
|
|
18
|
+
|
|
19
|
+
### Modified Capabilities
|
|
20
|
+
|
|
21
|
+
- `folder-management`: `list_folders` gains `limit` and optional `status` filter
|
|
22
|
+
- `tag-management`: `list_tags` gains `limit` and optional `status` filter
|
|
23
|
+
|
|
24
|
+
## Impact
|
|
25
|
+
|
|
26
|
+
- `src/schemas/shapes.ts`: add `ListFoldersFilter` and `ListTagsFilter` schemas
|
|
27
|
+
- `src/schemas/index.ts`: export new filter schemas
|
|
28
|
+
- `src/snippets/list_folders.js`, `list_tags.js`: add filter + limit logic
|
|
29
|
+
- `src/tools/listFolders.ts`, `listTags.ts`: add filter + limit params
|
|
30
|
+
- Unit and integration tests for both tools
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
## MODIFIED Requirements
|
|
2
|
+
|
|
3
|
+
### Requirement: List folders
|
|
4
|
+
|
|
5
|
+
The system SHALL provide a `list_folders` tool that returns folders in the OmniFocus database as an array of summaries, each containing `{id, name, path, parentId, status}`. The `path` field SHALL use the canonical ` ▸ ` separator and SHALL equal the folder's name for top-level folders. The `parentId` field SHALL be `null` for top-level folders. The `status` field SHALL be one of `"active" | "dropped"`. The tool SHALL accept an optional `status` filter string and an optional `limit` integer (default 200). When `status` is omitted, all folders are returned regardless of status. When `limit` is omitted, at most 200 folders are returned.
|
|
6
|
+
|
|
7
|
+
#### Scenario: All folders returned by default
|
|
8
|
+
- **WHEN** `list_folders` is called with no arguments
|
|
9
|
+
- **THEN** the tool returns all folders (active and dropped) up to the limit
|
|
10
|
+
|
|
11
|
+
#### Scenario: Top-level folders have null parent
|
|
12
|
+
- **WHEN** a folder exists at the root of the folder hierarchy
|
|
13
|
+
- **THEN** its summary carries `parentId: null` and `path` equal to its name
|
|
14
|
+
|
|
15
|
+
#### Scenario: Filter by status returns only matching folders
|
|
16
|
+
- **WHEN** `list_folders` is called with `{ filter: { status: "active" } }`
|
|
17
|
+
- **THEN** only active folders are returned
|
|
18
|
+
|
|
19
|
+
#### Scenario: Limit caps the number of returned folders
|
|
20
|
+
- **WHEN** `list_folders` is called with `{ limit: 5 }`
|
|
21
|
+
- **THEN** at most 5 folders are returned
|
package/openspec/changes/archive/2026-04-09-folder-tag-list-filtering/specs/tag-management/spec.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
## MODIFIED Requirements
|
|
2
|
+
|
|
3
|
+
### Requirement: List tags
|
|
4
|
+
|
|
5
|
+
The system SHALL provide a `list_tags` tool that returns tags in the OmniFocus database as an array of summaries, each containing `{id, name, path, parentId, status}`. Tags form a tree in OmniFocus; the `path` field SHALL use the canonical ` ▸ ` separator and represent the full ancestor chain. The `parentId` field SHALL be `null` for top-level tags. The `status` field SHALL be one of `"active" | "onHold" | "dropped"`. The tool SHALL accept an optional `status` filter string and an optional `limit` integer (default 200). When `status` is omitted, all tags are returned regardless of status. When `limit` is omitted, at most 200 tags are returned.
|
|
6
|
+
|
|
7
|
+
#### Scenario: All tags returned by default
|
|
8
|
+
- **WHEN** `list_tags` is called with no arguments
|
|
9
|
+
- **THEN** the tool returns all tags (active, onHold, and dropped) up to the limit
|
|
10
|
+
|
|
11
|
+
#### Scenario: On-hold tag is reported with correct status
|
|
12
|
+
- **WHEN** the database contains a tag that has been placed on hold
|
|
13
|
+
- **THEN** that tag appears in the result with `status: "onHold"`
|
|
14
|
+
|
|
15
|
+
#### Scenario: Filter by status returns only matching tags
|
|
16
|
+
- **WHEN** `list_tags` is called with `{ filter: { status: "active" } }`
|
|
17
|
+
- **THEN** only active tags are returned
|
|
18
|
+
|
|
19
|
+
#### Scenario: Limit caps the number of returned tags
|
|
20
|
+
- **WHEN** `list_tags` is called with `{ limit: 5 }`
|
|
21
|
+
- **THEN** at most 5 tags are returned
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
## 1. Schema changes (src/schemas/)
|
|
2
|
+
|
|
3
|
+
- [x] 1.1 Define `ListFoldersFilter` zod schema: `{ status: FolderStatus.optional() }` in `shapes.ts`
|
|
4
|
+
- [x] 1.2 Define `ListTagsFilter` zod schema: `{ status: TagStatus.optional() }` in `shapes.ts`
|
|
5
|
+
- [x] 1.3 Export `ListFoldersFilter` and `ListTagsFilter` from `src/schemas/index.ts`
|
|
6
|
+
|
|
7
|
+
## 2. Snippet updates (src/snippets/)
|
|
8
|
+
|
|
9
|
+
- [x] 2.1 Update `list_folders.js`: add `filter` and `limit` args; apply status filter when `args.filter?.status` is provided; apply limit (default 200) as slice after filter
|
|
10
|
+
- [x] 2.2 Update `list_tags.js`: add `filter` and `limit` args; apply status filter when `args.filter?.status` is provided; apply limit (default 200) as slice after filter
|
|
11
|
+
|
|
12
|
+
## 3. Tool handler updates (src/tools/)
|
|
13
|
+
|
|
14
|
+
- [x] 3.1 Update `listFolders.ts`: add `filter: ListFoldersFilter.optional()` and `limit: z.number().int().positive().optional()` to schema; pass through to `runSnippet`; update description
|
|
15
|
+
- [x] 3.2 Update `listTags.ts`: add `filter: ListTagsFilter.optional()` and `limit: z.number().int().positive().optional()` to schema; pass through to `runSnippet`; update description
|
|
16
|
+
|
|
17
|
+
## 4. Unit tests (test/unit/)
|
|
18
|
+
|
|
19
|
+
- [x] 4.1 Add schema tests for `ListFoldersFilter`: valid with status "active"; valid with status "dropped"; valid empty; invalid status value rejected
|
|
20
|
+
- [x] 4.2 Add schema tests for `ListTagsFilter`: valid with status "active"; valid with status "onHold"; valid with status "dropped"; valid empty; invalid status value rejected
|
|
21
|
+
|
|
22
|
+
## 5. Integration tests (test/integration/)
|
|
23
|
+
|
|
24
|
+
- [x] 5.1 `listFoldersFiltered.int.test.ts`: verify status filter returns only folders with that status
|
|
25
|
+
- [x] 5.2 `listFoldersFiltered.int.test.ts`: verify limit caps results
|
|
26
|
+
- [x] 5.3 `listFoldersFiltered.int.test.ts`: verify no filter returns all folders (including dropped)
|
|
27
|
+
- [x] 5.4 `listTagsFiltered.int.test.ts`: verify status filter returns only tags with that status
|
|
28
|
+
- [x] 5.5 `listTagsFiltered.int.test.ts`: verify limit caps results
|
|
29
|
+
- [x] 5.6 `listTagsFiltered.int.test.ts`: verify no filter returns all tags
|
|
30
|
+
|
|
31
|
+
## 6. Verification
|
|
32
|
+
|
|
33
|
+
- [x] 6.1 `npm run typecheck` clean
|
|
34
|
+
- [x] 6.2 `npm test` (unit suite) clean
|
|
35
|
+
- [x] 6.3 Manually run integration suite
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
## Context
|
|
2
|
+
|
|
3
|
+
OmniFocus supports moving tasks between containers (projects or parent tasks) and moving projects between folders via the `moveSections` and `append`/`prepend` OmniJS APIs. Currently no MCP tool exposes this capability.
|
|
4
|
+
|
|
5
|
+
The OmniJS API for moving: `moveTasks([task], destination)` where destination is a `Project` or `Task`. For projects: `moveProjects([project], folder)` where folder is a `Folder` or `null` for top-level. Direct property assignment (`task.containingProject`, `project.parentFolder`) is read-only and does not work.
|
|
6
|
+
|
|
7
|
+
## Goals / Non-Goals
|
|
8
|
+
|
|
9
|
+
**Goals:**
|
|
10
|
+
- `move_task`: move a task to a project (top-level) or make it a subtask of another task
|
|
11
|
+
- `move_project`: move a project to a folder or to the top level
|
|
12
|
+
|
|
13
|
+
**Non-Goals:**
|
|
14
|
+
- Moving folders (rename/reparent of folders — separate concern)
|
|
15
|
+
- Reordering within a container (OmniJS exposes position but it's complex)
|
|
16
|
+
- Moving tags
|
|
17
|
+
|
|
18
|
+
## Decisions
|
|
19
|
+
|
|
20
|
+
### Decision 1: move_task requires exactly one destination
|
|
21
|
+
|
|
22
|
+
`move_task` accepts `{ id, projectId?, parentTaskId? }`. Exactly one of `projectId` or `parentTaskId` must be provided — enforced by Zod `.refine()` at the TypeScript boundary and validated again in the snippet. Returns the updated task summary.
|
|
23
|
+
|
|
24
|
+
### Decision 2: Moving to a project places task at end of project's task list
|
|
25
|
+
|
|
26
|
+
OmniJS `task.containingProject = project` places the task at the end. This is the natural default; no position parameter needed.
|
|
27
|
+
|
|
28
|
+
### Decision 3: move_project accepts folderId as string or null
|
|
29
|
+
|
|
30
|
+
`null` means move to top level (no parent folder). OmniJS: `project.parentFolder = null` removes the folder assignment.
|
|
31
|
+
|
|
32
|
+
### Decision 4: Not-found errors for all ID lookups
|
|
33
|
+
|
|
34
|
+
Same pattern as existing write tools: `NotFoundError` if task, project, folder, or parentTask ID is not found. Snippet throws; bridge catches and wraps as `ExecutionError`.
|
|
35
|
+
|
|
36
|
+
### Decision 5: Return updated summary, not full detail
|
|
37
|
+
|
|
38
|
+
`move_task` returns a `TaskSummary`. `move_project` returns a `ProjectSummary`. Consistent with create/edit patterns.
|
|
39
|
+
|
|
40
|
+
## Risks / Trade-offs
|
|
41
|
+
|
|
42
|
+
- **OmniJS assignment semantics** — setting `task.containingProject` may behave differently than `task.parentTask`. Both need integration testing to verify the task ends up in the right place.
|
|
43
|
+
- **Inbox tasks** — moving an inbox task to a project is valid and expected to work. Moving a project task to inbox is not supported (OmniJS doesn't expose inbox assignment on tasks directly). Document this limitation.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
## Why
|
|
2
|
+
|
|
3
|
+
There is no way to move a task to a different project or make it a subtask, and no way to move a project to a different folder. These are common reorganization actions in OmniFocus that an LLM assistant should be able to perform on the user's behalf.
|
|
4
|
+
|
|
5
|
+
## What Changes
|
|
6
|
+
|
|
7
|
+
- Add `move_task` tool: moves a task to a project (top-level) or makes it a subtask of another task
|
|
8
|
+
- Add `move_project` tool: moves a project to a folder or to the top level
|
|
9
|
+
|
|
10
|
+
## Capabilities
|
|
11
|
+
|
|
12
|
+
### New Capabilities
|
|
13
|
+
|
|
14
|
+
- `move-operations`: moving tasks between projects/parents and moving projects between folders
|
|
15
|
+
|
|
16
|
+
### Modified Capabilities
|
|
17
|
+
|
|
18
|
+
_(none — new tools, no existing requirement changes)_
|
|
19
|
+
|
|
20
|
+
## Impact
|
|
21
|
+
|
|
22
|
+
- `src/snippets/move_task.js`, `src/snippets/move_project.js`: new OmniJS snippets
|
|
23
|
+
- `src/tools/moveTask.ts`, `src/tools/moveProject.ts`: new tool handlers
|
|
24
|
+
- `src/tools/index.ts`: register new tools
|
|
25
|
+
- `src/runtime/snippetLoader.ts`: allowlist new snippet names
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
## Requirements
|
|
2
|
+
|
|
3
|
+
### Requirement: Move task to a new container
|
|
4
|
+
|
|
5
|
+
The system SHALL provide a `move_task` tool that accepts `{id: string, projectId?: string, parentTaskId?: string}` and moves the specified task to the given container. Exactly one of `projectId` or `parentTaskId` SHALL be provided; if both or neither are given, the tool SHALL return a validation error. When `projectId` is provided, the task SHALL become a top-level task in that project. When `parentTaskId` is provided, the task SHALL become a direct subtask of that task. The tool SHALL return a structured not-found error if any of the IDs do not correspond to existing objects.
|
|
6
|
+
|
|
7
|
+
#### Scenario: Move task to a project
|
|
8
|
+
- **WHEN** `move_task` is called with `{id: "t1", projectId: "p2"}`
|
|
9
|
+
- **THEN** the task appears as a top-level task in project p2 and is no longer in its previous container
|
|
10
|
+
|
|
11
|
+
#### Scenario: Make task a subtask
|
|
12
|
+
- **WHEN** `move_task` is called with `{id: "t1", parentTaskId: "t2"}`
|
|
13
|
+
- **THEN** the task becomes a direct child of task t2
|
|
14
|
+
|
|
15
|
+
#### Scenario: Both destinations provided is a validation error
|
|
16
|
+
- **WHEN** `move_task` is called with both `projectId` and `parentTaskId`
|
|
17
|
+
- **THEN** the tool returns a validation error before any snippet executes
|
|
18
|
+
|
|
19
|
+
#### Scenario: Non-existent task ID returns not-found error
|
|
20
|
+
- **WHEN** `move_task` is called with an ID that does not correspond to any task
|
|
21
|
+
- **THEN** the tool returns a structured not-found error
|
|
22
|
+
|
|
23
|
+
### Requirement: Move project to a folder
|
|
24
|
+
|
|
25
|
+
The system SHALL provide a `move_project` tool that accepts `{id: string, folderId: string | null}` and moves the specified project to the given folder. When `folderId` is `null`, the project SHALL be moved to the top level (no containing folder). The tool SHALL return a structured not-found error if the project ID or folder ID does not correspond to an existing object.
|
|
26
|
+
|
|
27
|
+
#### Scenario: Move project to a folder
|
|
28
|
+
- **WHEN** `move_project` is called with `{id: "p1", folderId: "f2"}`
|
|
29
|
+
- **THEN** the project is now contained within folder f2
|
|
30
|
+
|
|
31
|
+
#### Scenario: Move project to top level
|
|
32
|
+
- **WHEN** `move_project` is called with `{id: "p1", folderId: null}`
|
|
33
|
+
- **THEN** the project has no parent folder
|
|
34
|
+
|
|
35
|
+
#### Scenario: Non-existent project ID returns not-found error
|
|
36
|
+
- **WHEN** `move_project` is called with a project ID that does not exist
|
|
37
|
+
- **THEN** the tool returns a structured not-found error
|
|
38
|
+
|
|
39
|
+
#### Scenario: Non-existent folder ID returns not-found error
|
|
40
|
+
- **WHEN** `move_project` is called with a folderId that does not correspond to any folder
|
|
41
|
+
- **THEN** the tool returns a structured not-found error
|