@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.
- package/dist/schemas/shapes.d.ts +3 -33
- package/dist/schemas/shapes.d.ts.map +1 -1
- package/dist/schemas/shapes.js +3 -5
- package/dist/schemas/shapes.js.map +1 -1
- package/dist/server.js +3 -5
- package/dist/server.js.map +1 -1
- package/dist/tools/createTask.d.ts +1 -11
- package/dist/tools/createTask.d.ts.map +1 -1
- package/dist/tools/editTask.d.ts +1 -11
- package/dist/tools/editTask.d.ts.map +1 -1
- package/dist/tools/index.d.ts +2 -22
- package/dist/tools/index.d.ts.map +1 -1
- package/package.json +7 -1
- package/src/snippets/edit_task.js +4 -6
- package/.claude/commands/opsx/apply.md +0 -152
- package/.claude/commands/opsx/archive.md +0 -157
- package/.claude/commands/opsx/bulk-archive.md +0 -242
- package/.claude/commands/opsx/continue.md +0 -114
- package/.claude/commands/opsx/explore.md +0 -173
- package/.claude/commands/opsx/ff.md +0 -97
- package/.claude/commands/opsx/new.md +0 -69
- package/.claude/commands/opsx/onboard.md +0 -550
- package/.claude/commands/opsx/propose.md +0 -106
- package/.claude/commands/opsx/sync.md +0 -134
- package/.claude/commands/opsx/verify.md +0 -164
- package/.claude/skills/openspec-apply-change/SKILL.md +0 -156
- package/.claude/skills/openspec-archive-change/SKILL.md +0 -114
- package/.claude/skills/openspec-bulk-archive-change/SKILL.md +0 -246
- package/.claude/skills/openspec-continue-change/SKILL.md +0 -118
- package/.claude/skills/openspec-explore/SKILL.md +0 -288
- package/.claude/skills/openspec-ff-change/SKILL.md +0 -101
- package/.claude/skills/openspec-new-change/SKILL.md +0 -74
- package/.claude/skills/openspec-onboard/SKILL.md +0 -554
- package/.claude/skills/openspec-propose/SKILL.md +0 -110
- package/.claude/skills/openspec-sync-specs/SKILL.md +0 -138
- package/.claude/skills/openspec-verify-change/SKILL.md +0 -168
- package/CONTRIBUTING.md +0 -83
- package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/.openspec.yaml +0 -2
- package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/design.md +0 -162
- package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/proposal.md +0 -49
- package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/attachments/spec.md +0 -9
- package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/batch-operations/spec.md +0 -9
- package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/database-inspection/spec.md +0 -9
- package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/execution-runtime/spec.md +0 -69
- package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/folder-management/spec.md +0 -25
- package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/forecast/spec.md +0 -9
- package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/identity-resolution/spec.md +0 -45
- package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/perspective-management/spec.md +0 -9
- package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/project-management/spec.md +0 -25
- package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/recurrence/spec.md +0 -9
- package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/settings/spec.md +0 -9
- package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/tag-management/spec.md +0 -25
- package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/task-management/spec.md +0 -29
- package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/url-automation/spec.md +0 -9
- package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/window-state/spec.md +0 -9
- package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/tasks.md +0 -84
- package/openspec/changes/archive/2026-04-09-folder-crud/.openspec.yaml +0 -2
- package/openspec/changes/archive/2026-04-09-folder-crud/design.md +0 -58
- package/openspec/changes/archive/2026-04-09-folder-crud/proposal.md +0 -28
- package/openspec/changes/archive/2026-04-09-folder-crud/specs/folder-write/spec.md +0 -45
- package/openspec/changes/archive/2026-04-09-folder-crud/tasks.md +0 -41
- package/openspec/changes/archive/2026-04-09-folder-tag-list-filtering/.openspec.yaml +0 -2
- package/openspec/changes/archive/2026-04-09-folder-tag-list-filtering/design.md +0 -38
- package/openspec/changes/archive/2026-04-09-folder-tag-list-filtering/proposal.md +0 -30
- package/openspec/changes/archive/2026-04-09-folder-tag-list-filtering/specs/folder-management/spec.md +0 -21
- package/openspec/changes/archive/2026-04-09-folder-tag-list-filtering/specs/tag-management/spec.md +0 -21
- package/openspec/changes/archive/2026-04-09-folder-tag-list-filtering/tasks.md +0 -35
- package/openspec/changes/archive/2026-04-09-move-operations/.openspec.yaml +0 -2
- package/openspec/changes/archive/2026-04-09-move-operations/design.md +0 -43
- package/openspec/changes/archive/2026-04-09-move-operations/proposal.md +0 -25
- package/openspec/changes/archive/2026-04-09-move-operations/specs/move-operations/spec.md +0 -41
- package/openspec/changes/archive/2026-04-09-move-operations/tasks.md +0 -40
- package/openspec/changes/archive/2026-04-09-project-crud/.openspec.yaml +0 -2
- package/openspec/changes/archive/2026-04-09-project-crud/design.md +0 -60
- package/openspec/changes/archive/2026-04-09-project-crud/proposal.md +0 -29
- package/openspec/changes/archive/2026-04-09-project-crud/specs/project-write/spec.md +0 -74
- package/openspec/changes/archive/2026-04-09-project-crud/tasks.md +0 -48
- package/openspec/changes/archive/2026-04-09-project-filtering/.openspec.yaml +0 -2
- package/openspec/changes/archive/2026-04-09-project-filtering/design.md +0 -52
- package/openspec/changes/archive/2026-04-09-project-filtering/proposal.md +0 -26
- package/openspec/changes/archive/2026-04-09-project-filtering/specs/project-filtering/spec.md +0 -66
- package/openspec/changes/archive/2026-04-09-project-filtering/specs/project-management/spec.md +0 -13
- package/openspec/changes/archive/2026-04-09-project-filtering/tasks.md +0 -41
- package/openspec/changes/archive/2026-04-09-tag-crud/.openspec.yaml +0 -2
- package/openspec/changes/archive/2026-04-09-tag-crud/design.md +0 -45
- package/openspec/changes/archive/2026-04-09-tag-crud/proposal.md +0 -28
- package/openspec/changes/archive/2026-04-09-tag-crud/specs/tag-write/spec.md +0 -49
- package/openspec/changes/archive/2026-04-09-tag-crud/tasks.md +0 -41
- package/openspec/changes/archive/2026-04-09-task-crud/.openspec.yaml +0 -2
- package/openspec/changes/archive/2026-04-09-task-crud/design.md +0 -62
- package/openspec/changes/archive/2026-04-09-task-crud/proposal.md +0 -29
- package/openspec/changes/archive/2026-04-09-task-crud/specs/task-management/spec.md +0 -17
- package/openspec/changes/archive/2026-04-09-task-crud/specs/task-write/spec.md +0 -89
- package/openspec/changes/archive/2026-04-09-task-crud/tasks.md +0 -55
- package/openspec/changes/archive/2026-04-09-task-filtering/.openspec.yaml +0 -2
- package/openspec/changes/archive/2026-04-09-task-filtering/design.md +0 -61
- package/openspec/changes/archive/2026-04-09-task-filtering/proposal.md +0 -26
- package/openspec/changes/archive/2026-04-09-task-filtering/specs/task-filtering/spec.md +0 -63
- package/openspec/changes/archive/2026-04-09-task-filtering/specs/task-management/spec.md +0 -17
- package/openspec/changes/archive/2026-04-09-task-filtering/tasks.md +0 -42
- package/openspec/changes/archive/2026-04-10-planned-date/.openspec.yaml +0 -2
- package/openspec/changes/archive/2026-04-10-planned-date/design.md +0 -27
- package/openspec/changes/archive/2026-04-10-planned-date/proposal.md +0 -29
- package/openspec/changes/archive/2026-04-10-planned-date/specs/task-management/spec.md +0 -29
- package/openspec/changes/archive/2026-04-10-planned-date/specs/task-write/spec.md +0 -69
- package/openspec/changes/archive/2026-04-10-planned-date/tasks.md +0 -26
- package/openspec/changes/archive/2026-04-10-task-recurrence/.openspec.yaml +0 -2
- package/openspec/changes/archive/2026-04-10-task-recurrence/design.md +0 -81
- package/openspec/changes/archive/2026-04-10-task-recurrence/proposal.md +0 -28
- package/openspec/changes/archive/2026-04-10-task-recurrence/specs/recurrence/spec.md +0 -47
- package/openspec/changes/archive/2026-04-10-task-recurrence/specs/task-management/spec.md +0 -25
- package/openspec/changes/archive/2026-04-10-task-recurrence/specs/task-write/spec.md +0 -61
- package/openspec/changes/archive/2026-04-10-task-recurrence/tasks.md +0 -39
- package/openspec/config.yaml +0 -20
- package/openspec/specs/attachments/spec.md +0 -15
- package/openspec/specs/batch-operations/spec.md +0 -15
- package/openspec/specs/database-inspection/spec.md +0 -15
- package/openspec/specs/execution-runtime/spec.md +0 -75
- package/openspec/specs/folder-management/spec.md +0 -39
- package/openspec/specs/folder-write/spec.md +0 -45
- package/openspec/specs/forecast/spec.md +0 -15
- package/openspec/specs/identity-resolution/spec.md +0 -51
- package/openspec/specs/move-operations/spec.md +0 -41
- package/openspec/specs/perspective-management/spec.md +0 -15
- package/openspec/specs/project-filtering/spec.md +0 -72
- package/openspec/specs/project-management/spec.md +0 -31
- package/openspec/specs/project-write/spec.md +0 -79
- package/openspec/specs/recurrence/spec.md +0 -51
- package/openspec/specs/settings/spec.md +0 -15
- package/openspec/specs/tag-management/spec.md +0 -39
- package/openspec/specs/tag-write/spec.md +0 -49
- package/openspec/specs/task-filtering/spec.md +0 -63
- package/openspec/specs/task-management/spec.md +0 -51
- package/openspec/specs/task-write/spec.md +0 -115
- package/openspec/specs/url-automation/spec.md +0 -15
- package/openspec/specs/window-state/spec.md +0 -15
- package/scripts/cleanup-fixtures.ts +0 -89
- package/server.json +0 -21
- package/src/runtime/bridge.ts +0 -97
- package/src/runtime/index.ts +0 -4
- package/src/runtime/jxaShim.ts +0 -55
- package/src/runtime/resultProtocol.ts +0 -62
- package/src/runtime/snippetLoader.ts +0 -79
- package/src/schemas/enums.ts +0 -32
- package/src/schemas/index.ts +0 -38
- package/src/schemas/shapes.ts +0 -267
- package/src/server.ts +0 -58
- package/src/tools/completeProject.ts +0 -21
- package/src/tools/completeTask.ts +0 -23
- package/src/tools/createFolder.ts +0 -20
- package/src/tools/createProject.ts +0 -20
- package/src/tools/createTag.ts +0 -20
- package/src/tools/createTask.ts +0 -20
- package/src/tools/deleteFolder.ts +0 -24
- package/src/tools/deleteProject.ts +0 -24
- package/src/tools/deleteTag.ts +0 -24
- package/src/tools/deleteTask.ts +0 -26
- package/src/tools/dropProject.ts +0 -21
- package/src/tools/dropTask.ts +0 -23
- package/src/tools/editFolder.ts +0 -19
- package/src/tools/editProject.ts +0 -20
- package/src/tools/editTag.ts +0 -20
- package/src/tools/editTask.ts +0 -20
- package/src/tools/getFolder.ts +0 -24
- package/src/tools/getProject.ts +0 -24
- package/src/tools/getTag.ts +0 -24
- package/src/tools/getTask.ts +0 -24
- package/src/tools/index.ts +0 -85
- package/src/tools/listFolders.ts +0 -32
- package/src/tools/listProjects.ts +0 -32
- package/src/tools/listTags.ts +0 -32
- package/src/tools/listTasks.ts +0 -56
- package/src/tools/moveProject.ts +0 -20
- package/src/tools/moveTask.ts +0 -20
- package/src/tools/resolveName.ts +0 -37
- package/test/integration/.gitkeep +0 -0
- package/test/integration/completeProject.int.test.ts +0 -25
- package/test/integration/completeTask.int.test.ts +0 -30
- package/test/integration/createFolder.int.test.ts +0 -50
- package/test/integration/createProject.int.test.ts +0 -49
- package/test/integration/createTag.int.test.ts +0 -52
- package/test/integration/createTask.int.test.ts +0 -55
- package/test/integration/deleteFolder.int.test.ts +0 -64
- package/test/integration/deleteProject.int.test.ts +0 -31
- package/test/integration/deleteTag.int.test.ts +0 -61
- package/test/integration/deleteTask.int.test.ts +0 -36
- package/test/integration/dropProject.int.test.ts +0 -24
- package/test/integration/dropTask.int.test.ts +0 -29
- package/test/integration/editFolder.int.test.ts +0 -43
- package/test/integration/editProject.int.test.ts +0 -39
- package/test/integration/editTag.int.test.ts +0 -43
- package/test/integration/editTask.int.test.ts +0 -56
- package/test/integration/fixtures.ts +0 -219
- package/test/integration/getTask.int.test.ts +0 -64
- package/test/integration/listFoldersFiltered.int.test.ts +0 -98
- package/test/integration/listProjects.int.test.ts +0 -73
- package/test/integration/listProjectsFiltered.int.test.ts +0 -96
- package/test/integration/listTagsFiltered.int.test.ts +0 -54
- package/test/integration/listTasksFiltered.int.test.ts +0 -141
- package/test/integration/moveProject.int.test.ts +0 -57
- package/test/integration/moveTask.int.test.ts +0 -61
- package/test/integration/plannedDate.int.test.ts +0 -72
- package/test/integration/preflight.ts +0 -60
- package/test/integration/resolveName.int.test.ts +0 -86
- package/test/integration/taskRecurrence.int.test.ts +0 -106
- package/test/unit/.gitkeep +0 -0
- package/test/unit/bridge.injection.test.ts +0 -66
- package/test/unit/resultProtocol.test.ts +0 -71
- package/test/unit/schemas.createFolder.test.ts +0 -38
- package/test/unit/schemas.createProject.test.ts +0 -115
- package/test/unit/schemas.createTask.test.ts +0 -87
- package/test/unit/schemas.editTag.test.ts +0 -64
- package/test/unit/schemas.folderTagFiltering.test.ts +0 -42
- package/test/unit/schemas.listProjects.test.ts +0 -44
- package/test/unit/schemas.moveOperations.test.ts +0 -60
- package/test/unit/schemas.recurrence.test.ts +0 -120
- package/test/unit/schemas.test.ts +0 -126
- package/test/unit/snippetLoader.test.ts +0 -56
- package/test/unit/tools.deleteTask.test.ts +0 -19
- package/test/unit/tools.listTasks.test.ts +0 -126
- package/tsconfig.json +0 -19
- package/vitest.config.ts +0 -8
- 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
|
package/openspec/changes/archive/2026-04-09-folder-tag-list-filtering/specs/tag-management/spec.md
DELETED
|
@@ -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,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,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,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
|