@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,30 +0,0 @@
1
- ## Why
2
-
3
- `list_folders` and `list_tags` return all items with no way to cap results or filter by status. While folders and tags are typically fewer than tasks or projects, users with large databases may have hundreds of tags (including dropped ones). Adding `limit` and optional `status` filtering completes the filtering pattern applied to `list_tasks` and `list_projects`.
4
-
5
- ## What Changes
6
-
7
- - Add `limit` parameter to `list_folders` (default 200)
8
- - Add `status` filter to `list_folders`: when provided, restrict to folders with that status (`active` or `dropped`); when omitted, return all folders
9
- - Add `limit` parameter to `list_tags` (default 200)
10
- - Add `status` filter to `list_tags`: when provided, restrict to tags with that status (`active`, `onHold`, or `dropped`); when omitted, return all tags
11
- - All filtering happens inside the OmniJS snippet
12
-
13
- ## Capabilities
14
-
15
- ### New Capabilities
16
-
17
- _(none)_
18
-
19
- ### Modified Capabilities
20
-
21
- - `folder-management`: `list_folders` gains `limit` and optional `status` filter
22
- - `tag-management`: `list_tags` gains `limit` and optional `status` filter
23
-
24
- ## Impact
25
-
26
- - `src/schemas/shapes.ts`: add `ListFoldersFilter` and `ListTagsFilter` schemas
27
- - `src/schemas/index.ts`: export new filter schemas
28
- - `src/snippets/list_folders.js`, `list_tags.js`: add filter + limit logic
29
- - `src/tools/listFolders.ts`, `listTags.ts`: add filter + limit params
30
- - Unit and integration tests for both tools
@@ -1,21 +0,0 @@
1
- ## MODIFIED Requirements
2
-
3
- ### Requirement: List folders
4
-
5
- The system SHALL provide a `list_folders` tool that returns folders in the OmniFocus database as an array of summaries, each containing `{id, name, path, parentId, status}`. The `path` field SHALL use the canonical ` ▸ ` separator and SHALL equal the folder's name for top-level folders. The `parentId` field SHALL be `null` for top-level folders. The `status` field SHALL be one of `"active" | "dropped"`. The tool SHALL accept an optional `status` filter string and an optional `limit` integer (default 200). When `status` is omitted, all folders are returned regardless of status. When `limit` is omitted, at most 200 folders are returned.
6
-
7
- #### Scenario: All folders returned by default
8
- - **WHEN** `list_folders` is called with no arguments
9
- - **THEN** the tool returns all folders (active and dropped) up to the limit
10
-
11
- #### Scenario: Top-level folders have null parent
12
- - **WHEN** a folder exists at the root of the folder hierarchy
13
- - **THEN** its summary carries `parentId: null` and `path` equal to its name
14
-
15
- #### Scenario: Filter by status returns only matching folders
16
- - **WHEN** `list_folders` is called with `{ filter: { status: "active" } }`
17
- - **THEN** only active folders are returned
18
-
19
- #### Scenario: Limit caps the number of returned folders
20
- - **WHEN** `list_folders` is called with `{ limit: 5 }`
21
- - **THEN** at most 5 folders are returned
@@ -1,21 +0,0 @@
1
- ## MODIFIED Requirements
2
-
3
- ### Requirement: List tags
4
-
5
- The system SHALL provide a `list_tags` tool that returns tags in the OmniFocus database as an array of summaries, each containing `{id, name, path, parentId, status}`. Tags form a tree in OmniFocus; the `path` field SHALL use the canonical ` ▸ ` separator and represent the full ancestor chain. The `parentId` field SHALL be `null` for top-level tags. The `status` field SHALL be one of `"active" | "onHold" | "dropped"`. The tool SHALL accept an optional `status` filter string and an optional `limit` integer (default 200). When `status` is omitted, all tags are returned regardless of status. When `limit` is omitted, at most 200 tags are returned.
6
-
7
- #### Scenario: All tags returned by default
8
- - **WHEN** `list_tags` is called with no arguments
9
- - **THEN** the tool returns all tags (active, onHold, and dropped) up to the limit
10
-
11
- #### Scenario: On-hold tag is reported with correct status
12
- - **WHEN** the database contains a tag that has been placed on hold
13
- - **THEN** that tag appears in the result with `status: "onHold"`
14
-
15
- #### Scenario: Filter by status returns only matching tags
16
- - **WHEN** `list_tags` is called with `{ filter: { status: "active" } }`
17
- - **THEN** only active tags are returned
18
-
19
- #### Scenario: Limit caps the number of returned tags
20
- - **WHEN** `list_tags` is called with `{ limit: 5 }`
21
- - **THEN** at most 5 tags are returned
@@ -1,35 +0,0 @@
1
- ## 1. Schema changes (src/schemas/)
2
-
3
- - [x] 1.1 Define `ListFoldersFilter` zod schema: `{ status: FolderStatus.optional() }` in `shapes.ts`
4
- - [x] 1.2 Define `ListTagsFilter` zod schema: `{ status: TagStatus.optional() }` in `shapes.ts`
5
- - [x] 1.3 Export `ListFoldersFilter` and `ListTagsFilter` from `src/schemas/index.ts`
6
-
7
- ## 2. Snippet updates (src/snippets/)
8
-
9
- - [x] 2.1 Update `list_folders.js`: add `filter` and `limit` args; apply status filter when `args.filter?.status` is provided; apply limit (default 200) as slice after filter
10
- - [x] 2.2 Update `list_tags.js`: add `filter` and `limit` args; apply status filter when `args.filter?.status` is provided; apply limit (default 200) as slice after filter
11
-
12
- ## 3. Tool handler updates (src/tools/)
13
-
14
- - [x] 3.1 Update `listFolders.ts`: add `filter: ListFoldersFilter.optional()` and `limit: z.number().int().positive().optional()` to schema; pass through to `runSnippet`; update description
15
- - [x] 3.2 Update `listTags.ts`: add `filter: ListTagsFilter.optional()` and `limit: z.number().int().positive().optional()` to schema; pass through to `runSnippet`; update description
16
-
17
- ## 4. Unit tests (test/unit/)
18
-
19
- - [x] 4.1 Add schema tests for `ListFoldersFilter`: valid with status "active"; valid with status "dropped"; valid empty; invalid status value rejected
20
- - [x] 4.2 Add schema tests for `ListTagsFilter`: valid with status "active"; valid with status "onHold"; valid with status "dropped"; valid empty; invalid status value rejected
21
-
22
- ## 5. Integration tests (test/integration/)
23
-
24
- - [x] 5.1 `listFoldersFiltered.int.test.ts`: verify status filter returns only folders with that status
25
- - [x] 5.2 `listFoldersFiltered.int.test.ts`: verify limit caps results
26
- - [x] 5.3 `listFoldersFiltered.int.test.ts`: verify no filter returns all folders (including dropped)
27
- - [x] 5.4 `listTagsFiltered.int.test.ts`: verify status filter returns only tags with that status
28
- - [x] 5.5 `listTagsFiltered.int.test.ts`: verify limit caps results
29
- - [x] 5.6 `listTagsFiltered.int.test.ts`: verify no filter returns all tags
30
-
31
- ## 6. Verification
32
-
33
- - [x] 6.1 `npm run typecheck` clean
34
- - [x] 6.2 `npm test` (unit suite) clean
35
- - [x] 6.3 Manually run integration suite
@@ -1,2 +0,0 @@
1
- schema: spec-driven
2
- created: 2026-04-10
@@ -1,43 +0,0 @@
1
- ## Context
2
-
3
- OmniFocus supports moving tasks between containers (projects or parent tasks) and moving projects between folders via the `moveSections` and `append`/`prepend` OmniJS APIs. Currently no MCP tool exposes this capability.
4
-
5
- The OmniJS API for moving: `moveTasks([task], destination)` where destination is a `Project` or `Task`. For projects: `moveProjects([project], folder)` where folder is a `Folder` or `null` for top-level. Direct property assignment (`task.containingProject`, `project.parentFolder`) is read-only and does not work.
6
-
7
- ## Goals / Non-Goals
8
-
9
- **Goals:**
10
- - `move_task`: move a task to a project (top-level) or make it a subtask of another task
11
- - `move_project`: move a project to a folder or to the top level
12
-
13
- **Non-Goals:**
14
- - Moving folders (rename/reparent of folders — separate concern)
15
- - Reordering within a container (OmniJS exposes position but it's complex)
16
- - Moving tags
17
-
18
- ## Decisions
19
-
20
- ### Decision 1: move_task requires exactly one destination
21
-
22
- `move_task` accepts `{ id, projectId?, parentTaskId? }`. Exactly one of `projectId` or `parentTaskId` must be provided — enforced by Zod `.refine()` at the TypeScript boundary and validated again in the snippet. Returns the updated task summary.
23
-
24
- ### Decision 2: Moving to a project places task at end of project's task list
25
-
26
- OmniJS `task.containingProject = project` places the task at the end. This is the natural default; no position parameter needed.
27
-
28
- ### Decision 3: move_project accepts folderId as string or null
29
-
30
- `null` means move to top level (no parent folder). OmniJS: `project.parentFolder = null` removes the folder assignment.
31
-
32
- ### Decision 4: Not-found errors for all ID lookups
33
-
34
- Same pattern as existing write tools: `NotFoundError` if task, project, folder, or parentTask ID is not found. Snippet throws; bridge catches and wraps as `ExecutionError`.
35
-
36
- ### Decision 5: Return updated summary, not full detail
37
-
38
- `move_task` returns a `TaskSummary`. `move_project` returns a `ProjectSummary`. Consistent with create/edit patterns.
39
-
40
- ## Risks / Trade-offs
41
-
42
- - **OmniJS assignment semantics** — setting `task.containingProject` may behave differently than `task.parentTask`. Both need integration testing to verify the task ends up in the right place.
43
- - **Inbox tasks** — moving an inbox task to a project is valid and expected to work. Moving a project task to inbox is not supported (OmniJS doesn't expose inbox assignment on tasks directly). Document this limitation.
@@ -1,25 +0,0 @@
1
- ## Why
2
-
3
- There is no way to move a task to a different project or make it a subtask, and no way to move a project to a different folder. These are common reorganization actions in OmniFocus that an LLM assistant should be able to perform on the user's behalf.
4
-
5
- ## What Changes
6
-
7
- - Add `move_task` tool: moves a task to a project (top-level) or makes it a subtask of another task
8
- - Add `move_project` tool: moves a project to a folder or to the top level
9
-
10
- ## Capabilities
11
-
12
- ### New Capabilities
13
-
14
- - `move-operations`: moving tasks between projects/parents and moving projects between folders
15
-
16
- ### Modified Capabilities
17
-
18
- _(none — new tools, no existing requirement changes)_
19
-
20
- ## Impact
21
-
22
- - `src/snippets/move_task.js`, `src/snippets/move_project.js`: new OmniJS snippets
23
- - `src/tools/moveTask.ts`, `src/tools/moveProject.ts`: new tool handlers
24
- - `src/tools/index.ts`: register new tools
25
- - `src/runtime/snippetLoader.ts`: allowlist new snippet names
@@ -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,40 +0,0 @@
1
- ## 1. Schema changes (src/schemas/)
2
-
3
- - [x] 1.1 Define `MoveTaskInput` zod schema: `{ id: IdSchema, projectId: IdSchema.optional(), parentTaskId: IdSchema.optional() }` with `.refine()` that exactly one of `projectId` or `parentTaskId` is provided
4
- - [x] 1.2 Define `MoveProjectInput` zod schema: `{ id: IdSchema, folderId: IdSchema.nullable() }`
5
- - [x] 1.3 Export `MoveTaskInput` and `MoveProjectInput` from `src/schemas/index.ts`
6
-
7
- ## 2. Snippets (src/snippets/)
8
-
9
- - [x] 2.1 Create `move_task.js`: resolve task by ID (NotFoundError if missing); if `projectId` provided, resolve project (NotFoundError if missing), assign `task.containingProject = project`; if `parentTaskId` provided, resolve parent task (NotFoundError if missing), assign `task.parentTask = parentTask`; return updated `TaskSummary` fields
10
- - [x] 2.2 Create `move_project.js`: resolve project by ID (NotFoundError if missing); if `folderId` is non-null, resolve folder (NotFoundError if missing), assign `project.parentFolder = folder`; if `folderId` is null, assign `project.parentFolder = null`; return updated `ProjectSummary` fields
11
-
12
- ## 3. Tool handlers (src/tools/)
13
-
14
- - [x] 3.1 Create `moveTask.ts`: schema uses `MoveTaskInput`, handler calls `runSnippet("move_task", input)`, parses result as `TaskSummary`, exports `moveTaskTool`
15
- - [x] 3.2 Create `moveProject.ts`: schema uses `MoveProjectInput`, handler calls `runSnippet("move_project", input)`, parses result as `ProjectSummary`, exports `moveProjectTool`
16
-
17
- ## 4. Registration
18
-
19
- - [x] 4.1 Add `move_task` and `move_project` to `ALLOWED_SNIPPETS` in `src/runtime/snippetLoader.ts`
20
- - [x] 4.2 Import and add `moveTaskTool` and `moveProjectTool` to `allTools` in `src/tools/index.ts`
21
-
22
- ## 5. Unit tests (test/unit/)
23
-
24
- - [x] 5.1 Add schema tests for `MoveTaskInput`: valid with projectId only; valid with parentTaskId only; invalid with both; invalid with neither
25
- - [x] 5.2 Add schema tests for `MoveProjectInput`: valid with folderId string; valid with folderId null; invalid with missing id
26
-
27
- ## 6. Integration tests (test/integration/)
28
-
29
- - [x] 6.1 `moveTask.int.test.ts`: create task in project A; move to project B; verify task appears in project B
30
- - [x] 6.2 `moveTask.int.test.ts`: create task; make it a subtask of another task via parentTaskId
31
- - [x] 6.3 `moveTask.int.test.ts`: non-existent task ID returns NotFoundError
32
- - [x] 6.4 `moveProject.int.test.ts`: create project in folder A; move to folder B; verify project is in folder B
33
- - [x] 6.5 `moveProject.int.test.ts`: move project to top level (folderId: null)
34
- - [x] 6.6 `moveProject.int.test.ts`: non-existent project ID returns NotFoundError
35
-
36
- ## 7. Verification
37
-
38
- - [x] 7.1 `npm run typecheck` clean
39
- - [x] 7.2 `npm test` (unit suite) clean
40
- - [x] 7.3 Manually run integration suite
@@ -1,2 +0,0 @@
1
- schema: spec-driven
2
- created: 2026-04-09
@@ -1,60 +0,0 @@
1
- ## Context
2
-
3
- Projects in OmniJS have more writable fields than tasks: type (`sequential`, `containsSingletonActions`), review interval, and folder placement. Status transitions use dedicated methods. This design follows the same patterns established in `task-crud`.
4
-
5
- OmniJS project write operations:
6
- - `new Project(name, position)` — creates a project; `position` is a `Folder` or omitted for top-level
7
- - Property assignment for scalars: `name`, `note`, `flagged`, `deferDate`, `dueDate`, `sequential`, `containsSingletonActions`
8
- - `project.status = Project.Status.Active / OnHold` — for hold/active transitions
9
- - `project.markComplete()` — dedicated method for done
10
- - `project.status = Project.Status.Dropped` — drops the project (`project.drop()` is not available via `evaluateJavascript`)
11
- - `project.reviewInterval.steps = n` — only `steps` can be mutated in-place; `Project.ReviewInterval` is a CallbackObject in the `evaluateJavascript` context and cannot be constructed with `new`, so the unit cannot be changed and the interval cannot be cleared (OmniJS rejects `null`)
12
- - Tags: `project.addTag(tag)` / `project.removeTag(tag)` — same pattern as tasks
13
- - `deleteObject(project)` — permanent deletion including all tasks
14
-
15
- ## Goals / Non-Goals
16
-
17
- **Goals:**
18
- - Expose create, edit, complete, drop, and delete for projects
19
- - Support folder placement on create (top-level or inside a folder)
20
- - Edit any subset of fields in one call including review interval and type
21
- - Replace-not-merge tag semantics (consistent with task-crud)
22
-
23
- **Non-Goals:**
24
- - Moving a project to a different folder (separate `move-operations` change)
25
- - Creating tasks inside a new project in the same call (use `create_task` after)
26
- - Recurrence rules (separate `recurrence` change)
27
-
28
- ## Decisions
29
-
30
- ### Decision 1: `type` mapped to OmniJS properties
31
-
32
- Project type is not a single OmniJS property — it is encoded across two booleans:
33
- - `sequential: true` → `"sequential"`
34
- - `containsSingletonActions: true` → `"singleActions"`
35
- - both false → `"parallel"`
36
-
37
- `create_project` and `edit_project` accept the enum string `"parallel" | "sequential" | "singleActions"` and the snippet maps it to the correct property assignments.
38
-
39
- ### Decision 2: `reviewInterval` — steps only, in-place mutation
40
-
41
- `edit_project` and `create_project` accept `reviewInterval` as `{steps: number, unit: "days" | "weeks" | "months" | "years"}`. Only the `steps` field can actually be applied: `project.reviewInterval.steps = n` mutates the existing interval in place.
42
-
43
- **Limitations discovered during implementation:** `Project.ReviewInterval` is a CallbackObject in the `evaluateJavascript` context — `new Project.ReviewInterval(steps, unit)` throws. `Project.ReviewInterval.Unit` is also undefined in this context, so the unit cannot be changed. Setting `project.reviewInterval = null` is rejected by OmniJS ("must be set to a non-null value"). As a result, the `unit` field in `reviewInterval` input is accepted by the schema but silently ignored at runtime.
44
-
45
- **Note:** Read output (`get_project`) returns the string form `"1 weeks"` — that is a read-side concern unchanged by this limitation.
46
-
47
- ### Decision 3: Status transitions via dedicated tools and property assignment
48
-
49
- `complete_project` calls `markComplete()`. `drop_project` sets `project.status = Project.Status.Dropped` — `project.drop()` does not exist in the `evaluateJavascript` context. Active/on-hold are set via `project.status = Project.Status.Active / OnHold` inside `edit_project` (these are property assignments, not method calls). This keeps the common "put on hold" operation accessible through `edit_project` without requiring a dedicated tool.
50
-
51
- ### Decision 4: `delete_project` description warns about task cascade
52
-
53
- `deleteObject(project)` removes the project and all its tasks. The tool description explicitly states this. Consistent with `delete_task` and `delete_folder` confirmation pattern.
54
-
55
- ## Risks / Trade-offs
56
-
57
- - **`new Project(name)` without a position** creates a top-level project. Verified in OmniJS.
58
- - **`new Project(name, folder)` with a Folder object** places the project inside that folder.
59
- - **Review interval unit enum values** — OmniJS `Project.ReviewInterval.Unit` has `.days`, `.weeks`, `.months`, `.years`. Snippet should map string to the enum member, not pass the string directly.
60
- - **Changing type from `singleActions`** may silently fail if the project has tasks structured for that mode — low risk for typical usage, not worth guarding against in v1.
@@ -1,29 +0,0 @@
1
- ## Why
2
-
3
- The bootstrap change delivered read-only project access. Callers can observe projects but cannot create, modify, complete, drop, or delete them — making the server unsuitable for any workflow that involves acting on projects.
4
-
5
- ## What Changes
6
-
7
- - Add `create_project` tool: create a project in a folder or at the top level
8
- - Add `edit_project` tool: modify any subset of a project's scalar fields, type, tags, and review interval in a single call
9
- - Add `complete_project` tool: mark a project done
10
- - Add `drop_project` tool: mark a project dropped
11
- - Add `delete_project` tool: permanently delete a project and all its tasks (tool description instructs the AI to confirm with the user before invoking)
12
-
13
- ## Capabilities
14
-
15
- ### New Capabilities
16
-
17
- - `project-write`: Create, edit, complete, drop, and permanently delete OmniFocus projects
18
-
19
- ### Modified Capabilities
20
-
21
- _(none — existing `project-management` read requirements are unchanged)_
22
-
23
- ## Impact
24
-
25
- - 5 new MCP tools registered in `src/server.ts`
26
- - 5 new tool handler files in `src/tools/`
27
- - 5 new OmniJS snippets in `src/snippets/`
28
- - `ALLOWED_SNIPPETS` allowlist in `src/runtime/snippetLoader.ts` must be extended
29
- - Integration tests will mutate real OmniFocus data (scoped to fixture folder per existing pattern)
@@ -1,74 +0,0 @@
1
- ## ADDED Requirements
2
-
3
- ### Requirement: Create project
4
-
5
- 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.
6
-
7
- #### Scenario: Create top-level project
8
- - **WHEN** `create_project` is called with `{name: "My Project"}` and no `folderId`
9
- - **THEN** the tool creates the project at the top level and returns its full detail record including a stable `id`
10
-
11
- #### Scenario: Create project inside a folder
12
- - **WHEN** `create_project` is called with `{name: "My Project", folderId: "abc123"}`
13
- - **THEN** the tool creates the project inside the specified folder and returns its full detail record with `folderPath` reflecting the folder
14
-
15
- #### Scenario: Create sequential project
16
- - **WHEN** `create_project` is called with `{name: "My Project", type: "sequential"}`
17
- - **THEN** the returned project detail includes `type: "sequential"`
18
-
19
- #### Scenario: Non-existent folder returns not-found error
20
- - **WHEN** `create_project` is called with a `folderId` that does not correspond to any folder
21
- - **THEN** the tool returns a structured not-found error
22
-
23
- ### Requirement: Edit project
24
-
25
- 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.
26
-
27
- #### Scenario: Put project on hold
28
- - **WHEN** `edit_project` is called with `{id: "abc123", status: "onHold"}`
29
- - **THEN** the project's status becomes `"onHold"` and all other fields are unchanged
30
-
31
- #### Scenario: Set review interval
32
- - **WHEN** `edit_project` is called with `{id: "abc123", reviewInterval: {steps: 2, unit: "weeks"}}`
33
- - **THEN** the project's review interval is updated and the returned detail reflects the change
34
-
35
-
36
- #### Scenario: Non-existent project returns not-found error
37
- - **WHEN** `edit_project` is called with an ID that does not correspond to any project
38
- - **THEN** the tool returns a structured not-found error
39
-
40
- ### Requirement: Complete project
41
-
42
- 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.
43
-
44
- #### Scenario: Complete an existing project
45
- - **WHEN** `complete_project` is called with the ID of an active project
46
- - **THEN** the project's status becomes `"done"` and the tool returns the updated detail record
47
-
48
- #### Scenario: Non-existent project returns not-found error
49
- - **WHEN** `complete_project` is called with an ID that does not correspond to any project
50
- - **THEN** the tool returns a structured not-found error
51
-
52
- ### Requirement: Drop project
53
-
54
- 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.
55
-
56
- #### Scenario: Drop an existing project
57
- - **WHEN** `drop_project` is called with the ID of an active project
58
- - **THEN** the project's status becomes `"dropped"` and the tool returns the updated detail record
59
-
60
- #### Scenario: Non-existent project returns not-found error
61
- - **WHEN** `drop_project` is called with an ID that does not correspond to any project
62
- - **THEN** the tool returns a structured not-found error
63
-
64
- ### Requirement: Delete project
65
-
66
- 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.
67
-
68
- #### Scenario: Delete an existing project
69
- - **WHEN** `delete_project` is called with the ID of an existing project
70
- - **THEN** the project and all its tasks are permanently removed from OmniFocus and the tool returns a confirmation envelope
71
-
72
- #### Scenario: Non-existent project returns not-found error
73
- - **WHEN** `delete_project` is called with an ID that does not correspond to any project
74
- - **THEN** the tool returns a structured not-found error
@@ -1,48 +0,0 @@
1
- ## 1. Snippets (src/snippets/)
2
-
3
- - [x] 1.1 `create_project.js`: accept `{name, folderId?, note?, type?, status?, flagged?, deferDate?, dueDate?, reviewInterval?, tagIds?}`; resolve folder by ID if provided; map `type` string to `sequential`/`containsSingletonActions` booleans; set `status` to `Project.Status.OnHold` if `"onHold"`; construct `new Project.ReviewInterval(steps, unit)` if `reviewInterval` provided; resolve and assign tags by ID; return full project detail envelope
4
- - [x] 1.2 `edit_project.js`: accept `{id, name?, note?, type?, status?, flagged?, deferDate?, dueDate?, reviewInterval?, tagIds?}`; resolve project by ID; apply only provided fields; map `type` and `status` to OmniJS equivalents; handle `null` for date/reviewInterval to clear; replace tag set when `tagIds` provided; return updated full detail envelope
5
- - [x] 1.3 `complete_project.js`: accept `{id}`; resolve project by ID; call `project.markComplete()`; return updated full detail envelope
6
- - [x] 1.4 `drop_project.js`: accept `{id}`; resolve project by ID; use `project.status = Project.Status.Dropped` (project.drop() is not available via evaluateJavascript); return updated full detail envelope
7
- - [x] 1.5 `delete_project.js`: accept `{id}`; resolve project by ID; call `deleteObject(project)`; return `{ok: true, data: {id}}`
8
-
9
- ## 2. Snippet allowlist
10
-
11
- - [x] 2.1 Add `create_project`, `edit_project`, `complete_project`, `drop_project`, `delete_project` to `ALLOWED_SNIPPETS` in `src/runtime/snippetLoader.ts`
12
-
13
- ## 3. Schemas (src/schemas/shapes.ts)
14
-
15
- - [x] 3.1 Define `ReviewIntervalInput` zod schema: `{steps: z.number().int().positive(), unit: z.enum(["days","weeks","months","years"])}`
16
- - [x] 3.2 Define `CreateProjectInput` zod schema with all optional fields including `reviewInterval: ReviewIntervalInput.optional()`
17
- - [x] 3.3 Define `EditProjectInput` zod schema — `id` required; date fields accept `string | null`; `reviewInterval` accepts `ReviewIntervalInput` (null not supported — OmniJS rejects null for this property)
18
-
19
- ## 4. Tool handlers (src/tools/)
20
-
21
- - [x] 4.1 `createProject.ts`: validate input with `CreateProjectInput`; invoke `runSnippet("create_project", args)`; validate result against `ProjectDetail`; return
22
- - [x] 4.2 `editProject.ts`: validate input with `EditProjectInput`; invoke `runSnippet("edit_project", args)`; validate result against `ProjectDetail`; return
23
- - [x] 4.3 `completeProject.ts`: input `{id: IdSchema}`; invoke `runSnippet("complete_project", {id})`; validate result against `ProjectDetail`; return
24
- - [x] 4.4 `dropProject.ts`: input `{id: IdSchema}`; invoke `runSnippet("drop_project", {id})`; validate result against `ProjectDetail`; return
25
- - [x] 4.5 `deleteProject.ts`: input `{id: IdSchema}`; tool description MUST state the AI should confirm with the user before invoking and note that all tasks in the project are permanently deleted; invoke `runSnippet("delete_project", {id})`; return confirmation
26
-
27
- ## 5. Server registration
28
-
29
- - [x] 5.1 Export all five new tool definitions from `src/tools/index.ts`
30
-
31
- ## 6. Unit tests (test/unit/)
32
-
33
- - [x] 6.1 `schemas.createProject.test.ts`: valid inputs pass; invalid `type` enum rejected; invalid `reviewInterval.unit` rejected; null dates accepted in edit schema
34
-
35
- ## 7. Integration tests (test/integration/)
36
-
37
- - [x] 7.1 `createProject.int.test.ts`: create top-level project; create project in folder; verify `folderPath` and `type`
38
- - [x] 7.2 `editProject.int.test.ts`: edit name; set on-hold; set review interval steps; (clear review interval not supported — OmniJS rejects null)
39
- - [x] 7.3 `completeProject.int.test.ts`: complete a project, verify status = `"done"`
40
- - [x] 7.4 `dropProject.int.test.ts`: drop a project, verify status = `"dropped"`
41
- - [x] 7.5 `deleteProject.int.test.ts`: delete a project, verify subsequent `get_project` returns not-found
42
-
43
- ## 8. Verification
44
-
45
- - [x] 8.1 `npm run typecheck` clean
46
- - [x] 8.2 `npm test` (unit suite) clean
47
- - [x] 8.3 Manually run integration suite; verify fixture cleanup
48
- - [ ] 8.4 Connect to Claude Desktop; exercise all five tools against a real database
@@ -1,2 +0,0 @@
1
- schema: spec-driven
2
- created: 2026-04-10
@@ -1,52 +0,0 @@
1
- ## Context
2
-
3
- `list_projects` currently fetches all projects and returns a flat `ProjectSummary` array with no filtering. This mirrors the pre-filtering state of `list_tasks`. The task-filtering change established the pattern: filter inside the OmniJS snippet before data crosses the JXA bridge, enrich the summary shape with fields needed for filtering and display, change the default to exclude terminal statuses.
4
-
5
- `ProjectSummary` currently omits `flagged` and the parent folder ID (it has `folderPath` string but not `folderId`). Without `folderId`, the LLM cannot cross-reference a listed project with a known folder ID, and cannot filter by folder ID from returned results.
6
-
7
- ## Goals / Non-Goals
8
-
9
- **Goals:**
10
- - Filter projects by `status[]`, `folderId` (recursive subtree), and `flagged` inside the snippet
11
- - Enrich `ProjectSummary` with `flagged` and `folderId`
12
- - Change default behavior: exclude `done` and `dropped` projects when no `status` filter is provided
13
- - Cap results with a configurable `limit` (default 100)
14
-
15
- **Non-Goals:**
16
- - Filtering by tag, due date, or review date (can be added later)
17
- - Filtering `list_folders` or `list_tags` (separate changes if needed)
18
- - Pagination / cursor-based continuation
19
-
20
- ## Decisions
21
-
22
- ### Decision 1: Same pattern as task-filtering — filter in snippet
23
-
24
- All filter logic runs inside `evaluateJavascript`. The TypeScript layer validates the schema and passes filter args through. Consistent with task-filtering; avoids any future performance concerns if project counts grow.
25
-
26
- ### Decision 2: Enrich ProjectSummary with flagged and folderId
27
-
28
- `ProjectSummary` gains:
29
- - `flagged: boolean` — needed to support the `flagged` filter and display flagged state in results
30
- - `folderId: string | null` — the direct parent folder's `id.primaryKey`, or `null` for top-level projects
31
-
32
- `folderPath` is kept as-is (useful for display). `folderId` is added alongside it as the machine-readable reference. No other detail fields are promoted to the summary.
33
-
34
- ### Decision 3: Default excludes done and dropped
35
-
36
- When `filter.status` is omitted, the snippet excludes `Project.Status.Done` and `Project.Status.Dropped`. This mirrors the task-filtering default and is the correct behavior for LLM planning queries. Done/dropped projects can be retrieved explicitly with `status: ["done"]` or `status: ["dropped"]`.
37
-
38
- ### Decision 4: folderId filter is recursive
39
-
40
- When `filter.folderId` is provided, the snippet returns projects whose parent folder chain includes the specified folder ID — i.e., direct children and all nested descendants. This is consistent with how `list_tasks` treats `scope.folderId`.
41
-
42
- Implementation: check `p.flattenedProjects` on the resolved folder rather than walking `flattenedProjects` globally and checking ancestry. More efficient and correct.
43
-
44
- ### Decision 5: limit default 100
45
-
46
- Projects are fewer than tasks; 100 is a sensible default. Callers may increase it.
47
-
48
- ## Risks / Trade-offs
49
-
50
- - **Breaking default behavior** — same risk as task-filtering. Acceptable: single consumer, new default is strictly more useful.
51
- - **folderId recursive filter uses folder.flattenedProjects** — this is the cleanest OmniJS approach. If the folder ID is invalid, return an empty array (not an error) since this is a filter, not a scope requirement. Actually: throw NotFoundError to be consistent with list_tasks scope behavior.
52
- - **flagged on ProjectSummary is a new required field** — any existing unit test fixtures for ProjectSummary will need updating (same situation as TaskSummary in task-filtering).
@@ -1,26 +0,0 @@
1
- ## Why
2
-
3
- `list_projects` currently returns every project in the database with no filtering. Users with years of OmniFocus history may have hundreds of completed (`done`) and dropped projects, making it impractical for an LLM to identify what's actually active. Applying the same filter-in-snippet pattern established by task-filtering gives the LLM a useful, scoped view of the project list.
4
-
5
- ## What Changes
6
-
7
- - `list_projects` accepts an optional `filter` object: `status` (array), `folderId` (recursive subtree), `flagged`
8
- - `list_projects` accepts an optional `limit` (default 100)
9
- - **BREAKING**: Default behavior changes — when no `status` filter is given, `done` and `dropped` projects are excluded. Pass `status: ["done"]` explicitly to retrieve completed projects.
10
- - `ProjectSummary` is enriched with `flagged` (boolean) and `folderId` (string | null — the direct parent folder's ID) so the LLM can reason about results without fetching full project detail
11
-
12
- ## Capabilities
13
-
14
- ### New Capabilities
15
- - `project-filtering`: Filter parameters and enriched summary for `list_projects`
16
-
17
- ### Modified Capabilities
18
- - `project-management`: `list_projects` tool signature changes (new filter/limit params, enriched return shape, changed default behavior)
19
-
20
- ## Impact
21
-
22
- - `src/snippets/list_projects.js` — rewritten to apply filters and return enriched fields
23
- - `src/schemas/shapes.ts` — `ProjectSummary` gains `flagged` and `folderId`; new `ListProjectsFilter` schema
24
- - `src/tools/listProjects.ts` — input schema updated with filter and limit
25
- - `test/unit/` — schema tests for new filter type
26
- - `test/integration/listProjectsFiltered.int.test.ts` — new integration tests for filter combinations