@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,39 +0,0 @@
|
|
|
1
|
-
## 1. Schema changes (src/schemas/)
|
|
2
|
-
|
|
3
|
-
- [x] 1.1 Define `RepetitionRuleInput` zod schema: `{ frequency, interval (default 1), daysOfWeek?, method }` with `.refine()` that daysOfWeek is only valid when frequency is "weekly"
|
|
4
|
-
- [x] 1.2 Define `RepetitionRuleDetail` zod schema: `{ frequency, interval, daysOfWeek?, method }` (no refine needed — read-only)
|
|
5
|
-
- [x] 1.3 Add `repetitionRule: RepetitionRuleInput.optional()` to `CreateTaskInput`
|
|
6
|
-
- [x] 1.4 Add `repetitionRule: RepetitionRuleInput.nullable().optional()` to `EditTaskInput`
|
|
7
|
-
- [x] 1.5 Add `repetitionRule: RepetitionRuleDetail.nullable()` to `TaskDetail`
|
|
8
|
-
- [x] 1.6 Export `RepetitionRuleInput` and `RepetitionRuleDetail` from `src/schemas/index.ts`
|
|
9
|
-
|
|
10
|
-
## 2. Snippet updates (src/snippets/)
|
|
11
|
-
|
|
12
|
-
- [x] 2.1 Update `create_task.js`: after task creation, if `args.repetitionRule` is provided, construct RRULE string from frequency/interval/daysOfWeek and call `Task.RepetitionRule.make(rrule, method)`; include `repetitionRule` in returned TaskDetail
|
|
13
|
-
- [x] 2.2 Update `edit_task.js`: if `args.repetitionRule === null` assign `task.repetitionRule = null`; if `args.repetitionRule` is an object, construct and assign the rule; if `args.repetitionRule` is undefined, leave unchanged; include `repetitionRule` in returned TaskDetail
|
|
14
|
-
- [x] 2.3 Update `get_task.js`: read `task.repetitionRule`; if null return `null`; otherwise parse `ruleString` (extract FREQ, INTERVAL, BYDAY via string ops) and map method enum to string; return structured `repetitionRule` field
|
|
15
|
-
|
|
16
|
-
## 3. Helper: RRULE construction and parsing
|
|
17
|
-
|
|
18
|
-
- [x] 3.1 In `create_task.js` and `edit_task.js`: implement `buildRrule(rule)` helper that produces the RRULE string — `FREQ=X;INTERVAL=N` plus `BYDAY=MO,WE,...` when daysOfWeek is present
|
|
19
|
-
- [x] 3.2 In `create_task.js`, `edit_task.js`, and `get_task.js`: implement `parseRepetitionRule(rule)` helper that reads `rule.ruleString` and `rule.method`, extracts FREQ/INTERVAL/BYDAY via string split, maps back to structured fields
|
|
20
|
-
|
|
21
|
-
## 4. Unit tests (test/unit/)
|
|
22
|
-
|
|
23
|
-
- [x] 4.1 Add schema tests for `RepetitionRuleInput`: valid daily; valid weekly with daysOfWeek; valid monthly; valid yearly; invalid daysOfWeek on non-weekly frequency rejected; interval must be positive integer
|
|
24
|
-
- [x] 4.2 Add schema tests for extended `CreateTaskInput`: valid with repetitionRule; valid without (backward compat)
|
|
25
|
-
- [x] 4.3 Add schema tests for extended `EditTaskInput`: valid with repetitionRule object; valid with repetitionRule null (clear); valid omitting repetitionRule (unchanged)
|
|
26
|
-
|
|
27
|
-
## 5. Integration tests (test/integration/)
|
|
28
|
-
|
|
29
|
-
- [x] 5.1 `taskRecurrence.int.test.ts`: create task with daily repetition; verify get_task returns repetitionRule with frequency "daily"
|
|
30
|
-
- [x] 5.2 `taskRecurrence.int.test.ts`: create task with weekly repetition on Mon/Wed/Fri; verify get_task returns correct daysOfWeek
|
|
31
|
-
- [x] 5.3 `taskRecurrence.int.test.ts`: edit existing task to add monthly repetition; verify get_task reflects new rule
|
|
32
|
-
- [x] 5.4 `taskRecurrence.int.test.ts`: edit task to clear repetition (null); verify get_task returns repetitionRule: null
|
|
33
|
-
- [x] 5.5 `taskRecurrence.int.test.ts`: create task without repetitionRule; verify get_task returns repetitionRule: null (backward compat)
|
|
34
|
-
|
|
35
|
-
## 6. Verification
|
|
36
|
-
|
|
37
|
-
- [x] 6.1 `npm run typecheck` clean
|
|
38
|
-
- [x] 6.2 `npm test` (unit suite) clean
|
|
39
|
-
- [x] 6.3 Manually run integration suite
|
package/openspec/config.yaml
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
schema: spec-driven
|
|
2
|
-
|
|
3
|
-
# Project context (optional)
|
|
4
|
-
# This is shown to AI when creating artifacts.
|
|
5
|
-
# Add your tech stack, conventions, style guides, domain knowledge, etc.
|
|
6
|
-
# Example:
|
|
7
|
-
# context: |
|
|
8
|
-
# Tech stack: TypeScript, React, Node.js
|
|
9
|
-
# We use conventional commits
|
|
10
|
-
# Domain: e-commerce platform
|
|
11
|
-
|
|
12
|
-
# Per-artifact rules (optional)
|
|
13
|
-
# Add custom rules for specific artifacts.
|
|
14
|
-
# Example:
|
|
15
|
-
# rules:
|
|
16
|
-
# proposal:
|
|
17
|
-
# - Keep proposals under 500 words
|
|
18
|
-
# - Always include a "Non-goals" section
|
|
19
|
-
# tasks:
|
|
20
|
-
# - Break tasks into chunks of max 2 hours
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
# attachments
|
|
2
|
-
|
|
3
|
-
## Purpose
|
|
4
|
-
|
|
5
|
-
Covers listing, reading, adding, and removing file attachments on task notes via the OmniJS `FileWrapper` API. Individual tools and on-wire encoding decisions will be defined in the `attachments` change.
|
|
6
|
-
|
|
7
|
-
## Requirements
|
|
8
|
-
|
|
9
|
-
### Requirement: Capability declared
|
|
10
|
-
|
|
11
|
-
The `attachments` capability SHALL cover listing, reading, adding, and removing file attachments on task notes via the OmniJS `FileWrapper` API. The on-wire representation of attachment contents across the MCP boundary (inline base64, filesystem path reference, or a hybrid with a size threshold) SHALL be decided when the `attachments` change is drafted. Requirements for individual tools SHALL be added by that change.
|
|
12
|
-
|
|
13
|
-
#### Scenario: Capability is named and scoped
|
|
14
|
-
- **WHEN** a future change proposes adding attachment tools
|
|
15
|
-
- **THEN** it lands requirements under this capability and makes the on-wire encoding decision explicit
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
# batch-operations
|
|
2
|
-
|
|
3
|
-
## Purpose
|
|
4
|
-
|
|
5
|
-
Covers batch variants of every mutating tool with typed-array validation, partial-success semantics, and single-snippet execution. Individual tools will be defined in the `batch-operations` change.
|
|
6
|
-
|
|
7
|
-
## Requirements
|
|
8
|
-
|
|
9
|
-
### Requirement: Capability declared
|
|
10
|
-
|
|
11
|
-
The `batch-operations` capability SHALL cover batch variants of every mutating tool (create, update, delete, move, complete, assign). Batch tools SHALL accept strictly typed arrays validated at the TypeScript boundary (never serialized JSON strings) and SHALL return a per-item result array with partial-success semantics: each element is an `{ok, data?, error?}` envelope carrying that item's outcome independent of other items. Batch tools SHALL execute as a single OmniJS snippet that iterates internally, not as a loop of per-item bridge invocations. Requirements for individual batch tools SHALL be added by the `batch-operations` change.
|
|
12
|
-
|
|
13
|
-
#### Scenario: Capability is named and semantics are fixed
|
|
14
|
-
- **WHEN** a future change proposes adding batch tools
|
|
15
|
-
- **THEN** it lands requirements under this capability with typed-array validation and partial-success result semantics as invariants
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
# database-inspection
|
|
2
|
-
|
|
3
|
-
## Purpose
|
|
4
|
-
|
|
5
|
-
Covers scoped, paged, and filtered traversal of the full OmniFocus database, including a `dump_database` tool, inbox inspection, and database metadata. Individual tools will be defined in the `forecast-and-inspection` change.
|
|
6
|
-
|
|
7
|
-
## Requirements
|
|
8
|
-
|
|
9
|
-
### Requirement: Capability declared
|
|
10
|
-
|
|
11
|
-
The `database-inspection` capability SHALL cover scoped, paged, and filtered traversal of the full OmniFocus database, including a `dump_database` tool that accepts `{scope, id?, include, format}` parameters, inbox inspection, and database metadata (name, sync status, object counts). The default behavior of `dump_database` SHALL exclude completed tasks, dropped entities, and task notes to keep payloads manageable; those are opt-in via the `include` parameter. Requirements for individual tools SHALL be added by the `forecast-and-inspection` change.
|
|
12
|
-
|
|
13
|
-
#### Scenario: Capability is named and scoped
|
|
14
|
-
- **WHEN** a future change proposes adding database-inspection tools
|
|
15
|
-
- **THEN** it lands requirements under this capability rather than inventing a new capability name
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
# execution-runtime
|
|
2
|
-
|
|
3
|
-
## Purpose
|
|
4
|
-
|
|
5
|
-
Defines the contract for executing OmniFocus domain operations through the OmniJS/JXA bridge, including snippet injection, result protocol, caching, and timeouts.
|
|
6
|
-
|
|
7
|
-
## Requirements
|
|
8
|
-
|
|
9
|
-
### Requirement: OmniJS execution via JXA bridge
|
|
10
|
-
|
|
11
|
-
The system SHALL execute every OmniFocus domain operation inside OmniFocus's OmniJS runtime by invoking `Application('OmniFocus').evaluateJavascript(snippet)` from a JXA host process spawned via `osascript -l JavaScript`. The JXA host SHALL NOT invoke any OmniFocus scripting-dictionary method other than `evaluateJavascript` for domain operations.
|
|
12
|
-
|
|
13
|
-
#### Scenario: Tool invocation routes through the bridge
|
|
14
|
-
- **WHEN** a tool handler invokes the runtime with a snippet and args
|
|
15
|
-
- **THEN** the runtime spawns `osascript -l JavaScript`, passes a JXA wrapper that calls `Application('OmniFocus').evaluateJavascript` with the prepared snippet, and awaits stdout
|
|
16
|
-
|
|
17
|
-
#### Scenario: Scripting-dictionary domain methods are forbidden
|
|
18
|
-
- **WHEN** a contributor adds a tool handler that calls e.g. `Application('OmniFocus').defaultDocument.projects` directly
|
|
19
|
-
- **THEN** a unit test (or review) flags the call as a runtime-contract violation and the change is rejected
|
|
20
|
-
|
|
21
|
-
### Requirement: Snippet argument injection via JSON literal
|
|
22
|
-
|
|
23
|
-
The system SHALL construct executable scripts by replacing exactly one `__ARGS__` placeholder in a static `.js` snippet template with the result of `JSON.stringify(args)`. No other interpolation of user-supplied data into script source is permitted anywhere in the codebase.
|
|
24
|
-
|
|
25
|
-
#### Scenario: Apostrophes and quotes survive injection
|
|
26
|
-
- **WHEN** a tool is invoked with `args = {name: "Finn's \"birthday\""}`
|
|
27
|
-
- **THEN** the executed snippet receives `args.name === "Finn's \"birthday\""` and no syntax error occurs
|
|
28
|
-
|
|
29
|
-
#### Scenario: Unicode and newlines survive injection
|
|
30
|
-
- **WHEN** a tool is invoked with `args = {note: "line1\nline2 — emoji 🎯"}`
|
|
31
|
-
- **THEN** the executed snippet receives the note verbatim including the newline and unicode code points
|
|
32
|
-
|
|
33
|
-
#### Scenario: Snippets are standalone and paste-ready
|
|
34
|
-
- **WHEN** a developer opens any file under `src/snippets/`
|
|
35
|
-
- **THEN** the file is a valid JavaScript program that can be pasted into OmniFocus's Automation Console after replacing `__ARGS__` with a hand-written object literal, with no additional preprocessing required
|
|
36
|
-
|
|
37
|
-
### Requirement: One-line JSON result protocol
|
|
38
|
-
|
|
39
|
-
The system SHALL represent every bridge invocation's result as exactly one line of JSON on the JXA host's stdout, matching the envelope `{ok: true, data: <value>} | {ok: false, error: {name: string, message: string, stack?: string}}`. The TypeScript runtime SHALL read stdout, extract the first JSON-parseable line, and return the parsed envelope to the caller. A result envelope with `ok: false` SHALL be thrown as a structured error with the error details preserved.
|
|
40
|
-
|
|
41
|
-
#### Scenario: Successful operation returns data envelope
|
|
42
|
-
- **WHEN** a snippet completes without throwing and its final expression is `JSON.stringify({ok: true, data: {id: "abc"}})`
|
|
43
|
-
- **THEN** the TS runtime receives `{ok: true, data: {id: "abc"}}` and returns `{id: "abc"}` to the tool handler
|
|
44
|
-
|
|
45
|
-
#### Scenario: Snippet exception produces error envelope
|
|
46
|
-
- **WHEN** a snippet throws an `Error` with message "not found"
|
|
47
|
-
- **THEN** the JXA shim catches the exception, prints `{ok: false, error: {name: "Error", message: "not found", ...}}` as one line on stdout, and the TS runtime throws a structured error carrying the name, message, and stack
|
|
48
|
-
|
|
49
|
-
#### Scenario: Extraneous stdout chatter does not corrupt results
|
|
50
|
-
- **WHEN** any process in the pipeline writes incidental non-JSON output to stdout before the result line
|
|
51
|
-
- **THEN** the TS runtime still parses the result envelope correctly by selecting the first complete JSON-parseable line
|
|
52
|
-
|
|
53
|
-
### Requirement: Snippet loader
|
|
54
|
-
|
|
55
|
-
The system SHALL load snippet templates from `src/snippets/<name>.js` relative to the compiled server's known snippet root, caching the file contents in memory after the first read. The loader SHALL reject snippets that do not contain exactly one `__ARGS__` token.
|
|
56
|
-
|
|
57
|
-
#### Scenario: Snippet is loaded and cached
|
|
58
|
-
- **WHEN** a tool handler invokes the runtime with snippet name `"list_projects"` twice in the same process
|
|
59
|
-
- **THEN** the file `list_projects.js` is read from disk exactly once and the cached content is used for the second invocation
|
|
60
|
-
|
|
61
|
-
#### Scenario: Snippet with zero placeholders is rejected
|
|
62
|
-
- **WHEN** a snippet file contains no `__ARGS__` token
|
|
63
|
-
- **THEN** the loader throws a contract violation at load time, not at runtime
|
|
64
|
-
|
|
65
|
-
#### Scenario: Snippet with multiple placeholders is rejected
|
|
66
|
-
- **WHEN** a snippet file contains two or more `__ARGS__` tokens
|
|
67
|
-
- **THEN** the loader throws a contract violation at load time
|
|
68
|
-
|
|
69
|
-
### Requirement: Script timeout
|
|
70
|
-
|
|
71
|
-
The system SHALL enforce a configurable per-invocation timeout on `osascript` execution, defaulting to 30 seconds, and SHALL terminate the child process and throw a timeout error when exceeded.
|
|
72
|
-
|
|
73
|
-
#### Scenario: Long-running snippet is terminated
|
|
74
|
-
- **WHEN** a snippet runs longer than the configured timeout
|
|
75
|
-
- **THEN** the runtime sends SIGTERM to the `osascript` process and throws a timeout error identifying the snippet name and elapsed time
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
# folder-management
|
|
2
|
-
|
|
3
|
-
## Purpose
|
|
4
|
-
|
|
5
|
-
Defines tools for reading and listing OmniFocus folders, including full folder listing and detail retrieval by ID.
|
|
6
|
-
|
|
7
|
-
## Requirements
|
|
8
|
-
|
|
9
|
-
### Requirement: List folders
|
|
10
|
-
|
|
11
|
-
The system SHALL provide a `list_folders` tool that returns folders in the OmniFocus database as an array of summaries, each containing `{id, name, path, parentId, status}`. The `path` field SHALL use the canonical ` ▸ ` separator and SHALL equal the folder's name for top-level folders. The `parentId` field SHALL be `null` for top-level folders. The `status` field SHALL be one of `"active" | "dropped"`. The tool SHALL accept an optional `status` filter string and an optional `limit` integer (default 200). When `status` is omitted, all folders are returned regardless of status. When `limit` is omitted, at most 200 folders are returned.
|
|
12
|
-
|
|
13
|
-
#### Scenario: All folders returned by default
|
|
14
|
-
- **WHEN** `list_folders` is called with no arguments
|
|
15
|
-
- **THEN** the tool returns all folders (active and dropped) up to the limit
|
|
16
|
-
|
|
17
|
-
#### Scenario: Top-level folders have null parent
|
|
18
|
-
- **WHEN** a folder exists at the root of the folder hierarchy
|
|
19
|
-
- **THEN** its summary carries `parentId: null` and `path` equal to its name
|
|
20
|
-
|
|
21
|
-
#### Scenario: Filter by status returns only matching folders
|
|
22
|
-
- **WHEN** `list_folders` is called with `{ filter: { status: "active" } }`
|
|
23
|
-
- **THEN** only active folders are returned
|
|
24
|
-
|
|
25
|
-
#### Scenario: Limit caps the number of returned folders
|
|
26
|
-
- **WHEN** `list_folders` is called with `{ limit: 5 }`
|
|
27
|
-
- **THEN** at most 5 folders are returned
|
|
28
|
-
|
|
29
|
-
### Requirement: Get folder by ID
|
|
30
|
-
|
|
31
|
-
The system SHALL provide a `get_folder` tool that accepts `{id: string}` and returns the full detail record of the named folder, including `{id, name, path, parentId, status, childFolderIds, projectIds}`. If no folder exists with that ID, the tool SHALL return a structured not-found error.
|
|
32
|
-
|
|
33
|
-
#### Scenario: Existing folder returns full detail with children
|
|
34
|
-
- **WHEN** `get_folder` is called with the ID of an existing folder
|
|
35
|
-
- **THEN** the tool returns the folder's full detail including the IDs of its immediate child folders and immediate child projects
|
|
36
|
-
|
|
37
|
-
#### Scenario: Missing folder returns not-found error
|
|
38
|
-
- **WHEN** `get_folder` is called with an ID that does not correspond to any folder
|
|
39
|
-
- **THEN** the tool returns a structured error with a not-found code
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
## ADDED Requirements
|
|
2
|
-
|
|
3
|
-
### Requirement: Create folder
|
|
4
|
-
|
|
5
|
-
The system SHALL provide a `create_folder` tool that creates a new OmniFocus folder and returns its full detail record. The tool SHALL accept `{name: string, parentFolderId?: string}`. If `parentFolderId` is provided the folder SHALL be created nested inside that folder; otherwise it SHALL be created at the top level.
|
|
6
|
-
|
|
7
|
-
#### Scenario: Create top-level folder
|
|
8
|
-
- **WHEN** `create_folder` is called with `{name: "Work"}` and no `parentFolderId`
|
|
9
|
-
- **THEN** the tool creates the folder at the top level and returns its full detail record including a stable `id`
|
|
10
|
-
|
|
11
|
-
#### Scenario: Create nested folder
|
|
12
|
-
- **WHEN** `create_folder` is called with `{name: "Active", parentFolderId: "abc123"}`
|
|
13
|
-
- **THEN** the tool creates the folder inside the specified parent folder and returns its full detail record with `path` reflecting the full ancestor chain
|
|
14
|
-
|
|
15
|
-
#### Scenario: Non-existent parent folder returns not-found error
|
|
16
|
-
- **WHEN** `create_folder` is called with a `parentFolderId` that does not correspond to any folder
|
|
17
|
-
- **THEN** the tool returns a structured not-found error
|
|
18
|
-
|
|
19
|
-
### Requirement: Edit folder
|
|
20
|
-
|
|
21
|
-
The system SHALL provide an `edit_folder` tool that renames an existing folder and returns its updated full detail record. The tool SHALL accept `{id: string, name: string}`.
|
|
22
|
-
|
|
23
|
-
#### Scenario: Rename a folder
|
|
24
|
-
- **WHEN** `edit_folder` is called with `{id: "abc123", name: "Personal"}`
|
|
25
|
-
- **THEN** the folder's name is updated and the tool returns the updated detail record with the new name reflected in `path`
|
|
26
|
-
|
|
27
|
-
#### Scenario: Non-existent folder returns not-found error
|
|
28
|
-
- **WHEN** `edit_folder` is called with an ID that does not correspond to any folder
|
|
29
|
-
- **THEN** the tool returns a structured not-found error
|
|
30
|
-
|
|
31
|
-
### Requirement: Delete folder
|
|
32
|
-
|
|
33
|
-
The system SHALL provide a `delete_folder` tool that permanently and recursively deletes a folder, all child folders, all projects within those folders, and all tasks within those projects. The tool description SHALL instruct the AI to confirm with the user before invoking, explicitly stating that the entire subtree — child folders, projects, and all tasks — is permanently deleted and cannot be undone.
|
|
34
|
-
|
|
35
|
-
#### Scenario: Delete a folder and all its contents
|
|
36
|
-
- **WHEN** `delete_folder` is called with the ID of an existing folder
|
|
37
|
-
- **THEN** the folder, all descendant folders, all projects within those folders, and all tasks within those projects are permanently removed from OmniFocus and the tool returns a confirmation envelope
|
|
38
|
-
|
|
39
|
-
#### Scenario: Delete empty folder
|
|
40
|
-
- **WHEN** `delete_folder` is called with the ID of a folder that contains no projects or child folders
|
|
41
|
-
- **THEN** the folder is removed and the tool returns a confirmation envelope
|
|
42
|
-
|
|
43
|
-
#### Scenario: Non-existent folder returns not-found error
|
|
44
|
-
- **WHEN** `delete_folder` is called with an ID that does not correspond to any folder
|
|
45
|
-
- **THEN** the tool returns a structured not-found error
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
# forecast
|
|
2
|
-
|
|
3
|
-
## Purpose
|
|
4
|
-
|
|
5
|
-
Covers OmniFocus Forecast data, including forecast days, tasks due or deferred on a given day, the configured forecast tag, and forecast badge counts. Individual tools will be defined in the `forecast-and-inspection` change.
|
|
6
|
-
|
|
7
|
-
## Requirements
|
|
8
|
-
|
|
9
|
-
### Requirement: Capability declared
|
|
10
|
-
|
|
11
|
-
The `forecast` capability SHALL cover OmniFocus Forecast data, including forecast days, tasks due or deferred on a given day, the configured forecast tag, and forecast badge counts. Requirements for individual tools SHALL be added by the `forecast-and-inspection` change.
|
|
12
|
-
|
|
13
|
-
#### Scenario: Capability is named and scoped
|
|
14
|
-
- **WHEN** a future change proposes adding forecast tools
|
|
15
|
-
- **THEN** it lands requirements under this capability rather than inventing a new capability name
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
# identity-resolution
|
|
2
|
-
|
|
3
|
-
## Purpose
|
|
4
|
-
|
|
5
|
-
Defines how OmniFocus entities are addressed and resolved — canonical ID addressing, name/path-based lookup with ambiguity reporting, and the folder path format.
|
|
6
|
-
|
|
7
|
-
## Requirements
|
|
8
|
-
|
|
9
|
-
### Requirement: Canonical ID addressing
|
|
10
|
-
|
|
11
|
-
The system SHALL use `id.primaryKey` as the canonical identifier for every addressable OmniFocus entity (Task, Project, Folder, Tag, Perspective). Every read tool SHALL return an `id` field carrying this value. Every write tool (in this and future changes) SHALL accept an `id` parameter as its primary addressing mode.
|
|
12
|
-
|
|
13
|
-
#### Scenario: Read tools return primary key
|
|
14
|
-
- **WHEN** a caller invokes `list_projects`
|
|
15
|
-
- **THEN** every returned element has an `id` field equal to the OmniJS expression `project.id.primaryKey` for that project
|
|
16
|
-
|
|
17
|
-
#### Scenario: IDs are stable within a session
|
|
18
|
-
- **WHEN** a caller reads a project's ID via `list_projects`, then reads the same project again via `get_project` using that ID
|
|
19
|
-
- **THEN** the second read succeeds and returns the same project, without re-resolving by name
|
|
20
|
-
|
|
21
|
-
### Requirement: Name and path resolution with ambiguity reporting
|
|
22
|
-
|
|
23
|
-
The system SHALL provide a `resolve_name` tool that accepts `{type, query, scope?}` and returns a list of candidate entities `[{id, name, path, type, ...}]`. When more than one entity matches the query, the tool SHALL return all matches and SHALL NOT silently pick a winner. The caller is responsible for disambiguating.
|
|
24
|
-
|
|
25
|
-
#### Scenario: Unique match returns single candidate
|
|
26
|
-
- **WHEN** `resolve_name` is called with `{type: "project", query: "Q4 Planning"}` and exactly one project has that name
|
|
27
|
-
- **THEN** the tool returns a list with one element containing the project's id, name, and full folder path
|
|
28
|
-
|
|
29
|
-
#### Scenario: Ambiguous match returns all candidates
|
|
30
|
-
- **WHEN** `resolve_name` is called with `{type: "project", query: "Inbox Cleanup"}` and two projects have that name under different folders
|
|
31
|
-
- **THEN** the tool returns a list with both candidates, each carrying a distinct id and distinct folder path
|
|
32
|
-
|
|
33
|
-
#### Scenario: No match returns empty list
|
|
34
|
-
- **WHEN** `resolve_name` is called with a query that matches no entity of the requested type
|
|
35
|
-
- **THEN** the tool returns an empty list and does not throw an error
|
|
36
|
-
|
|
37
|
-
#### Scenario: Path-qualified query narrows scope
|
|
38
|
-
- **WHEN** `resolve_name` is called with `{type: "folder", query: "Acme", scope: "Work ▸ Clients"}`
|
|
39
|
-
- **THEN** the tool returns only folders named "Acme" that are direct or transitive children of the folder at path "Work ▸ Clients"
|
|
40
|
-
|
|
41
|
-
### Requirement: Folder path addressing
|
|
42
|
-
|
|
43
|
-
The system SHALL represent folder paths as strings using the separator ` ▸ ` (U+25B8 with surrounding spaces) and SHALL support resolving a folder by its full path as an alternative to its ID. When a path is ambiguous (the same path exists under multiple roots, which cannot occur for folders but can for tags), the resolution SHALL report the ambiguity through `resolve_name`.
|
|
44
|
-
|
|
45
|
-
#### Scenario: Folder resolves by full path
|
|
46
|
-
- **WHEN** `resolve_name` is called with `{type: "folder", query: "Work ▸ Clients ▸ Acme"}`
|
|
47
|
-
- **THEN** the tool returns the folder whose full ancestor chain matches that path exactly
|
|
48
|
-
|
|
49
|
-
#### Scenario: Returned paths use the canonical separator
|
|
50
|
-
- **WHEN** any read tool returns a folder path or a project's containing folder path
|
|
51
|
-
- **THEN** the path uses ` ▸ ` as the separator between ancestor names
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
## Requirements
|
|
2
|
-
|
|
3
|
-
### Requirement: Move task to a new container
|
|
4
|
-
|
|
5
|
-
The system SHALL provide a `move_task` tool that accepts `{id: string, projectId?: string, parentTaskId?: string}` and moves the specified task to the given container. Exactly one of `projectId` or `parentTaskId` SHALL be provided; if both or neither are given, the tool SHALL return a validation error. When `projectId` is provided, the task SHALL become a top-level task in that project. When `parentTaskId` is provided, the task SHALL become a direct subtask of that task. The tool SHALL return a structured not-found error if any of the IDs do not correspond to existing objects.
|
|
6
|
-
|
|
7
|
-
#### Scenario: Move task to a project
|
|
8
|
-
- **WHEN** `move_task` is called with `{id: "t1", projectId: "p2"}`
|
|
9
|
-
- **THEN** the task appears as a top-level task in project p2 and is no longer in its previous container
|
|
10
|
-
|
|
11
|
-
#### Scenario: Make task a subtask
|
|
12
|
-
- **WHEN** `move_task` is called with `{id: "t1", parentTaskId: "t2"}`
|
|
13
|
-
- **THEN** the task becomes a direct child of task t2
|
|
14
|
-
|
|
15
|
-
#### Scenario: Both destinations provided is a validation error
|
|
16
|
-
- **WHEN** `move_task` is called with both `projectId` and `parentTaskId`
|
|
17
|
-
- **THEN** the tool returns a validation error before any snippet executes
|
|
18
|
-
|
|
19
|
-
#### Scenario: Non-existent task ID returns not-found error
|
|
20
|
-
- **WHEN** `move_task` is called with an ID that does not correspond to any task
|
|
21
|
-
- **THEN** the tool returns a structured not-found error
|
|
22
|
-
|
|
23
|
-
### Requirement: Move project to a folder
|
|
24
|
-
|
|
25
|
-
The system SHALL provide a `move_project` tool that accepts `{id: string, folderId: string | null}` and moves the specified project to the given folder. When `folderId` is `null`, the project SHALL be moved to the top level (no containing folder). The tool SHALL return a structured not-found error if the project ID or folder ID does not correspond to an existing object.
|
|
26
|
-
|
|
27
|
-
#### Scenario: Move project to a folder
|
|
28
|
-
- **WHEN** `move_project` is called with `{id: "p1", folderId: "f2"}`
|
|
29
|
-
- **THEN** the project is now contained within folder f2
|
|
30
|
-
|
|
31
|
-
#### Scenario: Move project to top level
|
|
32
|
-
- **WHEN** `move_project` is called with `{id: "p1", folderId: null}`
|
|
33
|
-
- **THEN** the project has no parent folder
|
|
34
|
-
|
|
35
|
-
#### Scenario: Non-existent project ID returns not-found error
|
|
36
|
-
- **WHEN** `move_project` is called with a project ID that does not exist
|
|
37
|
-
- **THEN** the tool returns a structured not-found error
|
|
38
|
-
|
|
39
|
-
#### Scenario: Non-existent folder ID returns not-found error
|
|
40
|
-
- **WHEN** `move_project` is called with a folderId that does not correspond to any folder
|
|
41
|
-
- **THEN** the tool returns a structured not-found error
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
# perspective-management
|
|
2
|
-
|
|
3
|
-
## Purpose
|
|
4
|
-
|
|
5
|
-
Covers listing built-in and custom perspectives, reading perspective metadata, and activating a perspective in a document window. Individual tools will be defined in the `perspectives-and-windows` change.
|
|
6
|
-
|
|
7
|
-
## Requirements
|
|
8
|
-
|
|
9
|
-
### Requirement: Capability declared
|
|
10
|
-
|
|
11
|
-
The `perspective-management` capability SHALL cover listing built-in and custom perspectives, reading perspective metadata, and activating a perspective in a document window. Custom perspective *creation and editing* are a non-goal pending verification of OmniJS scriptability of `Perspective.Custom` definitions; if a future investigation proves the API is available, the non-goal may be revisited by a later change. Requirements for individual tools SHALL be added by the `perspectives-and-windows` change.
|
|
12
|
-
|
|
13
|
-
#### Scenario: Capability is named and scoped
|
|
14
|
-
- **WHEN** a future change proposes adding perspective tools
|
|
15
|
-
- **THEN** it lands requirements under this capability rather than inventing a new capability name
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
# project-filtering
|
|
2
|
-
|
|
3
|
-
## Purpose
|
|
4
|
-
|
|
5
|
-
Defines filtering and limiting behavior for the `list_projects` tool, including status, folder, and flagged filters, as well as result limits and enriched project summary fields.
|
|
6
|
-
|
|
7
|
-
## Requirements
|
|
8
|
-
|
|
9
|
-
### Requirement: Filter list_projects results
|
|
10
|
-
|
|
11
|
-
The system SHALL allow `list_projects` to accept an optional `filter` object that restricts which projects are returned. All filter fields are optional and combine as AND conditions. When `filter.status` is omitted, the tool SHALL exclude projects with status `done` or `dropped` by default.
|
|
12
|
-
|
|
13
|
-
Filter fields:
|
|
14
|
-
- `status` (array of ProjectStatus): return only projects whose status is in the array; overrides the default exclusion of done/dropped
|
|
15
|
-
- `folderId` (string): return only projects within the specified folder or any of its descendant folders; throws not-found if the folder ID does not exist
|
|
16
|
-
- `flagged` (boolean): when `true`, return only flagged projects
|
|
17
|
-
|
|
18
|
-
#### Scenario: Default excludes done and dropped projects
|
|
19
|
-
- **WHEN** `list_projects` is called with no filter
|
|
20
|
-
- **THEN** the tool returns only projects with status `active` or `onHold`
|
|
21
|
-
|
|
22
|
-
#### Scenario: Filter by status array
|
|
23
|
-
- **WHEN** `list_projects` is called with `{ filter: { status: ["active"] } }`
|
|
24
|
-
- **THEN** the tool returns only active projects
|
|
25
|
-
|
|
26
|
-
#### Scenario: Explicit status filter retrieves done projects
|
|
27
|
-
- **WHEN** `list_projects` is called with `{ filter: { status: ["done"] } }`
|
|
28
|
-
- **THEN** the tool returns completed projects (the default exclusion does not apply)
|
|
29
|
-
|
|
30
|
-
#### Scenario: Filter by folderId returns projects in subtree
|
|
31
|
-
- **WHEN** `list_projects` is called with `{ filter: { folderId: "abc123" } }`
|
|
32
|
-
- **THEN** the tool returns only projects whose parent folder chain includes the specified folder
|
|
33
|
-
|
|
34
|
-
#### Scenario: Non-existent folderId returns not-found error
|
|
35
|
-
- **WHEN** `list_projects` is called with a `folderId` that does not correspond to any folder
|
|
36
|
-
- **THEN** the tool returns a structured not-found error
|
|
37
|
-
|
|
38
|
-
#### Scenario: Filter by flagged
|
|
39
|
-
- **WHEN** `list_projects` is called with `{ filter: { flagged: true } }`
|
|
40
|
-
- **THEN** the tool returns only flagged projects, excluding done and dropped projects
|
|
41
|
-
|
|
42
|
-
#### Scenario: Combined filters act as AND
|
|
43
|
-
- **WHEN** `list_projects` is called with `{ filter: { status: ["active"], flagged: true } }`
|
|
44
|
-
- **THEN** the tool returns only projects that are both active AND flagged
|
|
45
|
-
|
|
46
|
-
### Requirement: Limit list_projects results
|
|
47
|
-
|
|
48
|
-
The system SHALL allow `list_projects` to accept an optional `limit` integer that caps the number of projects returned. When `limit` is omitted, a default of 100 SHALL apply.
|
|
49
|
-
|
|
50
|
-
#### Scenario: Default limit of 100
|
|
51
|
-
- **WHEN** `list_projects` is called without a `limit` and more than 100 projects match
|
|
52
|
-
- **THEN** the tool returns at most 100 projects
|
|
53
|
-
|
|
54
|
-
#### Scenario: Custom limit
|
|
55
|
-
- **WHEN** `list_projects` is called with `{ limit: 20 }`
|
|
56
|
-
- **THEN** the tool returns at most 20 projects
|
|
57
|
-
|
|
58
|
-
### Requirement: Enriched project summary includes flagged and folderId
|
|
59
|
-
|
|
60
|
-
The `ProjectSummary` returned by `list_projects` SHALL include `flagged` (boolean) and `folderId` (string or null) in addition to existing fields. `folderId` SHALL be the direct parent folder's `id.primaryKey`, or `null` for top-level projects.
|
|
61
|
-
|
|
62
|
-
#### Scenario: Summary includes flagged
|
|
63
|
-
- **WHEN** `list_projects` returns a flagged project
|
|
64
|
-
- **THEN** the project summary includes `flagged: true`
|
|
65
|
-
|
|
66
|
-
#### Scenario: Summary includes folderId for nested project
|
|
67
|
-
- **WHEN** `list_projects` returns a project inside a folder
|
|
68
|
-
- **THEN** the project summary includes `folderId` set to the parent folder's ID
|
|
69
|
-
|
|
70
|
-
#### Scenario: Summary includes folderId null for top-level project
|
|
71
|
-
- **WHEN** `list_projects` returns a top-level project with no containing folder
|
|
72
|
-
- **THEN** the project summary includes `folderId: null`
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
# project-management
|
|
2
|
-
|
|
3
|
-
## Purpose
|
|
4
|
-
|
|
5
|
-
Defines tools for reading and listing OmniFocus projects, including full project listing and detail retrieval by ID.
|
|
6
|
-
|
|
7
|
-
## Requirements
|
|
8
|
-
|
|
9
|
-
### Requirement: List projects
|
|
10
|
-
|
|
11
|
-
The system SHALL provide a `list_projects` tool that returns projects in the OmniFocus database as an array of summaries, each containing `{id, name, folderPath, folderId, status, type, flagged}`. The `status` field SHALL be one of `"active" | "onHold" | "done" | "dropped"`. The `type` field SHALL be one of `"parallel" | "sequential" | "singleActions"`. The `folderPath` field SHALL use the canonical ` ▸ ` separator and SHALL be an empty string for top-level projects. The `folderId` field SHALL be the direct parent folder's ID or `null` for top-level projects. The tool SHALL accept an optional `filter` object (see project-filtering spec) and an optional `limit` integer (default 100). When no `filter.status` is provided, projects with status `done` or `dropped` SHALL be excluded by default.
|
|
12
|
-
|
|
13
|
-
#### Scenario: Active and on-hold projects returned by default
|
|
14
|
-
- **WHEN** `list_projects` is called with no arguments
|
|
15
|
-
- **THEN** the tool returns only projects with status `active` or `onHold`, each with id, name, folderPath, folderId, status, type, and flagged populated
|
|
16
|
-
|
|
17
|
-
#### Scenario: Single Actions list is reported with correct type
|
|
18
|
-
- **WHEN** the database contains a Single Actions list project
|
|
19
|
-
- **THEN** that project appears in the result with `type: "singleActions"`, never as `"parallel"` or `"sequential"`
|
|
20
|
-
|
|
21
|
-
### Requirement: Get project by ID
|
|
22
|
-
|
|
23
|
-
The system SHALL provide a `get_project` tool that accepts `{id: string}` and returns the full detail record of the named project, including `{id, name, note, folderPath, status, type, flagged, deferDate, dueDate, completionDate, reviewInterval, nextReviewDate, lastReviewDate, tagIds}`. If no project exists with that ID, the tool SHALL return a structured not-found error.
|
|
24
|
-
|
|
25
|
-
#### Scenario: Existing project returns full detail
|
|
26
|
-
- **WHEN** `get_project` is called with the ID of an existing project
|
|
27
|
-
- **THEN** the tool returns the project's full detail record
|
|
28
|
-
|
|
29
|
-
#### Scenario: Missing project returns not-found error
|
|
30
|
-
- **WHEN** `get_project` is called with an ID that does not correspond to any project
|
|
31
|
-
- **THEN** the tool returns a structured error with a not-found code
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
# Spec: Project Write
|
|
2
|
-
|
|
3
|
-
## Purpose
|
|
4
|
-
|
|
5
|
-
Provides MCP tools for creating and mutating OmniFocus projects: creating new projects, editing project properties, marking projects complete or dropped, and permanently deleting projects.
|
|
6
|
-
|
|
7
|
-
## Requirements
|
|
8
|
-
|
|
9
|
-
### Requirement: Create project
|
|
10
|
-
|
|
11
|
-
The system SHALL provide a `create_project` tool that creates a new OmniFocus project and returns its full detail record. The tool SHALL accept `{name: string, folderId?: string, note?: string, type?: "parallel" | "sequential" | "singleActions", status?: "active" | "onHold", flagged?: boolean, deferDate?: string, dueDate?: string, reviewInterval?: {steps: number, unit: "days" | "weeks" | "months" | "years"}, tagIds?: string[]}`. If `folderId` is provided the project SHALL be created inside that folder; otherwise it SHALL be created at the top level.
|
|
12
|
-
|
|
13
|
-
#### Scenario: Create top-level project
|
|
14
|
-
- **WHEN** `create_project` is called with `{name: "My Project"}` and no `folderId`
|
|
15
|
-
- **THEN** the tool creates the project at the top level and returns its full detail record including a stable `id`
|
|
16
|
-
|
|
17
|
-
#### Scenario: Create project inside a folder
|
|
18
|
-
- **WHEN** `create_project` is called with `{name: "My Project", folderId: "abc123"}`
|
|
19
|
-
- **THEN** the tool creates the project inside the specified folder and returns its full detail record with `folderPath` reflecting the folder
|
|
20
|
-
|
|
21
|
-
#### Scenario: Create sequential project
|
|
22
|
-
- **WHEN** `create_project` is called with `{name: "My Project", type: "sequential"}`
|
|
23
|
-
- **THEN** the returned project detail includes `type: "sequential"`
|
|
24
|
-
|
|
25
|
-
#### Scenario: Non-existent folder returns not-found error
|
|
26
|
-
- **WHEN** `create_project` is called with a `folderId` that does not correspond to any folder
|
|
27
|
-
- **THEN** the tool returns a structured not-found error
|
|
28
|
-
|
|
29
|
-
### Requirement: Edit project
|
|
30
|
-
|
|
31
|
-
The system SHALL provide an `edit_project` tool that modifies an existing project and returns its updated full detail record. The tool SHALL accept `{id: string}` plus any subset of `{name?: string, note?: string, type?: "parallel" | "sequential" | "singleActions", status?: "active" | "onHold", flagged?: boolean, deferDate?: string | null, dueDate?: string | null, reviewInterval?: {steps: number, unit: "days" | "weeks" | "months" | "years"}, tagIds?: string[]}`. Fields omitted from the call SHALL be left unchanged. When `tagIds` is provided it SHALL replace the project's entire tag set. Passing `null` for a date SHALL clear the field. When `reviewInterval` is provided, only the `steps` value is updated; the `unit` field is accepted by the schema but cannot be changed at runtime due to OmniJS API constraints in the `evaluateJavascript` context.
|
|
32
|
-
|
|
33
|
-
#### Scenario: Put project on hold
|
|
34
|
-
- **WHEN** `edit_project` is called with `{id: "abc123", status: "onHold"}`
|
|
35
|
-
- **THEN** the project's status becomes `"onHold"` and all other fields are unchanged
|
|
36
|
-
|
|
37
|
-
#### Scenario: Set review interval
|
|
38
|
-
- **WHEN** `edit_project` is called with `{id: "abc123", reviewInterval: {steps: 2, unit: "weeks"}}`
|
|
39
|
-
- **THEN** the project's review interval is updated and the returned detail reflects the change
|
|
40
|
-
|
|
41
|
-
#### Scenario: Non-existent project returns not-found error
|
|
42
|
-
- **WHEN** `edit_project` is called with an ID that does not correspond to any project
|
|
43
|
-
- **THEN** the tool returns a structured not-found error
|
|
44
|
-
|
|
45
|
-
### Requirement: Complete project
|
|
46
|
-
|
|
47
|
-
The system SHALL provide a `complete_project` tool that marks an existing project done using OmniJS `markComplete()` and returns the project's updated full detail record.
|
|
48
|
-
|
|
49
|
-
#### Scenario: Complete an existing project
|
|
50
|
-
- **WHEN** `complete_project` is called with the ID of an active project
|
|
51
|
-
- **THEN** the project's status becomes `"done"` and the tool returns the updated detail record
|
|
52
|
-
|
|
53
|
-
#### Scenario: Non-existent project returns not-found error
|
|
54
|
-
- **WHEN** `complete_project` is called with an ID that does not correspond to any project
|
|
55
|
-
- **THEN** the tool returns a structured not-found error
|
|
56
|
-
|
|
57
|
-
### Requirement: Drop project
|
|
58
|
-
|
|
59
|
-
The system SHALL provide a `drop_project` tool that marks an existing project dropped using OmniJS `drop()` and returns the project's updated full detail record.
|
|
60
|
-
|
|
61
|
-
#### Scenario: Drop an existing project
|
|
62
|
-
- **WHEN** `drop_project` is called with the ID of an active project
|
|
63
|
-
- **THEN** the project's status becomes `"dropped"` and the tool returns the updated detail record
|
|
64
|
-
|
|
65
|
-
#### Scenario: Non-existent project returns not-found error
|
|
66
|
-
- **WHEN** `drop_project` is called with an ID that does not correspond to any project
|
|
67
|
-
- **THEN** the tool returns a structured not-found error
|
|
68
|
-
|
|
69
|
-
### Requirement: Delete project
|
|
70
|
-
|
|
71
|
-
The system SHALL provide a `delete_project` tool that permanently deletes a project and all its tasks using OmniJS `deleteObject()`. The tool description SHALL instruct the AI to confirm with the user before invoking this tool, noting that deletion is permanent and removes all tasks within the project.
|
|
72
|
-
|
|
73
|
-
#### Scenario: Delete an existing project
|
|
74
|
-
- **WHEN** `delete_project` is called with the ID of an existing project
|
|
75
|
-
- **THEN** the project and all its tasks are permanently removed from OmniFocus and the tool returns a confirmation envelope
|
|
76
|
-
|
|
77
|
-
#### Scenario: Non-existent project returns not-found error
|
|
78
|
-
- **WHEN** `delete_project` is called with an ID that does not correspond to any project
|
|
79
|
-
- **THEN** the tool returns a structured not-found error
|