@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,69 @@
|
|
|
1
|
+
## MODIFIED 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, plannedDate?: string, dueDate?: string, estimatedMinutes?: number, projectId?: string, parentTaskId?: string, tagIds?: string[], repetitionRule?: RepetitionRuleInput}`. 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. When `repetitionRule` is provided, the task SHALL have the specified recurrence set at creation.
|
|
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
|
+
#### Scenario: Create task with repetition rule
|
|
28
|
+
- **WHEN** `create_task` is called with `{ name: "Weekly review", repetitionRule: { frequency: "weekly", interval: 1, method: "start" } }`
|
|
29
|
+
- **THEN** the task is created with the specified recurrence and the returned TaskDetail includes the parsed repetitionRule
|
|
30
|
+
|
|
31
|
+
#### Scenario: Create task with planned date
|
|
32
|
+
- **WHEN** `create_task` is called with `{ name: "Clean kitchen", plannedDate: "2026-04-15T09:00:00Z" }`
|
|
33
|
+
- **THEN** the task is created with the specified planned date and the returned TaskDetail includes `plannedDate`
|
|
34
|
+
|
|
35
|
+
### Requirement: Edit task
|
|
36
|
+
|
|
37
|
+
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, plannedDate?: string | null, dueDate?: string | null, estimatedMinutes?: number | null, tagIds?: string[], repetitionRule?: RepetitionRuleInput | null}`. 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. Passing `repetitionRule: null` SHALL clear the task's recurrence; passing a `RepetitionRuleInput` object SHALL set or replace the recurrence; omitting `repetitionRule` SHALL leave the existing recurrence unchanged.
|
|
38
|
+
|
|
39
|
+
#### Scenario: Edit a single field
|
|
40
|
+
- **WHEN** `edit_task` is called with `{id: "abc123", flagged: true}`
|
|
41
|
+
- **THEN** only the `flagged` field is changed; all other fields retain their previous values
|
|
42
|
+
|
|
43
|
+
#### Scenario: Replace tag set
|
|
44
|
+
- **WHEN** `edit_task` is called with `{id: "abc123", tagIds: ["t1", "t2"]}`
|
|
45
|
+
- **THEN** the task's tags are set to exactly `["t1", "t2"]`, replacing any previously assigned tags
|
|
46
|
+
|
|
47
|
+
#### Scenario: Clear a date field
|
|
48
|
+
- **WHEN** `edit_task` is called with `{id: "abc123", dueDate: null}`
|
|
49
|
+
- **THEN** the task's due date is cleared
|
|
50
|
+
|
|
51
|
+
#### Scenario: Clear planned date
|
|
52
|
+
- **WHEN** `edit_task` is called with `{id: "abc123", plannedDate: null}`
|
|
53
|
+
- **THEN** the task's planned date is cleared
|
|
54
|
+
|
|
55
|
+
#### Scenario: Non-existent task returns not-found error
|
|
56
|
+
- **WHEN** `edit_task` is called with an ID that does not correspond to any task
|
|
57
|
+
- **THEN** the tool returns a structured not-found error
|
|
58
|
+
|
|
59
|
+
#### Scenario: Non-existent tag ID returns not-found error
|
|
60
|
+
- **WHEN** `edit_task` is called with a `tagIds` array containing an ID that does not correspond to any tag
|
|
61
|
+
- **THEN** the tool returns a structured not-found error and the task is not modified
|
|
62
|
+
|
|
63
|
+
#### Scenario: Set repetition via edit
|
|
64
|
+
- **WHEN** `edit_task` is called with `{ id: "t1", repetitionRule: { frequency: "monthly", interval: 1, method: "dueDate" } }`
|
|
65
|
+
- **THEN** the task's recurrence is set and all other fields are unchanged
|
|
66
|
+
|
|
67
|
+
#### Scenario: Clear repetition via edit
|
|
68
|
+
- **WHEN** `edit_task` is called with `{ id: "t1", repetitionRule: null }`
|
|
69
|
+
- **THEN** the task's recurrence is cleared and all other fields are unchanged
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
## 1. Schema changes (src/schemas/)
|
|
2
|
+
|
|
3
|
+
- [x] 1.1 Add `plannedDate: z.string().datetime().optional()` to `CreateTaskInput` in `shapes.ts`
|
|
4
|
+
- [x] 1.2 Add `plannedDate: z.string().datetime().nullable().optional()` to `EditTaskInput` in `shapes.ts`
|
|
5
|
+
- [x] 1.3 Add `plannedDate: z.string().datetime().nullable()` to `TaskDetail` in `shapes.ts`
|
|
6
|
+
|
|
7
|
+
## 2. Snippet updates (src/snippets/)
|
|
8
|
+
|
|
9
|
+
- [x] 2.1 Update `create_task.js`: set `task.plannedDate` when provided; include `plannedDate: isoOrNull(task.plannedDate)` in returned TaskDetail
|
|
10
|
+
- [x] 2.2 Update `edit_task.js`: set/clear `task.plannedDate` using same pattern as deferDate/dueDate; include in returned TaskDetail
|
|
11
|
+
- [x] 2.3 Update `get_task.js`: include `plannedDate: isoOrNull(task.plannedDate)` in returned detail
|
|
12
|
+
- [x] 2.4 Update `complete_task.js`: include `plannedDate: isoOrNull(task.plannedDate)` in returned TaskDetail
|
|
13
|
+
- [x] 2.5 Update `drop_task.js`: include `plannedDate: isoOrNull(task.plannedDate)` in returned TaskDetail
|
|
14
|
+
|
|
15
|
+
## 3. Integration tests (test/integration/)
|
|
16
|
+
|
|
17
|
+
- [x] 3.1 `plannedDate.int.test.ts`: create task with plannedDate; verify get_task returns it
|
|
18
|
+
- [x] 3.2 `plannedDate.int.test.ts`: edit task to set plannedDate; verify get_task reflects it
|
|
19
|
+
- [x] 3.3 `plannedDate.int.test.ts`: edit task to clear plannedDate (null); verify get_task returns null
|
|
20
|
+
- [x] 3.4 `plannedDate.int.test.ts`: create task without plannedDate; verify get_task returns null (backward compat)
|
|
21
|
+
|
|
22
|
+
## 4. Verification
|
|
23
|
+
|
|
24
|
+
- [x] 4.1 `npm run typecheck` clean
|
|
25
|
+
- [x] 4.2 `npm test` (unit suite) clean
|
|
26
|
+
- [x] 4.3 Manually run integration suite
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
## Context
|
|
2
|
+
|
|
3
|
+
OmniFocus tasks carry a `Task.RepetitionRule` which combines an ICS RRULE string with a `RepetitionMethod` enum (`Fixed`, `DueDate`, `Start`). The OmniJS API is:
|
|
4
|
+
- `task.repetitionRule = new Task.RepetitionRule(rruleString, method)` — set
|
|
5
|
+
- `task.repetitionRule = null` — clear
|
|
6
|
+
- `task.repetitionRule.ruleString` — read the RRULE back
|
|
7
|
+
- `task.repetitionRule.method` — read the method back
|
|
8
|
+
|
|
9
|
+
The OmniFocus UI exposes: frequency (daily/weekly/monthly/yearly), interval (every N units), days-of-week (weekly only), and repeat method (fixed interval / due date / completion date). This change targets exactly that surface.
|
|
10
|
+
|
|
11
|
+
## Goals / Non-Goals
|
|
12
|
+
|
|
13
|
+
**Goals:**
|
|
14
|
+
- `create_task`: accept optional `repetitionRule` to set recurrence at creation
|
|
15
|
+
- `edit_task`: accept optional `repetitionRule` (nullable to clear) on existing tasks
|
|
16
|
+
- `get_task` / `TaskDetail`: return current repetition as structured fields (or null)
|
|
17
|
+
- Validate that `daysOfWeek` is only meaningful on `frequency: "weekly"`
|
|
18
|
+
|
|
19
|
+
**Non-Goals:**
|
|
20
|
+
- RRULE patterns not reachable from the OmniFocus UI (e.g., "every 2nd Tuesday of the month", end-by-count, end-by-date)
|
|
21
|
+
- Project repetition (projects don't have `repetitionRule` in OmniJS)
|
|
22
|
+
- Raw RRULE escape hatch — scope is UI surface only
|
|
23
|
+
|
|
24
|
+
## Decisions
|
|
25
|
+
|
|
26
|
+
### Decision 1: Structured schema, no raw rrule
|
|
27
|
+
|
|
28
|
+
Input schema uses structured fields (`frequency`, `interval`, `daysOfWeek`, `method`). The snippet constructs the RRULE internally via `new Task.RepetitionRule(rruleString, methodEnum)`. This keeps the API LLM-friendly and prevents invalid RRULE strings from being passed in.
|
|
29
|
+
|
|
30
|
+
Considered: raw `{ rrule, method }` input. Rejected because LLMs may produce invalid RRULE syntax, and the UI surface is small enough to model fully without an escape hatch.
|
|
31
|
+
|
|
32
|
+
**OmniJS API notes (discovered empirically):**
|
|
33
|
+
- Constructor: `new Task.RepetitionRule(rrule, method)` — NOT `.make()`
|
|
34
|
+
- `Task.RepetitionMethod.Fixed` — fixed interval
|
|
35
|
+
- `Task.RepetitionMethod.DueDate` — repeat from due date
|
|
36
|
+
- `Task.RepetitionMethod.DeferUntilDate` — repeat from completion date (maps to our `"start"` method)
|
|
37
|
+
- Enum values have no `.name` property; use `String(enumValue)` which returns e.g. `"[object Task.RepetitionMethod: DeferUntilDate]"`
|
|
38
|
+
|
|
39
|
+
### Decision 2: RepetitionRuleInput is its own schema, embedded in CreateTaskInput and EditTaskInput
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
RepetitionRuleInput = z.object({
|
|
43
|
+
frequency: z.enum(["daily", "weekly", "monthly", "yearly"]),
|
|
44
|
+
interval: z.number().int().positive().default(1),
|
|
45
|
+
daysOfWeek: z.array(z.enum(["sunday","monday","tuesday","wednesday","thursday","friday","saturday"])).optional(),
|
|
46
|
+
method: z.enum(["fixed", "dueDate", "start"]),
|
|
47
|
+
})
|
|
48
|
+
.refine(d => !d.daysOfWeek || d.frequency === "weekly", {
|
|
49
|
+
message: "daysOfWeek is only valid when frequency is 'weekly'"
|
|
50
|
+
})
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
In `EditTaskInput`: `repetitionRule: RepetitionRuleInput.nullable().optional()` — omit to leave unchanged, `null` to clear, object to set.
|
|
54
|
+
|
|
55
|
+
### Decision 3: Read side returns structured fields parsed from RRULE
|
|
56
|
+
|
|
57
|
+
`TaskDetail.repetitionRule` is `RepetitionRuleDetail | null`:
|
|
58
|
+
```
|
|
59
|
+
RepetitionRuleDetail = z.object({
|
|
60
|
+
frequency: z.enum(["daily","weekly","monthly","yearly"]),
|
|
61
|
+
interval: z.number(),
|
|
62
|
+
daysOfWeek: z.array(...).optional(),
|
|
63
|
+
method: z.enum(["fixed","dueDate","start"]),
|
|
64
|
+
})
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The snippet parses `task.repetitionRule.ruleString` using simple regex/split to extract FREQ, INTERVAL, and BYDAY. If parsing fails (unsupported RRULE set outside the UI), return `{ rrule: rawString, method }` as a fallback with a `_raw: true` flag — this way the LLM sees something rather than crashing.
|
|
68
|
+
|
|
69
|
+
### Decision 4: RRULE construction in snippet, not TypeScript
|
|
70
|
+
|
|
71
|
+
Keeps the logic next to the OmniJS API call. TypeScript layer only validates the structured input and passes it through.
|
|
72
|
+
|
|
73
|
+
### Decision 5: daysOfWeek maps to BYDAY abbreviations
|
|
74
|
+
|
|
75
|
+
Day mapping: sunday→SU, monday→MO, tuesday→TU, wednesday→WE, thursday→TH, friday→FR, saturday→SA. When `daysOfWeek` is absent for a weekly rule, no `BYDAY` component is added (repeats on the same weekday as the task's due/defer date).
|
|
76
|
+
|
|
77
|
+
## Risks / Trade-offs
|
|
78
|
+
|
|
79
|
+
- **Tasks without a due or defer date + Fixed method**: OmniFocus behavior is undefined (no anchor date). Document in tool description; not validated by the schema.
|
|
80
|
+
- **RRULE parsing on read**: Simple regex parsing covers the UI surface; complex patterns set via OmniJS directly will hit the `_raw` fallback.
|
|
81
|
+
- **interval default**: Schema defaults `interval` to 1 but it must be passed explicitly in the RRULE string (`INTERVAL=1`) — OmniFocus requires it.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
## Why
|
|
2
|
+
|
|
3
|
+
OmniFocus tasks support repeat intervals, but the MCP server has no way to set, edit, or read repetition rules — forcing users to manually configure recurrence in OmniFocus after every task creation. This gap means AI-driven task creation is incomplete for any workflow that involves repeating tasks.
|
|
4
|
+
|
|
5
|
+
## What Changes
|
|
6
|
+
|
|
7
|
+
- `create_task`: add optional `repetitionRule` field to set recurrence at creation time
|
|
8
|
+
- `edit_task`: add optional `repetitionRule` field (nullable to clear recurrence) on existing tasks
|
|
9
|
+
- `get_task` / `TaskDetail`: return current `repetitionRule` as structured fields (or null if none)
|
|
10
|
+
- New `RepetitionRuleInput` and `RepetitionRuleDetail` schemas covering the OmniFocus UI surface: frequency, interval, optional days-of-week (weekly only), and repeat method
|
|
11
|
+
|
|
12
|
+
## Capabilities
|
|
13
|
+
|
|
14
|
+
### New Capabilities
|
|
15
|
+
|
|
16
|
+
- `recurrence`: Requirements for reading and writing OmniFocus task repetition rules (frequency, interval, daysOfWeek, method). Note: the `recurrence` spec already exists as a placeholder — this change fills it in.
|
|
17
|
+
|
|
18
|
+
### Modified Capabilities
|
|
19
|
+
|
|
20
|
+
- `task-write`: `create_task` and `edit_task` gain a `repetitionRule` parameter
|
|
21
|
+
- `task-management`: `get_task` returns `repetitionRule` in `TaskDetail`
|
|
22
|
+
|
|
23
|
+
## Impact
|
|
24
|
+
|
|
25
|
+
- `src/schemas/shapes.ts`: new `RepetitionRuleInput` and `RepetitionRuleDetail` types; `CreateTaskInput`, `EditTaskInput`, `TaskDetail` extended
|
|
26
|
+
- `src/schemas/index.ts`: new exports
|
|
27
|
+
- `src/snippets/create_task.js`, `edit_task.js`, `get_task.js`: snippet updates
|
|
28
|
+
- No breaking changes — all new fields are optional; existing callers unaffected
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
## MODIFIED Requirements
|
|
2
|
+
|
|
3
|
+
### Requirement: Capability declared
|
|
4
|
+
|
|
5
|
+
The `recurrence` capability covers the construction, assignment, reading, and clearing of `Task.RepetitionRule` values on OmniFocus tasks. The capability exposes a structured schema matching the OmniFocus UI surface: `frequency` (`daily` | `weekly` | `monthly` | `yearly`), `interval` (every N units, ≥1), `daysOfWeek` (weekly only, any subset of the 7 days), and `method` (`fixed` | `dueDate` | `start`). Repetition is set and cleared through `create_task` and `edit_task`; the current rule is returned by `get_task` as structured fields parsed from the underlying RRULE. Raw RRULE strings are not exposed as input — all recurrence is expressed through the structured schema.
|
|
6
|
+
|
|
7
|
+
#### Scenario: Capability is named and scoped
|
|
8
|
+
- **WHEN** a change proposes adding or modifying recurrence-related behavior
|
|
9
|
+
- **THEN** it lands requirements under this capability
|
|
10
|
+
|
|
11
|
+
## ADDED Requirements
|
|
12
|
+
|
|
13
|
+
### Requirement: Set repetition rule on task
|
|
14
|
+
|
|
15
|
+
The system SHALL accept a `repetitionRule` field in `create_task` and `edit_task`. When provided, the snippet SHALL construct an ICS RRULE string from the structured fields and assign it via `Task.RepetitionRule.make(rrule, method)`. The `daysOfWeek` field SHALL only be valid when `frequency` is `"weekly"`; providing it for any other frequency SHALL be a validation error. When `edit_task` receives `repetitionRule: null`, the snippet SHALL assign `task.repetitionRule = null` to clear the rule. When `edit_task` omits `repetitionRule` entirely, the existing rule SHALL be left unchanged.
|
|
16
|
+
|
|
17
|
+
#### Scenario: Set daily repetition at creation
|
|
18
|
+
- **WHEN** `create_task` is called with `{ name: "Stand-up", repetitionRule: { frequency: "daily", interval: 1, method: "fixed" } }`
|
|
19
|
+
- **THEN** the created task has a repetition rule of every day (fixed interval) and the returned TaskDetail includes the parsed repetitionRule
|
|
20
|
+
|
|
21
|
+
#### Scenario: Set weekly repetition on specific days
|
|
22
|
+
- **WHEN** `create_task` is called with `{ repetitionRule: { frequency: "weekly", interval: 1, daysOfWeek: ["monday", "wednesday", "friday"], method: "start" } }`
|
|
23
|
+
- **THEN** the task repeats every Mon/Wed/Fri from completion date
|
|
24
|
+
|
|
25
|
+
#### Scenario: Edit task to add repetition
|
|
26
|
+
- **WHEN** `edit_task` is called with `{ id: "t1", repetitionRule: { frequency: "monthly", interval: 1, method: "dueDate" } }`
|
|
27
|
+
- **THEN** the task's repetition rule is set to monthly (due date) and all other fields are unchanged
|
|
28
|
+
|
|
29
|
+
#### Scenario: Clear repetition via null
|
|
30
|
+
- **WHEN** `edit_task` is called with `{ id: "t1", repetitionRule: null }`
|
|
31
|
+
- **THEN** the task's repetition rule is cleared and `get_task` returns `repetitionRule: null` for that task
|
|
32
|
+
|
|
33
|
+
#### Scenario: daysOfWeek on non-weekly frequency is a validation error
|
|
34
|
+
- **WHEN** `create_task` or `edit_task` is called with `{ repetitionRule: { frequency: "daily", daysOfWeek: ["monday"], method: "fixed" } }`
|
|
35
|
+
- **THEN** the tool returns a validation error before any snippet executes
|
|
36
|
+
|
|
37
|
+
### Requirement: Return repetition rule from get_task
|
|
38
|
+
|
|
39
|
+
The system SHALL include a `repetitionRule` field in the `TaskDetail` returned by `get_task`. When a task has no repetition rule, the field SHALL be `null`. When a task has a rule, the field SHALL be a structured object with `frequency`, `interval`, `daysOfWeek` (omitted when not applicable), and `method`, parsed from the underlying RRULE and RepetitionMethod.
|
|
40
|
+
|
|
41
|
+
#### Scenario: get_task returns null when no repetition set
|
|
42
|
+
- **WHEN** `get_task` is called for a task with no repetition rule
|
|
43
|
+
- **THEN** the returned TaskDetail includes `repetitionRule: null`
|
|
44
|
+
|
|
45
|
+
#### Scenario: get_task returns structured repetition fields
|
|
46
|
+
- **WHEN** `get_task` is called for a task with a weekly repetition rule
|
|
47
|
+
- **THEN** the returned TaskDetail includes `repetitionRule` with `frequency: "weekly"`, correct `interval`, `daysOfWeek` array, and `method`
|
|
@@ -0,0 +1,25 @@
|
|
|
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, 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. 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`, and `repetitionRule`
|
|
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: Missing task returns not-found error
|
|
24
|
+
- **WHEN** `get_task` is called with an ID that does not correspond to any task
|
|
25
|
+
- **THEN** the tool returns a structured error with a not-found code and does not throw an unhandled exception
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
## MODIFIED 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[], repetitionRule?: RepetitionRuleInput}`. 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. When `repetitionRule` is provided, the task SHALL have the specified recurrence set at creation.
|
|
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
|
+
#### Scenario: Create task with repetition rule
|
|
28
|
+
- **WHEN** `create_task` is called with `{ name: "Weekly review", repetitionRule: { frequency: "weekly", interval: 1, method: "start" } }`
|
|
29
|
+
- **THEN** the task is created with the specified recurrence and the returned TaskDetail includes the parsed repetitionRule
|
|
30
|
+
|
|
31
|
+
### Requirement: Edit task
|
|
32
|
+
|
|
33
|
+
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[], repetitionRule?: RepetitionRuleInput | null}`. 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. Passing `repetitionRule: null` SHALL clear the task's recurrence; passing a `RepetitionRuleInput` object SHALL set or replace the recurrence; omitting `repetitionRule` SHALL leave the existing recurrence unchanged.
|
|
34
|
+
|
|
35
|
+
#### Scenario: Edit a single field
|
|
36
|
+
- **WHEN** `edit_task` is called with `{id: "abc123", flagged: true}`
|
|
37
|
+
- **THEN** only the `flagged` field is changed; all other fields retain their previous values
|
|
38
|
+
|
|
39
|
+
#### Scenario: Replace tag set
|
|
40
|
+
- **WHEN** `edit_task` is called with `{id: "abc123", tagIds: ["t1", "t2"]}`
|
|
41
|
+
- **THEN** the task's tags are set to exactly `["t1", "t2"]`, replacing any previously assigned tags
|
|
42
|
+
|
|
43
|
+
#### Scenario: Clear a date field
|
|
44
|
+
- **WHEN** `edit_task` is called with `{id: "abc123", dueDate: null}`
|
|
45
|
+
- **THEN** the task's due date is cleared
|
|
46
|
+
|
|
47
|
+
#### Scenario: Non-existent task returns not-found error
|
|
48
|
+
- **WHEN** `edit_task` is called with an ID that does not correspond to any task
|
|
49
|
+
- **THEN** the tool returns a structured not-found error
|
|
50
|
+
|
|
51
|
+
#### Scenario: Non-existent tag ID returns not-found error
|
|
52
|
+
- **WHEN** `edit_task` is called with a `tagIds` array containing an ID that does not correspond to any tag
|
|
53
|
+
- **THEN** the tool returns a structured not-found error and the task is not modified
|
|
54
|
+
|
|
55
|
+
#### Scenario: Set repetition via edit
|
|
56
|
+
- **WHEN** `edit_task` is called with `{ id: "t1", repetitionRule: { frequency: "monthly", interval: 1, method: "dueDate" } }`
|
|
57
|
+
- **THEN** the task's recurrence is set and all other fields are unchanged
|
|
58
|
+
|
|
59
|
+
#### Scenario: Clear repetition via edit
|
|
60
|
+
- **WHEN** `edit_task` is called with `{ id: "t1", repetitionRule: null }`
|
|
61
|
+
- **THEN** the task's recurrence is cleared and all other fields are unchanged
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
## 1. Schema changes (src/schemas/)
|
|
2
|
+
|
|
3
|
+
- [x] 1.1 Define `RepetitionRuleInput` zod schema: `{ frequency, interval (default 1), daysOfWeek?, method }` with `.refine()` that daysOfWeek is only valid when frequency is "weekly"
|
|
4
|
+
- [x] 1.2 Define `RepetitionRuleDetail` zod schema: `{ frequency, interval, daysOfWeek?, method }` (no refine needed — read-only)
|
|
5
|
+
- [x] 1.3 Add `repetitionRule: RepetitionRuleInput.optional()` to `CreateTaskInput`
|
|
6
|
+
- [x] 1.4 Add `repetitionRule: RepetitionRuleInput.nullable().optional()` to `EditTaskInput`
|
|
7
|
+
- [x] 1.5 Add `repetitionRule: RepetitionRuleDetail.nullable()` to `TaskDetail`
|
|
8
|
+
- [x] 1.6 Export `RepetitionRuleInput` and `RepetitionRuleDetail` from `src/schemas/index.ts`
|
|
9
|
+
|
|
10
|
+
## 2. Snippet updates (src/snippets/)
|
|
11
|
+
|
|
12
|
+
- [x] 2.1 Update `create_task.js`: after task creation, if `args.repetitionRule` is provided, construct RRULE string from frequency/interval/daysOfWeek and call `Task.RepetitionRule.make(rrule, method)`; include `repetitionRule` in returned TaskDetail
|
|
13
|
+
- [x] 2.2 Update `edit_task.js`: if `args.repetitionRule === null` assign `task.repetitionRule = null`; if `args.repetitionRule` is an object, construct and assign the rule; if `args.repetitionRule` is undefined, leave unchanged; include `repetitionRule` in returned TaskDetail
|
|
14
|
+
- [x] 2.3 Update `get_task.js`: read `task.repetitionRule`; if null return `null`; otherwise parse `ruleString` (extract FREQ, INTERVAL, BYDAY via string ops) and map method enum to string; return structured `repetitionRule` field
|
|
15
|
+
|
|
16
|
+
## 3. Helper: RRULE construction and parsing
|
|
17
|
+
|
|
18
|
+
- [x] 3.1 In `create_task.js` and `edit_task.js`: implement `buildRrule(rule)` helper that produces the RRULE string — `FREQ=X;INTERVAL=N` plus `BYDAY=MO,WE,...` when daysOfWeek is present
|
|
19
|
+
- [x] 3.2 In `create_task.js`, `edit_task.js`, and `get_task.js`: implement `parseRepetitionRule(rule)` helper that reads `rule.ruleString` and `rule.method`, extracts FREQ/INTERVAL/BYDAY via string split, maps back to structured fields
|
|
20
|
+
|
|
21
|
+
## 4. Unit tests (test/unit/)
|
|
22
|
+
|
|
23
|
+
- [x] 4.1 Add schema tests for `RepetitionRuleInput`: valid daily; valid weekly with daysOfWeek; valid monthly; valid yearly; invalid daysOfWeek on non-weekly frequency rejected; interval must be positive integer
|
|
24
|
+
- [x] 4.2 Add schema tests for extended `CreateTaskInput`: valid with repetitionRule; valid without (backward compat)
|
|
25
|
+
- [x] 4.3 Add schema tests for extended `EditTaskInput`: valid with repetitionRule object; valid with repetitionRule null (clear); valid omitting repetitionRule (unchanged)
|
|
26
|
+
|
|
27
|
+
## 5. Integration tests (test/integration/)
|
|
28
|
+
|
|
29
|
+
- [x] 5.1 `taskRecurrence.int.test.ts`: create task with daily repetition; verify get_task returns repetitionRule with frequency "daily"
|
|
30
|
+
- [x] 5.2 `taskRecurrence.int.test.ts`: create task with weekly repetition on Mon/Wed/Fri; verify get_task returns correct daysOfWeek
|
|
31
|
+
- [x] 5.3 `taskRecurrence.int.test.ts`: edit existing task to add monthly repetition; verify get_task reflects new rule
|
|
32
|
+
- [x] 5.4 `taskRecurrence.int.test.ts`: edit task to clear repetition (null); verify get_task returns repetitionRule: null
|
|
33
|
+
- [x] 5.5 `taskRecurrence.int.test.ts`: create task without repetitionRule; verify get_task returns repetitionRule: null (backward compat)
|
|
34
|
+
|
|
35
|
+
## 6. Verification
|
|
36
|
+
|
|
37
|
+
- [x] 6.1 `npm run typecheck` clean
|
|
38
|
+
- [x] 6.2 `npm test` (unit suite) clean
|
|
39
|
+
- [x] 6.3 Manually run integration suite
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
schema: spec-driven
|
|
2
|
+
|
|
3
|
+
# Project context (optional)
|
|
4
|
+
# This is shown to AI when creating artifacts.
|
|
5
|
+
# Add your tech stack, conventions, style guides, domain knowledge, etc.
|
|
6
|
+
# Example:
|
|
7
|
+
# context: |
|
|
8
|
+
# Tech stack: TypeScript, React, Node.js
|
|
9
|
+
# We use conventional commits
|
|
10
|
+
# Domain: e-commerce platform
|
|
11
|
+
|
|
12
|
+
# Per-artifact rules (optional)
|
|
13
|
+
# Add custom rules for specific artifacts.
|
|
14
|
+
# Example:
|
|
15
|
+
# rules:
|
|
16
|
+
# proposal:
|
|
17
|
+
# - Keep proposals under 500 words
|
|
18
|
+
# - Always include a "Non-goals" section
|
|
19
|
+
# tasks:
|
|
20
|
+
# - Break tasks into chunks of max 2 hours
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# attachments
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Covers listing, reading, adding, and removing file attachments on task notes via the OmniJS `FileWrapper` API. Individual tools and on-wire encoding decisions will be defined in the `attachments` change.
|
|
6
|
+
|
|
7
|
+
## Requirements
|
|
8
|
+
|
|
9
|
+
### Requirement: Capability declared
|
|
10
|
+
|
|
11
|
+
The `attachments` capability SHALL cover listing, reading, adding, and removing file attachments on task notes via the OmniJS `FileWrapper` API. The on-wire representation of attachment contents across the MCP boundary (inline base64, filesystem path reference, or a hybrid with a size threshold) SHALL be decided when the `attachments` change is drafted. Requirements for individual tools SHALL be added by that change.
|
|
12
|
+
|
|
13
|
+
#### Scenario: Capability is named and scoped
|
|
14
|
+
- **WHEN** a future change proposes adding attachment tools
|
|
15
|
+
- **THEN** it lands requirements under this capability and makes the on-wire encoding decision explicit
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# batch-operations
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Covers batch variants of every mutating tool with typed-array validation, partial-success semantics, and single-snippet execution. Individual tools will be defined in the `batch-operations` change.
|
|
6
|
+
|
|
7
|
+
## Requirements
|
|
8
|
+
|
|
9
|
+
### Requirement: Capability declared
|
|
10
|
+
|
|
11
|
+
The `batch-operations` capability SHALL cover batch variants of every mutating tool (create, update, delete, move, complete, assign). Batch tools SHALL accept strictly typed arrays validated at the TypeScript boundary (never serialized JSON strings) and SHALL return a per-item result array with partial-success semantics: each element is an `{ok, data?, error?}` envelope carrying that item's outcome independent of other items. Batch tools SHALL execute as a single OmniJS snippet that iterates internally, not as a loop of per-item bridge invocations. Requirements for individual batch tools SHALL be added by the `batch-operations` change.
|
|
12
|
+
|
|
13
|
+
#### Scenario: Capability is named and semantics are fixed
|
|
14
|
+
- **WHEN** a future change proposes adding batch tools
|
|
15
|
+
- **THEN** it lands requirements under this capability with typed-array validation and partial-success result semantics as invariants
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# database-inspection
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Covers scoped, paged, and filtered traversal of the full OmniFocus database, including a `dump_database` tool, inbox inspection, and database metadata. Individual tools will be defined in the `forecast-and-inspection` change.
|
|
6
|
+
|
|
7
|
+
## Requirements
|
|
8
|
+
|
|
9
|
+
### Requirement: Capability declared
|
|
10
|
+
|
|
11
|
+
The `database-inspection` capability SHALL cover scoped, paged, and filtered traversal of the full OmniFocus database, including a `dump_database` tool that accepts `{scope, id?, include, format}` parameters, inbox inspection, and database metadata (name, sync status, object counts). The default behavior of `dump_database` SHALL exclude completed tasks, dropped entities, and task notes to keep payloads manageable; those are opt-in via the `include` parameter. Requirements for individual tools SHALL be added by the `forecast-and-inspection` change.
|
|
12
|
+
|
|
13
|
+
#### Scenario: Capability is named and scoped
|
|
14
|
+
- **WHEN** a future change proposes adding database-inspection tools
|
|
15
|
+
- **THEN** it lands requirements under this capability rather than inventing a new capability name
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# execution-runtime
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Defines the contract for executing OmniFocus domain operations through the OmniJS/JXA bridge, including snippet injection, result protocol, caching, and timeouts.
|
|
6
|
+
|
|
7
|
+
## Requirements
|
|
8
|
+
|
|
9
|
+
### Requirement: OmniJS execution via JXA bridge
|
|
10
|
+
|
|
11
|
+
The system SHALL execute every OmniFocus domain operation inside OmniFocus's OmniJS runtime by invoking `Application('OmniFocus').evaluateJavascript(snippet)` from a JXA host process spawned via `osascript -l JavaScript`. The JXA host SHALL NOT invoke any OmniFocus scripting-dictionary method other than `evaluateJavascript` for domain operations.
|
|
12
|
+
|
|
13
|
+
#### Scenario: Tool invocation routes through the bridge
|
|
14
|
+
- **WHEN** a tool handler invokes the runtime with a snippet and args
|
|
15
|
+
- **THEN** the runtime spawns `osascript -l JavaScript`, passes a JXA wrapper that calls `Application('OmniFocus').evaluateJavascript` with the prepared snippet, and awaits stdout
|
|
16
|
+
|
|
17
|
+
#### Scenario: Scripting-dictionary domain methods are forbidden
|
|
18
|
+
- **WHEN** a contributor adds a tool handler that calls e.g. `Application('OmniFocus').defaultDocument.projects` directly
|
|
19
|
+
- **THEN** a unit test (or review) flags the call as a runtime-contract violation and the change is rejected
|
|
20
|
+
|
|
21
|
+
### Requirement: Snippet argument injection via JSON literal
|
|
22
|
+
|
|
23
|
+
The system SHALL construct executable scripts by replacing exactly one `__ARGS__` placeholder in a static `.js` snippet template with the result of `JSON.stringify(args)`. No other interpolation of user-supplied data into script source is permitted anywhere in the codebase.
|
|
24
|
+
|
|
25
|
+
#### Scenario: Apostrophes and quotes survive injection
|
|
26
|
+
- **WHEN** a tool is invoked with `args = {name: "Finn's \"birthday\""}`
|
|
27
|
+
- **THEN** the executed snippet receives `args.name === "Finn's \"birthday\""` and no syntax error occurs
|
|
28
|
+
|
|
29
|
+
#### Scenario: Unicode and newlines survive injection
|
|
30
|
+
- **WHEN** a tool is invoked with `args = {note: "line1\nline2 — emoji 🎯"}`
|
|
31
|
+
- **THEN** the executed snippet receives the note verbatim including the newline and unicode code points
|
|
32
|
+
|
|
33
|
+
#### Scenario: Snippets are standalone and paste-ready
|
|
34
|
+
- **WHEN** a developer opens any file under `src/snippets/`
|
|
35
|
+
- **THEN** the file is a valid JavaScript program that can be pasted into OmniFocus's Automation Console after replacing `__ARGS__` with a hand-written object literal, with no additional preprocessing required
|
|
36
|
+
|
|
37
|
+
### Requirement: One-line JSON result protocol
|
|
38
|
+
|
|
39
|
+
The system SHALL represent every bridge invocation's result as exactly one line of JSON on the JXA host's stdout, matching the envelope `{ok: true, data: <value>} | {ok: false, error: {name: string, message: string, stack?: string}}`. The TypeScript runtime SHALL read stdout, extract the first JSON-parseable line, and return the parsed envelope to the caller. A result envelope with `ok: false` SHALL be thrown as a structured error with the error details preserved.
|
|
40
|
+
|
|
41
|
+
#### Scenario: Successful operation returns data envelope
|
|
42
|
+
- **WHEN** a snippet completes without throwing and its final expression is `JSON.stringify({ok: true, data: {id: "abc"}})`
|
|
43
|
+
- **THEN** the TS runtime receives `{ok: true, data: {id: "abc"}}` and returns `{id: "abc"}` to the tool handler
|
|
44
|
+
|
|
45
|
+
#### Scenario: Snippet exception produces error envelope
|
|
46
|
+
- **WHEN** a snippet throws an `Error` with message "not found"
|
|
47
|
+
- **THEN** the JXA shim catches the exception, prints `{ok: false, error: {name: "Error", message: "not found", ...}}` as one line on stdout, and the TS runtime throws a structured error carrying the name, message, and stack
|
|
48
|
+
|
|
49
|
+
#### Scenario: Extraneous stdout chatter does not corrupt results
|
|
50
|
+
- **WHEN** any process in the pipeline writes incidental non-JSON output to stdout before the result line
|
|
51
|
+
- **THEN** the TS runtime still parses the result envelope correctly by selecting the first complete JSON-parseable line
|
|
52
|
+
|
|
53
|
+
### Requirement: Snippet loader
|
|
54
|
+
|
|
55
|
+
The system SHALL load snippet templates from `src/snippets/<name>.js` relative to the compiled server's known snippet root, caching the file contents in memory after the first read. The loader SHALL reject snippets that do not contain exactly one `__ARGS__` token.
|
|
56
|
+
|
|
57
|
+
#### Scenario: Snippet is loaded and cached
|
|
58
|
+
- **WHEN** a tool handler invokes the runtime with snippet name `"list_projects"` twice in the same process
|
|
59
|
+
- **THEN** the file `list_projects.js` is read from disk exactly once and the cached content is used for the second invocation
|
|
60
|
+
|
|
61
|
+
#### Scenario: Snippet with zero placeholders is rejected
|
|
62
|
+
- **WHEN** a snippet file contains no `__ARGS__` token
|
|
63
|
+
- **THEN** the loader throws a contract violation at load time, not at runtime
|
|
64
|
+
|
|
65
|
+
#### Scenario: Snippet with multiple placeholders is rejected
|
|
66
|
+
- **WHEN** a snippet file contains two or more `__ARGS__` tokens
|
|
67
|
+
- **THEN** the loader throws a contract violation at load time
|
|
68
|
+
|
|
69
|
+
### Requirement: Script timeout
|
|
70
|
+
|
|
71
|
+
The system SHALL enforce a configurable per-invocation timeout on `osascript` execution, defaulting to 30 seconds, and SHALL terminate the child process and throw a timeout error when exceeded.
|
|
72
|
+
|
|
73
|
+
#### Scenario: Long-running snippet is terminated
|
|
74
|
+
- **WHEN** a snippet runs longer than the configured timeout
|
|
75
|
+
- **THEN** the runtime sends SIGTERM to the `osascript` process and throws a timeout error identifying the snippet name and elapsed time
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# folder-management
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Defines tools for reading and listing OmniFocus folders, including full folder listing and detail retrieval by ID.
|
|
6
|
+
|
|
7
|
+
## Requirements
|
|
8
|
+
|
|
9
|
+
### Requirement: List folders
|
|
10
|
+
|
|
11
|
+
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.
|
|
12
|
+
|
|
13
|
+
#### Scenario: All folders returned by default
|
|
14
|
+
- **WHEN** `list_folders` is called with no arguments
|
|
15
|
+
- **THEN** the tool returns all folders (active and dropped) up to the limit
|
|
16
|
+
|
|
17
|
+
#### Scenario: Top-level folders have null parent
|
|
18
|
+
- **WHEN** a folder exists at the root of the folder hierarchy
|
|
19
|
+
- **THEN** its summary carries `parentId: null` and `path` equal to its name
|
|
20
|
+
|
|
21
|
+
#### Scenario: Filter by status returns only matching folders
|
|
22
|
+
- **WHEN** `list_folders` is called with `{ filter: { status: "active" } }`
|
|
23
|
+
- **THEN** only active folders are returned
|
|
24
|
+
|
|
25
|
+
#### Scenario: Limit caps the number of returned folders
|
|
26
|
+
- **WHEN** `list_folders` is called with `{ limit: 5 }`
|
|
27
|
+
- **THEN** at most 5 folders are returned
|
|
28
|
+
|
|
29
|
+
### Requirement: Get folder by ID
|
|
30
|
+
|
|
31
|
+
The system SHALL provide a `get_folder` tool that accepts `{id: string}` and returns the full detail record of the named folder, including `{id, name, path, parentId, status, childFolderIds, projectIds}`. If no folder exists with that ID, the tool SHALL return a structured not-found error.
|
|
32
|
+
|
|
33
|
+
#### Scenario: Existing folder returns full detail with children
|
|
34
|
+
- **WHEN** `get_folder` is called with the ID of an existing folder
|
|
35
|
+
- **THEN** the tool returns the folder's full detail including the IDs of its immediate child folders and immediate child projects
|
|
36
|
+
|
|
37
|
+
#### Scenario: Missing folder returns not-found error
|
|
38
|
+
- **WHEN** `get_folder` is called with an ID that does not correspond to any folder
|
|
39
|
+
- **THEN** the tool returns a structured error with a not-found code
|
|
@@ -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
|