things-api 0.11.0 → 0.13.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.
Files changed (208) hide show
  1. package/README.md +16 -5
  2. package/dist/audit/schema.d.ts +7 -0
  3. package/dist/audit/schema.js.map +1 -1
  4. package/dist/cli/canonical-ref.d.ts +21 -0
  5. package/dist/cli/canonical-ref.js +23 -0
  6. package/dist/cli/canonical-ref.js.map +1 -0
  7. package/dist/cli/commands/area.d.ts +18 -11
  8. package/dist/cli/commands/area.js +81 -77
  9. package/dist/cli/commands/area.js.map +1 -1
  10. package/dist/cli/commands/doctor.d.ts +1 -2
  11. package/dist/cli/commands/doctor.js +11 -1
  12. package/dist/cli/commands/doctor.js.map +1 -1
  13. package/dist/cli/commands/install-skill.d.ts +19 -4
  14. package/dist/cli/commands/install-skill.js +130 -52
  15. package/dist/cli/commands/install-skill.js.map +1 -1
  16. package/dist/cli/commands/mcp.js +21 -1
  17. package/dist/cli/commands/mcp.js.map +1 -1
  18. package/dist/cli/commands/project.d.ts +1 -0
  19. package/dist/cli/commands/project.js +92 -48
  20. package/dist/cli/commands/project.js.map +1 -1
  21. package/dist/cli/commands/reads.d.ts +5 -1
  22. package/dist/cli/commands/reads.js +110 -31
  23. package/dist/cli/commands/reads.js.map +1 -1
  24. package/dist/cli/commands/setup.js +11 -4
  25. package/dist/cli/commands/setup.js.map +1 -1
  26. package/dist/cli/commands/show.js +75 -36
  27. package/dist/cli/commands/show.js.map +1 -1
  28. package/dist/cli/commands/todo.js +2 -2
  29. package/dist/cli/commands/todo.js.map +1 -1
  30. package/dist/cli/commands/writes.js +795 -144
  31. package/dist/cli/commands/writes.js.map +1 -1
  32. package/dist/cli/did-you-mean.d.ts +14 -3
  33. package/dist/cli/did-you-mean.js +44 -7
  34. package/dist/cli/did-you-mean.js.map +1 -1
  35. package/dist/cli/dry-run.d.ts +55 -0
  36. package/dist/cli/dry-run.js +92 -0
  37. package/dist/cli/dry-run.js.map +1 -0
  38. package/dist/cli/glyphs.d.ts +16 -2
  39. package/dist/cli/glyphs.js +60 -12
  40. package/dist/cli/glyphs.js.map +1 -1
  41. package/dist/cli/help.js +69 -13
  42. package/dist/cli/help.js.map +1 -1
  43. package/dist/cli/main.js +8 -1
  44. package/dist/cli/main.js.map +1 -1
  45. package/dist/cli/move-hint.d.ts +3 -2
  46. package/dist/cli/move-hint.js +2 -2
  47. package/dist/cli/move-hint.js.map +1 -1
  48. package/dist/cli/read-driver.d.ts +53 -5
  49. package/dist/cli/read-driver.js +82 -15
  50. package/dist/cli/read-driver.js.map +1 -1
  51. package/dist/cli/ref-render.d.ts +14 -0
  52. package/dist/cli/ref-render.js +9 -0
  53. package/dist/cli/ref-render.js.map +1 -0
  54. package/dist/cli/render.d.ts +40 -14
  55. package/dist/cli/render.js +128 -55
  56. package/dist/cli/render.js.map +1 -1
  57. package/dist/cli/resolve-invocation.d.ts +7 -5
  58. package/dist/cli/resolve-invocation.js +16 -7
  59. package/dist/cli/resolve-invocation.js.map +1 -1
  60. package/dist/cli/skill-check.d.ts +12 -6
  61. package/dist/cli/skill-check.js +31 -14
  62. package/dist/cli/skill-check.js.map +1 -1
  63. package/dist/cli/skill.d.ts +8 -0
  64. package/dist/cli/skill.js +10 -0
  65. package/dist/cli/skill.js.map +1 -1
  66. package/dist/cli/verb-hint.js +1 -1
  67. package/dist/cli/verb-hint.js.map +1 -1
  68. package/dist/client.d.ts +158 -26
  69. package/dist/client.js +254 -38
  70. package/dist/client.js.map +1 -1
  71. package/dist/config.d.ts +66 -2
  72. package/dist/config.js +120 -12
  73. package/dist/config.js.map +1 -1
  74. package/dist/contracts.d.ts +187 -28
  75. package/dist/contracts.js +22 -1
  76. package/dist/contracts.js.map +1 -1
  77. package/dist/db/baselines/db-v26.js +4 -1
  78. package/dist/db/baselines/db-v26.js.map +1 -1
  79. package/dist/diagnose.d.ts +34 -2
  80. package/dist/diagnose.js +36 -1
  81. package/dist/diagnose.js.map +1 -1
  82. package/dist/index.d.ts +21 -8
  83. package/dist/index.js +26 -3
  84. package/dist/index.js.map +1 -1
  85. package/dist/mcp/server.d.ts +9 -0
  86. package/dist/mcp/server.js +790 -322
  87. package/dist/mcp/server.js.map +1 -1
  88. package/dist/model/entities.d.ts +57 -4
  89. package/dist/model/entities.js.map +1 -1
  90. package/dist/model/mappers.d.ts +2 -0
  91. package/dist/model/mappers.js +44 -2
  92. package/dist/model/mappers.js.map +1 -1
  93. package/dist/model/recurrence.d.ts +8 -1
  94. package/dist/model/recurrence.js.map +1 -1
  95. package/dist/model/serialize.js +15 -3
  96. package/dist/model/serialize.js.map +1 -1
  97. package/dist/read/area-filter.d.ts +76 -0
  98. package/dist/read/area-filter.js +58 -0
  99. package/dist/read/area-filter.js.map +1 -0
  100. package/dist/read/area-view.d.ts +22 -11
  101. package/dist/read/area-view.js +54 -28
  102. package/dist/read/area-view.js.map +1 -1
  103. package/dist/read/detail.js +15 -8
  104. package/dist/read/detail.js.map +1 -1
  105. package/dist/read/predicates.d.ts +18 -0
  106. package/dist/read/predicates.js +19 -0
  107. package/dist/read/predicates.js.map +1 -1
  108. package/dist/read/project-view.d.ts +91 -15
  109. package/dist/read/project-view.js +135 -37
  110. package/dist/read/project-view.js.map +1 -1
  111. package/dist/read/pseudo-area.d.ts +29 -0
  112. package/dist/read/pseudo-area.js +27 -0
  113. package/dist/read/pseudo-area.js.map +1 -0
  114. package/dist/read/queries.d.ts +175 -19
  115. package/dist/read/queries.js +376 -32
  116. package/dist/read/queries.js.map +1 -1
  117. package/dist/read/scope.d.ts +126 -0
  118. package/dist/read/scope.js +161 -0
  119. package/dist/read/scope.js.map +1 -0
  120. package/dist/read/search-rank.d.ts +2 -5
  121. package/dist/read/shape.d.ts +181 -0
  122. package/dist/read/shape.js +737 -0
  123. package/dist/read/shape.js.map +1 -0
  124. package/dist/read/show-target.d.ts +9 -1
  125. package/dist/read/show-target.js +77 -17
  126. package/dist/read/show-target.js.map +1 -1
  127. package/dist/read/stage.d.ts +199 -0
  128. package/dist/read/stage.js +125 -0
  129. package/dist/read/stage.js.map +1 -0
  130. package/dist/read/truncation.d.ts +7 -12
  131. package/dist/read/truncation.js +56 -21
  132. package/dist/read/truncation.js.map +1 -1
  133. package/dist/read/views.d.ts +68 -16
  134. package/dist/read/views.js +206 -52
  135. package/dist/read/views.js.map +1 -1
  136. package/dist/surface-copy.d.ts +16 -0
  137. package/dist/surface-copy.js +16 -0
  138. package/dist/surface-copy.js.map +1 -1
  139. package/dist/write/batch.d.ts +49 -10
  140. package/dist/write/batch.js +423 -71
  141. package/dist/write/batch.js.map +1 -1
  142. package/dist/write/commands.js +268 -55
  143. package/dist/write/commands.js.map +1 -1
  144. package/dist/write/guards.d.ts +1 -1
  145. package/dist/write/guards.js +71 -10
  146. package/dist/write/guards.js.map +1 -1
  147. package/dist/write/heading.d.ts +10 -1
  148. package/dist/write/heading.js +35 -5
  149. package/dist/write/heading.js.map +1 -1
  150. package/dist/write/make-repeating-project.d.ts +2 -2
  151. package/dist/write/make-repeating-project.js +9 -9
  152. package/dist/write/make-repeating-project.js.map +1 -1
  153. package/dist/write/move.d.ts +134 -0
  154. package/dist/write/move.js +1874 -0
  155. package/dist/write/move.js.map +1 -0
  156. package/dist/write/operations.d.ts +105 -19
  157. package/dist/write/operations.js +56 -7
  158. package/dist/write/operations.js.map +1 -1
  159. package/dist/write/opid.d.ts +31 -0
  160. package/dist/write/opid.js +30 -0
  161. package/dist/write/opid.js.map +1 -0
  162. package/dist/write/pipeline.d.ts +70 -3
  163. package/dist/write/pipeline.js +198 -33
  164. package/dist/write/pipeline.js.map +1 -1
  165. package/dist/write/pre-state.d.ts +172 -8
  166. package/dist/write/pre-state.js +467 -25
  167. package/dist/write/pre-state.js.map +1 -1
  168. package/dist/write/reorder.d.ts +23 -3
  169. package/dist/write/reorder.js +2106 -86
  170. package/dist/write/reorder.js.map +1 -1
  171. package/dist/write/reversibility.js +19 -7
  172. package/dist/write/reversibility.js.map +1 -1
  173. package/dist/write/scope-guard.d.ts +31 -0
  174. package/dist/write/scope-guard.js +162 -0
  175. package/dist/write/scope-guard.js.map +1 -0
  176. package/dist/write/undo.js +120 -13
  177. package/dist/write/undo.js.map +1 -1
  178. package/dist/write/vectors/applescript.js +16 -7
  179. package/dist/write/vectors/applescript.js.map +1 -1
  180. package/dist/write/vectors/shortcuts.js +1 -1
  181. package/dist/write/vectors/shortcuts.js.map +1 -1
  182. package/dist/write/vectors/simulator.js +83 -27
  183. package/dist/write/vectors/simulator.js.map +1 -1
  184. package/dist/write/vectors/types.d.ts +11 -0
  185. package/dist/write/vectors/ui-certification.d.ts +3 -3
  186. package/dist/write/vectors/ui-certification.js +41 -5
  187. package/dist/write/vectors/ui-certification.js.map +1 -1
  188. package/dist/write/vectors/ui-drag.d.ts +21 -1
  189. package/dist/write/vectors/ui-drag.js +138 -27
  190. package/dist/write/vectors/ui-drag.js.map +1 -1
  191. package/dist/write/vectors/ui-recipes.d.ts +21 -1
  192. package/dist/write/vectors/ui-recipes.js +166 -7
  193. package/dist/write/vectors/ui-recipes.js.map +1 -1
  194. package/dist/write/vectors/ui.d.ts +25 -1
  195. package/dist/write/vectors/ui.js +142 -33
  196. package/dist/write/vectors/ui.js.map +1 -1
  197. package/dist/write/verify/delta.d.ts +17 -1
  198. package/dist/write/verify/delta.js +106 -29
  199. package/dist/write/verify/delta.js.map +1 -1
  200. package/package.json +7 -1
  201. package/schema/envelope.schema.json +397 -0
  202. package/skills/things-cli/SKILL.md +36 -17
  203. package/skills/things-cli/references/banner.md +35 -0
  204. package/skills/things-cli/references/contracts.md +32 -9
  205. package/skills/things-cli/references/errors.md +57 -0
  206. package/skills/things-cli/references/gui.md +1 -1
  207. package/skills/things-cli/references/model.md +18 -7
  208. package/skills/things-cli/references/ordering.md +88 -0
