things-api 0.15.0 → 0.17.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 (145) hide show
  1. package/README.md +4 -4
  2. package/dist/audit/schema.d.ts +8 -0
  3. package/dist/audit/schema.js.map +1 -1
  4. package/dist/cli/commands/install-skill.js +9 -2
  5. package/dist/cli/commands/install-skill.js.map +1 -1
  6. package/dist/cli/commands/repeat-flags.d.ts +14 -4
  7. package/dist/cli/commands/repeat-flags.js +113 -33
  8. package/dist/cli/commands/repeat-flags.js.map +1 -1
  9. package/dist/cli/commands/todo.js +49 -2
  10. package/dist/cli/commands/todo.js.map +1 -1
  11. package/dist/cli/commands/writes.js +241 -66
  12. package/dist/cli/commands/writes.js.map +1 -1
  13. package/dist/cli/help.js +23 -13
  14. package/dist/cli/help.js.map +1 -1
  15. package/dist/cli/interrupt.d.ts +42 -0
  16. package/dist/cli/interrupt.js +105 -0
  17. package/dist/cli/interrupt.js.map +1 -0
  18. package/dist/cli/main.js +5 -0
  19. package/dist/cli/main.js.map +1 -1
  20. package/dist/cli/read-driver.d.ts +2 -2
  21. package/dist/cli/skill.d.ts +24 -0
  22. package/dist/cli/skill.js +40 -0
  23. package/dist/cli/skill.js.map +1 -1
  24. package/dist/client.d.ts +53 -12
  25. package/dist/client.js +9 -1
  26. package/dist/client.js.map +1 -1
  27. package/dist/config.d.ts +39 -0
  28. package/dist/config.js +16 -1
  29. package/dist/config.js.map +1 -1
  30. package/dist/contracts.d.ts +9 -2
  31. package/dist/contracts.js +1 -1
  32. package/dist/contracts.js.map +1 -1
  33. package/dist/index.d.ts +5 -2
  34. package/dist/index.js +7 -0
  35. package/dist/index.js.map +1 -1
  36. package/dist/mcp/server.js +132 -31
  37. package/dist/mcp/server.js.map +1 -1
  38. package/dist/model/entities.d.ts +35 -0
  39. package/dist/model/entities.js.map +1 -1
  40. package/dist/model/occurrences.d.ts +14 -1
  41. package/dist/model/occurrences.js +20 -4
  42. package/dist/model/occurrences.js.map +1 -1
  43. package/dist/model/recurrence.d.ts +60 -9
  44. package/dist/model/recurrence.js +76 -19
  45. package/dist/model/recurrence.js.map +1 -1
  46. package/dist/paths.d.ts +8 -0
  47. package/dist/paths.js +10 -0
  48. package/dist/paths.js.map +1 -1
  49. package/dist/read/detail.js +49 -1
  50. package/dist/read/detail.js.map +1 -1
  51. package/dist/read/queries.d.ts +12 -0
  52. package/dist/read/queries.js +14 -0
  53. package/dist/read/queries.js.map +1 -1
  54. package/dist/read/shape.d.ts +25 -22
  55. package/dist/read/shape.js +81 -55
  56. package/dist/read/shape.js.map +1 -1
  57. package/dist/read/stage.d.ts +5 -5
  58. package/dist/read/stage.js +2 -2
  59. package/dist/read/views.d.ts +1 -1
  60. package/dist/read/views.js +4 -4
  61. package/dist/surface-copy.d.ts +2 -2
  62. package/dist/surface-copy.js +2 -2
  63. package/dist/sync-health.js +2 -1
  64. package/dist/sync-health.js.map +1 -1
  65. package/dist/trace/tracer.d.ts +87 -0
  66. package/dist/trace/tracer.js +167 -0
  67. package/dist/trace/tracer.js.map +1 -0
  68. package/dist/write/batch.d.ts +28 -3
  69. package/dist/write/batch.js +162 -46
  70. package/dist/write/batch.js.map +1 -1
  71. package/dist/write/clear-reminder.js +24 -21
  72. package/dist/write/clear-reminder.js.map +1 -1
  73. package/dist/write/clone.d.ts +4 -0
  74. package/dist/write/clone.js +531 -0
  75. package/dist/write/clone.js.map +1 -0
  76. package/dist/write/commands.js +140 -49
  77. package/dist/write/commands.js.map +1 -1
  78. package/dist/write/completion-context.d.ts +57 -0
  79. package/dist/write/completion-context.js +49 -0
  80. package/dist/write/completion-context.js.map +1 -0
  81. package/dist/write/failure-hints.d.ts +10 -1
  82. package/dist/write/failure-hints.js +8 -0
  83. package/dist/write/failure-hints.js.map +1 -1
  84. package/dist/write/guards.d.ts +1 -1
  85. package/dist/write/guards.js +43 -5
  86. package/dist/write/guards.js.map +1 -1
  87. package/dist/write/make-repeating-project.d.ts +2 -3
  88. package/dist/write/make-repeating-project.js +2 -73
  89. package/dist/write/make-repeating-project.js.map +1 -1
  90. package/dist/write/move.js +8 -1
  91. package/dist/write/move.js.map +1 -1
  92. package/dist/write/operations.d.ts +140 -9
  93. package/dist/write/operations.js +4 -1
  94. package/dist/write/operations.js.map +1 -1
  95. package/dist/write/pipeline.d.ts +77 -0
  96. package/dist/write/pipeline.js +298 -20
  97. package/dist/write/pipeline.js.map +1 -1
  98. package/dist/write/pre-state.d.ts +1 -1
  99. package/dist/write/pre-state.js +9 -3
  100. package/dist/write/pre-state.js.map +1 -1
  101. package/dist/write/preserve-modified.d.ts +55 -0
  102. package/dist/write/preserve-modified.js +106 -0
  103. package/dist/write/preserve-modified.js.map +1 -0
  104. package/dist/write/promote-clone.d.ts +32 -0
  105. package/dist/write/promote-clone.js +867 -0
  106. package/dist/write/promote-clone.js.map +1 -0
  107. package/dist/write/repeat-anchor.d.ts +110 -0
  108. package/dist/write/repeat-anchor.js +238 -0
  109. package/dist/write/repeat-anchor.js.map +1 -0
  110. package/dist/write/repeat-asserts.d.ts +28 -0
  111. package/dist/write/repeat-asserts.js +169 -0
  112. package/dist/write/repeat-asserts.js.map +1 -0
  113. package/dist/write/repeat-rule.js +27 -12
  114. package/dist/write/repeat-rule.js.map +1 -1
  115. package/dist/write/resolution-timestamps.js +22 -0
  116. package/dist/write/resolution-timestamps.js.map +1 -1
  117. package/dist/write/reversibility.js +27 -14
  118. package/dist/write/reversibility.js.map +1 -1
  119. package/dist/write/undo.d.ts +11 -1
  120. package/dist/write/undo.js +126 -10
  121. package/dist/write/undo.js.map +1 -1
  122. package/dist/write/vectors/session-reachability.d.ts +78 -0
  123. package/dist/write/vectors/session-reachability.js +103 -0
  124. package/dist/write/vectors/session-reachability.js.map +1 -0
  125. package/dist/write/vectors/simulator.js +100 -30
  126. package/dist/write/vectors/simulator.js.map +1 -1
  127. package/dist/write/vectors/types.d.ts +117 -10
  128. package/dist/write/vectors/ui-certification.d.ts +1 -1
  129. package/dist/write/vectors/ui-certification.js +10 -3
  130. package/dist/write/vectors/ui-certification.js.map +1 -1
  131. package/dist/write/vectors/ui-recipes.d.ts +8 -0
  132. package/dist/write/vectors/ui-recipes.js +120 -22
  133. package/dist/write/vectors/ui-recipes.js.map +1 -1
  134. package/dist/write/vectors/ui.d.ts +79 -13
  135. package/dist/write/vectors/ui.js +415 -59
  136. package/dist/write/vectors/ui.js.map +1 -1
  137. package/dist/write/verify/delta.d.ts +12 -14
  138. package/dist/write/verify/delta.js +35 -34
  139. package/dist/write/verify/delta.js.map +1 -1
  140. package/package.json +1 -1
  141. package/schema/envelope.schema.json +22 -0
  142. package/skills/things-cli/SKILL.md +7 -3
  143. package/skills/things-cli/references/banner.md +1 -1
  144. package/skills/things-cli/references/contracts.md +17 -9
  145. package/skills/things-cli/references/data-model.md +2 -1
