@scardis/omnifocus-mcp 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (210) hide show
  1. package/package.json +7 -1
  2. package/.claude/commands/opsx/apply.md +0 -152
  3. package/.claude/commands/opsx/archive.md +0 -157
  4. package/.claude/commands/opsx/bulk-archive.md +0 -242
  5. package/.claude/commands/opsx/continue.md +0 -114
  6. package/.claude/commands/opsx/explore.md +0 -173
  7. package/.claude/commands/opsx/ff.md +0 -97
  8. package/.claude/commands/opsx/new.md +0 -69
  9. package/.claude/commands/opsx/onboard.md +0 -550
  10. package/.claude/commands/opsx/propose.md +0 -106
  11. package/.claude/commands/opsx/sync.md +0 -134
  12. package/.claude/commands/opsx/verify.md +0 -164
  13. package/.claude/skills/openspec-apply-change/SKILL.md +0 -156
  14. package/.claude/skills/openspec-archive-change/SKILL.md +0 -114
  15. package/.claude/skills/openspec-bulk-archive-change/SKILL.md +0 -246
  16. package/.claude/skills/openspec-continue-change/SKILL.md +0 -118
  17. package/.claude/skills/openspec-explore/SKILL.md +0 -288
  18. package/.claude/skills/openspec-ff-change/SKILL.md +0 -101
  19. package/.claude/skills/openspec-new-change/SKILL.md +0 -74
  20. package/.claude/skills/openspec-onboard/SKILL.md +0 -554
  21. package/.claude/skills/openspec-propose/SKILL.md +0 -110
  22. package/.claude/skills/openspec-sync-specs/SKILL.md +0 -138
  23. package/.claude/skills/openspec-verify-change/SKILL.md +0 -168
  24. package/CONTRIBUTING.md +0 -83
  25. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/.openspec.yaml +0 -2
  26. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/design.md +0 -162
  27. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/proposal.md +0 -49
  28. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/attachments/spec.md +0 -9
  29. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/batch-operations/spec.md +0 -9
  30. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/database-inspection/spec.md +0 -9
  31. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/execution-runtime/spec.md +0 -69
  32. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/folder-management/spec.md +0 -25
  33. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/forecast/spec.md +0 -9
  34. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/identity-resolution/spec.md +0 -45
  35. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/perspective-management/spec.md +0 -9
  36. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/project-management/spec.md +0 -25
  37. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/recurrence/spec.md +0 -9
  38. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/settings/spec.md +0 -9
  39. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/tag-management/spec.md +0 -25
  40. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/task-management/spec.md +0 -29
  41. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/url-automation/spec.md +0 -9
  42. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/window-state/spec.md +0 -9
  43. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/tasks.md +0 -84
  44. package/openspec/changes/archive/2026-04-09-folder-crud/.openspec.yaml +0 -2
  45. package/openspec/changes/archive/2026-04-09-folder-crud/design.md +0 -58
  46. package/openspec/changes/archive/2026-04-09-folder-crud/proposal.md +0 -28
  47. package/openspec/changes/archive/2026-04-09-folder-crud/specs/folder-write/spec.md +0 -45
  48. package/openspec/changes/archive/2026-04-09-folder-crud/tasks.md +0 -41
  49. package/openspec/changes/archive/2026-04-09-folder-tag-list-filtering/.openspec.yaml +0 -2
  50. package/openspec/changes/archive/2026-04-09-folder-tag-list-filtering/design.md +0 -38
  51. package/openspec/changes/archive/2026-04-09-folder-tag-list-filtering/proposal.md +0 -30
  52. package/openspec/changes/archive/2026-04-09-folder-tag-list-filtering/specs/folder-management/spec.md +0 -21
  53. package/openspec/changes/archive/2026-04-09-folder-tag-list-filtering/specs/tag-management/spec.md +0 -21
  54. package/openspec/changes/archive/2026-04-09-folder-tag-list-filtering/tasks.md +0 -35
  55. package/openspec/changes/archive/2026-04-09-move-operations/.openspec.yaml +0 -2
  56. package/openspec/changes/archive/2026-04-09-move-operations/design.md +0 -43
  57. package/openspec/changes/archive/2026-04-09-move-operations/proposal.md +0 -25
  58. package/openspec/changes/archive/2026-04-09-move-operations/specs/move-operations/spec.md +0 -41
  59. package/openspec/changes/archive/2026-04-09-move-operations/tasks.md +0 -40
  60. package/openspec/changes/archive/2026-04-09-project-crud/.openspec.yaml +0 -2
  61. package/openspec/changes/archive/2026-04-09-project-crud/design.md +0 -60
  62. package/openspec/changes/archive/2026-04-09-project-crud/proposal.md +0 -29
  63. package/openspec/changes/archive/2026-04-09-project-crud/specs/project-write/spec.md +0 -74
  64. package/openspec/changes/archive/2026-04-09-project-crud/tasks.md +0 -48
  65. package/openspec/changes/archive/2026-04-09-project-filtering/.openspec.yaml +0 -2
  66. package/openspec/changes/archive/2026-04-09-project-filtering/design.md +0 -52
  67. package/openspec/changes/archive/2026-04-09-project-filtering/proposal.md +0 -26
  68. package/openspec/changes/archive/2026-04-09-project-filtering/specs/project-filtering/spec.md +0 -66
  69. package/openspec/changes/archive/2026-04-09-project-filtering/specs/project-management/spec.md +0 -13
  70. package/openspec/changes/archive/2026-04-09-project-filtering/tasks.md +0 -41
  71. package/openspec/changes/archive/2026-04-09-tag-crud/.openspec.yaml +0 -2
  72. package/openspec/changes/archive/2026-04-09-tag-crud/design.md +0 -45
  73. package/openspec/changes/archive/2026-04-09-tag-crud/proposal.md +0 -28
  74. package/openspec/changes/archive/2026-04-09-tag-crud/specs/tag-write/spec.md +0 -49
  75. package/openspec/changes/archive/2026-04-09-tag-crud/tasks.md +0 -41
  76. package/openspec/changes/archive/2026-04-09-task-crud/.openspec.yaml +0 -2
  77. package/openspec/changes/archive/2026-04-09-task-crud/design.md +0 -62
  78. package/openspec/changes/archive/2026-04-09-task-crud/proposal.md +0 -29
  79. package/openspec/changes/archive/2026-04-09-task-crud/specs/task-management/spec.md +0 -17
  80. package/openspec/changes/archive/2026-04-09-task-crud/specs/task-write/spec.md +0 -89
  81. package/openspec/changes/archive/2026-04-09-task-crud/tasks.md +0 -55
  82. package/openspec/changes/archive/2026-04-09-task-filtering/.openspec.yaml +0 -2
  83. package/openspec/changes/archive/2026-04-09-task-filtering/design.md +0 -61
  84. package/openspec/changes/archive/2026-04-09-task-filtering/proposal.md +0 -26
  85. package/openspec/changes/archive/2026-04-09-task-filtering/specs/task-filtering/spec.md +0 -63
  86. package/openspec/changes/archive/2026-04-09-task-filtering/specs/task-management/spec.md +0 -17
  87. package/openspec/changes/archive/2026-04-09-task-filtering/tasks.md +0 -42
  88. package/openspec/changes/archive/2026-04-10-planned-date/.openspec.yaml +0 -2
  89. package/openspec/changes/archive/2026-04-10-planned-date/design.md +0 -27
  90. package/openspec/changes/archive/2026-04-10-planned-date/proposal.md +0 -29
  91. package/openspec/changes/archive/2026-04-10-planned-date/specs/task-management/spec.md +0 -29
  92. package/openspec/changes/archive/2026-04-10-planned-date/specs/task-write/spec.md +0 -69
  93. package/openspec/changes/archive/2026-04-10-planned-date/tasks.md +0 -26
  94. package/openspec/changes/archive/2026-04-10-task-recurrence/.openspec.yaml +0 -2
  95. package/openspec/changes/archive/2026-04-10-task-recurrence/design.md +0 -81
  96. package/openspec/changes/archive/2026-04-10-task-recurrence/proposal.md +0 -28
  97. package/openspec/changes/archive/2026-04-10-task-recurrence/specs/recurrence/spec.md +0 -47
  98. package/openspec/changes/archive/2026-04-10-task-recurrence/specs/task-management/spec.md +0 -25
  99. package/openspec/changes/archive/2026-04-10-task-recurrence/specs/task-write/spec.md +0 -61
  100. package/openspec/changes/archive/2026-04-10-task-recurrence/tasks.md +0 -39
  101. package/openspec/config.yaml +0 -20
  102. package/openspec/specs/attachments/spec.md +0 -15
  103. package/openspec/specs/batch-operations/spec.md +0 -15
  104. package/openspec/specs/database-inspection/spec.md +0 -15
  105. package/openspec/specs/execution-runtime/spec.md +0 -75
  106. package/openspec/specs/folder-management/spec.md +0 -39
  107. package/openspec/specs/folder-write/spec.md +0 -45
  108. package/openspec/specs/forecast/spec.md +0 -15
  109. package/openspec/specs/identity-resolution/spec.md +0 -51
  110. package/openspec/specs/move-operations/spec.md +0 -41
  111. package/openspec/specs/perspective-management/spec.md +0 -15
  112. package/openspec/specs/project-filtering/spec.md +0 -72
  113. package/openspec/specs/project-management/spec.md +0 -31
  114. package/openspec/specs/project-write/spec.md +0 -79
  115. package/openspec/specs/recurrence/spec.md +0 -51
  116. package/openspec/specs/settings/spec.md +0 -15
  117. package/openspec/specs/tag-management/spec.md +0 -39
  118. package/openspec/specs/tag-write/spec.md +0 -49
  119. package/openspec/specs/task-filtering/spec.md +0 -63
  120. package/openspec/specs/task-management/spec.md +0 -51
  121. package/openspec/specs/task-write/spec.md +0 -115
  122. package/openspec/specs/url-automation/spec.md +0 -15
  123. package/openspec/specs/window-state/spec.md +0 -15
  124. package/scripts/cleanup-fixtures.ts +0 -89
  125. package/server.json +0 -21
  126. package/src/runtime/bridge.ts +0 -97
  127. package/src/runtime/index.ts +0 -4
  128. package/src/runtime/jxaShim.ts +0 -55
  129. package/src/runtime/resultProtocol.ts +0 -62
  130. package/src/runtime/snippetLoader.ts +0 -79
  131. package/src/schemas/enums.ts +0 -32
  132. package/src/schemas/index.ts +0 -38
  133. package/src/schemas/shapes.ts +0 -263
  134. package/src/server.ts +0 -56
  135. package/src/tools/completeProject.ts +0 -21
  136. package/src/tools/completeTask.ts +0 -23
  137. package/src/tools/createFolder.ts +0 -20
  138. package/src/tools/createProject.ts +0 -20
  139. package/src/tools/createTag.ts +0 -20
  140. package/src/tools/createTask.ts +0 -20
  141. package/src/tools/deleteFolder.ts +0 -24
  142. package/src/tools/deleteProject.ts +0 -24
  143. package/src/tools/deleteTag.ts +0 -24
  144. package/src/tools/deleteTask.ts +0 -26
  145. package/src/tools/dropProject.ts +0 -21
  146. package/src/tools/dropTask.ts +0 -23
  147. package/src/tools/editFolder.ts +0 -19
  148. package/src/tools/editProject.ts +0 -20
  149. package/src/tools/editTag.ts +0 -20
  150. package/src/tools/editTask.ts +0 -20
  151. package/src/tools/getFolder.ts +0 -24
  152. package/src/tools/getProject.ts +0 -24
  153. package/src/tools/getTag.ts +0 -24
  154. package/src/tools/getTask.ts +0 -24
  155. package/src/tools/index.ts +0 -85
  156. package/src/tools/listFolders.ts +0 -32
  157. package/src/tools/listProjects.ts +0 -32
  158. package/src/tools/listTags.ts +0 -32
  159. package/src/tools/listTasks.ts +0 -56
  160. package/src/tools/moveProject.ts +0 -20
  161. package/src/tools/moveTask.ts +0 -20
  162. package/src/tools/resolveName.ts +0 -37
  163. package/test/integration/.gitkeep +0 -0
  164. package/test/integration/completeProject.int.test.ts +0 -25
  165. package/test/integration/completeTask.int.test.ts +0 -30
  166. package/test/integration/createFolder.int.test.ts +0 -50
  167. package/test/integration/createProject.int.test.ts +0 -49
  168. package/test/integration/createTag.int.test.ts +0 -52
  169. package/test/integration/createTask.int.test.ts +0 -55
  170. package/test/integration/deleteFolder.int.test.ts +0 -64
  171. package/test/integration/deleteProject.int.test.ts +0 -31
  172. package/test/integration/deleteTag.int.test.ts +0 -61
  173. package/test/integration/deleteTask.int.test.ts +0 -36
  174. package/test/integration/dropProject.int.test.ts +0 -24
  175. package/test/integration/dropTask.int.test.ts +0 -29
  176. package/test/integration/editFolder.int.test.ts +0 -43
  177. package/test/integration/editProject.int.test.ts +0 -39
  178. package/test/integration/editTag.int.test.ts +0 -43
  179. package/test/integration/editTask.int.test.ts +0 -56
  180. package/test/integration/fixtures.ts +0 -219
  181. package/test/integration/getTask.int.test.ts +0 -64
  182. package/test/integration/listFoldersFiltered.int.test.ts +0 -98
  183. package/test/integration/listProjects.int.test.ts +0 -73
  184. package/test/integration/listProjectsFiltered.int.test.ts +0 -96
  185. package/test/integration/listTagsFiltered.int.test.ts +0 -54
  186. package/test/integration/listTasksFiltered.int.test.ts +0 -141
  187. package/test/integration/moveProject.int.test.ts +0 -57
  188. package/test/integration/moveTask.int.test.ts +0 -61
  189. package/test/integration/plannedDate.int.test.ts +0 -72
  190. package/test/integration/preflight.ts +0 -60
  191. package/test/integration/resolveName.int.test.ts +0 -86
  192. package/test/integration/taskRecurrence.int.test.ts +0 -106
  193. package/test/unit/.gitkeep +0 -0
  194. package/test/unit/bridge.injection.test.ts +0 -66
  195. package/test/unit/resultProtocol.test.ts +0 -71
  196. package/test/unit/schemas.createFolder.test.ts +0 -38
  197. package/test/unit/schemas.createProject.test.ts +0 -115
  198. package/test/unit/schemas.createTask.test.ts +0 -87
  199. package/test/unit/schemas.editTag.test.ts +0 -64
  200. package/test/unit/schemas.folderTagFiltering.test.ts +0 -42
  201. package/test/unit/schemas.listProjects.test.ts +0 -44
  202. package/test/unit/schemas.moveOperations.test.ts +0 -60
  203. package/test/unit/schemas.recurrence.test.ts +0 -98
  204. package/test/unit/schemas.test.ts +0 -126
  205. package/test/unit/snippetLoader.test.ts +0 -56
  206. package/test/unit/tools.deleteTask.test.ts +0 -19
  207. package/test/unit/tools.listTasks.test.ts +0 -126
  208. package/tsconfig.json +0 -19
  209. package/vitest.config.ts +0 -8
  210. package/vitest.integration.config.ts +0 -18
