@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
package/openspec/changes/archive/2026-04-09-project-filtering/specs/project-filtering/spec.md
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
## ADDED Requirements
|
|
2
|
-
|
|
3
|
-
### Requirement: Filter list_projects results
|
|
4
|
-
|
|
5
|
-
The system SHALL allow `list_projects` to accept an optional `filter` object that restricts which projects are returned. All filter fields are optional and combine as AND conditions. When `filter.status` is omitted, the tool SHALL exclude projects with status `done` or `dropped` by default.
|
|
6
|
-
|
|
7
|
-
Filter fields:
|
|
8
|
-
- `status` (array of ProjectStatus): return only projects whose status is in the array; overrides the default exclusion of done/dropped
|
|
9
|
-
- `folderId` (string): return only projects within the specified folder or any of its descendant folders; throws not-found if the folder ID does not exist
|
|
10
|
-
- `flagged` (boolean): when `true`, return only flagged projects
|
|
11
|
-
|
|
12
|
-
#### Scenario: Default excludes done and dropped projects
|
|
13
|
-
- **WHEN** `list_projects` is called with no filter
|
|
14
|
-
- **THEN** the tool returns only projects with status `active` or `onHold`
|
|
15
|
-
|
|
16
|
-
#### Scenario: Filter by status array
|
|
17
|
-
- **WHEN** `list_projects` is called with `{ filter: { status: ["active"] } }`
|
|
18
|
-
- **THEN** the tool returns only active projects
|
|
19
|
-
|
|
20
|
-
#### Scenario: Explicit status filter retrieves done projects
|
|
21
|
-
- **WHEN** `list_projects` is called with `{ filter: { status: ["done"] } }`
|
|
22
|
-
- **THEN** the tool returns completed projects (the default exclusion does not apply)
|
|
23
|
-
|
|
24
|
-
#### Scenario: Filter by folderId returns projects in subtree
|
|
25
|
-
- **WHEN** `list_projects` is called with `{ filter: { folderId: "abc123" } }`
|
|
26
|
-
- **THEN** the tool returns only projects whose parent folder chain includes the specified folder
|
|
27
|
-
|
|
28
|
-
#### Scenario: Non-existent folderId returns not-found error
|
|
29
|
-
- **WHEN** `list_projects` is called with a `folderId` that does not correspond to any folder
|
|
30
|
-
- **THEN** the tool returns a structured not-found error
|
|
31
|
-
|
|
32
|
-
#### Scenario: Filter by flagged
|
|
33
|
-
- **WHEN** `list_projects` is called with `{ filter: { flagged: true } }`
|
|
34
|
-
- **THEN** the tool returns only flagged projects, excluding done and dropped projects
|
|
35
|
-
|
|
36
|
-
#### Scenario: Combined filters act as AND
|
|
37
|
-
- **WHEN** `list_projects` is called with `{ filter: { status: ["active"], flagged: true } }`
|
|
38
|
-
- **THEN** the tool returns only projects that are both active AND flagged
|
|
39
|
-
|
|
40
|
-
### Requirement: Limit list_projects results
|
|
41
|
-
|
|
42
|
-
The system SHALL allow `list_projects` to accept an optional `limit` integer that caps the number of projects returned. When `limit` is omitted, a default of 100 SHALL apply.
|
|
43
|
-
|
|
44
|
-
#### Scenario: Default limit of 100
|
|
45
|
-
- **WHEN** `list_projects` is called without a `limit` and more than 100 projects match
|
|
46
|
-
- **THEN** the tool returns at most 100 projects
|
|
47
|
-
|
|
48
|
-
#### Scenario: Custom limit
|
|
49
|
-
- **WHEN** `list_projects` is called with `{ limit: 20 }`
|
|
50
|
-
- **THEN** the tool returns at most 20 projects
|
|
51
|
-
|
|
52
|
-
### Requirement: Enriched project summary includes flagged and folderId
|
|
53
|
-
|
|
54
|
-
The `ProjectSummary` returned by `list_projects` SHALL include `flagged` (boolean) and `folderId` (string or null) in addition to existing fields. `folderId` SHALL be the direct parent folder's `id.primaryKey`, or `null` for top-level projects.
|
|
55
|
-
|
|
56
|
-
#### Scenario: Summary includes flagged
|
|
57
|
-
- **WHEN** `list_projects` returns a flagged project
|
|
58
|
-
- **THEN** the project summary includes `flagged: true`
|
|
59
|
-
|
|
60
|
-
#### Scenario: Summary includes folderId for nested project
|
|
61
|
-
- **WHEN** `list_projects` returns a project inside a folder
|
|
62
|
-
- **THEN** the project summary includes `folderId` set to the parent folder's ID
|
|
63
|
-
|
|
64
|
-
#### Scenario: Summary includes folderId null for top-level project
|
|
65
|
-
- **WHEN** `list_projects` returns a top-level project with no containing folder
|
|
66
|
-
- **THEN** the project summary includes `folderId: null`
|
package/openspec/changes/archive/2026-04-09-project-filtering/specs/project-management/spec.md
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
## MODIFIED Requirements
|
|
2
|
-
|
|
3
|
-
### Requirement: List projects
|
|
4
|
-
|
|
5
|
-
The system SHALL provide a `list_projects` tool that returns projects in the OmniFocus database as an array of summaries, each containing `{id, name, folderPath, folderId, status, type, flagged}`. The `status` field SHALL be one of `"active" | "onHold" | "done" | "dropped"`. The `type` field SHALL be one of `"parallel" | "sequential" | "singleActions"`. The `folderPath` field SHALL use the canonical ` ▸ ` separator and SHALL be an empty string for top-level projects. The `folderId` field SHALL be the direct parent folder's ID or `null` for top-level projects. The tool SHALL accept an optional `filter` object (see project-filtering spec) and an optional `limit` integer (default 100). When no `filter.status` is provided, projects with status `done` or `dropped` SHALL be excluded by default.
|
|
6
|
-
|
|
7
|
-
#### Scenario: Active and on-hold projects returned by default
|
|
8
|
-
- **WHEN** `list_projects` is called with no arguments
|
|
9
|
-
- **THEN** the tool returns only projects with status `active` or `onHold`, each with id, name, folderPath, folderId, status, type, and flagged populated
|
|
10
|
-
|
|
11
|
-
#### Scenario: Single Actions list is reported with correct type
|
|
12
|
-
- **WHEN** the database contains a Single Actions list project
|
|
13
|
-
- **THEN** that project appears in the result with `type: "singleActions"`, never as `"parallel"` or `"sequential"`
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
## 1. Schema changes (src/schemas/)
|
|
2
|
-
|
|
3
|
-
- [x] 1.1 Add `flagged: z.boolean()` and `folderId: IdSchema.nullable()` to `ProjectSummary` in `shapes.ts`
|
|
4
|
-
- [x] 1.2 Define `ListProjectsFilter` zod schema: `{ status: z.array(ProjectStatus).optional(), folderId: IdSchema.optional(), flagged: z.literal(true).optional() }`
|
|
5
|
-
- [x] 1.3 Export `ListProjectsFilter` from `src/schemas/index.ts`
|
|
6
|
-
|
|
7
|
-
## 2. Snippet rewrite (src/snippets/list_projects.js)
|
|
8
|
-
|
|
9
|
-
- [x] 2.1 Add `flagged` and `folderId` (parent folder's `id.primaryKey` or `null`) to the project mapping helper
|
|
10
|
-
- [x] 2.2 Implement default status exclusion: when `args.filter?.status` is absent, exclude `Project.Status.Done` and `Project.Status.Dropped`
|
|
11
|
-
- [x] 2.3 Implement `status` array filter: when `args.filter?.status` is provided, keep only projects whose status maps to one of the given strings
|
|
12
|
-
- [x] 2.4 Implement `folderId` filter: resolve the folder by ID (throw `NotFoundError` if not found), then use `folder.flattenedProjects` to get the matching set before applying other filters
|
|
13
|
-
- [x] 2.5 Implement `flagged` filter: when `args.filter?.flagged` is `true`, keep only flagged projects
|
|
14
|
-
- [x] 2.6 Apply `args.limit` (default 100) as a slice after all filters
|
|
15
|
-
|
|
16
|
-
## 3. Tool handler update (src/tools/listProjects.ts)
|
|
17
|
-
|
|
18
|
-
- [x] 3.1 Add `filter: ListProjectsFilter.optional()` and `limit: z.number().int().positive().optional()` to `listProjectsSchema`
|
|
19
|
-
- [x] 3.2 Pass `filter` and `limit` through to `runSnippet`
|
|
20
|
-
- [x] 3.3 Update tool description to document filter params and new default-exclusion behavior
|
|
21
|
-
|
|
22
|
-
## 4. Unit tests (test/unit/)
|
|
23
|
-
|
|
24
|
-
- [x] 4.1 Search for any `ProjectSummary` fixtures in unit tests and add `flagged` and `folderId` fields
|
|
25
|
-
- [x] 4.2 Add schema tests for `ListProjectsFilter`: valid filter passes; invalid status enum rejected; `flagged: false` rejected (must be literal `true` or absent)
|
|
26
|
-
|
|
27
|
-
## 5. Integration tests (test/integration/)
|
|
28
|
-
|
|
29
|
-
- [x] 5.1 `listProjectsFiltered.int.test.ts`: create projects with known properties; verify default excludes done/dropped projects
|
|
30
|
-
- [x] 5.2 Verify `status` filter: create a completed project; confirm absent by default and present when `status: ["done"]` is passed
|
|
31
|
-
- [x] 5.3 Verify `folderId` filter: confirm only projects in that folder's subtree are returned
|
|
32
|
-
- [x] 5.4 Verify `flagged` filter: create flagged and unflagged projects; confirm only flagged returned
|
|
33
|
-
- [x] 5.5 Verify `limit`: confirm result is capped
|
|
34
|
-
- [x] 5.6 Verify enriched summary: returned projects include correct `flagged` and `folderId` fields
|
|
35
|
-
|
|
36
|
-
## 6. Verification
|
|
37
|
-
|
|
38
|
-
- [x] 6.1 `npm run typecheck` clean
|
|
39
|
-
- [x] 6.2 `npm test` (unit suite) clean
|
|
40
|
-
- [x] 6.3 Manually run integration suite; verify fixture cleanup
|
|
41
|
-
- [ ] 6.4 Connect to Claude Desktop; exercise filter queries against a real database
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
## Context
|
|
2
|
-
|
|
3
|
-
Tags in OmniJS form a tree (parent/child nesting) and have a `status` property (`active`, `onHold`, `dropped`). Write operations are straightforward. Deleting a tag with child tags removes the entire subtree; tasks that held the tag have it automatically removed by OmniFocus.
|
|
4
|
-
|
|
5
|
-
OmniJS tag write operations:
|
|
6
|
-
- `new Tag(name)` — creates a top-level tag
|
|
7
|
-
- `new Tag(name, parentTag)` — creates a child tag nested under `parentTag`
|
|
8
|
-
- `tag.name = "..."` — rename
|
|
9
|
-
- `tag.status = Tag.Status.Active / OnHold / Dropped` — status transitions
|
|
10
|
-
- `deleteObject(tag)` — removes the tag; child tags and task associations are cleaned up by OmniFocus automatically (unlike folders/projects, no manual cascade needed)
|
|
11
|
-
|
|
12
|
-
## Goals / Non-Goals
|
|
13
|
-
|
|
14
|
-
**Goals:**
|
|
15
|
-
- Create top-level and child tags
|
|
16
|
-
- Rename tags and change their status
|
|
17
|
-
- Delete tags (OmniFocus handles the cascade automatically)
|
|
18
|
-
|
|
19
|
-
**Non-Goals:**
|
|
20
|
-
- Moving a tag to a different parent (separate `move-operations` change)
|
|
21
|
-
|
|
22
|
-
## Decisions
|
|
23
|
-
|
|
24
|
-
### Decision 1: `deleteObject(tag)` is safe without manual cascade
|
|
25
|
-
|
|
26
|
-
Unlike folders, `deleteObject(tag)` in OmniJS removes the tag, its child tags, and all task/project tag associations automatically. No recursive snippet logic is needed.
|
|
27
|
-
|
|
28
|
-
**Verification needed during implementation:** confirm child tags are removed and task associations are cleaned. If this proves incorrect, apply the folder recursive pattern.
|
|
29
|
-
|
|
30
|
-
### Decision 2: `edit_tag` exposes both `name` and `status`
|
|
31
|
-
|
|
32
|
-
Tags have a meaningful `status` (`active`, `onHold`, `dropped`) that users set intentionally. Unlike folder status (which is derived), tag status is directly writable and useful — e.g., putting a context tag on hold while travelling. Both `name` and `status` are optional fields in `edit_tag`.
|
|
33
|
-
|
|
34
|
-
### Decision 3: `create_tag` placement via optional `parentTagId`
|
|
35
|
-
|
|
36
|
-
- `parentTagId` omitted → top-level tag
|
|
37
|
-
- `parentTagId` provided → child tag nested under that tag
|
|
38
|
-
|
|
39
|
-
Same pattern as `create_folder` with `parentFolderId`.
|
|
40
|
-
|
|
41
|
-
## Risks / Trade-offs
|
|
42
|
-
|
|
43
|
-
- **Deleting a tag with many children** — automatic cascade by OmniFocus; no performance concern beyond normal `deleteObject` overhead.
|
|
44
|
-
- **`tag.status` enum values** — OmniJS uses `Tag.Status.Active`, `Tag.Status.OnHold`, `Tag.Status.Dropped`. Snippet must map input strings to the correct enum members.
|
|
45
|
-
- **Renaming a tag used in many tasks** — OmniFocus updates all associations automatically (tags are objects with stable IDs, not strings).
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
## Why
|
|
2
|
-
|
|
3
|
-
The bootstrap change delivered read-only tag access. Callers can read the tag hierarchy but cannot create new tags, rename existing ones, or delete them — blocking workflows that involve tagging tasks with tags that don't yet exist or maintaining the tag taxonomy.
|
|
4
|
-
|
|
5
|
-
## What Changes
|
|
6
|
-
|
|
7
|
-
- Add `create_tag` tool: create a top-level tag or a child tag nested under an existing tag
|
|
8
|
-
- Add `edit_tag` tool: rename a tag
|
|
9
|
-
- Add `delete_tag` tool: permanently delete a tag; tasks that held the tag have it removed (tool description instructs the AI to confirm with the user before invoking)
|
|
10
|
-
|
|
11
|
-
## Capabilities
|
|
12
|
-
|
|
13
|
-
### New Capabilities
|
|
14
|
-
|
|
15
|
-
- `tag-write`: Create, rename, and permanently delete OmniFocus tags
|
|
16
|
-
|
|
17
|
-
### Modified Capabilities
|
|
18
|
-
|
|
19
|
-
_(none — existing `tag-management` read requirements are unchanged)_
|
|
20
|
-
|
|
21
|
-
## Impact
|
|
22
|
-
|
|
23
|
-
- 3 new MCP tools registered in `src/server.ts`
|
|
24
|
-
- 3 new tool handler files in `src/tools/`
|
|
25
|
-
- 3 new OmniJS snippets in `src/snippets/`
|
|
26
|
-
- `ALLOWED_SNIPPETS` allowlist in `src/runtime/snippetLoader.ts` must be extended
|
|
27
|
-
- Deleting a tag with child tags also removes all descendants; tool description should note this
|
|
28
|
-
- Integration tests will mutate real OmniFocus data (scoped to fixture folder per existing pattern)
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
## ADDED Requirements
|
|
2
|
-
|
|
3
|
-
### Requirement: Create tag
|
|
4
|
-
|
|
5
|
-
The system SHALL provide a `create_tag` tool that creates a new OmniFocus tag and returns its full detail record. The tool SHALL accept `{name: string, parentTagId?: string}`. If `parentTagId` is provided the tag SHALL be created nested under that tag; otherwise it SHALL be created at the top level.
|
|
6
|
-
|
|
7
|
-
#### Scenario: Create top-level tag
|
|
8
|
-
- **WHEN** `create_tag` is called with `{name: "Waiting"}` and no `parentTagId`
|
|
9
|
-
- **THEN** the tool creates the tag at the top level and returns its full detail record including a stable `id`
|
|
10
|
-
|
|
11
|
-
#### Scenario: Create child tag
|
|
12
|
-
- **WHEN** `create_tag` is called with `{name: "Email", parentTagId: "abc123"}`
|
|
13
|
-
- **THEN** the tool creates the tag nested under the specified parent tag and returns its full detail record with `path` and `parentId` set correctly
|
|
14
|
-
|
|
15
|
-
#### Scenario: Non-existent parent tag returns not-found error
|
|
16
|
-
- **WHEN** `create_tag` is called with a `parentTagId` that does not correspond to any tag
|
|
17
|
-
- **THEN** the tool returns a structured not-found error
|
|
18
|
-
|
|
19
|
-
### Requirement: Edit tag
|
|
20
|
-
|
|
21
|
-
The system SHALL provide an `edit_tag` tool that modifies an existing tag and returns its updated full detail record. The tool SHALL accept `{id: string}` plus any subset of `{name?: string, status?: "active" | "onHold" | "dropped"}`. Fields omitted SHALL be left unchanged.
|
|
22
|
-
|
|
23
|
-
#### Scenario: Rename a tag
|
|
24
|
-
- **WHEN** `edit_tag` is called with `{id: "abc123", name: "Delegated"}`
|
|
25
|
-
- **THEN** the tag's name is updated and the tool returns the updated detail record
|
|
26
|
-
|
|
27
|
-
#### Scenario: Put tag on hold
|
|
28
|
-
- **WHEN** `edit_tag` is called with `{id: "abc123", status: "onHold"}`
|
|
29
|
-
- **THEN** the tag's status becomes `"onHold"` and the tool returns the updated detail record
|
|
30
|
-
|
|
31
|
-
#### Scenario: Non-existent tag returns not-found error
|
|
32
|
-
- **WHEN** `edit_tag` is called with an ID that does not correspond to any tag
|
|
33
|
-
- **THEN** the tool returns a structured not-found error
|
|
34
|
-
|
|
35
|
-
### Requirement: Delete tag
|
|
36
|
-
|
|
37
|
-
The system SHALL provide a `delete_tag` tool that permanently deletes a tag and all its child tags using OmniJS `deleteObject()`. Tasks and projects that held the tag have it removed automatically by OmniFocus. The tool description SHALL instruct the AI to confirm with the user before invoking, noting that child tags are also deleted.
|
|
38
|
-
|
|
39
|
-
#### Scenario: Delete a tag
|
|
40
|
-
- **WHEN** `delete_tag` is called with the ID of an existing tag
|
|
41
|
-
- **THEN** the tag is permanently removed from OmniFocus, all tasks that held it have it removed, and the tool returns a confirmation envelope
|
|
42
|
-
|
|
43
|
-
#### Scenario: Delete tag with children removes entire subtree
|
|
44
|
-
- **WHEN** `delete_tag` is called with the ID of a tag that has child tags
|
|
45
|
-
- **THEN** the tag and all its descendant tags are deleted
|
|
46
|
-
|
|
47
|
-
#### Scenario: Non-existent tag returns not-found error
|
|
48
|
-
- **WHEN** `delete_tag` is called with an ID that does not correspond to any tag
|
|
49
|
-
- **THEN** the tool returns a structured not-found error
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
## 1. Snippets (src/snippets/)
|
|
2
|
-
|
|
3
|
-
- [x] 1.1 `create_tag.js`: accept `{name, parentTagId?}`; if `parentTagId` provided resolve parent tag by ID and throw `NotFoundError` if missing; create `new Tag(name)` or `new Tag(name, parentTag)`; return full tag detail envelope
|
|
4
|
-
- [x] 1.2 `edit_tag.js`: accept `{id, name?, status?}`; resolve tag by ID; apply only provided fields; map `status` string to `Tag.Status` enum member; return updated full tag detail envelope
|
|
5
|
-
- [x] 1.3 `delete_tag.js`: accept `{id}`; resolve tag by ID; call `deleteObject(tag)` (OmniFocus handles child tag and association cleanup automatically); return `{ok: true, data: {id}}`
|
|
6
|
-
|
|
7
|
-
## 2. Snippet allowlist
|
|
8
|
-
|
|
9
|
-
- [x] 2.1 Add `create_tag`, `edit_tag`, `delete_tag` to `ALLOWED_SNIPPETS` in `src/runtime/snippetLoader.ts`
|
|
10
|
-
|
|
11
|
-
## 3. Schemas (src/schemas/shapes.ts)
|
|
12
|
-
|
|
13
|
-
- [x] 3.1 Define `CreateTagInput` zod schema: `{name: z.string().min(1), parentTagId: IdSchema.optional()}`
|
|
14
|
-
- [x] 3.2 Define `EditTagInput` zod schema: `{id: IdSchema, name: z.string().min(1).optional(), status: TagStatus.optional()}`; add `.refine()` requiring at least one of `name` or `status`
|
|
15
|
-
|
|
16
|
-
## 4. Tool handlers (src/tools/)
|
|
17
|
-
|
|
18
|
-
- [x] 4.1 `createTag.ts`: validate input with `CreateTagInput`; invoke `runSnippet("create_tag", args)`; validate result against `TagDetail`; return
|
|
19
|
-
- [x] 4.2 `editTag.ts`: validate input with `EditTagInput`; invoke `runSnippet("edit_tag", args)`; validate result against `TagDetail`; return
|
|
20
|
-
- [x] 4.3 `deleteTag.ts`: input `{id: IdSchema}`; tool description MUST state the AI should confirm with the user before invoking and note that child tags are also permanently deleted; invoke `runSnippet("delete_tag", {id})`; return confirmation
|
|
21
|
-
|
|
22
|
-
## 5. Server registration
|
|
23
|
-
|
|
24
|
-
- [x] 5.1 Export all three new tool definitions from `src/tools/index.ts`
|
|
25
|
-
|
|
26
|
-
## 6. Unit tests (test/unit/)
|
|
27
|
-
|
|
28
|
-
- [x] 6.1 `schemas.editTag.test.ts`: valid inputs pass; empty object (neither name nor status) rejected by refine; invalid status enum rejected
|
|
29
|
-
|
|
30
|
-
## 7. Integration tests (test/integration/)
|
|
31
|
-
|
|
32
|
-
- [x] 7.1 `createTag.int.test.ts`: create top-level tag; create child tag; verify `path`, `parentId`, `childTagIds`
|
|
33
|
-
- [x] 7.2 `editTag.int.test.ts`: rename a tag; put tag on hold; verify status and name
|
|
34
|
-
- [x] 7.3 `deleteTag.int.test.ts`: delete a tag; verify subsequent `get_tag` returns not-found; verify child tags also gone
|
|
35
|
-
|
|
36
|
-
## 8. Verification
|
|
37
|
-
|
|
38
|
-
- [x] 8.1 `npm run typecheck` clean
|
|
39
|
-
- [x] 8.2 `npm test` (unit suite) clean
|
|
40
|
-
- [x] 8.3 Manually run integration suite; verify fixture cleanup
|
|
41
|
-
- [ ] 8.4 Connect to Claude Desktop; exercise all three tools against a real database
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
## Context
|
|
2
|
-
|
|
3
|
-
The bootstrap change established the JXA→OmniJS bridge, snippet pattern, and result protocol. All five write tools follow those same patterns. The key question for writes is: what does OmniJS expose for creating and mutating tasks, and what are the edge cases?
|
|
4
|
-
|
|
5
|
-
OmniJS task write operations:
|
|
6
|
-
- `new Task(name, position)` — creates a task; `position` is a `Project` or `Task` (for subtasks)
|
|
7
|
-
- `task.name = "..."` — property assignment for most scalar fields
|
|
8
|
-
- `task.markComplete()` — dedicated method (not property assignment)
|
|
9
|
-
- `task.drop()` — dedicated method
|
|
10
|
-
- `deleteObject(task)` — permanent deletion
|
|
11
|
-
- Tags: `task.addTag(tag)` / `task.removeTag(tag)` — manipulate by Tag object, not ID
|
|
12
|
-
|
|
13
|
-
## Goals / Non-Goals
|
|
14
|
-
|
|
15
|
-
**Goals:**
|
|
16
|
-
- Expose create, edit, complete, drop, and delete for tasks
|
|
17
|
-
- Support inbox tasks (no project), project tasks, and subtasks
|
|
18
|
-
- Edit any subset of fields in one call (fat edit — no single-field tools)
|
|
19
|
-
- Permanently delete with a tool description that instructs the AI to confirm before invoking
|
|
20
|
-
|
|
21
|
-
**Non-Goals:**
|
|
22
|
-
- Moving tasks between projects/containers (separate `move-operations` change)
|
|
23
|
-
- Recurrence rules (separate `recurrence` change)
|
|
24
|
-
- Batch operations (separate change)
|
|
25
|
-
|
|
26
|
-
## Decisions
|
|
27
|
-
|
|
28
|
-
### Decision 1: Tag manipulation by ID, resolved inside the snippet
|
|
29
|
-
|
|
30
|
-
Tags are assigned via `task.addTag(tagObject)` / `task.removeTag(tagObject)`, which require a live `Tag` object. The snippet will receive tag IDs and resolve them via `flattenedTags.find(t => t.id.primaryKey === id)` inline. This keeps the bridge contract clean (IDs only) without requiring a separate resolution round-trip.
|
|
31
|
-
|
|
32
|
-
**Alternative considered:** Accept tag names. Rejected — names are ambiguous; IDs are stable.
|
|
33
|
-
|
|
34
|
-
### Decision 2: `edit_task` replaces tags, not merges
|
|
35
|
-
|
|
36
|
-
When `tagIds` is provided to `edit_task`, the snippet replaces the task's full tag set (remove all, add new). When `tagIds` is omitted, tags are untouched. This is the least surprising contract: callers who want to add one tag still need to pass the full desired set, but they can read it from `get_task` first.
|
|
37
|
-
|
|
38
|
-
**Alternative considered:** Separate `add_tags` / `remove_tags` fields. Rejected — more surface area with no material benefit given that `get_task` is cheap.
|
|
39
|
-
|
|
40
|
-
### Decision 3: `create_task` placement via optional `projectId` and `parentTaskId`
|
|
41
|
-
|
|
42
|
-
- Both omitted → inbox
|
|
43
|
-
- `projectId` only → task added to project root
|
|
44
|
-
- `parentTaskId` only → subtask; inherits project from parent
|
|
45
|
-
- Both provided → error (ambiguous placement; caller must choose)
|
|
46
|
-
|
|
47
|
-
This mirrors how OmniFocus itself treats task placement.
|
|
48
|
-
|
|
49
|
-
### Decision 4: `complete_task` and `drop_task` as separate tools from `edit_task`
|
|
50
|
-
|
|
51
|
-
Status transitions in OmniJS require method calls (`markComplete()`, `drop()`), not property assignment. Exposing them as dedicated tools makes the intent unambiguous and avoids a complex status-to-method dispatch inside `edit_task`. It also makes tool descriptions cleaner for the LLM.
|
|
52
|
-
|
|
53
|
-
### Decision 5: `delete_task` tool description as the confirmation guardrail
|
|
54
|
-
|
|
55
|
-
The server executes `deleteObject(task)` unconditionally when called. The confirmation requirement is enforced through the MCP tool description, which instructs the AI to ask the user explicitly before invoking this tool. This is consistent with how MCP tool descriptions guide AI behavior.
|
|
56
|
-
|
|
57
|
-
## Risks / Trade-offs
|
|
58
|
-
|
|
59
|
-
- **Partial edit with bad field value** → OmniJS will throw; the error envelope propagates back as an `ExecutionError`. No partial writes occur because OmniJS is synchronous within `evaluateJavascript`.
|
|
60
|
-
- **Tag ID not found during edit** → Snippet should throw a descriptive `NotFoundError` for the missing tag ID, not silently skip it.
|
|
61
|
-
- **Inbox placement** → `new Task(name, inbox)` requires passing the `inbox` object. In OmniJS the inbox is accessible as `inbox` (bare global). Verified pattern from the read-side exploration.
|
|
62
|
-
- **deleteObject on a task with subtasks** → OmniJS deletes the task and all subtasks. Tool description should note this.
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
## Why
|
|
2
|
-
|
|
3
|
-
The bootstrap change delivered read-only task access. Callers can observe tasks but cannot create, modify, complete, drop, or delete them — making the server unsuitable for any workflow that involves acting on tasks, not just reading them.
|
|
4
|
-
|
|
5
|
-
## What Changes
|
|
6
|
-
|
|
7
|
-
- Add `create_task` tool: create a task in a project, as a subtask, or in the inbox
|
|
8
|
-
- Add `edit_task` tool: modify any subset of a task's scalar fields and tag assignments in a single call
|
|
9
|
-
- Add `complete_task` tool: mark a task complete
|
|
10
|
-
- Add `drop_task` tool: mark a task dropped
|
|
11
|
-
- Add `delete_task` tool: permanently delete a task (tool description instructs the AI to confirm with the user before invoking)
|
|
12
|
-
|
|
13
|
-
## Capabilities
|
|
14
|
-
|
|
15
|
-
### New Capabilities
|
|
16
|
-
|
|
17
|
-
- `task-write`: Create, edit, complete, drop, and permanently delete OmniFocus tasks
|
|
18
|
-
|
|
19
|
-
### Modified Capabilities
|
|
20
|
-
|
|
21
|
-
- `task-management`: Extend existing spec to add write requirements alongside the existing read requirements
|
|
22
|
-
|
|
23
|
-
## Impact
|
|
24
|
-
|
|
25
|
-
- 5 new MCP tools registered in `src/server.ts`
|
|
26
|
-
- 5 new tool handler files in `src/tools/`
|
|
27
|
-
- 5 new OmniJS snippets in `src/snippets/`
|
|
28
|
-
- `ALLOWED_SNIPPETS` allowlist in `src/runtime/snippetLoader.ts` must be extended
|
|
29
|
-
- Integration tests will mutate real OmniFocus data (scoped to fixture folder per existing pattern)
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
## MODIFIED Requirements
|
|
2
|
-
|
|
3
|
-
### Requirement: Get task by ID
|
|
4
|
-
|
|
5
|
-
The system SHALL provide a `get_task` tool that accepts `{id: string}` and returns the full detail record of the named task, including `{id, name, note, status, flagged, deferDate, dueDate, completionDate, estimatedMinutes, containerId, containerType, tagIds, parentTaskId}`. The `parentTaskId` field SHALL be `null` for top-level tasks and SHALL contain the parent task's `id.primaryKey` for subtasks. If no task exists with that ID, the tool SHALL return a structured not-found error.
|
|
6
|
-
|
|
7
|
-
#### Scenario: Existing task returns full detail
|
|
8
|
-
- **WHEN** `get_task` is called with the ID of an existing task
|
|
9
|
-
- **THEN** the tool returns the task's full detail record including all scalar fields, the list of tag IDs assigned to it, and `parentTaskId`
|
|
10
|
-
|
|
11
|
-
#### Scenario: Subtask includes parentTaskId
|
|
12
|
-
- **WHEN** `get_task` is called with the ID of a subtask
|
|
13
|
-
- **THEN** the returned record includes `parentTaskId` set to the parent task's stable ID
|
|
14
|
-
|
|
15
|
-
#### Scenario: Missing task returns not-found error
|
|
16
|
-
- **WHEN** `get_task` is called with an ID that does not correspond to any task
|
|
17
|
-
- **THEN** the tool returns a structured error with a not-found code and does not throw an unhandled exception
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
## ADDED Requirements
|
|
2
|
-
|
|
3
|
-
### Requirement: Create task
|
|
4
|
-
|
|
5
|
-
The system SHALL provide a `create_task` tool that creates a new OmniFocus task and returns its full detail record. The tool SHALL accept `{name: string, note?: string, flagged?: boolean, deferDate?: string, dueDate?: string, estimatedMinutes?: number, projectId?: string, parentTaskId?: string, tagIds?: string[]}`. Placement SHALL be determined as follows: if both `projectId` and `parentTaskId` are provided the tool SHALL return an error; if only `projectId` is provided the task is placed at the project root; if only `parentTaskId` is provided the task is created as a subtask inheriting its parent's project; if neither is provided the task is placed in the inbox.
|
|
6
|
-
|
|
7
|
-
#### Scenario: Create inbox task
|
|
8
|
-
- **WHEN** `create_task` is called with `{name: "Buy milk"}` and no `projectId` or `parentTaskId`
|
|
9
|
-
- **THEN** the tool creates the task in the OmniFocus inbox and returns its full detail record including a stable `id`
|
|
10
|
-
|
|
11
|
-
#### Scenario: Create task in a project
|
|
12
|
-
- **WHEN** `create_task` is called with `{name: "Write tests", projectId: "abc123"}`
|
|
13
|
-
- **THEN** the tool creates the task at the root of the specified project and returns its full detail record
|
|
14
|
-
|
|
15
|
-
#### Scenario: Create subtask
|
|
16
|
-
- **WHEN** `create_task` is called with `{name: "Review PR", parentTaskId: "xyz789"}`
|
|
17
|
-
- **THEN** the tool creates the task as a child of the specified parent task and returns its full detail record
|
|
18
|
-
|
|
19
|
-
#### Scenario: Ambiguous placement is rejected
|
|
20
|
-
- **WHEN** `create_task` is called with both `projectId` and `parentTaskId`
|
|
21
|
-
- **THEN** the tool returns a validation error before any snippet executes
|
|
22
|
-
|
|
23
|
-
#### Scenario: Non-existent project returns not-found error
|
|
24
|
-
- **WHEN** `create_task` is called with a `projectId` that does not correspond to any project
|
|
25
|
-
- **THEN** the tool returns a structured not-found error
|
|
26
|
-
|
|
27
|
-
### Requirement: Edit task
|
|
28
|
-
|
|
29
|
-
The system SHALL provide an `edit_task` tool that modifies an existing task and returns its updated full detail record. The tool SHALL accept `{id: string}` plus any subset of `{name?: string, note?: string, flagged?: boolean, deferDate?: string | null, dueDate?: string | null, estimatedMinutes?: number | null, tagIds?: string[]}`. Fields omitted from the call SHALL be left unchanged. When `tagIds` is provided it SHALL replace the task's entire tag set; when omitted, tags SHALL be unchanged. Passing `null` for a date or `estimatedMinutes` SHALL clear the field.
|
|
30
|
-
|
|
31
|
-
#### Scenario: Edit a single field
|
|
32
|
-
- **WHEN** `edit_task` is called with `{id: "abc123", flagged: true}`
|
|
33
|
-
- **THEN** only the `flagged` field is changed; all other fields retain their previous values
|
|
34
|
-
|
|
35
|
-
#### Scenario: Replace tag set
|
|
36
|
-
- **WHEN** `edit_task` is called with `{id: "abc123", tagIds: ["t1", "t2"]}`
|
|
37
|
-
- **THEN** the task's tags are set to exactly `["t1", "t2"]`, replacing any previously assigned tags
|
|
38
|
-
|
|
39
|
-
#### Scenario: Clear a date field
|
|
40
|
-
- **WHEN** `edit_task` is called with `{id: "abc123", dueDate: null}`
|
|
41
|
-
- **THEN** the task's due date is cleared
|
|
42
|
-
|
|
43
|
-
#### Scenario: Non-existent task returns not-found error
|
|
44
|
-
- **WHEN** `edit_task` is called with an ID that does not correspond to any task
|
|
45
|
-
- **THEN** the tool returns a structured not-found error
|
|
46
|
-
|
|
47
|
-
#### Scenario: Non-existent tag ID returns not-found error
|
|
48
|
-
- **WHEN** `edit_task` is called with a `tagIds` array containing an ID that does not correspond to any tag
|
|
49
|
-
- **THEN** the tool returns a structured not-found error and the task is not modified
|
|
50
|
-
|
|
51
|
-
### Requirement: Complete task
|
|
52
|
-
|
|
53
|
-
The system SHALL provide a `complete_task` tool that marks an existing task complete using OmniJS `markComplete()` and returns the task's updated full detail record.
|
|
54
|
-
|
|
55
|
-
#### Scenario: Complete an existing task
|
|
56
|
-
- **WHEN** `complete_task` is called with the ID of an available task
|
|
57
|
-
- **THEN** the task's status becomes `"complete"` and the tool returns the updated detail record
|
|
58
|
-
|
|
59
|
-
#### Scenario: Non-existent task returns not-found error
|
|
60
|
-
- **WHEN** `complete_task` is called with an ID that does not correspond to any task
|
|
61
|
-
- **THEN** the tool returns a structured not-found error
|
|
62
|
-
|
|
63
|
-
### Requirement: Drop task
|
|
64
|
-
|
|
65
|
-
The system SHALL provide a `drop_task` tool that marks an existing task dropped using OmniJS `drop()` and returns the task's updated full detail record.
|
|
66
|
-
|
|
67
|
-
#### Scenario: Drop an existing task
|
|
68
|
-
- **WHEN** `drop_task` is called with the ID of an available task
|
|
69
|
-
- **THEN** the task's status becomes `"dropped"` and the tool returns the updated detail record
|
|
70
|
-
|
|
71
|
-
#### Scenario: Non-existent task returns not-found error
|
|
72
|
-
- **WHEN** `drop_task` is called with an ID that does not correspond to any task
|
|
73
|
-
- **THEN** the tool returns a structured not-found error
|
|
74
|
-
|
|
75
|
-
### Requirement: Delete task
|
|
76
|
-
|
|
77
|
-
The system SHALL provide a `delete_task` tool that permanently deletes a task and all its subtasks using OmniJS `deleteObject()`. The tool description SHALL instruct the AI to confirm with the user before invoking this tool, noting that deletion is permanent and includes all subtasks.
|
|
78
|
-
|
|
79
|
-
#### Scenario: Delete an existing task
|
|
80
|
-
- **WHEN** `delete_task` is called with the ID of an existing task
|
|
81
|
-
- **THEN** the task and all its subtasks are permanently removed from OmniFocus and the tool returns a confirmation envelope
|
|
82
|
-
|
|
83
|
-
#### Scenario: Delete task with subtasks removes all children
|
|
84
|
-
- **WHEN** `delete_task` is called with the ID of a task that has subtasks
|
|
85
|
-
- **THEN** the task and all descendant subtasks are deleted
|
|
86
|
-
|
|
87
|
-
#### Scenario: Non-existent task returns not-found error
|
|
88
|
-
- **WHEN** `delete_task` is called with an ID that does not correspond to any task
|
|
89
|
-
- **THEN** the tool returns a structured not-found error
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
## 1. Snippets (src/snippets/)
|
|
2
|
-
|
|
3
|
-
- [x] 1.1 `create_task.js`: accept `{name, note?, flagged?, deferDate?, dueDate?, estimatedMinutes?, projectId?, parentTaskId?, tagIds?}`; resolve placement (inbox / project root / subtask); throw `ConflictError` if both `projectId` and `parentTaskId` provided; throw `NotFoundError` on missing project, parent task, or tag ID; return full task detail envelope
|
|
4
|
-
- [x] 1.2 `edit_task.js`: accept `{id, name?, note?, flagged?, deferDate?, dueDate?, estimatedMinutes?, tagIds?}`; resolve task by ID; apply only provided fields; when `tagIds` present replace full tag set (remove all, add each by ID); pass `null` dates/estimatedMinutes to clear; return updated full detail envelope
|
|
5
|
-
- [x] 1.3 `complete_task.js`: accept `{id}`; resolve task by ID; call `task.markComplete()`; return updated full detail envelope
|
|
6
|
-
- [x] 1.4 `drop_task.js`: accept `{id}`; resolve task by ID; call `task.drop()`; return updated full detail envelope
|
|
7
|
-
- [x] 1.5 `delete_task.js`: accept `{id}`; resolve task by ID; call `deleteObject(task)`; return `{ok: true, data: {id}}`
|
|
8
|
-
|
|
9
|
-
## 2. Snippet allowlist
|
|
10
|
-
|
|
11
|
-
- [x] 2.1 Add `create_task`, `edit_task`, `complete_task`, `drop_task`, `delete_task` to `ALLOWED_SNIPPETS` in `src/runtime/snippetLoader.ts`
|
|
12
|
-
|
|
13
|
-
## 3. Schemas (src/schemas/shapes.ts)
|
|
14
|
-
|
|
15
|
-
- [x] 3.1 Add `parentTaskId: z.string().nullable()` to `TaskDetail` schema
|
|
16
|
-
- [x] 3.2 Define `CreateTaskInput` zod schema with all optional fields and the mutual-exclusion refinement for `projectId` + `parentTaskId`
|
|
17
|
-
- [x] 3.3 Define `EditTaskInput` zod schema — `id` required, all other fields optional; date fields accept `string | null`; `estimatedMinutes` accepts `number | null`
|
|
18
|
-
|
|
19
|
-
## 4. Tool handlers (src/tools/)
|
|
20
|
-
|
|
21
|
-
- [x] 4.1 `createTask.ts`: validate input with `CreateTaskInput`; invoke `runSnippet("create_task", args)`; validate result against `TaskDetail`; return
|
|
22
|
-
- [x] 4.2 `editTask.ts`: validate input with `EditTaskInput`; invoke `runSnippet("edit_task", args)`; validate result against `TaskDetail`; return
|
|
23
|
-
- [x] 4.3 `completeTask.ts`: input `{id: IdSchema}`; invoke `runSnippet("complete_task", {id})`; validate result against `TaskDetail`; return
|
|
24
|
-
- [x] 4.4 `dropTask.ts`: input `{id: IdSchema}`; invoke `runSnippet("drop_task", {id})`; validate result against `TaskDetail`; return
|
|
25
|
-
- [x] 4.5 `deleteTask.ts`: input `{id: IdSchema}`; tool description MUST state the AI should confirm with the user before invoking and note that deletion is permanent and includes all subtasks; invoke `runSnippet("delete_task", {id})`; return confirmation
|
|
26
|
-
|
|
27
|
-
## 5. Server registration
|
|
28
|
-
|
|
29
|
-
- [x] 5.1 Export all five new tool definitions from `src/tools/index.ts`
|
|
30
|
-
- [x] 5.2 Verify `src/server.ts` picks them up via the `allTools` barrel (no change needed if barrel is already dynamic)
|
|
31
|
-
|
|
32
|
-
## 6. get_task update
|
|
33
|
-
|
|
34
|
-
- [x] 6.1 Update `src/snippets/get_task.js` to include `parentTaskId` in the returned detail (`t.parentTask ? t.parentTask.id.primaryKey : null`)
|
|
35
|
-
- [x] 6.2 Update `src/tools/getTask.ts` to validate against the updated `TaskDetail` schema
|
|
36
|
-
|
|
37
|
-
## 7. Unit tests (test/unit/)
|
|
38
|
-
|
|
39
|
-
- [x] 7.1 `schemas.createTask.test.ts`: valid inputs pass; both `projectId` + `parentTaskId` rejected; missing name rejected; null dates accepted in edit schema
|
|
40
|
-
- [x] 7.2 `tools.deleteTask.test.ts`: verify tool description contains confirmation language
|
|
41
|
-
|
|
42
|
-
## 8. Integration tests (test/integration/)
|
|
43
|
-
|
|
44
|
-
- [x] 8.1 `createTask.int.test.ts`: create inbox task, verify returned ID; create project task, verify containerId; create subtask, verify parentTaskId
|
|
45
|
-
- [x] 8.2 `editTask.int.test.ts`: edit name; edit flags; replace tag set; clear due date
|
|
46
|
-
- [x] 8.3 `completeTask.int.test.ts`: complete a task, verify status = `"complete"`
|
|
47
|
-
- [x] 8.4 `dropTask.int.test.ts`: drop a task, verify status = `"dropped"`
|
|
48
|
-
- [x] 8.5 `deleteTask.int.test.ts`: delete a task, verify subsequent `get_task` returns not-found
|
|
49
|
-
|
|
50
|
-
## 9. Verification
|
|
51
|
-
|
|
52
|
-
- [x] 9.1 `npm run typecheck` clean
|
|
53
|
-
- [x] 9.2 `npm test` (unit suite) clean
|
|
54
|
-
- [x] 9.3 Manually run integration suite; verify fixture cleanup
|
|
55
|
-
- [ ] 9.4 Connect to Claude Desktop; exercise all five tools against a real database
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
## Context
|
|
2
|
-
|
|
3
|
-
`list_tasks` currently fetches all tasks in scope and returns a flat `TaskSummary` array with no server-side filtering. For large databases, `scope: { all: true }` can return thousands of tasks — far too many for an LLM to reason over. Filtering must happen inside the OmniJS snippet before data crosses the JXA bridge; post-hoc filtering in TypeScript would still pay the full serialization cost.
|
|
4
|
-
|
|
5
|
-
`TaskSummary` currently omits `dueDate` and `tagIds`. Without these, filtered results are opaque: if the LLM asks "what's due today?" it can't present due dates in its response without issuing a `get_task` call per result.
|
|
6
|
-
|
|
7
|
-
## Goals / Non-Goals
|
|
8
|
-
|
|
9
|
-
**Goals:**
|
|
10
|
-
- Filter tasks by `flagged`, `status[]`, `tagId`, and `dueBeforeDate` inside the snippet
|
|
11
|
-
- Enrich `TaskSummary` with `dueDate` and `tagIds`
|
|
12
|
-
- Change default behavior: exclude `complete` and `dropped` tasks when no `status` filter is provided
|
|
13
|
-
- Cap results with a configurable `limit` (default 200)
|
|
14
|
-
|
|
15
|
-
**Non-Goals:**
|
|
16
|
-
- Multi-tag filtering with AND/OR semantics (future work)
|
|
17
|
-
- Full-text search on task name or note
|
|
18
|
-
- Pagination / cursor-based continuation (hard cap only)
|
|
19
|
-
- Filtering `list_projects` (separate change)
|
|
20
|
-
|
|
21
|
-
## Decisions
|
|
22
|
-
|
|
23
|
-
### Decision 1: Filter in snippet, not TypeScript
|
|
24
|
-
|
|
25
|
-
All filter logic runs inside `evaluateJavascript`. The TypeScript layer passes filter args through and validates the schema. This avoids deserializing thousands of tasks only to discard most of them.
|
|
26
|
-
|
|
27
|
-
Alternative considered: filter in TS. Rejected because the JXA serialization cost is paid per-task regardless — a 5000-task DB would serialize all 5000 even if only 10 match.
|
|
28
|
-
|
|
29
|
-
### Decision 2: Enrich TaskSummary with dueDate and tagIds
|
|
30
|
-
|
|
31
|
-
`TaskSummary` gains two fields:
|
|
32
|
-
- `dueDate: string | null` — ISO datetime
|
|
33
|
-
- `tagIds: string[]`
|
|
34
|
-
|
|
35
|
-
These are the fields most likely to appear in filtering queries and most useful to display in results. Other detail fields (`note`, `deferDate`, `estimatedMinutes`) remain in `TaskDetail` only.
|
|
36
|
-
|
|
37
|
-
This is a non-breaking addition to the shape — existing consumers receive new optional fields.
|
|
38
|
-
|
|
39
|
-
### Decision 3: Default excludes complete and dropped
|
|
40
|
-
|
|
41
|
-
When `filter.status` is omitted, the snippet filters out `Task.Status.Completed` and `Task.Status.Dropped`. This is a breaking behavior change but the correct default for LLM-driven queries. The previous default (return everything) was only useful for bulk inspection, which can still be achieved with `status: ["available", "blocked", "dueSoon", "next", "overdue", "complete", "dropped"]`.
|
|
42
|
-
|
|
43
|
-
### Decision 4: Status filter as array, tag filter as single ID
|
|
44
|
-
|
|
45
|
-
`status` accepts an array of `TaskStatus` values. This lets callers express compound queries ("available OR overdue") in a single call.
|
|
46
|
-
|
|
47
|
-
`tagId` accepts a single ID — the 80% use case. Multi-tag AND/OR filtering adds significant complexity for uncertain benefit in v1.
|
|
48
|
-
|
|
49
|
-
### Decision 5: dueBeforeDate is inclusive
|
|
50
|
-
|
|
51
|
-
`dueBeforeDate` matches tasks where `task.dueDate <= dueBeforeDate`. "Due before end of today" is expressed as the end-of-day ISO timestamp. Null due dates never match.
|
|
52
|
-
|
|
53
|
-
### Decision 6: limit default 200, passed as snippet arg
|
|
54
|
-
|
|
55
|
-
The limit is enforced inside the snippet (slice after filtering) so the cap applies before serialization. Default 200. Callers may increase it up to any value — there is no server-side maximum, just the JXA timeout.
|
|
56
|
-
|
|
57
|
-
## Risks / Trade-offs
|
|
58
|
-
|
|
59
|
-
- **Breaking default behavior** — consumers that relied on `list_tasks` returning complete tasks will see a behavior change. Risk is low: this is a local MCP server with a single consumer (Claude Desktop), and the new default is strictly more useful.
|
|
60
|
-
- **tagId filter requires flattenedTags lookup** — to check `task.tags`, OmniJS traverses the tag tree. On databases with thousands of tags this is fast in practice but not free.
|
|
61
|
-
- **limit does not paginate** — if a user has 500 overdue tasks and limit is 200, they see the first 200 with no way to retrieve the rest. Acceptable for v1; pagination is future work.
|