things-api 0.5.0
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/LICENSE +21 -0
- package/README.md +77 -0
- package/bin/things.js +9 -0
- package/dist/audit/log.d.ts +10 -0
- package/dist/audit/log.js +31 -0
- package/dist/audit/log.js.map +1 -0
- package/dist/audit/schema.d.ts +37 -0
- package/dist/audit/schema.js +9 -0
- package/dist/audit/schema.js.map +1 -0
- package/dist/cli/commands/doctor.d.ts +9 -0
- package/dist/cli/commands/doctor.js +60 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/mcp.d.ts +7 -0
- package/dist/cli/commands/mcp.js +26 -0
- package/dist/cli/commands/mcp.js.map +1 -0
- package/dist/cli/commands/project.d.ts +5 -0
- package/dist/cli/commands/project.js +50 -0
- package/dist/cli/commands/project.js.map +1 -0
- package/dist/cli/commands/reads.d.ts +21 -0
- package/dist/cli/commands/reads.js +295 -0
- package/dist/cli/commands/reads.js.map +1 -0
- package/dist/cli/commands/snapshot.d.ts +7 -0
- package/dist/cli/commands/snapshot.js +22 -0
- package/dist/cli/commands/snapshot.js.map +1 -0
- package/dist/cli/commands/todo.d.ts +6 -0
- package/dist/cli/commands/todo.js +43 -0
- package/dist/cli/commands/todo.js.map +1 -0
- package/dist/cli/commands/writes.d.ts +9 -0
- package/dist/cli/commands/writes.js +994 -0
- package/dist/cli/commands/writes.js.map +1 -0
- package/dist/cli/exit-codes.d.ts +26 -0
- package/dist/cli/exit-codes.js +26 -0
- package/dist/cli/exit-codes.js.map +1 -0
- package/dist/cli/main.d.ts +4 -0
- package/dist/cli/main.js +73 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/output.d.ts +42 -0
- package/dist/cli/output.js +16 -0
- package/dist/cli/output.js.map +1 -0
- package/dist/cli/style.d.ts +6 -0
- package/dist/cli/style.js +25 -0
- package/dist/cli/style.js.map +1 -0
- package/dist/client.d.ts +184 -0
- package/dist/client.js +187 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +31 -0
- package/dist/config.js +76 -0
- package/dist/config.js.map +1 -0
- package/dist/contracts.d.ts +79 -0
- package/dist/contracts.js +46 -0
- package/dist/contracts.js.map +1 -0
- package/dist/db/baselines/db-v26.d.ts +8 -0
- package/dist/db/baselines/db-v26.js +16 -0
- package/dist/db/baselines/db-v26.js.map +1 -0
- package/dist/db/baselines/index.d.ts +3 -0
- package/dist/db/baselines/index.js +4 -0
- package/dist/db/baselines/index.js.map +1 -0
- package/dist/db/connection.d.ts +22 -0
- package/dist/db/connection.js +40 -0
- package/dist/db/connection.js.map +1 -0
- package/dist/db/fingerprint.d.ts +42 -0
- package/dist/db/fingerprint.js +82 -0
- package/dist/db/fingerprint.js.map +1 -0
- package/dist/db/locate.d.ts +13 -0
- package/dist/db/locate.js +44 -0
- package/dist/db/locate.js.map +1 -0
- package/dist/db/schema.d.ts +37 -0
- package/dist/db/schema.js +81 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/diagnose.d.ts +67 -0
- package/dist/diagnose.js +143 -0
- package/dist/diagnose.js.map +1 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/server.d.ts +8 -0
- package/dist/mcp/server.js +1002 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/model/dates.d.ts +38 -0
- package/dist/model/dates.js +104 -0
- package/dist/model/dates.js.map +1 -0
- package/dist/model/entities.d.ts +117 -0
- package/dist/model/entities.js +21 -0
- package/dist/model/entities.js.map +1 -0
- package/dist/model/mappers.d.ts +55 -0
- package/dist/model/mappers.js +105 -0
- package/dist/model/mappers.js.map +1 -0
- package/dist/model/occurrences.d.ts +46 -0
- package/dist/model/occurrences.js +164 -0
- package/dist/model/occurrences.js.map +1 -0
- package/dist/model/recurrence.d.ts +52 -0
- package/dist/model/recurrence.js +164 -0
- package/dist/model/recurrence.js.map +1 -0
- package/dist/paths.d.ts +6 -0
- package/dist/paths.js +30 -0
- package/dist/paths.js.map +1 -0
- package/dist/read/detail.d.ts +8 -0
- package/dist/read/detail.js +34 -0
- package/dist/read/detail.js.map +1 -0
- package/dist/read/project-view.d.ts +34 -0
- package/dist/read/project-view.js +87 -0
- package/dist/read/project-view.js.map +1 -0
- package/dist/read/queries.d.ts +39 -0
- package/dist/read/queries.js +134 -0
- package/dist/read/queries.js.map +1 -0
- package/dist/read/snapshot.d.ts +25 -0
- package/dist/read/snapshot.js +39 -0
- package/dist/read/snapshot.js.map +1 -0
- package/dist/read/tags.d.ts +17 -0
- package/dist/read/tags.js +64 -0
- package/dist/read/tags.js.map +1 -0
- package/dist/read/views.d.ts +152 -0
- package/dist/read/views.js +322 -0
- package/dist/read/views.js.map +1 -0
- package/dist/surface-copy.d.ts +15 -0
- package/dist/surface-copy.js +16 -0
- package/dist/surface-copy.js.map +1 -0
- package/dist/write/automation-probe.d.ts +12 -0
- package/dist/write/automation-probe.js +59 -0
- package/dist/write/automation-probe.js.map +1 -0
- package/dist/write/batch.d.ts +51 -0
- package/dist/write/batch.js +97 -0
- package/dist/write/batch.js.map +1 -0
- package/dist/write/capabilities.d.ts +16 -0
- package/dist/write/capabilities.js +16 -0
- package/dist/write/capabilities.js.map +1 -0
- package/dist/write/commands.d.ts +32 -0
- package/dist/write/commands.js +1274 -0
- package/dist/write/commands.js.map +1 -0
- package/dist/write/environment.d.ts +29 -0
- package/dist/write/environment.js +105 -0
- package/dist/write/environment.js.map +1 -0
- package/dist/write/experimental.d.ts +7 -0
- package/dist/write/experimental.js +39 -0
- package/dist/write/experimental.js.map +1 -0
- package/dist/write/failure-hints.d.ts +32 -0
- package/dist/write/failure-hints.js +71 -0
- package/dist/write/failure-hints.js.map +1 -0
- package/dist/write/guards.d.ts +21 -0
- package/dist/write/guards.js +312 -0
- package/dist/write/guards.js.map +1 -0
- package/dist/write/lock.d.ts +10 -0
- package/dist/write/lock.js +70 -0
- package/dist/write/lock.js.map +1 -0
- package/dist/write/operations.d.ts +262 -0
- package/dist/write/operations.js +34 -0
- package/dist/write/operations.js.map +1 -0
- package/dist/write/pipeline.d.ts +90 -0
- package/dist/write/pipeline.js +375 -0
- package/dist/write/pipeline.js.map +1 -0
- package/dist/write/planner.d.ts +40 -0
- package/dist/write/planner.js +57 -0
- package/dist/write/planner.js.map +1 -0
- package/dist/write/pre-state.d.ts +125 -0
- package/dist/write/pre-state.js +299 -0
- package/dist/write/pre-state.js.map +1 -0
- package/dist/write/reopen.d.ts +29 -0
- package/dist/write/reopen.js +63 -0
- package/dist/write/reopen.js.map +1 -0
- package/dist/write/reorder.d.ts +19 -0
- package/dist/write/reorder.js +285 -0
- package/dist/write/reorder.js.map +1 -0
- package/dist/write/undo.d.ts +53 -0
- package/dist/write/undo.js +703 -0
- package/dist/write/undo.js.map +1 -0
- package/dist/write/vectors/applescript.d.ts +5 -0
- package/dist/write/vectors/applescript.js +216 -0
- package/dist/write/vectors/applescript.js.map +1 -0
- package/dist/write/vectors/registry.d.ts +2 -0
- package/dist/write/vectors/registry.js +11 -0
- package/dist/write/vectors/registry.js.map +1 -0
- package/dist/write/vectors/types.d.ts +48 -0
- package/dist/write/vectors/types.js +2 -0
- package/dist/write/vectors/types.js.map +1 -0
- package/dist/write/vectors/url-scheme.d.ts +3 -0
- package/dist/write/vectors/url-scheme.js +176 -0
- package/dist/write/vectors/url-scheme.js.map +1 -0
- package/dist/write/verify/delta.d.ts +126 -0
- package/dist/write/verify/delta.js +281 -0
- package/dist/write/verify/delta.js.map +1 -0
- package/dist/write/verify/poller.d.ts +24 -0
- package/dist/write/verify/poller.js +28 -0
- package/dist/write/verify/poller.js.map +1 -0
- package/package.json +52 -0
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import { isRepeatingTemplate } from "./pre-state.js";
|
|
2
|
+
export const HAZARD_IDS = [
|
|
3
|
+
"H-REPEAT-SCHEDULE",
|
|
4
|
+
"H-PROJECT-COMPLETE-CHILDREN",
|
|
5
|
+
"H-CHECKLIST-REPLACE",
|
|
6
|
+
"H-REOPEN-RESOLVED-PROJECT",
|
|
7
|
+
"H-UNKNOWN-TAG",
|
|
8
|
+
"H-UNKNOWN-DESTINATION",
|
|
9
|
+
"H-AMBIGUOUS-HEADING",
|
|
10
|
+
"H-PERMANENT-DELETE",
|
|
11
|
+
"H-REORDER-SCOPE",
|
|
12
|
+
"H-REMINDER-SCOPE",
|
|
13
|
+
"H-TAG-SUBTREE-DELETE",
|
|
14
|
+
"H-BACKDATE-OPEN",
|
|
15
|
+
];
|
|
16
|
+
/** Ops where a schedule/status-class write on a repeating template is destructive. */
|
|
17
|
+
const REPEAT_SENSITIVE = [
|
|
18
|
+
"todo.complete",
|
|
19
|
+
"todo.cancel",
|
|
20
|
+
"todo.reopen",
|
|
21
|
+
"todo.move",
|
|
22
|
+
"todo.delete",
|
|
23
|
+
"todo.duplicate", // unvalidated on templates (E07 probed a plain to-do)
|
|
24
|
+
"todo.restore", // unvalidated on templates (E15 probed a plain to-do)
|
|
25
|
+
"project.move", // unvalidated on repeating projects (E14 probed a plain project)
|
|
26
|
+
"project.duplicate", // unvalidated on repeating projects (E17 probed a plain project)
|
|
27
|
+
"project.cancel", // unvalidated on repeating projects (P01 probed a plain project)
|
|
28
|
+
"project.reopen", // unvalidated on repeating projects (P02/P05 probed plain projects)
|
|
29
|
+
"project.restore", // unvalidated on repeating projects (P06 probed a plain project)
|
|
30
|
+
];
|
|
31
|
+
const GUARDS = {
|
|
32
|
+
"H-REPEAT-SCHEDULE": ({ op, params, pre }) => {
|
|
33
|
+
if (!isRepeatingTemplate(pre.target))
|
|
34
|
+
return null;
|
|
35
|
+
const touchesSchedule = op === "todo.update" && (params["when"] !== undefined || params["deadline"] !== undefined);
|
|
36
|
+
if (!touchesSchedule && !REPEAT_SENSITIVE.includes(op))
|
|
37
|
+
return null;
|
|
38
|
+
return {
|
|
39
|
+
hazard: "H-REPEAT-SCHEDULE",
|
|
40
|
+
detail: "target is a repeating template: URL scheduling writes crash Things (T12/U12); " +
|
|
41
|
+
"status/move/delete on templates are unvalidated",
|
|
42
|
+
remediation: "edit the repeat rule in the Things app; title/notes updates and checklist " +
|
|
43
|
+
"replacement remain allowed on templates",
|
|
44
|
+
};
|
|
45
|
+
},
|
|
46
|
+
"H-PROJECT-COMPLETE-CHILDREN": ({ op, params, pre }) => {
|
|
47
|
+
if (op !== "project.complete" && op !== "project.cancel")
|
|
48
|
+
return null;
|
|
49
|
+
if (pre.openChildren.length === 0)
|
|
50
|
+
return null;
|
|
51
|
+
const auto = op === "project.complete" ? "auto-complete" : "auto-cancel";
|
|
52
|
+
if (params["children"] === auto)
|
|
53
|
+
return null;
|
|
54
|
+
return {
|
|
55
|
+
hazard: "H-PROJECT-COMPLETE-CHILDREN",
|
|
56
|
+
detail: `project has ${pre.openChildren.length} open child to-do(s); the URL write would ` +
|
|
57
|
+
`silently ${op === "project.complete" ? "auto-complete" : "auto-cancel"} them ` +
|
|
58
|
+
"(no prompt, unlike the UI — T08/U08, P01)",
|
|
59
|
+
remediation: `resolve the children first, or pass children='${auto}' (--children ${auto})`,
|
|
60
|
+
};
|
|
61
|
+
},
|
|
62
|
+
"H-CHECKLIST-REPLACE": ({ op, pre, acks }) => {
|
|
63
|
+
if (op !== "todo.replace-checklist")
|
|
64
|
+
return null;
|
|
65
|
+
if (pre.checklistCount === 0 || acks.acknowledgeChecklistReset === true)
|
|
66
|
+
return null;
|
|
67
|
+
return {
|
|
68
|
+
hazard: "H-CHECKLIST-REPLACE",
|
|
69
|
+
detail: `target has ${pre.checklistCount} existing checklist item(s); replacement is wholesale ` +
|
|
70
|
+
"and destroys per-item completion state (T07/U07)",
|
|
71
|
+
remediation: "pass acknowledgeChecklistReset (--acknowledge-checklist-reset)",
|
|
72
|
+
};
|
|
73
|
+
},
|
|
74
|
+
"H-REOPEN-RESOLVED-PROJECT": ({ op, pre, acks }) => {
|
|
75
|
+
if (op !== "todo.add" && op !== "todo.move")
|
|
76
|
+
return null;
|
|
77
|
+
const dest = pre.destProject?.resolved;
|
|
78
|
+
if (dest === undefined || dest === null)
|
|
79
|
+
return null;
|
|
80
|
+
const status = pre.destProjectStatus;
|
|
81
|
+
if (status === null || status === "open")
|
|
82
|
+
return null;
|
|
83
|
+
if (acks.acknowledgeProjectReopen === true)
|
|
84
|
+
return null;
|
|
85
|
+
return {
|
|
86
|
+
hazard: "H-REOPEN-RESOLVED-PROJECT",
|
|
87
|
+
detail: `destination project "${dest.title}" is ${status}; adding an open child reopens it (T19/U19)`,
|
|
88
|
+
remediation: "pass acknowledgeProjectReopen (--acknowledge-project-reopen)",
|
|
89
|
+
};
|
|
90
|
+
},
|
|
91
|
+
"H-UNKNOWN-TAG": ({ pre }) => {
|
|
92
|
+
if (pre.missingTags.length === 0)
|
|
93
|
+
return null;
|
|
94
|
+
return {
|
|
95
|
+
hazard: "H-UNKNOWN-TAG",
|
|
96
|
+
detail: `tag(s) not found: ${pre.missingTags.join(", ")} — the app silently ignores unknown ` +
|
|
97
|
+
"tags (T03/U03), so this would be a partial write",
|
|
98
|
+
remediation: "create the tag first (things tag add) or fix the spelling",
|
|
99
|
+
};
|
|
100
|
+
},
|
|
101
|
+
"H-UNKNOWN-DESTINATION": ({ op, params, pre }) => {
|
|
102
|
+
const problems = [];
|
|
103
|
+
if (pre.destProject !== null && pre.destProject.resolved === null) {
|
|
104
|
+
problems.push(pre.destProject.matches > 1 ? "project reference is ambiguous" : "project not found");
|
|
105
|
+
}
|
|
106
|
+
if (pre.destArea !== null && pre.destArea.resolved === null) {
|
|
107
|
+
problems.push(pre.destArea.matches > 1 ? "area reference is ambiguous" : "area not found");
|
|
108
|
+
}
|
|
109
|
+
if (pre.entityTarget !== null && pre.entityTarget.resolved === null) {
|
|
110
|
+
problems.push(pre.entityTarget.matches > 1 ? "target reference is ambiguous" : "target not found");
|
|
111
|
+
}
|
|
112
|
+
const needsTarget = typeof params["uuid"] === "string" &&
|
|
113
|
+
op !== "todo.add" &&
|
|
114
|
+
op !== "project.add" &&
|
|
115
|
+
pre.target === null;
|
|
116
|
+
if (needsTarget)
|
|
117
|
+
problems.push(`no record with uuid ${String(params["uuid"])}`);
|
|
118
|
+
if (pre.target !== null) {
|
|
119
|
+
// Type/state preconditions for the Tier-2 ops: the probed commands
|
|
120
|
+
// address `project id` / trashed `to do id` specifically (E14/E15/E17).
|
|
121
|
+
if ((op === "project.move" || op === "project.duplicate") && pre.target.type !== "project") {
|
|
122
|
+
problems.push(`target ${String(params["uuid"])} is a ${pre.target.type}, not a project`);
|
|
123
|
+
}
|
|
124
|
+
if ((op === "project.cancel" || op === "project.reopen" || op === "project.restore") &&
|
|
125
|
+
pre.target.type !== "project") {
|
|
126
|
+
problems.push(`target ${String(params["uuid"])} is a ${pre.target.type}, not a project`);
|
|
127
|
+
}
|
|
128
|
+
else if (op === "project.cancel" && pre.target.type === "project") {
|
|
129
|
+
// Only open->canceled is probed (P01); re-canceling resolved
|
|
130
|
+
// projects is unvalidated.
|
|
131
|
+
if (pre.target.status !== "open") {
|
|
132
|
+
problems.push(`target project is already ${pre.target.status} — cancel needs an open project`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
else if (op === "project.reopen" && pre.target.type === "project") {
|
|
136
|
+
if (pre.target.status === "open") {
|
|
137
|
+
problems.push("target project is already open — nothing to reopen");
|
|
138
|
+
}
|
|
139
|
+
if (pre.target.trashed) {
|
|
140
|
+
problems.push("target project is in the Trash — restore it first (things project restore)");
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
else if (op === "project.restore" && pre.target.type === "project" && !pre.target.trashed) {
|
|
144
|
+
problems.push(`target ${String(params["uuid"])} is not in the Trash — restore only applies to ` +
|
|
145
|
+
"trashed projects");
|
|
146
|
+
}
|
|
147
|
+
if (op === "todo.restore") {
|
|
148
|
+
if (pre.target.type !== "to-do") {
|
|
149
|
+
problems.push(`target ${String(params["uuid"])} is a ${pre.target.type} — restore is only ` +
|
|
150
|
+
"validated for to-dos (E15); restore a trashed project via the app");
|
|
151
|
+
}
|
|
152
|
+
else if (!pre.target.trashed) {
|
|
153
|
+
problems.push(`target ${String(params["uuid"])} is not in the Trash — restore only applies to ` +
|
|
154
|
+
"trashed to-dos (a live item would just be de-scheduled into the Inbox)");
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (problems.length === 0)
|
|
159
|
+
return null;
|
|
160
|
+
return {
|
|
161
|
+
hazard: "H-UNKNOWN-DESTINATION",
|
|
162
|
+
detail: `${problems.join("; ")} — unknown destinations are silent no-ops in the app (T06/U06)`,
|
|
163
|
+
remediation: "verify the uuid/name with `things search` or `things areas`/`things projects`",
|
|
164
|
+
};
|
|
165
|
+
},
|
|
166
|
+
"H-AMBIGUOUS-HEADING": ({ pre }) => {
|
|
167
|
+
if (pre.destHeading === null)
|
|
168
|
+
return null;
|
|
169
|
+
if (pre.destHeading.resolved !== null)
|
|
170
|
+
return null;
|
|
171
|
+
return {
|
|
172
|
+
hazard: "H-AMBIGUOUS-HEADING",
|
|
173
|
+
detail: pre.destHeading.matches > 1
|
|
174
|
+
? "multiple headings with that name exist in the destination project"
|
|
175
|
+
: "heading not found in the destination project (the heading param never creates one — T09/U09)",
|
|
176
|
+
remediation: "rename the duplicate headings, or omit --heading",
|
|
177
|
+
};
|
|
178
|
+
},
|
|
179
|
+
"H-REMINDER-SCOPE": ({ op, params }) => {
|
|
180
|
+
if (op !== "todo.add" && op !== "todo.update" && op !== "project.update")
|
|
181
|
+
return null;
|
|
182
|
+
if (!("reminder" in params) || params["reminder"] === undefined)
|
|
183
|
+
return null;
|
|
184
|
+
const when = params["when"];
|
|
185
|
+
const isDate = typeof when === "string" && /^\d{4}-\d{2}-\d{2}$/.test(when);
|
|
186
|
+
if (params["reminder"] === null) {
|
|
187
|
+
// The clear IS a bare when= write — but only today/evening honor it;
|
|
188
|
+
// dated reminders are STICKY (persist through same-date AND re-dated
|
|
189
|
+
// bare when=, R20/R21). No URL clear path exists for them.
|
|
190
|
+
if (when === "today" || when === "evening")
|
|
191
|
+
return null;
|
|
192
|
+
return {
|
|
193
|
+
hazard: "H-REMINDER-SCOPE",
|
|
194
|
+
detail: isDate
|
|
195
|
+
? "dated reminders cannot be cleared via the URL scheme — they persist through " +
|
|
196
|
+
"bare when= re-schedules (R20/R21)"
|
|
197
|
+
: "clearing a reminder IS a bare when= write (R07) — when: today|evening must be " +
|
|
198
|
+
"re-stated in the same call",
|
|
199
|
+
remediation: isDate
|
|
200
|
+
? "re-schedule with `--when today --clear-reminder` first (then re-date), or clear " +
|
|
201
|
+
"it in the app"
|
|
202
|
+
: "pass when today|evening together with --clear-reminder",
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
if (when === "today" || when === "evening" || isDate)
|
|
206
|
+
return null;
|
|
207
|
+
return {
|
|
208
|
+
hazard: "H-REMINDER-SCOPE",
|
|
209
|
+
detail: "reminders require a scheduled when: today|evening|YYYY-MM-DD (R-suite; " +
|
|
210
|
+
"anytime/someday carry no date for the reminder to attach to)",
|
|
211
|
+
remediation: "pass when today|evening|YYYY-MM-DD together with the reminder",
|
|
212
|
+
};
|
|
213
|
+
},
|
|
214
|
+
"H-BACKDATE-OPEN": ({ op, params, pre }) => {
|
|
215
|
+
if (op !== "todo.backdate" || params["completionDate"] === undefined)
|
|
216
|
+
return null;
|
|
217
|
+
const status = pre.target?.type === "to-do" ? pre.target.status : null;
|
|
218
|
+
if (status === "completed" || status === "canceled")
|
|
219
|
+
return null;
|
|
220
|
+
return {
|
|
221
|
+
hazard: "H-BACKDATE-OPEN",
|
|
222
|
+
detail: "completionDate can only be rewritten on a completed or canceled to-do — this one " +
|
|
223
|
+
`is ${status ?? "not a to-do / not found"}`,
|
|
224
|
+
remediation: "complete it first (todo.complete), then backdate",
|
|
225
|
+
};
|
|
226
|
+
},
|
|
227
|
+
"H-REORDER-SCOPE": ({ op, params, pre }) => {
|
|
228
|
+
if (op !== "reorder" || pre.reorder === null)
|
|
229
|
+
return null;
|
|
230
|
+
const problems = [];
|
|
231
|
+
// The `reorder` operation IS the native private command. Evening must
|
|
232
|
+
// never reach it: a native reorder normalizes startBucket to the Today
|
|
233
|
+
// list, silently de-evening-ing members (O03). The evening scope is
|
|
234
|
+
// served by the bounce orchestrator in reorder.ts instead.
|
|
235
|
+
if (params["scope"] === "evening") {
|
|
236
|
+
return {
|
|
237
|
+
hazard: "H-REORDER-SCOPE",
|
|
238
|
+
detail: "evening reorder is bounce-only: the native command would silently clear " +
|
|
239
|
+
"startBucket on every listed item (O03)",
|
|
240
|
+
remediation: "use write.reorder / `things reorder --scope evening` (bounce strategy)",
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
if ((params["scope"] === "today" ||
|
|
244
|
+
params["scope"] === "evening" ||
|
|
245
|
+
params["scope"] === "inbox" ||
|
|
246
|
+
params["scope"] === "someday" ||
|
|
247
|
+
params["scope"] === "projects") &&
|
|
248
|
+
params["container"] !== undefined) {
|
|
249
|
+
problems.push("container is only valid for project/area/headings scopes");
|
|
250
|
+
}
|
|
251
|
+
const uuids = params["uuids"];
|
|
252
|
+
if (!Array.isArray(uuids) || uuids.length === 0) {
|
|
253
|
+
problems.push("no uuids given");
|
|
254
|
+
}
|
|
255
|
+
if (pre.reorder.duplicates.length > 0) {
|
|
256
|
+
problems.push(`duplicated uuid(s): ${pre.reorder.duplicates.join(", ")}`);
|
|
257
|
+
}
|
|
258
|
+
if (pre.reorder.mixedTypes) {
|
|
259
|
+
problems.push("mixes to-dos and projects in one area reorder — only same-type area reorders are " +
|
|
260
|
+
"validated (O05/O10 to-dos, O14 projects); issue two requests instead");
|
|
261
|
+
}
|
|
262
|
+
for (const r of pre.reorder.rejected) {
|
|
263
|
+
problems.push(`${r.uuid} ${r.reason}`);
|
|
264
|
+
}
|
|
265
|
+
if (problems.length === 0)
|
|
266
|
+
return null;
|
|
267
|
+
return {
|
|
268
|
+
hazard: "H-REORDER-SCOPE",
|
|
269
|
+
detail: `reorder request rejected — every uuid must be an eligible member of the scope, ` +
|
|
270
|
+
`exactly once: ${problems.join("; ")}`,
|
|
271
|
+
remediation: "read the scope first (things today / things project-view / things area) and pass " +
|
|
272
|
+
"only its eligible members in the desired order",
|
|
273
|
+
};
|
|
274
|
+
},
|
|
275
|
+
"H-TAG-SUBTREE-DELETE": ({ op, pre, acks }) => {
|
|
276
|
+
if (op !== "tag.delete")
|
|
277
|
+
return null;
|
|
278
|
+
if (pre.childTags.length === 0 || acks.acknowledgeTagSubtree === true)
|
|
279
|
+
return null;
|
|
280
|
+
return {
|
|
281
|
+
hazard: "H-TAG-SUBTREE-DELETE",
|
|
282
|
+
detail: `deleting this tag CASCADE-DELETES its ${pre.childTags.length} descendant tag(s) ` +
|
|
283
|
+
`permanently (P16): ${pre.childTags.join(", ")}`,
|
|
284
|
+
remediation: "re-parent the children first (things tag update <child> --parent <new>), or pass " +
|
|
285
|
+
"acknowledgeTagSubtree (--acknowledge-subtree) to delete the whole subtree",
|
|
286
|
+
};
|
|
287
|
+
},
|
|
288
|
+
"H-PERMANENT-DELETE": ({ op, acks }) => {
|
|
289
|
+
if (op !== "area.delete" && op !== "tag.delete" && op !== "trash.empty")
|
|
290
|
+
return null;
|
|
291
|
+
if (acks.dangerouslyPermanent === true)
|
|
292
|
+
return null;
|
|
293
|
+
const what = op === "trash.empty"
|
|
294
|
+
? "empty-trash hard-deletes every trashed row"
|
|
295
|
+
: `${op === "area.delete" ? "areas" : "tags"} are deleted PERMANENTLY (no Trash, A25/A26)`;
|
|
296
|
+
return {
|
|
297
|
+
hazard: "H-PERMANENT-DELETE",
|
|
298
|
+
detail: `${what}; no tombstones are written while sync is off`,
|
|
299
|
+
remediation: "pass dangerouslyPermanent (--dangerously-permanent) to proceed",
|
|
300
|
+
};
|
|
301
|
+
},
|
|
302
|
+
};
|
|
303
|
+
export function evaluateGuards(hazards, input) {
|
|
304
|
+
for (const id of hazards) {
|
|
305
|
+
const guard = GUARDS[id];
|
|
306
|
+
const block = guard(input);
|
|
307
|
+
if (block !== null)
|
|
308
|
+
return block;
|
|
309
|
+
}
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
//# sourceMappingURL=guards.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guards.js","sourceRoot":"","sources":["../../src/write/guards.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare class MutationLockError extends Error {
|
|
2
|
+
constructor(message: string);
|
|
3
|
+
}
|
|
4
|
+
export interface MutationLock {
|
|
5
|
+
release(): void;
|
|
6
|
+
}
|
|
7
|
+
export declare function acquireMutationLock(path: string, options?: {
|
|
8
|
+
waitMs?: number;
|
|
9
|
+
sleep?: (ms: number) => Promise<void>;
|
|
10
|
+
}): Promise<MutationLock>;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Advisory mutation lockfile: serializes mutations across concurrent CLI
|
|
3
|
+
* invocations so create-probe verification is never ambiguous (design §4).
|
|
4
|
+
* Stale locks (dead pid) are stolen; live locks are awaited with backoff.
|
|
5
|
+
*/
|
|
6
|
+
import { mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { dirname } from "node:path";
|
|
8
|
+
export class MutationLockError extends Error {
|
|
9
|
+
constructor(message) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = "MutationLockError";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function pidAlive(pid) {
|
|
15
|
+
try {
|
|
16
|
+
process.kill(pid, 0);
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export async function acquireMutationLock(path, options = {}) {
|
|
24
|
+
const waitMs = options.waitMs ?? 30_000;
|
|
25
|
+
const sleep = options.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
26
|
+
const deadline = Date.now() + waitMs;
|
|
27
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
28
|
+
for (;;) {
|
|
29
|
+
try {
|
|
30
|
+
const payload = { pid: process.pid, ts: new Date().toISOString() };
|
|
31
|
+
writeFileSync(path, JSON.stringify(payload), { flag: "wx" });
|
|
32
|
+
return {
|
|
33
|
+
release() {
|
|
34
|
+
try {
|
|
35
|
+
unlinkSync(path);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// already gone — fine
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
if (err.code !== "EEXIST")
|
|
45
|
+
throw err;
|
|
46
|
+
let holder = null;
|
|
47
|
+
try {
|
|
48
|
+
holder = JSON.parse(readFileSync(path, "utf8"));
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
holder = null; // torn write — treat as stale
|
|
52
|
+
}
|
|
53
|
+
if (holder === null || !pidAlive(holder.pid)) {
|
|
54
|
+
try {
|
|
55
|
+
unlinkSync(path); // stale: holder is dead
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// raced another steal — loop and retry
|
|
59
|
+
}
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (Date.now() >= deadline) {
|
|
63
|
+
throw new MutationLockError(`another mutation is in progress (pid ${holder.pid} since ${holder.ts}); ` +
|
|
64
|
+
`waited ${waitMs}ms for ${path}`);
|
|
65
|
+
}
|
|
66
|
+
await sleep(150);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=lock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lock.js","sourceRoot":"","sources":["../../src/write/lock.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IAC1C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;IAClC,CAAC;CACF;AAOD,SAAS,QAAQ,CAAC,GAAW;IAC3B,IAAI,CAAC;QACH,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAMD,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,IAAY,EACZ,UAAsE,EAAE;IAExE,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,MAAM,CAAC;IACxC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;IAC/E,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC;IACrC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAE9C,SAAS,CAAC;QACR,IAAI,CAAC;YACH,MAAM,OAAO,GAAgB,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC;YAChF,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;YAC7D,OAAO;gBACL,OAAO;oBACL,IAAI,CAAC;wBACH,UAAU,CAAC,IAAI,CAAC,CAAC;oBACnB,CAAC;oBAAC,MAAM,CAAC;wBACP,sBAAsB;oBACxB,CAAC;gBACH,CAAC;aACF,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ;gBAAE,MAAM,GAAG,CAAC;YAChE,IAAI,MAAM,GAAuB,IAAI,CAAC;YACtC,IAAI,CAAC;gBACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAgB,CAAC;YACjE,CAAC;YAAC,MAAM,CAAC;gBACP,MAAM,GAAG,IAAI,CAAC,CAAC,8BAA8B;YAC/C,CAAC;YACD,IAAI,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC7C,IAAI,CAAC;oBACH,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,wBAAwB;gBAC5C,CAAC;gBAAC,MAAM,CAAC;oBACP,uCAAuC;gBACzC,CAAC;gBACD,SAAS;YACX,CAAC;YACD,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,QAAQ,EAAE,CAAC;gBAC3B,MAAM,IAAI,iBAAiB,CACzB,wCAAwC,MAAM,CAAC,GAAG,UAAU,MAAM,CAAC,EAAE,KAAK;oBACxE,UAAU,MAAM,UAAU,IAAI,EAAE,CACnC,CAAC;YACJ,CAAC;YACD,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC;QACnB,CAAC;IACH,CAAC;AACH,CAAC"}
|