@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,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,2 @@
1
+ schema: spec-driven
2
+ created: 2026-04-10
@@ -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