@scardis/omnifocus-mcp 0.1.0 → 0.1.2

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 (223) hide show
  1. package/dist/schemas/shapes.d.ts +3 -33
  2. package/dist/schemas/shapes.d.ts.map +1 -1
  3. package/dist/schemas/shapes.js +3 -5
  4. package/dist/schemas/shapes.js.map +1 -1
  5. package/dist/server.js +3 -5
  6. package/dist/server.js.map +1 -1
  7. package/dist/tools/createTask.d.ts +1 -11
  8. package/dist/tools/createTask.d.ts.map +1 -1
  9. package/dist/tools/editTask.d.ts +1 -11
  10. package/dist/tools/editTask.d.ts.map +1 -1
  11. package/dist/tools/index.d.ts +2 -22
  12. package/dist/tools/index.d.ts.map +1 -1
  13. package/package.json +7 -1
  14. package/src/snippets/edit_task.js +4 -6
  15. package/.claude/commands/opsx/apply.md +0 -152
  16. package/.claude/commands/opsx/archive.md +0 -157
  17. package/.claude/commands/opsx/bulk-archive.md +0 -242
  18. package/.claude/commands/opsx/continue.md +0 -114
  19. package/.claude/commands/opsx/explore.md +0 -173
  20. package/.claude/commands/opsx/ff.md +0 -97
  21. package/.claude/commands/opsx/new.md +0 -69
  22. package/.claude/commands/opsx/onboard.md +0 -550
  23. package/.claude/commands/opsx/propose.md +0 -106
  24. package/.claude/commands/opsx/sync.md +0 -134
  25. package/.claude/commands/opsx/verify.md +0 -164
  26. package/.claude/skills/openspec-apply-change/SKILL.md +0 -156
  27. package/.claude/skills/openspec-archive-change/SKILL.md +0 -114
  28. package/.claude/skills/openspec-bulk-archive-change/SKILL.md +0 -246
  29. package/.claude/skills/openspec-continue-change/SKILL.md +0 -118
  30. package/.claude/skills/openspec-explore/SKILL.md +0 -288
  31. package/.claude/skills/openspec-ff-change/SKILL.md +0 -101
  32. package/.claude/skills/openspec-new-change/SKILL.md +0 -74
  33. package/.claude/skills/openspec-onboard/SKILL.md +0 -554
  34. package/.claude/skills/openspec-propose/SKILL.md +0 -110
  35. package/.claude/skills/openspec-sync-specs/SKILL.md +0 -138
  36. package/.claude/skills/openspec-verify-change/SKILL.md +0 -168
  37. package/CONTRIBUTING.md +0 -83
  38. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/.openspec.yaml +0 -2
  39. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/design.md +0 -162
  40. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/proposal.md +0 -49
  41. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/attachments/spec.md +0 -9
  42. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/batch-operations/spec.md +0 -9
  43. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/database-inspection/spec.md +0 -9
  44. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/execution-runtime/spec.md +0 -69
  45. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/folder-management/spec.md +0 -25
  46. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/forecast/spec.md +0 -9
  47. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/identity-resolution/spec.md +0 -45
  48. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/perspective-management/spec.md +0 -9
  49. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/project-management/spec.md +0 -25
  50. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/recurrence/spec.md +0 -9
  51. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/settings/spec.md +0 -9
  52. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/tag-management/spec.md +0 -25
  53. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/task-management/spec.md +0 -29
  54. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/url-automation/spec.md +0 -9
  55. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/window-state/spec.md +0 -9
  56. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/tasks.md +0 -84
  57. package/openspec/changes/archive/2026-04-09-folder-crud/.openspec.yaml +0 -2
  58. package/openspec/changes/archive/2026-04-09-folder-crud/design.md +0 -58
  59. package/openspec/changes/archive/2026-04-09-folder-crud/proposal.md +0 -28
  60. package/openspec/changes/archive/2026-04-09-folder-crud/specs/folder-write/spec.md +0 -45
  61. package/openspec/changes/archive/2026-04-09-folder-crud/tasks.md +0 -41
  62. package/openspec/changes/archive/2026-04-09-folder-tag-list-filtering/.openspec.yaml +0 -2
  63. package/openspec/changes/archive/2026-04-09-folder-tag-list-filtering/design.md +0 -38
  64. package/openspec/changes/archive/2026-04-09-folder-tag-list-filtering/proposal.md +0 -30
  65. package/openspec/changes/archive/2026-04-09-folder-tag-list-filtering/specs/folder-management/spec.md +0 -21
  66. package/openspec/changes/archive/2026-04-09-folder-tag-list-filtering/specs/tag-management/spec.md +0 -21
  67. package/openspec/changes/archive/2026-04-09-folder-tag-list-filtering/tasks.md +0 -35
  68. package/openspec/changes/archive/2026-04-09-move-operations/.openspec.yaml +0 -2
  69. package/openspec/changes/archive/2026-04-09-move-operations/design.md +0 -43
  70. package/openspec/changes/archive/2026-04-09-move-operations/proposal.md +0 -25
  71. package/openspec/changes/archive/2026-04-09-move-operations/specs/move-operations/spec.md +0 -41
  72. package/openspec/changes/archive/2026-04-09-move-operations/tasks.md +0 -40
  73. package/openspec/changes/archive/2026-04-09-project-crud/.openspec.yaml +0 -2
  74. package/openspec/changes/archive/2026-04-09-project-crud/design.md +0 -60
  75. package/openspec/changes/archive/2026-04-09-project-crud/proposal.md +0 -29
  76. package/openspec/changes/archive/2026-04-09-project-crud/specs/project-write/spec.md +0 -74
  77. package/openspec/changes/archive/2026-04-09-project-crud/tasks.md +0 -48
  78. package/openspec/changes/archive/2026-04-09-project-filtering/.openspec.yaml +0 -2
  79. package/openspec/changes/archive/2026-04-09-project-filtering/design.md +0 -52
  80. package/openspec/changes/archive/2026-04-09-project-filtering/proposal.md +0 -26
  81. package/openspec/changes/archive/2026-04-09-project-filtering/specs/project-filtering/spec.md +0 -66
  82. package/openspec/changes/archive/2026-04-09-project-filtering/specs/project-management/spec.md +0 -13
  83. package/openspec/changes/archive/2026-04-09-project-filtering/tasks.md +0 -41
  84. package/openspec/changes/archive/2026-04-09-tag-crud/.openspec.yaml +0 -2
  85. package/openspec/changes/archive/2026-04-09-tag-crud/design.md +0 -45
  86. package/openspec/changes/archive/2026-04-09-tag-crud/proposal.md +0 -28
  87. package/openspec/changes/archive/2026-04-09-tag-crud/specs/tag-write/spec.md +0 -49
  88. package/openspec/changes/archive/2026-04-09-tag-crud/tasks.md +0 -41
  89. package/openspec/changes/archive/2026-04-09-task-crud/.openspec.yaml +0 -2
  90. package/openspec/changes/archive/2026-04-09-task-crud/design.md +0 -62
  91. package/openspec/changes/archive/2026-04-09-task-crud/proposal.md +0 -29
  92. package/openspec/changes/archive/2026-04-09-task-crud/specs/task-management/spec.md +0 -17
  93. package/openspec/changes/archive/2026-04-09-task-crud/specs/task-write/spec.md +0 -89
  94. package/openspec/changes/archive/2026-04-09-task-crud/tasks.md +0 -55
  95. package/openspec/changes/archive/2026-04-09-task-filtering/.openspec.yaml +0 -2
  96. package/openspec/changes/archive/2026-04-09-task-filtering/design.md +0 -61
  97. package/openspec/changes/archive/2026-04-09-task-filtering/proposal.md +0 -26
  98. package/openspec/changes/archive/2026-04-09-task-filtering/specs/task-filtering/spec.md +0 -63
  99. package/openspec/changes/archive/2026-04-09-task-filtering/specs/task-management/spec.md +0 -17
  100. package/openspec/changes/archive/2026-04-09-task-filtering/tasks.md +0 -42
  101. package/openspec/changes/archive/2026-04-10-planned-date/.openspec.yaml +0 -2
  102. package/openspec/changes/archive/2026-04-10-planned-date/design.md +0 -27
  103. package/openspec/changes/archive/2026-04-10-planned-date/proposal.md +0 -29
  104. package/openspec/changes/archive/2026-04-10-planned-date/specs/task-management/spec.md +0 -29
  105. package/openspec/changes/archive/2026-04-10-planned-date/specs/task-write/spec.md +0 -69
  106. package/openspec/changes/archive/2026-04-10-planned-date/tasks.md +0 -26
  107. package/openspec/changes/archive/2026-04-10-task-recurrence/.openspec.yaml +0 -2
  108. package/openspec/changes/archive/2026-04-10-task-recurrence/design.md +0 -81
  109. package/openspec/changes/archive/2026-04-10-task-recurrence/proposal.md +0 -28
  110. package/openspec/changes/archive/2026-04-10-task-recurrence/specs/recurrence/spec.md +0 -47
  111. package/openspec/changes/archive/2026-04-10-task-recurrence/specs/task-management/spec.md +0 -25
  112. package/openspec/changes/archive/2026-04-10-task-recurrence/specs/task-write/spec.md +0 -61
  113. package/openspec/changes/archive/2026-04-10-task-recurrence/tasks.md +0 -39
  114. package/openspec/config.yaml +0 -20
  115. package/openspec/specs/attachments/spec.md +0 -15
  116. package/openspec/specs/batch-operations/spec.md +0 -15
  117. package/openspec/specs/database-inspection/spec.md +0 -15
  118. package/openspec/specs/execution-runtime/spec.md +0 -75
  119. package/openspec/specs/folder-management/spec.md +0 -39
  120. package/openspec/specs/folder-write/spec.md +0 -45
  121. package/openspec/specs/forecast/spec.md +0 -15
  122. package/openspec/specs/identity-resolution/spec.md +0 -51
  123. package/openspec/specs/move-operations/spec.md +0 -41
  124. package/openspec/specs/perspective-management/spec.md +0 -15
  125. package/openspec/specs/project-filtering/spec.md +0 -72
  126. package/openspec/specs/project-management/spec.md +0 -31
  127. package/openspec/specs/project-write/spec.md +0 -79
  128. package/openspec/specs/recurrence/spec.md +0 -51
  129. package/openspec/specs/settings/spec.md +0 -15
  130. package/openspec/specs/tag-management/spec.md +0 -39
  131. package/openspec/specs/tag-write/spec.md +0 -49
  132. package/openspec/specs/task-filtering/spec.md +0 -63
  133. package/openspec/specs/task-management/spec.md +0 -51
  134. package/openspec/specs/task-write/spec.md +0 -115
  135. package/openspec/specs/url-automation/spec.md +0 -15
  136. package/openspec/specs/window-state/spec.md +0 -15
  137. package/scripts/cleanup-fixtures.ts +0 -89
  138. package/server.json +0 -21
  139. package/src/runtime/bridge.ts +0 -97
  140. package/src/runtime/index.ts +0 -4
  141. package/src/runtime/jxaShim.ts +0 -55
  142. package/src/runtime/resultProtocol.ts +0 -62
  143. package/src/runtime/snippetLoader.ts +0 -79
  144. package/src/schemas/enums.ts +0 -32
  145. package/src/schemas/index.ts +0 -38
  146. package/src/schemas/shapes.ts +0 -267
  147. package/src/server.ts +0 -58
  148. package/src/tools/completeProject.ts +0 -21
  149. package/src/tools/completeTask.ts +0 -23
  150. package/src/tools/createFolder.ts +0 -20
  151. package/src/tools/createProject.ts +0 -20
  152. package/src/tools/createTag.ts +0 -20
  153. package/src/tools/createTask.ts +0 -20
  154. package/src/tools/deleteFolder.ts +0 -24
  155. package/src/tools/deleteProject.ts +0 -24
  156. package/src/tools/deleteTag.ts +0 -24
  157. package/src/tools/deleteTask.ts +0 -26
  158. package/src/tools/dropProject.ts +0 -21
  159. package/src/tools/dropTask.ts +0 -23
  160. package/src/tools/editFolder.ts +0 -19
  161. package/src/tools/editProject.ts +0 -20
  162. package/src/tools/editTag.ts +0 -20
  163. package/src/tools/editTask.ts +0 -20
  164. package/src/tools/getFolder.ts +0 -24
  165. package/src/tools/getProject.ts +0 -24
  166. package/src/tools/getTag.ts +0 -24
  167. package/src/tools/getTask.ts +0 -24
  168. package/src/tools/index.ts +0 -85
  169. package/src/tools/listFolders.ts +0 -32
  170. package/src/tools/listProjects.ts +0 -32
  171. package/src/tools/listTags.ts +0 -32
  172. package/src/tools/listTasks.ts +0 -56
  173. package/src/tools/moveProject.ts +0 -20
  174. package/src/tools/moveTask.ts +0 -20
  175. package/src/tools/resolveName.ts +0 -37
  176. package/test/integration/.gitkeep +0 -0
  177. package/test/integration/completeProject.int.test.ts +0 -25
  178. package/test/integration/completeTask.int.test.ts +0 -30
  179. package/test/integration/createFolder.int.test.ts +0 -50
  180. package/test/integration/createProject.int.test.ts +0 -49
  181. package/test/integration/createTag.int.test.ts +0 -52
  182. package/test/integration/createTask.int.test.ts +0 -55
  183. package/test/integration/deleteFolder.int.test.ts +0 -64
  184. package/test/integration/deleteProject.int.test.ts +0 -31
  185. package/test/integration/deleteTag.int.test.ts +0 -61
  186. package/test/integration/deleteTask.int.test.ts +0 -36
  187. package/test/integration/dropProject.int.test.ts +0 -24
  188. package/test/integration/dropTask.int.test.ts +0 -29
  189. package/test/integration/editFolder.int.test.ts +0 -43
  190. package/test/integration/editProject.int.test.ts +0 -39
  191. package/test/integration/editTag.int.test.ts +0 -43
  192. package/test/integration/editTask.int.test.ts +0 -56
  193. package/test/integration/fixtures.ts +0 -219
  194. package/test/integration/getTask.int.test.ts +0 -64
  195. package/test/integration/listFoldersFiltered.int.test.ts +0 -98
  196. package/test/integration/listProjects.int.test.ts +0 -73
  197. package/test/integration/listProjectsFiltered.int.test.ts +0 -96
  198. package/test/integration/listTagsFiltered.int.test.ts +0 -54
  199. package/test/integration/listTasksFiltered.int.test.ts +0 -141
  200. package/test/integration/moveProject.int.test.ts +0 -57
  201. package/test/integration/moveTask.int.test.ts +0 -61
  202. package/test/integration/plannedDate.int.test.ts +0 -72
  203. package/test/integration/preflight.ts +0 -60
  204. package/test/integration/resolveName.int.test.ts +0 -86
  205. package/test/integration/taskRecurrence.int.test.ts +0 -106
  206. package/test/unit/.gitkeep +0 -0
  207. package/test/unit/bridge.injection.test.ts +0 -66
  208. package/test/unit/resultProtocol.test.ts +0 -71
  209. package/test/unit/schemas.createFolder.test.ts +0 -38
  210. package/test/unit/schemas.createProject.test.ts +0 -115
  211. package/test/unit/schemas.createTask.test.ts +0 -87
  212. package/test/unit/schemas.editTag.test.ts +0 -64
  213. package/test/unit/schemas.folderTagFiltering.test.ts +0 -42
  214. package/test/unit/schemas.listProjects.test.ts +0 -44
  215. package/test/unit/schemas.moveOperations.test.ts +0 -60
  216. package/test/unit/schemas.recurrence.test.ts +0 -120
  217. package/test/unit/schemas.test.ts +0 -126
  218. package/test/unit/snippetLoader.test.ts +0 -56
  219. package/test/unit/tools.deleteTask.test.ts +0 -19
  220. package/test/unit/tools.listTasks.test.ts +0 -126
  221. package/tsconfig.json +0 -19
  222. package/vitest.config.ts +0 -8
  223. package/vitest.integration.config.ts +0 -18