@@ -3,8 +3,9 @@
3
3
  * schema manifest so removed columns fail loudly (drift), never silently.
4
4
  */
5
5
  import type { DatabaseSync } from "node:sqlite";
6
- import type { Ref } from "../model/entities.ts";
6
+ import { type Ref } from "../model/entities.ts";
7
7
  import type { ChecklistRow, TaskRow } from "../model/mappers.ts";
8
+ import { type CandidateRef, type RefKind, type RefPromoter } from "./shape.ts";
8
9
  /** Rows that repeat via a template are normal; template rows are invisible in list views. */
9
10
  export declare const NOT_TEMPLATE = "(t.rt1_recurrenceRule IS NULL AND t.repeater IS NULL)";
10
11
  /**
@@ -83,37 +84,44 @@ export declare function stripThingsUri(ref: string): string;
83
84
  * that matches nothing reads identically wherever the miss is reported.
84
85
  */
85
86
  export declare function noUuidMatch(entity: string, ref: string): string;
86
- /** A disambiguation candidate for a reference-resolution failure. */
87
- export interface RefCandidate {
88
- uuid: string;
89
- title: string;
90
- /** Optional context that distinguishes same-named candidates (area for a project, parent path for a tag). */
91
- context?: string;
92
- }
93
87
  /**
94
88
  * A reference (uuid / partial-uuid / name) that did not resolve to exactly one
95
89
  * entity. Extends RangeError so every existing `instanceof RangeError` handler
96
90
  * keeps treating it as a usage-class failure — but the surfaces that know about
97
91
  * it (CLI --json envelope, MCP tool result) additionally lift the structured
98
- * `candidates` onto `error.details.candidates` so an agent can self-correct
92
+ * `candidates` onto `error.detail.candidates` so an agent can self-correct
99
93
  * without re-parsing the prose message. `code` mirrors the envelope error code.
100
94
  *
101
95
  * PUBLIC API — exported from src/index.ts. This is the one error the consumer
102
96
  * surfaces catch to render structured disambiguation; its `code`
103
- * ("not-found" | "ambiguous") and `candidates` ({@link RefCandidate}[]) are the
104
- * documented machine shape (docs/design/architecture.md, Consumer boundary).
97
+ * ("not-found" | "ambiguous") and `candidates` ({@link CandidateRef}[], the ONE
98
+ * fixed candidate shape) are the documented machine shape
99
+ * (docs/design/architecture.md, Consumer boundary). The list is capped at
100
+ * {@link CANDIDATE_CAP}; on overflow the `message` states the total.
105
101
  */
