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