@@ -1,66 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
-
3
- /**
4
- * Proves Design Decision 2:
5
- * JSON.stringify(args) produces a valid JS expression for any args value,
6
- * including apostrophes, quotes, backslashes, newlines, and unicode.
7
- *
8
- * We test the injection logic directly without hitting the bridge or OmniFocus.
9
- */
10
-
11
- const TRICKY_ARGS = {
12
- name: "Finn's \"birthday\" 🎯",
13
- note: "line1\nline2\ttabbed",
14
- path: "Work ▸ Clients ▸ Acme",
15
- backslash: "C:\\Users\\test",
16
- unicode: "\u2603 snowman \u0000 null",
17
- };
18
-
19
- function injectArgs(template: string, args: unknown): string {
20
- return template.replace("__ARGS__", JSON.stringify(args));
21
- }
22
-
23
- describe("argument injection safety", () => {
24
- it("produces valid JavaScript for tricky string values", () => {
25
- const template = "(() => { const args = __ARGS__; return args; })()";
26
- const injected = injectArgs(template, TRICKY_ARGS);
27
- // new Function wraps the body in a function; we eval the IIFE directly
28
- let result: unknown;
29
- expect(() => {
30
- result = new Function(`return ${injected}`)();
31
- }).not.toThrow();
32
- expect(result).toEqual(TRICKY_ARGS);
33
- });
34
-
35
- it("recovers apostrophes exactly", () => {
36
- const template = "(() => { const args = __ARGS__; return args; })()";
37
- const args = { name: "it's a test's value" };
38
- const injected = injectArgs(template, args);
39
- const result = new Function(`return ${injected}`)() as typeof args;
40
- expect(result.name).toBe("it's a test's value");
41
- });
42
-
43
- it("recovers double quotes exactly", () => {
44
- const template = "(() => { const args = __ARGS__; return args; })()";
45
- const args = { name: 'She said "hello"' };
46
- const injected = injectArgs(template, args);
47
- const result = new Function(`return ${injected}`)() as typeof args;
48
- expect(result.name).toBe('She said "hello"');
49
- });
50
-
51
- it("recovers newlines exactly", () => {
52
- const template = "(() => { const args = __ARGS__; return args; })()";
53
- const args = { note: "line1\nline2\nline3" };
54
- const injected = injectArgs(template, args);
55
- const result = new Function(`return ${injected}`)() as typeof args;
56
- expect(result.note).toBe("line1\nline2\nline3");
57
- });
58
-
59
- it("recovers unicode exactly", () => {
60
- const template = "(() => { const args = __ARGS__; return args; })()";
61
- const args = { symbol: "▸ ★ 🎉" };
62
- const injected = injectArgs(template, args);
63
- const result = new Function(`return ${injected}`)() as typeof args;
64
- expect(result.symbol).toBe("▸ ★ 🎉");
65
- });
66
- });
@@ -1,71 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { parseResultLine, ExecutionError } from "../../src/runtime/resultProtocol.js";
3
-
4
- describe("parseResultLine", () => {
5
- it("parses a well-formed success envelope", () => {
6
- const stdout = JSON.stringify({ ok: true, data: { id: "abc" } });
7
- const result = parseResultLine(stdout);
8
- expect(result.ok).toBe(true);
9
- if (result.ok) {
10
- expect(result.data).toEqual({ id: "abc" });
11
- }
12
- });
13
-
14
- it("parses a well-formed error envelope", () => {
15
- const stdout = JSON.stringify({
16
- ok: false,
17
- error: { name: "NotFoundError", message: "Not found", stack: "..." },
18
- });
19
- const result = parseResultLine(stdout);
20
- expect(result.ok).toBe(false);
21
- if (!result.ok) {
22
- expect(result.error.name).toBe("NotFoundError");
23
- expect(result.error.message).toBe("Not found");
24
- }
25
- });
26
-
27
- it("selects first parseable JSON line when preceded by chatter", () => {
28
- const stdout =
29
- "osascript warning: something\nnot json at all\n" +
30
- JSON.stringify({ ok: true, data: [1, 2, 3] }) +
31
- "\n";
32
- const result = parseResultLine(stdout);
33
- expect(result.ok).toBe(true);
34
- if (result.ok) {
35
- expect(result.data).toEqual([1, 2, 3]);
36
- }
37
- });
38
-
39
- it("throws on empty stdout", () => {
40
- expect(() => parseResultLine("")).toThrow(/No valid JSON result envelope/);
41
- });
42
-
43
- it("throws on non-envelope JSON", () => {
44
- expect(() => parseResultLine('{"foo":"bar"}')).toThrow(/not a valid result envelope/);
45
- });
46
-
47
- it("throws on malformed-only stdout with no parseable lines", () => {
48
- expect(() => parseResultLine("not json\nalso not json\n")).toThrow(
49
- /No valid JSON result envelope/
50
- );
51
- });
52
- });
53
-
54
- describe("ExecutionError", () => {
55
- it("carries name, message, and stack from the error detail", () => {
56
- const err = new ExecutionError({
57
- name: "TypeError",
58
- message: "cannot read property",
59
- stack: "TypeError: cannot...\n at line 1",
60
- });
61
- expect(err.message).toBe("cannot read property");
62
- expect(err.errorName).toBe("TypeError");
63
- expect(err.remoteStack).toContain("TypeError:");
64
- expect(err.name).toBe("ExecutionError");
65
- });
66
-
67
- it("works without a stack field", () => {
68
- const err = new ExecutionError({ name: "Error", message: "boom" });
69
- expect(err.remoteStack).toBeUndefined();
70
- });
71
- });
@@ -1,38 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { CreateFolderInput, EditFolderInput } from "../../src/schemas/index.js";
3
-
4
- describe("CreateFolderInput schema", () => {
5
- it("accepts name only (top-level)", () => {
6
- expect(() => CreateFolderInput.parse({ name: "My Folder" })).not.toThrow();
7
- });
8
-
9
- it("accepts name with parentFolderId", () => {
10
- expect(() =>
11
- CreateFolderInput.parse({ name: "Nested", parentFolderId: "abc123" })
12
- ).not.toThrow();
13
- });
14
-
15
- it("rejects missing name", () => {
16
- expect(() => CreateFolderInput.parse({})).toThrow();
17
- });
18
-
19
- it("rejects empty name", () => {
20
- expect(() => CreateFolderInput.parse({ name: "" })).toThrow();
21
- });
22
- });
23
-
24
- describe("EditFolderInput schema", () => {
25
- it("accepts id and name", () => {
26
- expect(() =>
27
- EditFolderInput.parse({ id: "abc123", name: "Renamed" })
28
- ).not.toThrow();
29
- });
30
-
31
- it("rejects missing id", () => {
32
- expect(() => EditFolderInput.parse({ name: "Renamed" })).toThrow();
33
- });
34
-
35
- it("rejects empty name", () => {
36
- expect(() => EditFolderInput.parse({ id: "abc123", name: "" })).toThrow();
37
- });
38
- });
@@ -1,115 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { CreateProjectInput, EditProjectInput, ReviewIntervalInput } from "../../src/schemas/index.js";
3
-
4
- describe("ReviewIntervalInput schema", () => {
5
- it("accepts valid steps and unit", () => {
6
- expect(() =>
7
- ReviewIntervalInput.parse({ steps: 2, unit: "weeks" })
8
- ).not.toThrow();
9
- });
10
-
11
- it("rejects invalid unit", () => {
12
- expect(() =>
13
- ReviewIntervalInput.parse({ steps: 2, unit: "fortnights" })
14
- ).toThrow();
15
- });
16
-
17
- it("rejects non-positive steps", () => {
18
- expect(() =>
19
- ReviewIntervalInput.parse({ steps: 0, unit: "weeks" })
20
- ).toThrow();
21
- });
22
- });
23
-
24
- describe("CreateProjectInput schema", () => {
25
- it("accepts name only (top-level)", () => {
26
- expect(() => CreateProjectInput.parse({ name: "My Project" })).not.toThrow();
27
- });
28
-
29
- it("accepts name with folderId", () => {
30
- expect(() =>
31
- CreateProjectInput.parse({ name: "My Project", folderId: "abc123" })
32
- ).not.toThrow();
33
- });
34
-
35
- it("accepts valid type enum values", () => {
36
- for (const type of ["parallel", "sequential", "singleActions"] as const) {
37
- expect(() =>
38
- CreateProjectInput.parse({ name: "P", type })
39
- ).not.toThrow();
40
- }
41
- });
42
-
43
- it("rejects invalid type", () => {
44
- expect(() =>
45
- CreateProjectInput.parse({ name: "P", type: "waterfall" })
46
- ).toThrow();
47
- });
48
-
49
- it("accepts valid status values", () => {
50
- for (const status of ["active", "onHold"] as const) {
51
- expect(() =>
52
- CreateProjectInput.parse({ name: "P", status })
53
- ).not.toThrow();
54
- }
55
- });
56
-
57
- it("accepts reviewInterval", () => {
58
- expect(() =>
59
- CreateProjectInput.parse({
60
- name: "P",
61
- reviewInterval: { steps: 1, unit: "months" },
62
- })
63
- ).not.toThrow();
64
- });
65
-
66
- it("rejects missing name", () => {
67
- expect(() => CreateProjectInput.parse({})).toThrow();
68
- });
69
-
70
- it("rejects empty name", () => {
71
- expect(() => CreateProjectInput.parse({ name: "" })).toThrow();
72
- });
73
- });
74
-
75
- describe("EditProjectInput schema", () => {
76
- it("accepts id with a single field", () => {
77
- expect(() =>
78
- EditProjectInput.parse({ id: "abc123", status: "onHold" })
79
- ).not.toThrow();
80
- });
81
-
82
- it("accepts null deferDate to clear the field", () => {
83
- expect(() =>
84
- EditProjectInput.parse({ id: "abc123", deferDate: null })
85
- ).not.toThrow();
86
- });
87
-
88
- it("accepts null dueDate", () => {
89
- expect(() =>
90
- EditProjectInput.parse({ id: "abc123", dueDate: null })
91
- ).not.toThrow();
92
- });
93
-
94
- it("accepts structured reviewInterval", () => {
95
- expect(() =>
96
- EditProjectInput.parse({
97
- id: "abc123",
98
- reviewInterval: { steps: 2, unit: "weeks" },
99
- })
100
- ).not.toThrow();
101
- });
102
-
103
- it("rejects invalid reviewInterval unit", () => {
104
- expect(() =>
105
- EditProjectInput.parse({
106
- id: "abc123",
107
- reviewInterval: { steps: 2, unit: "fortnights" },
108
- })
109
- ).toThrow();
110
- });
111
-
112
- it("rejects missing id", () => {
113
- expect(() => EditProjectInput.parse({ name: "Updated" })).toThrow();
114
- });
115
- });
@@ -1,87 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { CreateTaskInput, EditTaskInput } from "../../src/schemas/index.js";
3
-
4
- describe("CreateTaskInput schema", () => {
5
- it("accepts name only (inbox placement)", () => {
6
- expect(() => CreateTaskInput.parse({ name: "Buy milk" })).not.toThrow();
7
- });
8
-
9
- it("accepts name with projectId", () => {
10
- expect(() =>
11
- CreateTaskInput.parse({ name: "Write tests", projectId: "abc123" })
12
- ).not.toThrow();
13
- });
14
-
15
- it("accepts name with parentTaskId", () => {
16
- expect(() =>
17
- CreateTaskInput.parse({ name: "Review PR", parentTaskId: "xyz789" })
18
- ).not.toThrow();
19
- });
20
-
21
- it("accepts all optional fields", () => {
22
- expect(() =>
23
- CreateTaskInput.parse({
24
- name: "Full task",
25
- note: "some note",
26
- flagged: true,
27
- deferDate: "2026-04-10T00:00:00.000Z",
28
- dueDate: "2026-04-15T00:00:00.000Z",
29
- estimatedMinutes: 30,
30
- projectId: "proj1",
31
- tagIds: ["tag1", "tag2"],
32
- })
33
- ).not.toThrow();
34
- });
35
-
36
- it("rejects both projectId and parentTaskId", () => {
37
- expect(() =>
38
- CreateTaskInput.parse({
39
- name: "Ambiguous",
40
- projectId: "proj1",
41
- parentTaskId: "task1",
42
- })
43
- ).toThrow();
44
- });
45
-
46
- it("rejects missing name", () => {
47
- expect(() => CreateTaskInput.parse({})).toThrow();
48
- });
49
-
50
- it("rejects empty name", () => {
51
- expect(() => CreateTaskInput.parse({ name: "" })).toThrow();
52
- });
53
- });
54
-
55
- describe("EditTaskInput schema", () => {
56
- it("accepts id with a single field", () => {
57
- expect(() =>
58
- EditTaskInput.parse({ id: "abc123", flagged: true })
59
- ).not.toThrow();
60
- });
61
-
62
- it("accepts null deferDate to clear the field", () => {
63
- expect(() =>
64
- EditTaskInput.parse({ id: "abc123", deferDate: null })
65
- ).not.toThrow();
66
- });
67
-
68
- it("accepts null dueDate", () => {
69
- expect(() =>
70
- EditTaskInput.parse({ id: "abc123", dueDate: null })
71
- ).not.toThrow();
72
- });
73
-
74
- it("accepts null estimatedMinutes", () => {
75
- expect(() =>
76
- EditTaskInput.parse({ id: "abc123", estimatedMinutes: null })
77
- ).not.toThrow();
78
- });
79
-
80
- it("rejects missing id", () => {
81
- expect(() => EditTaskInput.parse({ name: "Updated" })).toThrow();
82
- });
83
-
84
- it("rejects empty id", () => {
85
- expect(() => EditTaskInput.parse({ id: "", name: "Updated" })).toThrow();
86
- });
87
- });
@@ -1,64 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { CreateTagInput, EditTagInput } from "../../src/schemas/index.js";
3
-
4
- describe("CreateTagInput schema", () => {
5
- it("accepts name only (top-level)", () => {
6
- expect(() => CreateTagInput.parse({ name: "Waiting" })).not.toThrow();
7
- });
8
-
9
- it("accepts name with parentTagId", () => {
10
- expect(() =>
11
- CreateTagInput.parse({ name: "Email", parentTagId: "abc123" })
12
- ).not.toThrow();
13
- });
14
-
15
- it("rejects missing name", () => {
16
- expect(() => CreateTagInput.parse({})).toThrow();
17
- });
18
-
19
- it("rejects empty name", () => {
20
- expect(() => CreateTagInput.parse({ name: "" })).toThrow();
21
- });
22
- });
23
-
24
- describe("EditTagInput schema", () => {
25
- it("accepts id with name only", () => {
26
- expect(() =>
27
- EditTagInput.parse({ id: "abc123", name: "Renamed" })
28
- ).not.toThrow();
29
- });
30
-
31
- it("accepts id with status only", () => {
32
- expect(() =>
33
- EditTagInput.parse({ id: "abc123", status: "onHold" })
34
- ).not.toThrow();
35
- });
36
-
37
- it("accepts id with both name and status", () => {
38
- expect(() =>
39
- EditTagInput.parse({ id: "abc123", name: "New", status: "active" })
40
- ).not.toThrow();
41
- });
42
-
43
- it("accepts all valid status values", () => {
44
- for (const status of ["active", "onHold", "dropped"] as const) {
45
- expect(() =>
46
- EditTagInput.parse({ id: "abc123", status })
47
- ).not.toThrow();
48
- }
49
- });
50
-
51
- it("rejects neither name nor status (refine)", () => {
52
- expect(() => EditTagInput.parse({ id: "abc123" })).toThrow();
53
- });
54
-
55
- it("rejects invalid status enum", () => {
56
- expect(() =>
57
- EditTagInput.parse({ id: "abc123", status: "paused" })
58
- ).toThrow();
59
- });
60
-
61
- it("rejects missing id", () => {
62
- expect(() => EditTagInput.parse({ name: "X" })).toThrow();
63
- });
64
- });
@@ -1,42 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { ListFoldersFilter, ListTagsFilter } from "../../src/schemas/index.js";
3
-
4
- describe("ListFoldersFilter schema", () => {
5
- it("accepts empty filter object", () => {
6
- expect(() => ListFoldersFilter.parse({})).not.toThrow();
7
- });
8
-
9
- it("accepts status 'active'", () => {
10
- expect(() => ListFoldersFilter.parse({ status: "active" })).not.toThrow();
11
- });
12
-
13
- it("accepts status 'dropped'", () => {
14
- expect(() => ListFoldersFilter.parse({ status: "dropped" })).not.toThrow();
15
- });
16
-
17
- it("rejects invalid status value", () => {
18
- expect(() => ListFoldersFilter.parse({ status: "pending" })).toThrow();
19
- });
20
- });
21
-
22
- describe("ListTagsFilter schema", () => {
23
- it("accepts empty filter object", () => {
24
- expect(() => ListTagsFilter.parse({})).not.toThrow();
25
- });
26
-
27
- it("accepts status 'active'", () => {
28
- expect(() => ListTagsFilter.parse({ status: "active" })).not.toThrow();
29
- });
30
-
31
- it("accepts status 'onHold'", () => {
32
- expect(() => ListTagsFilter.parse({ status: "onHold" })).not.toThrow();
33
- });
34
-
35
- it("accepts status 'dropped'", () => {
36
- expect(() => ListTagsFilter.parse({ status: "dropped" })).not.toThrow();
37
- });
38
-
39
- it("rejects invalid status value", () => {
40
- expect(() => ListTagsFilter.parse({ status: "unknown" })).toThrow();
41
- });
42
- });
@@ -1,44 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { ListProjectsFilter } from "../../src/schemas/index.js";
3
-
4
- describe("ListProjectsFilter schema", () => {
5
- it("accepts empty filter object", () => {
6
- expect(() => ListProjectsFilter.parse({})).not.toThrow();
7
- });
8
-
9
- it("accepts status array", () => {
10
- expect(() =>
11
- ListProjectsFilter.parse({ status: ["active", "onHold"] })
12
- ).not.toThrow();
13
- });
14
-
15
- it("accepts folderId", () => {
16
- expect(() =>
17
- ListProjectsFilter.parse({ folderId: "folder123" })
18
- ).not.toThrow();
19
- });
20
-
21
- it("accepts flagged: true", () => {
22
- expect(() =>
23
- ListProjectsFilter.parse({ flagged: true })
24
- ).not.toThrow();
25
- });
26
-
27
- it("accepts all fields together", () => {
28
- expect(() =>
29
- ListProjectsFilter.parse({ status: ["active"], folderId: "f1", flagged: true })
30
- ).not.toThrow();
31
- });
32
-
33
- it("rejects invalid status enum in array", () => {
34
- expect(() =>
35
- ListProjectsFilter.parse({ status: ["active", "flying"] })
36
- ).toThrow();
37
- });
38
-
39
- it("rejects flagged: false (must be literal true or absent)", () => {
40
- expect(() =>
41
- ListProjectsFilter.parse({ flagged: false })
42
- ).toThrow();
43
- });
44
- });
@@ -1,60 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { MoveTaskInput, MoveProjectInput } from "../../src/schemas/index.js";
3
-
4
- describe("MoveTaskInput schema", () => {
5
- it("accepts projectId only", () => {
6
- expect(() =>
7
- MoveTaskInput.parse({ id: "t1", projectId: "p1" })
8
- ).not.toThrow();
9
- });
10
-
11
- it("accepts parentTaskId only", () => {
12
- expect(() =>
13
- MoveTaskInput.parse({ id: "t1", parentTaskId: "t2" })
14
- ).not.toThrow();
15
- });
16
-
17
- it("rejects both projectId and parentTaskId", () => {
18
- expect(() =>
19
- MoveTaskInput.parse({ id: "t1", projectId: "p1", parentTaskId: "t2" })
20
- ).toThrow();
21
- });
22
-
23
- it("rejects neither projectId nor parentTaskId", () => {
24
- expect(() =>
25
- MoveTaskInput.parse({ id: "t1" })
26
- ).toThrow();
27
- });
28
-
29
- it("rejects missing id", () => {
30
- expect(() =>
31
- MoveTaskInput.parse({ projectId: "p1" })
32
- ).toThrow();
33
- });
34
- });
35
-
36
- describe("MoveProjectInput schema", () => {
37
- it("accepts folderId as string", () => {
38
- expect(() =>
39
- MoveProjectInput.parse({ id: "p1", folderId: "f1" })
40
- ).not.toThrow();
41
- });
42
-
43
- it("accepts folderId as null", () => {
44
- expect(() =>
45
- MoveProjectInput.parse({ id: "p1", folderId: null })
46
- ).not.toThrow();
47
- });
48
-
49
- it("rejects missing id", () => {
50
- expect(() =>
51
- MoveProjectInput.parse({ folderId: "f1" })
52
- ).toThrow();
53
- });
54
-
55
- it("rejects missing folderId (undefined not same as null)", () => {
56
- expect(() =>
57
- MoveProjectInput.parse({ id: "p1" })
58
- ).toThrow();
59
- });
60
- });
@@ -1,98 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { RepetitionRuleInput, CreateTaskInput, EditTaskInput } from "../../src/schemas/index.js";
3
-
4
- describe("RepetitionRuleInput schema", () => {
5
- it("accepts daily repetition", () => {
6
- expect(() =>
7
- RepetitionRuleInput.parse({ frequency: "daily", interval: 1, method: "fixed" })
8
- ).not.toThrow();
9
- });
10
-
11
- it("accepts weekly repetition with daysOfWeek", () => {
12
- expect(() =>
13
- RepetitionRuleInput.parse({
14
- frequency: "weekly",
15
- interval: 2,
16
- daysOfWeek: ["monday", "wednesday", "friday"],
17
- method: "start",
18
- })
19
- ).not.toThrow();
20
- });
21
-
22
- it("accepts monthly repetition", () => {
23
- expect(() =>
24
- RepetitionRuleInput.parse({ frequency: "monthly", interval: 1, method: "dueDate" })
25
- ).not.toThrow();
26
- });
27
-
28
- it("accepts yearly repetition", () => {
29
- expect(() =>
30
- RepetitionRuleInput.parse({ frequency: "yearly", interval: 1, method: "fixed" })
31
- ).not.toThrow();
32
- });
33
-
34
- it("defaults interval to 1 when omitted", () => {
35
- const result = RepetitionRuleInput.parse({ frequency: "daily", method: "fixed" });
36
- expect(result.interval).toBe(1);
37
- });
38
-
39
- it("rejects interval of zero", () => {
40
- expect(() =>
41
- RepetitionRuleInput.parse({ frequency: "daily", interval: 0, method: "fixed" })
42
- ).toThrow();
43
- });
44
-
45
- it("rejects negative interval", () => {
46
- expect(() =>
47
- RepetitionRuleInput.parse({ frequency: "weekly", interval: -1, method: "fixed" })
48
- ).toThrow();
49
- });
50
-
51
- it("rejects invalid frequency", () => {
52
- expect(() =>
53
- RepetitionRuleInput.parse({ frequency: "hourly", interval: 1, method: "fixed" })
54
- ).toThrow();
55
- });
56
-
57
- it("rejects invalid method", () => {
58
- expect(() =>
59
- RepetitionRuleInput.parse({ frequency: "daily", interval: 1, method: "completion" })
60
- ).toThrow();
61
- });
62
- });
63
-
64
- describe("CreateTaskInput with repetitionRule", () => {
65
- it("accepts task without repetitionRule (backward compat)", () => {
66
- expect(() => CreateTaskInput.parse({ name: "Buy milk" })).not.toThrow();
67
- });
68
-
69
- it("accepts task with repetitionRule", () => {
70
- expect(() =>
71
- CreateTaskInput.parse({
72
- name: "Stand-up",
73
- repetitionRule: { frequency: "daily", interval: 1, method: "fixed" },
74
- })
75
- ).not.toThrow();
76
- });
77
- });
78
-
79
- describe("EditTaskInput with repetitionRule", () => {
80
- it("accepts edit without repetitionRule (leave unchanged)", () => {
81
- expect(() => EditTaskInput.parse({ id: "abc", flagged: true })).not.toThrow();
82
- });
83
-
84
- it("accepts edit with repetitionRule object (set rule)", () => {
85
- expect(() =>
86
- EditTaskInput.parse({
87
- id: "abc",
88
- repetitionRule: { frequency: "weekly", interval: 1, method: "start" },
89
- })
90
- ).not.toThrow();
91
- });
92
-
93
- it("accepts edit with clearRepetitionRule true (clear rule)", () => {
94
- expect(() =>
95
- EditTaskInput.parse({ id: "abc", clearRepetitionRule: true })
96
- ).not.toThrow();
97
- });
98
- });