106
102
  export declare class ReferenceResolutionError extends RangeError {
107
103
  readonly code: "not-found" | "ambiguous";
108
104
  readonly ref: string;
109
- readonly candidates: RefCandidate[];
105
+ readonly candidates: CandidateRef[];
110
106
  constructor(message: string, opts: {
111
107
  code: "not-found" | "ambiguous";
112
108
  ref: string;
113
- candidates?: RefCandidate[];
109
+ candidates?: CandidateRef[];
114
110
  });
115
111
  }
116
- export declare function resolveTaskUuidPrefix(db: DatabaseSync, refRaw: string, entity?: string): string;
112
+ /**
113
+ * An optional membership clause (on alias `t`) restricting a resolver to
114
+ * in-scope rows — the container-scope no-oracle mechanism. When supplied, an
115
+ * out-of-scope row resolves to "not found" through the IDENTICAL code path a
116
+ * nonexistent one does, so the two are byte-indistinguishable. Built by
117
+ * `src/read/scope.ts`; queries.ts treats it as opaque SQL to avoid a runtime
118
+ * import cycle.
119
+ */
120
+ export interface ScopeClause {
121
+ where: string;
122
+ binds: (string | number)[];
123
+ }
124
+ export declare function resolveTaskUuidPrefix(db: DatabaseSync, refRaw: string, entity?: string, scope?: ScopeClause): string;
117
125
  /**
118
126
  * Fold a name to its match key: NFC + case-fold + strip all whitespace and
119
127
  * dashes/hyphens (ASCII hyphen, the U+2010–2015 dash block, U+2212 minus).
@@ -121,6 +129,9 @@ export declare function resolveTaskUuidPrefix(db: DatabaseSync, refRaw: string,
121
129
  * docs/design/reference-resolution.md.
122
130
  */