@@ -7,6 +7,8 @@ import { areaMemberCounts, childTagTitles, classifyHeadingConvert, classifyHeadi
7
7
  import { resolveTagRefs } from "./tag-refs.js";
8
8
  import { PRIVATE_REORDER_COMMAND } from "./experimental.js";
9
9
  import { assertRepeatRule } from "./repeat-rule.js";
10
+ import { deadlineDriveNext } from "./repeat-anchor.js";
11
+ import { expectedRuleAssertions } from "./repeat-asserts.js";
10
12
  import { escapeAppleScript } from "./vectors/applescript.js";
11
13
  import { areaReorderSidebarRecipe, convertToProjectRecipe, dissolveHeadingRecipe, headingConvertToProjectRecipe, moveHeadingToProjectRecipe, makeRepeatingRecipe, pauseRepeatRecipe, projectMakeRepeatingRecipe, projectPauseRepeatRecipe, projectRescheduleRepeatRecipe, projectResumeRepeatRecipe, rescheduleRepeatRecipe, resumeRepeatRecipe, } from "./vectors/ui-recipes.js";
12
14
  import { buildRepeatingFingerprint } from "./verify/delta.js";
@@ -455,9 +457,12 @@ function statusSpec(op, urlParam, asStatus, scriptStatus) {
455
457
  return {
456
458
  op,
457
459
  hazards: ["H-UNKNOWN-DESTINATION", "H-REPEAT-SCHEDULE"],
458
- preRead(db, params) {
460
+ preRead(db, params, now, zone) {
459
461
  const pre = emptyPreState();
460
- pre.target = loadTarget(db, params.uuid);
462
+ // Load under the response clock so the target's Today marker (read by the
463
+ // HINTS1 completion-context) reflects the same calendar day the pipeline
464
+ // verifies under, not the host wall clock.
465
+ pre.target = loadTarget(db, params.uuid, now, zone);
461
466
  return pre;
462
467
  },
463
468
  expectedDelta(_pre, params) {
@@ -644,6 +649,33 @@ function checklistItemsJsonAttr(specs) {
644
649
  attributes: s.completed === undefined ? { title: s.title } : { title: s.title, completed: s.completed },
645
650
  }));
646
651
  }
652
+ /**
653
+ * Build a project json import's `items` array from a structured
654
+ * {@link ProjectItemSpec} list (the clone / rich-import path). Order IS the
655
+ * layout: a `heading` node produces a `{type:"heading"}` row and every following
656
+ * `to-do` node inherits it positionally (A4); a `to-do` before the first heading
657
+ * is a project-root child. Children are born OPEN — no `completed` attribute.
658
+ */
659
+ function projectItemsJsonAttr(items) {
660
+ return items.map((it) => {
661
+ if (it.kind === "heading") {
662
+ return { type: "heading", attributes: { title: it.title } };
663
+ }
664
+ const attrs = { title: it.title };
665
+ if (it.notes !== undefined)
666
+ attrs["notes"] = it.notes;
667
+ if (it.when !== undefined)
668
+ attrs["when"] = it.when;
669
+ if (it.deadline !== undefined)
670
+ attrs["deadline"] = it.deadline;
671
+ if (it.tags !== undefined)
672
+ attrs["tags"] = it.tags;
673
+ if (it.checklistItems !== undefined) {
674
+ attrs["checklist-items"] = checklistItemsJsonAttr(it.checklistItems.map((title) => ({ title })));
675
+ }
676
+ return { type: "to-do", attributes: attrs };
677
+ });
678
+ }
647
679
  function checklistSpecs(items) {
648
680
  const specs = items.map((i) => typeof i === "string"
649
681
  ? { title: i, completed: false }
@@ -715,6 +747,28 @@ const todoEditChecklistItem = {
715
747
  throw new Error(ORCHESTRATED_ONLY);
716
748
  },
717
749
  };
750
+ const CLONE_ORCHESTRATED_ONLY = "todo.clone / project.clone are delivered by the runCloneTodo / runCloneProject orchestrators (a " +
751
+ "compound over todo.add / project.add plus checklist / terminal-state follow-up legs); they have " +
752
+ "no atomic surface and are never dispatched directly through the pipeline";
753
+ function cloneStub(op) {
754
+ return {
755
+ op,
756
+ hazards: [],
757
+ preRead(db, params) {
758
+ const pre = emptyPreState();
759
+ pre.target = loadTarget(db, params.uuid);
760
+ return pre;
761
+ },
762
+ expectedDelta() {
763
+ throw new Error(CLONE_ORCHESTRATED_ONLY);
764
+ },
765
+ compile() {
766
+ throw new Error(CLONE_ORCHESTRATED_ONLY);
767
+ },
768
+ };
769
+ }
770
+ const todoClone = cloneStub("todo.clone");
771
+ const projectClone = cloneStub("project.clone");
718
772
  const todoDelete = {
719
773
  op: "todo.delete",
720
774
  hazards: ["H-UNKNOWN-DESTINATION", "H-REPEAT-SCHEDULE"],
@@ -744,6 +798,13 @@ const projectAdd = {
744
798
  throw new RangeError("--completed-at cannot seed child to-dos: a completed-project import reverts to open " +
745
799
  "unless every child is resolved (§5b) — create the project resolved, then add logged children");
746
800
  }
801
+ if (params.todos !== undefined && params.items !== undefined) {
802
+ throw new RangeError("project.add takes either `todos` or structured `items`, not both");
803
+ }
804
+ if (params.completedAt !== undefined && params.items !== undefined && params.items.length > 0) {
805
+ throw new RangeError("--completed-at cannot seed child items: a completed-project import reverts to open " +
806
+ "unless every child is resolved (§5b)");
807
+ }
747
808
  const pre = emptyPreState();
748
809
  if (containerGiven(params.area))
749
810
  pre.destArea = resolveArea(db, params.area);
@@ -779,7 +840,9 @@ const projectAdd = {
779
840
  compile(params, vector, pre, ctx) {
780
841
  if (vector !== "url-scheme")
781
842
  unsupportedVector(this.op, vector);
782
- if (addHasTimestamps(params)) {
843
+ // The json import is required for born-timestamps AND for structured `items`
844
+ // (headings / rich children) — the plain add-project URL carries neither.
845
+ if (addHasTimestamps(params) || params.items !== undefined) {
783
846
  const attrs = { title: params.title };
784
847
  if (params.notes !== undefined)
785
848
  attrs["notes"] = params.notes;
@@ -789,7 +852,10 @@ const projectAdd = {
789
852
  attrs["when"] = params.when;
790
853
  if (params.deadline !== undefined)
791
854
  attrs["deadline"] = params.deadline;
792
- if (params.todos !== undefined) {
855
+ if (params.items !== undefined) {
856
+ attrs["items"] = projectItemsJsonAttr(params.items);
857
+ }
858
+ else if (params.todos !== undefined) {
793
859
  attrs["items"] = params.todos.map((t) => ({ type: "to-do", attributes: { title: t } }));
794
860
  }
795
861
  }
@@ -1086,7 +1152,10 @@ const projectReopen = {
1086
1152
  };
1087
1153
  const projectRestore = {
1088
1154
  op: "project.restore",
1089
- hazards: ["H-UNKNOWN-DESTINATION"],
1155
+ // H-REPEAT-SCHEDULE: a trashed repeating PROJECT template cannot be restored
1156
+ // headlessly (Put Back only) — refuse categorically instead of the raw AS-301
1157
+ // no-op. (The to-do path already carries it; kind-parity, 2026-08-13.)
1158
+ hazards: ["H-UNKNOWN-DESTINATION", "H-REPEAT-SCHEDULE"],
1090
1159
  preRead(db, params) {
1091
1160
  const pre = emptyPreState();
1092
1161
  pre.target = loadTarget(db, params.uuid);
@@ -1848,8 +1917,23 @@ function ruleExtras(params) {
1848
1917
  ...(params.reminder !== undefined && { reminder: params.reminder }),
1849
1918
  ...(params.deadline !== undefined && { deadline: params.deadline }),
1850
1919
  ...(params.startDaysEarlier !== undefined && { startDaysEarlier: params.startDaysEarlier }),
1920
+ ...(params.next !== undefined && { next: params.next }),
1851
1921
  };
1852
1922
  }
1923
+ /**
1924
+ * Recipe extras for a RESCHEDULE, with the "Next:" drive date deadline-adjusted
1925
+ * (YANCH1 #493): `--when` is the scheduled START, but a deadlined rule anchors on
1926
+ * the DEADLINE, so the dialog's "Next:" field is driven with `when +
1927
+ * startDaysEarlier` and the app back-shifts the instance start to `--when`. The
1928
+ * expectedDelta keeps the RAW `params.next` (the cursor asserts the START).
1929
+ * make/add-repeating apply this shift upstream (promote-clone.ts) and pass an
1930
+ * already-adjusted rule, so only the reschedule compile needs it here.
1931
+ */
1932
+ function reschedRuleExtras(params) {
1933
+ const base = ruleExtras(params);
1934
+ const drive = deadlineDriveNext(params);
1935
+ return drive !== undefined ? { ...base, next: drive } : base;
1936
+ }
1853
1937
  /** ui ops all guard existence/type + the H-UI-DRIVE acknowledgement. */
1854
1938
  const UI_HAZARDS = ["H-UNKNOWN-DESTINATION", "H-UI-DRIVE"];
1855
1939
  const todoMakeRepeating = {
@@ -1870,9 +1954,12 @@ const todoMakeRepeating = {
1870
1954
  // rows), pick it by asserting it IS a template, then the `repeating` context
1871
1955
  // hardens discovery (restored time-bound, source-fingerprint tiebreak) and
1872
1956
  // derives instance + source fate for the enriched result. `expectedRule`
1873
- // makes the LANDED rule verifiable: a template minted with the wrong
1874
- // frequency/interval (the interval-field race, oddities §8l) becomes a
1875
- // verify-failed:mismatch instead of a silent ok.
1957
+ // makes the LANDED rule verifiable at FULL fidelity (#491): a template minted
1958
+ // with the wrong frequency/interval (the interval-field race, oddities §8l) OR
1959
+ // a dropped anchor / ends / deadline becomes a verify-failed:mismatch instead
1960
+ // of a silent ok. includeCursor:false — the fresh template's cursor follows
1961
+ // the app's spawn law (ANCH1), not the raw --when, so make verifies the rule
1962
+ // BLOB + deadline, not the cursor.
1876
1963
  return {
1877
1964
  mode: "create",
1878
1965
  probe: {
@@ -1884,11 +1971,7 @@ const todoMakeRepeating = {
1884
1971
  repeating: {
1885
1972
  sourceUuid: pre.target.uuid,
1886
1973
  fingerprint: buildRepeatingFingerprint(pre.target),
1887
- expectedRule: {
1888
- type: params.afterCompletion === true ? "after-completion" : "fixed",
1889
- unit: params.frequency,
1890
- interval: params.interval,
1891
- },
1974
+ expectedRule: expectedRuleAssertions(params, { includeCursor: false }),
1892
1975
  },
1893
1976
  }),
1894
1977
  },
@@ -1911,33 +1994,26 @@ const todoRescheduleRepeat = {
1911
1994
  return pre;
1912
1995
  },
1913
1996
  expectedDelta(_pre, params) {
1914
- // Identity PRESERVED (UI2-b): the same template uuid, rule mutated in
1915
- // place. Assert the decoded rule's TYPE + frequency + interval; ALSO capture
1916
- // the whole prior rule (+ deadline flag) so the undo can re-drive it
1917
- // faithfully. Asserting `type` (fixed vs after-completion) is what makes a
1918
- // fixed→after-completion conversion VERIFIABLE at all: in the field-report
1919
- // incident (0½ item 1) both rules were weekly/interval-2, so unit+interval
1920
- // never changed only the type flipped, and without this assertion verify
1921
- // could neither confirm the conversion landed nor tell the pre-drive
1922
- // idempotency check that the target had not yet been reached.
1997
+ // Identity PRESERVED (UI2-b): the same template uuid, rule mutated in place.
1998
+ // Assert the FULL requested rule (#491) — type + unit + interval + calendar
1999
+ // anchor + ends bound + deadline offset + the ANCH2 first-occurrence cursor —
2000
+ // built from the complete vocabulary by expectedRuleAssertions so the pre-drive
2001
+ // idempotency check skips ONLY when EVERY requested field already holds, and
2002
+ // the post-drive verify catches a wrong-anchor landing. A shallow unit+interval
2003
+ // subset false-noop'd a reschedule that changed only the monthly anchor /
2004
+ // deadline / ends (#491). ALSO capture the whole prior rule (+ deadline flag)
2005
+ // so the undo can re-drive it faithfully.
1923
2006
  return {
1924
2007
  mode: "update",
1925
2008
  uuid: params.uuid,
1926
- assert: [
1927
- {
1928
- field: "repeating.rule.type",
1929
- equals: params.afterCompletion === true ? "after-completion" : "fixed",
1930
- },
1931
- { field: "repeating.rule.unit", equals: params.frequency },
1932
- { field: "repeating.rule.interval", equals: params.interval },
1933
- ],
2009
+ assert: expectedRuleAssertions(params, { includeCursor: true }),
1934
2010
  capture: [{ field: "repeating.rule" }, { field: "repeating.deadlined" }],
1935
2011
  };
1936
2012
  },
1937
2013
  compile(params, vector) {
1938
2014
  if (vector !== "ui")
1939
2015
  unsupportedVector(this.op, vector);
1940
- return uiDrive(rescheduleRepeatRecipe(params.uuid, params.frequency, params.interval, ruleExtras(params)));
2016
+ return uiDrive(rescheduleRepeatRecipe(params.uuid, params.frequency, params.interval, reschedRuleExtras(params)));
1941
2017
  },
1942
2018
  };
1943
2019
  const todoPauseRepeat = {
@@ -2002,26 +2078,20 @@ const projectRescheduleRepeat = {
2002
2078
  expectedDelta(_pre, params) {
2003
2079
  // Identity PRESERVED (UIC2-a): same project uuid, rule mutated in place;
2004
2080
  // capture the prior rule (+ deadline flag) for the faithful undo. Assert the
2005
- // rule TYPE too (fixed vs after-completion) so a conversion that leaves
2006
- // unit+interval unchanged is still verifiable — see todo.reschedule-repeat.
2081
+ // FULL requested rule (#491) via expectedRuleAssertions same completeness as
2082
+ // todo.reschedule-repeat (a shallow unit+interval subset false-noop'd an
2083
+ // anchor/deadline/ends-only reschedule).
2007
2084
  return {
2008
2085
  mode: "update",
2009
2086
  uuid: params.uuid,
2010
- assert: [
2011
- {
2012
- field: "repeating.rule.type",
2013
- equals: params.afterCompletion === true ? "after-completion" : "fixed",
2014
- },
2015
- { field: "repeating.rule.unit", equals: params.frequency },
2016
- { field: "repeating.rule.interval", equals: params.interval },
2017
- ],
2087
+ assert: expectedRuleAssertions(params, { includeCursor: true }),
2018
2088
  capture: [{ field: "repeating.rule" }, { field: "repeating.deadlined" }],
2019
2089
  };
2020
2090
  },
2021
2091
  compile(params, vector) {
2022
2092
  if (vector !== "ui")
2023
2093
  unsupportedVector(this.op, vector);
2024
- return uiDrive(projectRescheduleRepeatRecipe(params.uuid, params.frequency, params.interval, ruleExtras(params)));
2094
+ return uiDrive(projectRescheduleRepeatRecipe(params.uuid, params.frequency, params.interval, reschedRuleExtras(params)));
2025
2095
  },
2026
2096
  };
2027
2097
  const projectPauseRepeat = {
@@ -2070,7 +2140,7 @@ const projectResumeRepeat = {
2070
2140
  };
2071
2141
  // project.make-repeating: the pure-AX row-selection op (UIC4). The command
2072
2142
  // spec drives the area / someday cases; the area-less-anytime coercion is done
2073
- // by the runMakeRepeatingProject orchestrator BEFORE this spec sees the target,
2143
+ // by the promoteProjectViaGui internal orchestrator BEFORE this spec sees the target,
2074
2144
  // so by drive time the taxonomy is always area or someday. A DIRECT dispatch on
2075
2145
  // an anytime project is refused by H-PROJECT-REPEAT (with the orchestrator as
2076
2146
  // the remediation).
@@ -2094,7 +2164,10 @@ const projectMakeRepeating = {
2094
2164
  // the current-occurrence instance. Pick the TEMPLATE by asserting it IS one,
2095
2165
  // excluding pre-existing same-title rows; the `repeating` context hardens
2096
2166
  // discovery and derives instance + source fate + childrenReplaced.
2097
- // `expectedRule` makes the landed rule verifiable (interval-race guard, §8l).
2167
+ // `expectedRule` makes the landed rule verifiable at FULL fidelity (#491):
2168
+ // a wrong frequency/interval (interval-race §8l) OR a dropped anchor/ends/
2169
+ // deadline is a mismatch, not a silent ok. includeCursor:false — the fresh
2170
+ // template's cursor follows the ANCH1 spawn law, not the raw --when.
2098
2171
  return {
2099
2172
  mode: "create",
2100
2173
  probe: {
@@ -2107,11 +2180,7 @@ const projectMakeRepeating = {
2107
2180
  sourceUuid: pre.target.uuid,
2108
2181
  fingerprint: buildRepeatingFingerprint(pre.target),
2109
2182
  subtreeUuids: pre.repeatSubtreeUuids ?? [],
2110
- expectedRule: {
2111
- type: params.afterCompletion === true ? "after-completion" : "fixed",
2112
- unit: params.frequency,
2113
- interval: params.interval,
2114
- },
2183
+ expectedRule: expectedRuleAssertions(params, { includeCursor: false }),
2115
2184
  },
2116
2185
  }),
2117
2186
  },
@@ -2148,6 +2217,25 @@ const projectAddRepeating = {
2148
2217
  throw new Error(ADD_REPEATING_ORCHESTRATED);
2149
2218
  },
2150
2219
  };
2220
+ // todo.add-repeating is delivered by the runAddRepeatingTodo orchestrator
2221
+ // (todo.add THEN native promote); it has no atomic surface and is never
2222
+ // dispatched directly through the pipeline (promote-clone.ts).
2223
+ const TODO_ADD_REPEATING_ORCHESTRATED = "todo.add-repeating is delivered by the runAddRepeatingTodo orchestrator (create the to-do, " +
2224
+ "then promote it to a repeating series); it has no atomic surface and is never dispatched " +
2225
+ "directly through the pipeline";
2226
+ const todoAddRepeating = {
2227
+ op: "todo.add-repeating",
2228
+ hazards: [],
2229
+ preRead() {
2230
+ return emptyPreState();
2231
+ },
2232
+ expectedDelta() {
2233
+ throw new Error(TODO_ADD_REPEATING_ORCHESTRATED);
2234
+ },
2235
+ compile() {
2236
+ throw new Error(TODO_ADD_REPEATING_ORCHESTRATED);
2237
+ },
2238
+ };
2151
2239
  const todoConvertToProject = {
2152
2240
  op: "todo.convert-to-project",
2153
2241
  hazards: UI_HAZARDS,
@@ -2489,6 +2577,9 @@ export const COMMANDS = {
2489
2577
  "area.reorder": areaReorderSidebar,
2490
2578
  "project.make-repeating": projectMakeRepeating,
2491
2579
  "project.add-repeating": projectAddRepeating,
2580
+ "todo.add-repeating": todoAddRepeating,
2581
+ "todo.clone": todoClone,
2582
+ "project.clone": projectClone,
2492
2583
  "log-now": logNow,
2493
2584
  };
2494
2585
  //# sourceMappingURL=commands.js.map