@@ -1,39 +0,0 @@
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
@@ -1,20 +0,0 @@
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
@@ -1,15 +0,0 @@
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
@@ -1,15 +0,0 @@
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
@@ -1,15 +0,0 @@
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
@@ -1,75 +0,0 @@
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
@@ -1,39 +0,0 @@
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
@@ -1,45 +0,0 @@
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
@@ -1,15 +0,0 @@
1
- # forecast
2
-
3
- ## Purpose
4
-
5
- Covers OmniFocus Forecast data, including forecast days, tasks due or deferred on a given day, the configured forecast tag, and forecast badge counts. Individual tools will be defined in the `forecast-and-inspection` change.
6
-
7
- ## Requirements
8
-
9
- ### Requirement: Capability declared
10
-
11
- The `forecast` capability SHALL cover OmniFocus Forecast data, including forecast days, tasks due or deferred on a given day, the configured forecast tag, and forecast badge counts. 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 forecast tools
15
- - **THEN** it lands requirements under this capability rather than inventing a new capability name
@@ -1,51 +0,0 @@
1
- # identity-resolution
2
-
3
- ## Purpose
4
-
5
- Defines how OmniFocus entities are addressed and resolved — canonical ID addressing, name/path-based lookup with ambiguity reporting, and the folder path format.
6
-
7
- ## Requirements
8
-
9
- ### Requirement: Canonical ID addressing
10
-
11
- The system SHALL use `id.primaryKey` as the canonical identifier for every addressable OmniFocus entity (Task, Project, Folder, Tag, Perspective). Every read tool SHALL return an `id` field carrying this value. Every write tool (in this and future changes) SHALL accept an `id` parameter as its primary addressing mode.
12
-
13
- #### Scenario: Read tools return primary key
14
- - **WHEN** a caller invokes `list_projects`
15
- - **THEN** every returned element has an `id` field equal to the OmniJS expression `project.id.primaryKey` for that project
16
-
17
- #### Scenario: IDs are stable within a session
18
- - **WHEN** a caller reads a project's ID via `list_projects`, then reads the same project again via `get_project` using that ID
19
- - **THEN** the second read succeeds and returns the same project, without re-resolving by name
20
-
21
- ### Requirement: Name and path resolution with ambiguity reporting
22
-
23
- The system SHALL provide a `resolve_name` tool that accepts `{type, query, scope?}` and returns a list of candidate entities `[{id, name, path, type, ...}]`. When more than one entity matches the query, the tool SHALL return all matches and SHALL NOT silently pick a winner. The caller is responsible for disambiguating.
24
-
25
- #### Scenario: Unique match returns single candidate
26
- - **WHEN** `resolve_name` is called with `{type: "project", query: "Q4 Planning"}` and exactly one project has that name
27
- - **THEN** the tool returns a list with one element containing the project's id, name, and full folder path
28
-
29
- #### Scenario: Ambiguous match returns all candidates
30
- - **WHEN** `resolve_name` is called with `{type: "project", query: "Inbox Cleanup"}` and two projects have that name under different folders
31
- - **THEN** the tool returns a list with both candidates, each carrying a distinct id and distinct folder path
32
-
33
- #### Scenario: No match returns empty list
34
- - **WHEN** `resolve_name` is called with a query that matches no entity of the requested type
35
- - **THEN** the tool returns an empty list and does not throw an error
36
-
37
- #### Scenario: Path-qualified query narrows scope
38
- - **WHEN** `resolve_name` is called with `{type: "folder", query: "Acme", scope: "Work ▸ Clients"}`
39
- - **THEN** the tool returns only folders named "Acme" that are direct or transitive children of the folder at path "Work ▸ Clients"
40
-
41
- ### Requirement: Folder path addressing
42
-
43
- The system SHALL represent folder paths as strings using the separator ` ▸ ` (U+25B8 with surrounding spaces) and SHALL support resolving a folder by its full path as an alternative to its ID. When a path is ambiguous (the same path exists under multiple roots, which cannot occur for folders but can for tags), the resolution SHALL report the ambiguity through `resolve_name`.
44
-
45
- #### Scenario: Folder resolves by full path
46
- - **WHEN** `resolve_name` is called with `{type: "folder", query: "Work ▸ Clients ▸ Acme"}`
47
- - **THEN** the tool returns the folder whose full ancestor chain matches that path exactly
48
-
49
- #### Scenario: Returned paths use the canonical separator
50
- - **WHEN** any read tool returns a folder path or a project's containing folder path
51
- - **THEN** the path uses ` ▸ ` as the separator between ancestor names
@@ -1,41 +0,0 @@
1
- ## Requirements
2
-
3
- ### Requirement: Move task to a new container
4
-
5
- The system SHALL provide a `move_task` tool that accepts `{id: string, projectId?: string, parentTaskId?: string}` and moves the specified task to the given container. Exactly one of `projectId` or `parentTaskId` SHALL be provided; if both or neither are given, the tool SHALL return a validation error. When `projectId` is provided, the task SHALL become a top-level task in that project. When `parentTaskId` is provided, the task SHALL become a direct subtask of that task. The tool SHALL return a structured not-found error if any of the IDs do not correspond to existing objects.
6
-
7
- #### Scenario: Move task to a project
8
- - **WHEN** `move_task` is called with `{id: "t1", projectId: "p2"}`
9
- - **THEN** the task appears as a top-level task in project p2 and is no longer in its previous container
10
-
11
- #### Scenario: Make task a subtask
12
- - **WHEN** `move_task` is called with `{id: "t1", parentTaskId: "t2"}`
13
- - **THEN** the task becomes a direct child of task t2
14
-
15
- #### Scenario: Both destinations provided is a validation error
16
- - **WHEN** `move_task` is called with both `projectId` and `parentTaskId`
17
- - **THEN** the tool returns a validation error before any snippet executes
18
-
19
- #### Scenario: Non-existent task ID returns not-found error
20
- - **WHEN** `move_task` is called with an ID that does not correspond to any task
21
- - **THEN** the tool returns a structured not-found error
22
-
23
- ### Requirement: Move project to a folder
24
-
25
- The system SHALL provide a `move_project` tool that accepts `{id: string, folderId: string | null}` and moves the specified project to the given folder. When `folderId` is `null`, the project SHALL be moved to the top level (no containing folder). The tool SHALL return a structured not-found error if the project ID or folder ID does not correspond to an existing object.
26
-
27
- #### Scenario: Move project to a folder
28
- - **WHEN** `move_project` is called with `{id: "p1", folderId: "f2"}`
29
- - **THEN** the project is now contained within folder f2
30
-
31
- #### Scenario: Move project to top level
32
- - **WHEN** `move_project` is called with `{id: "p1", folderId: null}`
33
- - **THEN** the project has no parent folder
34
-
35
- #### Scenario: Non-existent project ID returns not-found error
36
- - **WHEN** `move_project` is called with a project ID that does not exist
37
- - **THEN** the tool returns a structured not-found error
38
-
39
- #### Scenario: Non-existent folder ID returns not-found error
40
- - **WHEN** `move_project` is called with a folderId that does not correspond to any folder
41
- - **THEN** the tool returns a structured not-found error
@@ -1,15 +0,0 @@
1
- # perspective-management
2
-
3
- ## Purpose
4
-
5
- Covers listing built-in and custom perspectives, reading perspective metadata, and activating a perspective in a document window. Individual tools will be defined in the `perspectives-and-windows` change.
6
-
7
- ## Requirements
8
-
9
- ### Requirement: Capability declared
10
-
11
- The `perspective-management` capability SHALL cover listing built-in and custom perspectives, reading perspective metadata, and activating a perspective in a document window. Custom perspective *creation and editing* are a non-goal pending verification of OmniJS scriptability of `Perspective.Custom` definitions; if a future investigation proves the API is available, the non-goal may be revisited by a later change. Requirements for individual tools SHALL be added by the `perspectives-and-windows` change.
12
-
13
- #### Scenario: Capability is named and scoped
14
- - **WHEN** a future change proposes adding perspective tools
15
- - **THEN** it lands requirements under this capability rather than inventing a new capability name
@@ -1,72 +0,0 @@
1
- # project-filtering
2
-
3
- ## Purpose
4
-
5
- Defines filtering and limiting behavior for the `list_projects` tool, including status, folder, and flagged filters, as well as result limits and enriched project summary fields.
6
-
7
- ## Requirements
8
-
9
- ### Requirement: Filter list_projects results
10
-
11
- The system SHALL allow `list_projects` to accept an optional `filter` object that restricts which projects are returned. All filter fields are optional and combine as AND conditions. When `filter.status` is omitted, the tool SHALL exclude projects with status `done` or `dropped` by default.
12
-
13
- Filter fields:
14
- - `status` (array of ProjectStatus): return only projects whose status is in the array; overrides the default exclusion of done/dropped
15
- - `folderId` (string): return only projects within the specified folder or any of its descendant folders; throws not-found if the folder ID does not exist
16
- - `flagged` (boolean): when `true`, return only flagged projects
17
-
18
- #### Scenario: Default excludes done and dropped projects
19
- - **WHEN** `list_projects` is called with no filter
20
- - **THEN** the tool returns only projects with status `active` or `onHold`
21
-
22
- #### Scenario: Filter by status array
23
- - **WHEN** `list_projects` is called with `{ filter: { status: ["active"] } }`
24
- - **THEN** the tool returns only active projects
25
-
26
- #### Scenario: Explicit status filter retrieves done projects
27
- - **WHEN** `list_projects` is called with `{ filter: { status: ["done"] } }`
28
- - **THEN** the tool returns completed projects (the default exclusion does not apply)
29
-
30
- #### Scenario: Filter by folderId returns projects in subtree
31
- - **WHEN** `list_projects` is called with `{ filter: { folderId: "abc123" } }`
32
- - **THEN** the tool returns only projects whose parent folder chain includes the specified folder
33
-
34
- #### Scenario: Non-existent folderId returns not-found error
35
- - **WHEN** `list_projects` is called with a `folderId` that does not correspond to any folder
36
- - **THEN** the tool returns a structured not-found error
37
-
38
- #### Scenario: Filter by flagged
39
- - **WHEN** `list_projects` is called with `{ filter: { flagged: true } }`
40
- - **THEN** the tool returns only flagged projects, excluding done and dropped projects
41
-
42
- #### Scenario: Combined filters act as AND
43
- - **WHEN** `list_projects` is called with `{ filter: { status: ["active"], flagged: true } }`
44
- - **THEN** the tool returns only projects that are both active AND flagged
45
-
46
- ### Requirement: Limit list_projects results
47
-
48
- The system SHALL allow `list_projects` to accept an optional `limit` integer that caps the number of projects returned. When `limit` is omitted, a default of 100 SHALL apply.
49
-
50
- #### Scenario: Default limit of 100
51
- - **WHEN** `list_projects` is called without a `limit` and more than 100 projects match
52
- - **THEN** the tool returns at most 100 projects
53
-
54
- #### Scenario: Custom limit
55
- - **WHEN** `list_projects` is called with `{ limit: 20 }`
56
- - **THEN** the tool returns at most 20 projects
57
-
58
- ### Requirement: Enriched project summary includes flagged and folderId
59
-
60
- The `ProjectSummary` returned by `list_projects` SHALL include `flagged` (boolean) and `folderId` (string or null) in addition to existing fields. `folderId` SHALL be the direct parent folder's `id.primaryKey`, or `null` for top-level projects.
61
-
62
- #### Scenario: Summary includes flagged
63
- - **WHEN** `list_projects` returns a flagged project
64
- - **THEN** the project summary includes `flagged: true`
65
-
66
- #### Scenario: Summary includes folderId for nested project
67
- - **WHEN** `list_projects` returns a project inside a folder
68
- - **THEN** the project summary includes `folderId` set to the parent folder's ID
69
-
70
- #### Scenario: Summary includes folderId null for top-level project
71
- - **WHEN** `list_projects` returns a top-level project with no containing folder
72
- - **THEN** the project summary includes `folderId: null`
@@ -1,31 +0,0 @@
1
- # project-management
2
-
3
- ## Purpose
4
-
5
- Defines tools for reading and listing OmniFocus projects, including full project listing and detail retrieval by ID.
6
-
7
- ## Requirements
8
-
9
- ### Requirement: List projects
10
-
11
- The system SHALL provide a `list_projects` tool that returns projects in the OmniFocus database as an array of summaries, each containing `{id, name, folderPath, folderId, status, type, flagged}`. The `status` field SHALL be one of `"active" | "onHold" | "done" | "dropped"`. The `type` field SHALL be one of `"parallel" | "sequential" | "singleActions"`. The `folderPath` field SHALL use the canonical ` ▸ ` separator and SHALL be an empty string for top-level projects. The `folderId` field SHALL be the direct parent folder's ID or `null` for top-level projects. The tool SHALL accept an optional `filter` object (see project-filtering spec) and an optional `limit` integer (default 100). When no `filter.status` is provided, projects with status `done` or `dropped` SHALL be excluded by default.
12
-
13
- #### Scenario: Active and on-hold projects returned by default
14
- - **WHEN** `list_projects` is called with no arguments
15
- - **THEN** the tool returns only projects with status `active` or `onHold`, each with id, name, folderPath, folderId, status, type, and flagged populated
16
-
17
- #### Scenario: Single Actions list is reported with correct type
18
- - **WHEN** the database contains a Single Actions list project
19
- - **THEN** that project appears in the result with `type: "singleActions"`, never as `"parallel"` or `"sequential"`
20
-
21
- ### Requirement: Get project by ID
22
-
23
- The system SHALL provide a `get_project` tool that accepts `{id: string}` and returns the full detail record of the named project, including `{id, name, note, folderPath, status, type, flagged, deferDate, dueDate, completionDate, reviewInterval, nextReviewDate, lastReviewDate, tagIds}`. If no project exists with that ID, the tool SHALL return a structured not-found error.
24
-
25
- #### Scenario: Existing project returns full detail
26
- - **WHEN** `get_project` is called with the ID of an existing project
27
- - **THEN** the tool returns the project's full detail record
28
-
29
- #### Scenario: Missing project returns not-found error
30
- - **WHEN** `get_project` is called with an ID that does not correspond to any project
31
- - **THEN** the tool returns a structured error with a not-found code
@@ -1,79 +0,0 @@
1
- # Spec: Project Write
2
-
3
- ## Purpose
4
-
5
- Provides MCP tools for creating and mutating OmniFocus projects: creating new projects, editing project properties, marking projects complete or dropped, and permanently deleting projects.
6
-
7
- ## Requirements
8
-
9
- ### Requirement: Create project
10
-
11
- The system SHALL provide a `create_project` tool that creates a new OmniFocus project and returns its full detail record. The tool SHALL accept `{name: string, folderId?: string, note?: string, type?: "parallel" | "sequential" | "singleActions", status?: "active" | "onHold", flagged?: boolean, deferDate?: string, dueDate?: string, reviewInterval?: {steps: number, unit: "days" | "weeks" | "months" | "years"}, tagIds?: string[]}`. If `folderId` is provided the project SHALL be created inside that folder; otherwise it SHALL be created at the top level.
12
-
13
- #### Scenario: Create top-level project
14
- - **WHEN** `create_project` is called with `{name: "My Project"}` and no `folderId`
15
- - **THEN** the tool creates the project at the top level and returns its full detail record including a stable `id`
16
-
17
- #### Scenario: Create project inside a folder
18
- - **WHEN** `create_project` is called with `{name: "My Project", folderId: "abc123"}`
19
- - **THEN** the tool creates the project inside the specified folder and returns its full detail record with `folderPath` reflecting the folder
20
-
21
- #### Scenario: Create sequential project
22
- - **WHEN** `create_project` is called with `{name: "My Project", type: "sequential"}`
23
- - **THEN** the returned project detail includes `type: "sequential"`
24
-
25
- #### Scenario: Non-existent folder returns not-found error
26
- - **WHEN** `create_project` is called with a `folderId` that does not correspond to any folder
27
- - **THEN** the tool returns a structured not-found error
28
-
29
- ### Requirement: Edit project
30
-
31
- The system SHALL provide an `edit_project` tool that modifies an existing project and returns its updated full detail record. The tool SHALL accept `{id: string}` plus any subset of `{name?: string, note?: string, type?: "parallel" | "sequential" | "singleActions", status?: "active" | "onHold", flagged?: boolean, deferDate?: string | null, dueDate?: string | null, reviewInterval?: {steps: number, unit: "days" | "weeks" | "months" | "years"}, tagIds?: string[]}`. Fields omitted from the call SHALL be left unchanged. When `tagIds` is provided it SHALL replace the project's entire tag set. Passing `null` for a date SHALL clear the field. When `reviewInterval` is provided, only the `steps` value is updated; the `unit` field is accepted by the schema but cannot be changed at runtime due to OmniJS API constraints in the `evaluateJavascript` context.
32
-
33
- #### Scenario: Put project on hold
34
- - **WHEN** `edit_project` is called with `{id: "abc123", status: "onHold"}`
35
- - **THEN** the project's status becomes `"onHold"` and all other fields are unchanged
36
-
37
- #### Scenario: Set review interval
38
- - **WHEN** `edit_project` is called with `{id: "abc123", reviewInterval: {steps: 2, unit: "weeks"}}`
39
- - **THEN** the project's review interval is updated and the returned detail reflects the change
40
-
41
- #### Scenario: Non-existent project returns not-found error
42
- - **WHEN** `edit_project` is called with an ID that does not correspond to any project
43
- - **THEN** the tool returns a structured not-found error
44
-
45
- ### Requirement: Complete project
46
-
47
- The system SHALL provide a `complete_project` tool that marks an existing project done using OmniJS `markComplete()` and returns the project's updated full detail record.
48
-
49
- #### Scenario: Complete an existing project
50
- - **WHEN** `complete_project` is called with the ID of an active project
51
- - **THEN** the project's status becomes `"done"` and the tool returns the updated detail record
52
-
53
- #### Scenario: Non-existent project returns not-found error
54
- - **WHEN** `complete_project` is called with an ID that does not correspond to any project
55
- - **THEN** the tool returns a structured not-found error
56
-
57
- ### Requirement: Drop project
58
-
59
- The system SHALL provide a `drop_project` tool that marks an existing project dropped using OmniJS `drop()` and returns the project's updated full detail record.
60
-
61
- #### Scenario: Drop an existing project
62
- - **WHEN** `drop_project` is called with the ID of an active project
63
- - **THEN** the project's status becomes `"dropped"` and the tool returns the updated detail record
64
-
65
- #### Scenario: Non-existent project returns not-found error
66
- - **WHEN** `drop_project` is called with an ID that does not correspond to any project
67
- - **THEN** the tool returns a structured not-found error
68
-
69
- ### Requirement: Delete project
70
-
71
- The system SHALL provide a `delete_project` tool that permanently deletes a project and all its tasks 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 removes all tasks within the project.
72
-
73
- #### Scenario: Delete an existing project
74
- - **WHEN** `delete_project` is called with the ID of an existing project
75
- - **THEN** the project and all its tasks are permanently removed from OmniFocus and the tool returns a confirmation envelope
76
-
77
- #### Scenario: Non-existent project returns not-found error
78
- - **WHEN** `delete_project` is called with an ID that does not correspond to any project
79
- - **THEN** the tool returns a structured not-found error