123
131
  export declare function normalizeNameKey(s: string): string;
132
+ /** The fused TTY ref form `Title [8charPrefix]` — the round-trippable decorated ref every candidate renders. */
133
+ export declare const REF_PREFIX_LEN = 8;
134
+ export declare function fusedRef(title: string, uuid: string): string;
124
135
  export interface NamedResolution {
125
136
  resolved: {
126
137
  uuid: string;
@@ -147,7 +158,21 @@ export interface NamedResolution {
147
158
  */
148
159
  export declare function resolveNamedRef(db: DatabaseSync, table: string, extraWhere: string, extraBinds: (string | number)[], refRaw: string, options?: {
149
160
  prefixTier?: boolean;
161
+ scopeWhere?: string;
162
+ scopeBinds?: (string | number)[];
163
+ /**
164
+ * An extra clause AND-ed into the NAME tiers ONLY (exact/case-insensitive/
165
+ * normalized title) — never the uuid-exact or uuid-prefix tiers. Lets a
166
+ * resolver narrow name resolution (e.g. project write targets to OPEN rows)
167
+ * while a UUID / partial-uuid — explicit intent — still reaches every row.
168
+ */
169
+ nameExtraWhere?: string;
150
170
  }): NamedResolution;
171
+ export declare function deadNameMatchHint(counts: {
172
+ trashed?: number;
173
+ logbook?: number;
174
+ completed?: number;
175
+ }): string;
151
176
  /**
152
177
  * Resolve a PROJECT write target from a uuid, partial-uuid, or unique name.
153
178
  * Project write verbs (`things project update <ref>`, etc.) accept names
@@ -159,24 +184,135 @@ export declare function resolveNamedRef(db: DatabaseSync, table: string, extraWh
159
184
  * targeted "that is a to-do, not a project" message rather than a misleading
160
185
  * not-found. Otherwise the ref resolves as a project NAME through the SAME
161
186
  * tiered {@link resolveNamedRef} matching the read side uses (shared core, not
162
- * a fork) across projects (trashed included, for `project restore`), fail-
163
- * closed with a candidate listing on an ambiguous name so a duplicated project
164
- * title is disambiguated by uuid rather than guessed.
187
+ * a fork).
188
+ *
189
+ * The NAME pool is LIVE + OPEN-scoped by default (`trashed = 0 AND status = 0`)
190
+ * — a trashed OR completed/canceled project never resolves-by-name or appears as
191
+ * an ambiguity candidate for an ordinary write verb. A completed project is
192
+ * excluded deliberately (broader than "logged", which needs the log-boundary
193
+ * clock this pure resolver lacks): placing an open child in it strands the child
194
+ * one sweep later (PLOG1). A name that matches ONLY non-open/trashed rows fails
195
+ * not-found with an honest hint (`things trash` / target-by-uuid) rather than a
196
+ * dead candidate. A UUID / partial-uuid resolves FIRST (via
197
+ * {@link resolveTaskUuidPrefix}, status-blind), so explicit-uuid intent still
198
+ * reaches a completed/logged project. `includeNonOpen` (the `project.reopen`
199
+ * verb — its whole point is a non-open target) widens the name pool to
200
+ * completed/canceled rows; `includeTrashed` (the trash-domain `project.restore`
201
+ * op) widens it to trashed rows so a restore-by-name can disambiguate them.
202
+ * Fail-closed with a candidate listing on an ambiguous name.
165
203
  */
166
- export declare function resolveProjectWriteTarget(db: DatabaseSync, refRaw: string): string;
204
+ export declare function resolveProjectWriteTarget(db: DatabaseSync, refRaw: string, scope?: {
205
+ task: ScopeClause;
206
+ named: {
207
+ where: string;
208
+ binds: (string | number)[];
209
+ };
210
+ }, includeTrashed?: boolean, includeNonOpen?: boolean): string;
167
211
  export declare function resolveTagUuid(db: DatabaseSync, ref: string): string;
212
+ /** Options shared by the read-side project resolvers (a name-tier narrowing subset). */
213
+ interface ReadProjectOptions {
214
+ prefixTier?: boolean;
215
+ scopeWhere?: string;
216
+ scopeBinds?: (string | number)[];
217
+ }
218
+ /**
219
+ * A read-side PROJECT NAME resolution verdict under the liveness law (never
220
+ * throws) — the shared core behind the throwing {@link resolveProjectUuid}
221
+ * (`project show`) and the cross-kind {@link classifyShowTarget} router.
222
+ *
223
+ * The uuid / partial-uuid tiers reach EVERY project row (explicit intent stays
224
+ * able to view a trashed project by id); the NAME tiers resolve against LIVE
225
+ * (untrashed) rows only, so a dead twin never shadows a live one nor inflates
226
+ * an ambiguity count. When the live pool is empty, the TRASHED pool is consulted
227
+ * for the reads-only ergonomic fallback: a UNIQUELY-named trashed project still
228
+ * resolves by name (the render discloses it — the card's own `(trashed)` marker
229
+ * / `stage: "trash"`), while several dead twins report honestly instead.
230
+ */
231
+ export interface ReadProjectVerdict {
232
+ /** The resolved winner — a live unique, a uuid/partial-uuid, or the unique-dead fallback; else null. */
233
+ resolved: {
234
+ uuid: string;
235
+ title: string;
236
+ } | null;
237
+ /** ALL live name-matching rows at the deciding tier (0, 1, or many). */
238
+ liveRows: {
239
+ uuid: string;
240
+ title: string;
241
+ }[];
242
+ /** 0 / 1 / >1 — the LIVE match count (the ambiguity count when > 1). */
243
+ liveMatches: number;
244
+ /** Count of TRASHED-name twins — feeds the disclosure tail / dead-hint. */
245
+ trashedMatches: number;
246
+ }
247
+ export declare function readProjectNameVerdict(db: DatabaseSync, ref: string, options?: ReadProjectOptions): ReadProjectVerdict;
168
248
  /**
169
249
  * Write destinations stay strict (a trashed project is not a valid target);
170
250
  * READ surfaces pass `trashed: true` so a project in the Trash can still be
171
- * viewed its would-be-recovered children are only visible there.
251
+ * viewed. Under `trashed: true` the read-side liveness law applies: an explicit
252
+ * uuid / partial-uuid reaches any project, but a NAME resolves against LIVE rows
253
+ * only (a dead same-name twin never shadows a live one), with the reads-only
254
+ * unique-dead fallback + trash disclosure ({@link readProjectNameVerdict}).
172
255
  */
173
256
  export declare function resolveProjectUuid(db: DatabaseSync, ref: string, options?: {
174
257
  trashed?: boolean;
175
258
  prefixTier?: boolean;
259
+ scopeWhere?: string;
260
+ scopeBinds?: (string | number)[];
176
261
  }): string;
262
+ /**
263
+ * The disclosure tail appended to a read-side project ambiguity when the
264
+ * uuid-reachable pool holds additional TRASHED twins of the ambiguous name. The
265
+ * ambiguity COUNT stays over live rows (coherent with the rendered candidate
266
+ * list); the dead ones are disclosed separately rather than inflating it. Empty
267
+ * when none are trashed.
268
+ */
269
+ export declare function trashDisclosureTail(deadCount: number): string;
177
270
  export declare function resolveAreaUuid(db: DatabaseSync, ref: string, options?: {
178
271
  prefixTier?: boolean;
272
+ scopeWhere?: string;
273
+ scopeBinds?: (string | number)[];
179
274
  }): string;
275
+ /**
276
+ * Resolve a HEADING SELECTOR (spec §2) inside one project through the SAME
277
+ * tiered core every other ref uses: exact title, or uuid (partial-uuid too).
278
+ * There is deliberately NO index/ordinal form — the "reindex hazard" makes an
279
+ * ordinal silently re-target a different heading after any reorder. An
280
+ * empty-string selector is a legal literal query (the app creates titleless
281
+ * headings); duplicates (or several titleless headings) are a resolution
282
+ * PRECONDITION, not an invariant, so ambiguity fails closed with uuid-bearing
283
+ * candidates (the H-DUPLICATE-TAG precedent). Shared by `todo add/move
284
+ * --heading` (via {@link resolveHeadingRef} → the H-AMBIGUOUS-HEADING guard)
285
+ * and by every project heading verb (via the thrower below).
286
+ */
287
+ export declare function resolveHeadingRef(db: DatabaseSync, projectUuid: string, refRaw: string): NamedResolution;
288
+ /**
289
+ * Resolve a heading selector to its uuid+title within a project, throwing a
290
+ * {@link ReferenceResolutionError} (with uuid candidates on ambiguity) the
291
+ * consumer surfaces render as a usage error. The project-scoped heading verbs
292
+ * and the MCP heading tool resolve through this.
293
+ */
294
+ export declare function resolveHeadingUuid(db: DatabaseSync, projectUuid: string, refRaw: string): {
295
+ uuid: string;
296
+ title: string;
297
+ };
298
+ /**
299
+ * Does a container ref's bare TITLE round-trip through its own resolver, in its
300
+ * own scope, back to THIS entity's uuid? The single predicate the emit boundary
301
+ * consults for the JSON round-trip law — true only when the sole resolution is
302
+ * `entityUuid` (not-found / ambiguous / a different entity all → false, i.e. the
303
+ * title must promote its uuid sibling). See {@link resolveTitleForRoundTrip}.
304
+ */
305
+ export declare function titleRoundTrips(db: DatabaseSync, kind: RefKind, title: string, entityUuid: string, projectUuid?: string): boolean;
306
+ /**
307
+ * Build the {@link RefPromoter} the read-shaping transform ({@link shapeReadPayload})
308
+ * consults to decide flat-ref uuid promotion, memoized per (kind, title, scope)
309
+ * for ONE response emission so a large view never re-runs a resolution it has
310
+ * already made. A fresh promoter (fresh memo) per response — the consumer
311
+ * surfaces build one via the client. The memo caches the RESOLVED uuid (or null),
312
+ * independent of the comparison target, so twin entities sharing a title share
313
+ * the one resolution.
314
+ */
315
+ export declare function makeRefPromoter(db: DatabaseSync): RefPromoter;
180
316
  /**
181
317
  * A row's EFFECTIVE area: its own `area` link, else the area of its project,
182
318
  * else the area of its heading's project. To-dos nested in a project (or under a
@@ -193,6 +329,25 @@ export declare function resolveAreaUuid(db: DatabaseSync, ref: string, options?:
193
329
  export declare const EFFECTIVE_AREA = "COALESCE(\n t.area,\n (SELECT p.area FROM TMTask p WHERE p.uuid = t.project),\n (SELECT hp.area FROM TMTask h JOIN TMTask hp ON hp.uuid = h.project WHERE h.uuid = t.heading)\n)";
194
330
  export declare function fetchTaskRows(db: DatabaseSync, where: string, params?: unknown[]): TaskRow[];
195
331
  export declare function fetchTaskByUuid(db: DatabaseSync, uuid: string): TaskRow | null;
332
+ /**
333
+ * The uuid of a repeating template's LATEST spawned instance, or null when the
334
+ * template has none. The GUI-verified "Show Latest" law (SL1,
335
+ * docs/lab/sl1-show-latest.md, 2026-07-29): the pick is `max(creationDate)`
336
+ * among the template's instances — the most recently spawned occurrence — and
337
+ * NOTHING else. It is INDEPENDENT of `startDate`, `userModificationDate`,
338
+ * `stopDate`, and completion `status`: a completed newest-spawned instance is
339
+ * still the latest (the SL1 D1 case). No status filter; spans both to-do and
340
+ * project instances (`rt1_repeatingTemplate` points at the template regardless
341
+ * of type). `creationDate` is the occurrence midnight and unique per occurrence
342
+ * for a normal series, so ties are not expected.
343
+ *
344
+ * TRASHED instances are EXCLUDED (SL2, docs/lab/sl2-trash-dynamics.md, law L1):
345
+ * the GUI Show Latest never selects a trashed instance — it skips to the newest
346
+ * UNTRASHED one, and re-resolves live after an empty-trash. Only `trashed` is
347
+ * filtered; `status` is NOT (a COMPLETED newest-spawned instance is still the
348
+ * latest — SL1 D1). A template with no untrashed instances derives `null`.
349
+ */
350
+ export declare function latestInstanceUuid(db: DatabaseSync, templateUuid: string): string | null;
196
351
  export declare function fetchChecklistRows(db: DatabaseSync, taskUuid: string): ChecklistRow[];
197
352
  /**
198
353
  * Direct tags for a set of tasks, in one query. Returns uuid -> Ref[] in the
@@ -236,3 +391,4 @@ export declare function fetchTagsForTasks(db: DatabaseSync, taskUuids: string[])
236
391
  export declare function makeHeadingProjectResolver(db: DatabaseSync): (headingUuid: string) => Ref | null;
237
392
  /** Lazy uuid -> Ref resolver over TMTask + TMArea titles, cached per instance. */
238
393
  export declare function makeRefResolver(db: DatabaseSync): (uuid: string | null) => Ref | null;
394
+ export {};