things-api 0.11.0 → 0.12.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 (192) 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/commands/area.d.ts +18 -11
  5. package/dist/cli/commands/area.js +55 -67
  6. package/dist/cli/commands/area.js.map +1 -1
  7. package/dist/cli/commands/doctor.d.ts +1 -2
  8. package/dist/cli/commands/doctor.js +11 -1
  9. package/dist/cli/commands/doctor.js.map +1 -1
  10. package/dist/cli/commands/install-skill.d.ts +19 -4
  11. package/dist/cli/commands/install-skill.js +101 -52
  12. package/dist/cli/commands/install-skill.js.map +1 -1
  13. package/dist/cli/commands/mcp.js +9 -0
  14. package/dist/cli/commands/mcp.js.map +1 -1
  15. package/dist/cli/commands/project.d.ts +1 -0
  16. package/dist/cli/commands/project.js +20 -28
  17. package/dist/cli/commands/project.js.map +1 -1
  18. package/dist/cli/commands/reads.js +100 -28
  19. package/dist/cli/commands/reads.js.map +1 -1
  20. package/dist/cli/commands/show.js +32 -8
  21. package/dist/cli/commands/show.js.map +1 -1
  22. package/dist/cli/commands/todo.js +1 -1
  23. package/dist/cli/commands/todo.js.map +1 -1
  24. package/dist/cli/commands/writes.js +769 -143
  25. package/dist/cli/commands/writes.js.map +1 -1
  26. package/dist/cli/did-you-mean.d.ts +14 -3
  27. package/dist/cli/did-you-mean.js +26 -3
  28. package/dist/cli/did-you-mean.js.map +1 -1
  29. package/dist/cli/glyphs.d.ts +16 -2
  30. package/dist/cli/glyphs.js +60 -12
  31. package/dist/cli/glyphs.js.map +1 -1
  32. package/dist/cli/help.js +67 -12
  33. package/dist/cli/help.js.map +1 -1
  34. package/dist/cli/main.js +1 -1
  35. package/dist/cli/main.js.map +1 -1
  36. package/dist/cli/move-hint.d.ts +3 -2
  37. package/dist/cli/move-hint.js +2 -2
  38. package/dist/cli/move-hint.js.map +1 -1
  39. package/dist/cli/read-driver.d.ts +43 -5
  40. package/dist/cli/read-driver.js +81 -15
  41. package/dist/cli/read-driver.js.map +1 -1
  42. package/dist/cli/render.d.ts +19 -8
  43. package/dist/cli/render.js +59 -25
  44. package/dist/cli/render.js.map +1 -1
  45. package/dist/cli/resolve-invocation.d.ts +2 -2
  46. package/dist/cli/resolve-invocation.js +3 -3
  47. package/dist/cli/resolve-invocation.js.map +1 -1
  48. package/dist/cli/skill-check.d.ts +12 -6
  49. package/dist/cli/skill-check.js +31 -14
  50. package/dist/cli/skill-check.js.map +1 -1
  51. package/dist/cli/skill.d.ts +8 -0
  52. package/dist/cli/skill.js +10 -0
  53. package/dist/cli/skill.js.map +1 -1
  54. package/dist/cli/verb-hint.js +1 -1
  55. package/dist/cli/verb-hint.js.map +1 -1
  56. package/dist/client.d.ts +140 -19
  57. package/dist/client.js +253 -38
  58. package/dist/client.js.map +1 -1
  59. package/dist/config.d.ts +66 -2
  60. package/dist/config.js +120 -12
  61. package/dist/config.js.map +1 -1
  62. package/dist/contracts.d.ts +176 -28
  63. package/dist/contracts.js +22 -1
  64. package/dist/contracts.js.map +1 -1
  65. package/dist/diagnose.d.ts +34 -2
  66. package/dist/diagnose.js +36 -1
  67. package/dist/diagnose.js.map +1 -1
  68. package/dist/index.d.ts +19 -8
  69. package/dist/index.js +19 -3
  70. package/dist/index.js.map +1 -1
  71. package/dist/mcp/server.d.ts +9 -0
  72. package/dist/mcp/server.js +733 -304
  73. package/dist/mcp/server.js.map +1 -1
  74. package/dist/model/entities.d.ts +46 -3
  75. package/dist/model/entities.js.map +1 -1
  76. package/dist/model/mappers.d.ts +2 -0
  77. package/dist/model/mappers.js +41 -2
  78. package/dist/model/mappers.js.map +1 -1
  79. package/dist/model/recurrence.d.ts +8 -1
  80. package/dist/model/recurrence.js.map +1 -1
  81. package/dist/read/area-filter.d.ts +76 -0
  82. package/dist/read/area-filter.js +59 -0
  83. package/dist/read/area-filter.js.map +1 -0
  84. package/dist/read/area-view.d.ts +11 -11
  85. package/dist/read/area-view.js +50 -28
  86. package/dist/read/area-view.js.map +1 -1
  87. package/dist/read/detail.js +15 -8
  88. package/dist/read/detail.js.map +1 -1
  89. package/dist/read/predicates.d.ts +18 -0
  90. package/dist/read/predicates.js +19 -0
  91. package/dist/read/predicates.js.map +1 -1
  92. package/dist/read/project-view.d.ts +31 -15
  93. package/dist/read/project-view.js +72 -39
  94. package/dist/read/project-view.js.map +1 -1
  95. package/dist/read/pseudo-area.d.ts +29 -0
  96. package/dist/read/pseudo-area.js +27 -0
  97. package/dist/read/pseudo-area.js.map +1 -0
  98. package/dist/read/queries.d.ts +105 -18
  99. package/dist/read/queries.js +185 -30
  100. package/dist/read/queries.js.map +1 -1
  101. package/dist/read/scope.d.ts +126 -0
  102. package/dist/read/scope.js +162 -0
  103. package/dist/read/scope.js.map +1 -0
  104. package/dist/read/search-rank.d.ts +2 -5
  105. package/dist/read/shape.d.ts +162 -0
  106. package/dist/read/shape.js +686 -0
  107. package/dist/read/shape.js.map +1 -0
  108. package/dist/read/show-target.d.ts +9 -1
  109. package/dist/read/show-target.js +45 -4
  110. package/dist/read/show-target.js.map +1 -1
  111. package/dist/read/stage.d.ts +199 -0
  112. package/dist/read/stage.js +125 -0
  113. package/dist/read/stage.js.map +1 -0
  114. package/dist/read/truncation.d.ts +7 -5
  115. package/dist/read/truncation.js +33 -8
  116. package/dist/read/truncation.js.map +1 -1
  117. package/dist/read/views.d.ts +49 -9
  118. package/dist/read/views.js +193 -40
  119. package/dist/read/views.js.map +1 -1
  120. package/dist/surface-copy.d.ts +9 -0
  121. package/dist/surface-copy.js +9 -0
  122. package/dist/surface-copy.js.map +1 -1
  123. package/dist/write/batch.d.ts +49 -10
  124. package/dist/write/batch.js +423 -71
  125. package/dist/write/batch.js.map +1 -1
  126. package/dist/write/commands.js +266 -55
  127. package/dist/write/commands.js.map +1 -1
  128. package/dist/write/guards.d.ts +1 -1
  129. package/dist/write/guards.js +71 -10
  130. package/dist/write/guards.js.map +1 -1
  131. package/dist/write/heading.d.ts +10 -1
  132. package/dist/write/heading.js +35 -5
  133. package/dist/write/heading.js.map +1 -1
  134. package/dist/write/make-repeating-project.d.ts +2 -2
  135. package/dist/write/make-repeating-project.js +9 -9
  136. package/dist/write/make-repeating-project.js.map +1 -1
  137. package/dist/write/move.d.ts +130 -0
  138. package/dist/write/move.js +1533 -0
  139. package/dist/write/move.js.map +1 -0
  140. package/dist/write/operations.d.ts +105 -19
  141. package/dist/write/operations.js +56 -7
  142. package/dist/write/operations.js.map +1 -1
  143. package/dist/write/opid.d.ts +31 -0
  144. package/dist/write/opid.js +30 -0
  145. package/dist/write/opid.js.map +1 -0
  146. package/dist/write/pipeline.d.ts +70 -3
  147. package/dist/write/pipeline.js +198 -33
  148. package/dist/write/pipeline.js.map +1 -1
  149. package/dist/write/pre-state.d.ts +162 -8
  150. package/dist/write/pre-state.js +385 -20
  151. package/dist/write/pre-state.js.map +1 -1
  152. package/dist/write/reorder.d.ts +23 -3
  153. package/dist/write/reorder.js +1871 -85
  154. package/dist/write/reorder.js.map +1 -1
  155. package/dist/write/reversibility.js +19 -7
  156. package/dist/write/reversibility.js.map +1 -1
  157. package/dist/write/scope-guard.d.ts +31 -0
  158. package/dist/write/scope-guard.js +162 -0
  159. package/dist/write/scope-guard.js.map +1 -0
  160. package/dist/write/undo.js +120 -13
  161. package/dist/write/undo.js.map +1 -1
  162. package/dist/write/vectors/applescript.js +16 -7
  163. package/dist/write/vectors/applescript.js.map +1 -1
  164. package/dist/write/vectors/shortcuts.js +1 -1
  165. package/dist/write/vectors/shortcuts.js.map +1 -1
  166. package/dist/write/vectors/simulator.js +83 -27
  167. package/dist/write/vectors/simulator.js.map +1 -1
  168. package/dist/write/vectors/types.d.ts +11 -0
  169. package/dist/write/vectors/ui-certification.d.ts +3 -3
  170. package/dist/write/vectors/ui-certification.js +41 -5
  171. package/dist/write/vectors/ui-certification.js.map +1 -1
  172. package/dist/write/vectors/ui-drag.d.ts +21 -1
  173. package/dist/write/vectors/ui-drag.js +138 -27
  174. package/dist/write/vectors/ui-drag.js.map +1 -1
  175. package/dist/write/vectors/ui-recipes.d.ts +21 -1
  176. package/dist/write/vectors/ui-recipes.js +166 -7
  177. package/dist/write/vectors/ui-recipes.js.map +1 -1
  178. package/dist/write/vectors/ui.d.ts +25 -1
  179. package/dist/write/vectors/ui.js +142 -33
  180. package/dist/write/vectors/ui.js.map +1 -1
  181. package/dist/write/verify/delta.d.ts +17 -1
  182. package/dist/write/verify/delta.js +106 -29
  183. package/dist/write/verify/delta.js.map +1 -1
  184. package/package.json +7 -1
  185. package/schema/envelope.schema.json +383 -0
  186. package/skills/things-cli/SKILL.md +34 -17
  187. package/skills/things-cli/references/banner.md +35 -0
  188. package/skills/things-cli/references/contracts.md +30 -9
  189. package/skills/things-cli/references/errors.md +49 -0
  190. package/skills/things-cli/references/gui.md +1 -1
  191. package/skills/things-cli/references/model.md +15 -6
  192. package/skills/things-cli/references/ordering.md +71 -0
package/dist/config.js CHANGED
@@ -13,6 +13,41 @@ const PROFILE_DEFAULT_TIER = {
13
13
  workstation: 1,
14
14
  "dedicated-server": 2,
15
15
  };
16
+ /**
17
+ * A THINGS_API_* boolean override. Returns the forced value when the env var
18
+ * holds a recognized token, or undefined when unset/unrecognized (so the
19
+ * caller falls through to stored config, then the built-in default). The
20
+ * override is BIDIRECTIONAL — a recognized value always wins over stored
21
+ * config, so an env var can force a vector off as well as on. `trueToken` /
22
+ * `falseToken` let THINGS_API_AUDIT keep its legacy on/off vocabulary while
23
+ * the vectors use true/false.
24
+ */
25
+ function boolEnvOverride(raw, trueToken, falseToken) {
26
+ if (raw === trueToken)
27
+ return true;
28
+ if (raw === falseToken)
29
+ return false;
30
+ return undefined;
31
+ }
32
+ /** THINGS_API_PROFILE override, or undefined when unset/unrecognized. */
33
+ function profileEnvOverride(raw) {
34
+ return raw === "workstation" || raw === "dedicated-server" ? raw : undefined;
35
+ }
36
+ /** THINGS_API_MAX_DISRUPTION override, or undefined when unset/unrecognized. */
37
+ function tierEnvOverride(raw) {
38
+ return raw !== undefined && /^[0-3]$/.test(raw) ? Number(raw) : undefined;
39
+ }
40
+ /**
41
+ * A THINGS_API_BOUNCE_MAX_ITEMS override, or undefined when unset/unrecognized.
42
+ * Only a positive integer is accepted (a bounce touches at least one item);
43
+ * anything else falls through to stored config, then the built-in default.
44
+ */
45
+ function positiveIntEnvOverride(raw) {
46
+ if (raw === undefined || !/^[0-9]+$/.test(raw))
47
+ return undefined;
48
+ const n = Number(raw);
49
+ return Number.isInteger(n) && n > 0 ? n : undefined;
50
+ }
16
51
  function configFilePath(env) {
17
52
  return join(configDir(env), "config.json");
18
53
  }
@@ -27,13 +62,12 @@ export function loadConfig(env = process.env) {
27
62
  // Malformed config falls back to defaults; doctor reports it.
28
63
  }
29
64
  }
30
- const profile = env["THINGS_API_PROFILE"] === "dedicated-server" || file.profile === "dedicated-server"
31
- ? "dedicated-server"
32
- : "workstation";
33
- const envTier = env["THINGS_API_MAX_DISRUPTION"];
34
- const maxDisruption = (envTier !== undefined && /^[0-3]$/.test(envTier)
35
- ? Number(envTier)
36
- : (file.maxDisruption ?? PROFILE_DEFAULT_TIER[profile]));
65
+ // Precedence for every key: env > stored > default. A recognized env value
66
+ // always wins over stored config; an unset/unrecognized one falls through.
67
+ const profile = profileEnvOverride(env["THINGS_API_PROFILE"]) ?? file.profile ?? "workstation";
68
+ const maxDisruption = tierEnvOverride(env["THINGS_API_MAX_DISRUPTION"]) ??
69
+ file.maxDisruption ??
70
+ PROFILE_DEFAULT_TIER[profile];
37
71
  let username = "unknown";
38
72
  try {
39
73
  username = userInfo().username;
@@ -41,16 +75,32 @@ export function loadConfig(env = process.env) {
41
75
  catch {
42
76
  // leave "unknown"
43
77
  }
78
+ const auditEnv = boolEnvOverride(env["THINGS_API_AUDIT"], "on", "off");
79
+ const experimentalEnv = boolEnvOverride(env["THINGS_API_ALLOW_EXPERIMENTAL"], "true", "false");
80
+ const bounceEnabledEnv = boolEnvOverride(env["THINGS_API_BOUNCE_ENABLED"], "true", "false");
81
+ const bounceMaxItemsEnv = positiveIntEnvOverride(env["THINGS_API_BOUNCE_MAX_ITEMS"]);
82
+ const uiEnv = boolEnvOverride(env["THINGS_API_UI_ENABLED"], "true", "false");
83
+ // Scope precedence within the config layer: THINGS_API_SCOPE env > stored
84
+ // `scope` key. The MCP `--scope` flag outranks BOTH and is applied above this
85
+ // layer, at openThings(). Fail-closed resolution to a container happens there.
86
+ const scopeEnv = env["THINGS_API_SCOPE"];
87
+ const scope = scopeEnv !== undefined && scopeEnv !== ""
88
+ ? { ref: scopeEnv, source: "env" }
89
+ : file.scope !== undefined && file.scope !== ""
90
+ ? { ref: file.scope, source: "config" }
91
+ : null;
44
92
  return {
45
93
  profile,
46
94
  maxDisruption,
47
95
  actor: env["THINGS_API_ACTOR"] ?? file.actor ?? `${username}@cli`,
48
- auditEnabled: env["THINGS_API_AUDIT"] === "off" ? false : (file.auditEnabled ?? true),
96
+ auditEnabled: auditEnv ?? file.auditEnabled ?? true,
49
97
  acceptedFingerprint: file.acceptedFingerprint ?? null,
50
- allowExperimental: env["THINGS_API_ALLOW_EXPERIMENTAL"] === "true" || file.allowExperimental === true,
51
- ui: {
52
- enabled: env["THINGS_API_UI_ENABLED"] === "true" || file.uiEnabled === true,
53
- },
98
+ certifiedAppVersion: file.certifiedAppVersion ?? null,
99
+ allowExperimental: experimentalEnv ?? file.allowExperimental ?? true,
100
+ bounceEnabled: bounceEnabledEnv ?? file.bounceEnabled ?? true,
101
+ bounceMaxItems: bounceMaxItemsEnv ?? file.bounceMaxItems ?? 30,
102
+ ui: { enabled: uiEnv ?? file.uiEnabled ?? false },
103
+ scope,
54
104
  host: hostname(),
55
105
  };
56
106
  }
@@ -76,4 +126,62 @@ export function saveConfigKey(key, value, env = process.env) {
76
126
  }
77
127
  writeFileSync(path, `${JSON.stringify(file, null, 2)}\n`);
78
128
  }
129
+ function readConfigFile(env) {
130
+ const path = configFilePath(env);
131
+ if (existsSync(path)) {
132
+ try {
133
+ return JSON.parse(readFileSync(path, "utf8"));
134
+ }
135
+ catch {
136
+ // Malformed config → treated as no stored keys (doctor reports it).
137
+ }
138
+ }
139
+ return {};
140
+ }
141
+ /**
142
+ * The effective value + provenance of every config key, in a stable order —
143
+ * backing `things config get`. Env/stored/default detection mirrors
144
+ * {@link loadConfig} exactly, so a key reads `env` only when an env var actually
145
+ * overrode it (a THINGS_API_* value that loadConfig honors), `stored` when the
146
+ * on-disk config supplied it, `derived` for a read-only value computed from the
147
+ * environment or another key, and `default` otherwise.
148
+ */
149
+ function configKeyView(key, value, stored, fromEnv) {
150
+ return { key, value, source: fromEnv ? "env" : stored ? "stored" : "default" };
151
+ }
152
+ export function describeConfig(env = process.env) {
153
+ const file = readConfigFile(env);
154
+ const cfg = loadConfig(env);
155
+ const view = configKeyView;
156
+ const tierFromEnv = tierEnvOverride(env["THINGS_API_MAX_DISRUPTION"]) !== undefined;
157
+ const maxDisruption = {
158
+ key: "maxDisruption",
159
+ value: cfg.maxDisruption,
160
+ // env > stored > profile-derived default (the built-in fallback is
161
+ // computed from `profile`, so it reports `derived`, not `default`).
162
+ source: tierFromEnv ? "env" : file.maxDisruption !== undefined ? "stored" : "derived",
163
+ };
164
+ return [
165
+ view("profile", cfg.profile, file.profile !== undefined, profileEnvOverride(env["THINGS_API_PROFILE"]) !== undefined),
166
+ maxDisruption,
167
+ view("actor", cfg.actor, file.actor !== undefined, env["THINGS_API_ACTOR"] !== undefined),
168
+ view("auditEnabled", cfg.auditEnabled, file.auditEnabled !== undefined, boolEnvOverride(env["THINGS_API_AUDIT"], "on", "off") !== undefined),
169
+ view("accepted-fingerprint", cfg.acceptedFingerprint, file.acceptedFingerprint !== undefined, false),
170
+ view("certified-app-version", cfg.certifiedAppVersion, file.certifiedAppVersion !== undefined, false),
171
+ view("allow-experimental", cfg.allowExperimental, file.allowExperimental !== undefined, boolEnvOverride(env["THINGS_API_ALLOW_EXPERIMENTAL"], "true", "false") !== undefined),
172
+ view("bounce-enabled", cfg.bounceEnabled, file.bounceEnabled !== undefined, boolEnvOverride(env["THINGS_API_BOUNCE_ENABLED"], "true", "false") !== undefined),
173
+ view("bounce-max-items", cfg.bounceMaxItems, file.bounceMaxItems !== undefined, positiveIntEnvOverride(env["THINGS_API_BOUNCE_MAX_ITEMS"]) !== undefined),
174
+ view("ui-enabled", cfg.ui.enabled, file.uiEnabled !== undefined, boolEnvOverride(env["THINGS_API_UI_ENABLED"], "true", "false") !== undefined),
175
+ // The container scope's RAW ref (resolved to a container at open); env >
176
+ // stored. A stored value jails every process on this host — see the
177
+ // trust-model note in docs/design/container-scope.md.
178
+ view("scope", cfg.scope?.ref ?? null, file.scope !== undefined, env["THINGS_API_SCOPE"] !== undefined && env["THINGS_API_SCOPE"] !== ""),
179
+ // Read-only, computed from the environment; not settable via `config set`.
180
+ { key: "host", value: cfg.host, source: "derived" },
181
+ ];
182
+ }
183
+ /** One key's effective view, or undefined for an unknown key (`config get <key>`). */
184
+ export function getConfigKey(key, env = process.env) {
185
+ return describeConfig(env).find((v) => v.key === key);
186
+ }
79
187
  //# sourceMappingURL=config.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -13,7 +13,7 @@ export declare const API_VERSION = 1;
13
13
  * Package version, surfaced by `things --version` and the MCP serverInfo.
14
14
  * Kept in lockstep with package.json by a contract test.
15
15
  */
16
- export declare const PKG_VERSION = "0.11.0";
16
+ export declare const PKG_VERSION = "0.12.0";
17
17
  /**
18
18
  * Stable exit-code contract for the `things` CLI (mirrored by MCP error
19
19
  * codes). Part of the public API surface consumed by agents and scripts —
@@ -51,21 +51,29 @@ export interface SectionCount {
51
51
  total: number;
52
52
  }
53
53
  /**
54
- * List-view truncation metadata (ADDITIVE). Present on the meta of any read
55
- * command that applies a row limit; absent on unbounded/structured views.
56
- * `shown` items were returned of `total` that matched after all filters;
57
- * `limit` is the effective cap (null when the caller asked for all rows);
58
- * `truncated` is true exactly when `shown < total`. The dropped remainder is
59
- * `total - shown`. `sections` is present only on a split flat view (the Today
60
- * split), breaking the whole-view counts down per render section.
54
+ * The single truncation-metadata shape for every read (the `meta.truncation`
55
+ * field). `shown` items were returned of `total` that matched after all
56
+ * filters; `limit` is the effective cap (null when the caller asked for all
57
+ * rows, and always null on a grouped view whose caps are per-block); `truncated`
58
+ * is the UNIVERSAL completeness check — true exactly when anything was dropped
59
+ * (`shown < total`, or any block hid rows). The dropped remainder is
60
+ * `total - shown`.
61
+ *
62
+ * Two optional per-shape breakdowns hang off it: `sections` for a split flat
63
+ * view (the Today split — per render-section shown/total), and `blocks` for a
64
+ * grouped view (anytime/someday/`area show` — the identity-carrying per-block
65
+ * nesting). Exactly one of them is present on the shapes that have it; both are
66
+ * absent on a plain flat view.
61
67
  */
62
68
  export interface Truncation {
63
69
  shown: number;
64
70
  total: number;
65
71
  limit: number | null;
66
72
  truncated: boolean;
67
- /** Per-section shown/total breakdown for a split flat view; absent otherwise. */
73
+ /** Per-section shown/total breakdown for a split flat view (Today); absent otherwise. */
68
74
  sections?: SectionCount[];
75
+ /** Per-block nesting for a grouped view (anytime/someday/area card); absent otherwise. */
76
+ blocks?: GroupBlock[];
69
77
  }
70
78
  /**
71
79
  * One identity-carrying block of a grouped catalogue (anytime/someday) or a
@@ -103,12 +111,6 @@ export interface GroupBlock {
103
111
  /** Nested project blocks (anytime item-lists / someday active-project groups). Absent when none. */
104
112
  children?: GroupBlock[];
105
113
  }
106
- /** Grouped-view truncation metadata (ADDITIVE); the per-block counterpart of {@link Truncation}. */
107
- export interface GroupedTruncation {
108
- /** True when any block hid items. */
109
- truncated: boolean;
110
- blocks: GroupBlock[];
111
- }
112
114
  export interface EnvelopeMeta {
113
115
  /** Things database schema version (`Meta.databaseVersion`), null when no DB was opened. */
114
116
  dbVersion: number | null;
@@ -116,10 +118,13 @@ export interface EnvelopeMeta {
116
118
  fingerprint: "ok" | "drift" | "user-accepted" | "unknown";
117
119
  /** Wall-clock duration of the command in milliseconds. */
118
120
  elapsedMs: number;
119
- /** List-view truncation metadata; present only on limited flat read views. */
121
+ /**
122
+ * The read's completeness metadata (the single truncation shape). Present on
123
+ * any read that could drop rows — flat views (row `limit`), the Today split
124
+ * (`sections`), and grouped views (anytime/someday/`area show`, `blocks`).
125
+ * `meta.truncation.truncated` is the universal "did I see everything" check.
126
+ */
120
127
  truncation?: Truncation;
121
- /** Per-block truncation metadata; present only on grouped views (anytime/someday). */
122
- grouped?: GroupedTruncation;
123
128
  /**
124
129
  * The canonical `things …` command a sugar invocation normalized to (bare
125
130
  * noun, keyword-in-show, uuid/share-link routing). Present only on routed
@@ -143,6 +148,32 @@ export interface EnvelopeMeta {
143
148
  timezone: string;
144
149
  today: string;
145
150
  };
151
+ /**
152
+ * The active content filter this response was scoped to (ADDITIVE). Present
153
+ * ONLY when a scope was applied — currently `area` (the `--area` view filter),
154
+ * carrying the resolved area's uuid + title; ABSENT otherwise, so the wire
155
+ * shape is unchanged for unscoped reads (the `meta.clock` precedent).
156
+ */
157
+ filter?: {
158
+ area: {
159
+ uuid: string;
160
+ title: string;
161
+ };
162
+ };
163
+ /**
164
+ * The active container scope this response was jailed to (ADDITIVE). Present
165
+ * ONLY when a scope is in force (the MCP `--scope` flag / `THINGS_API_SCOPE` /
166
+ * a stored `scope`), naming the container + where the scope came from; ABSENT
167
+ * otherwise, so the wire shape is unchanged for unscoped reads. Lets an agent
168
+ * know its own jail — which is not an oracle for what lies outside it (the
169
+ * `meta.clock` / `meta.filter` additive precedent).
170
+ */
171
+ scope?: {
172
+ kind: "area" | "project";
173
+ uuid: string;
174
+ title: string;
175
+ source: "flag" | "env" | "config";
176
+ };
146
177
  }
147
178
  export interface OkEnvelope<T> {
148
179
  apiVersion: typeof API_VERSION;
@@ -152,13 +183,38 @@ export interface OkEnvelope<T> {
152
183
  data: T;
153
184
  meta: EnvelopeMeta;
154
185
  }
186
+ /**
187
+ * The registry of stable machine-readable error codes an error envelope can
188
+ * carry — the compiler IS the registry. Every `error.code` value the surfaces
189
+ * emit is a member of this union, and the human-readable meaning of each is
190
+ * frozen at v1.0 (new codes may still be ADDED after v1.0 — that is
191
+ * non-breaking; a documented code's MEANING never changes). The canonical
192
+ * per-code table (meaning + the `detail` keys each may carry) lives in
193
+ * `docs/contract.md` (The error-code registry).
194
+ *
195
+ * Two members are template-literal families rather than fixed strings, because
196
+ * their suffix is minted in the write layer and the core deliberately never
197
+ * depends on it (see {@link blockedCode} / {@link verifyFailedCode}):
198
+ * - `verify-failed:${reason}` — a single mutation executed but read-after-write
199
+ * verification failed; the suffix is the reason (`timeout` | `mismatch` |
200
+ * `silent-noop`). The bare `verify-failed` (no suffix) is the multi-leg
201
+ * move/reorder failure.
202
+ * - `blocked:${suffix}` — a mutation refused before touching the app; the
203
+ * suffix is the specific hazard id (`H-…`) when one is named, else the block
204
+ * reason (`drift` | `disruption-tier` | `lock` | `environment` | `clock` |
205
+ * `scope`). The bare `blocked` is a policy refusal from the move planner.
206
+ *
207
+ * `blocked:drift` maps to exit code 5 (DriftBlocked); every other `blocked:*`
208
+ * maps to exit code 4 (Blocked).
209
+ */
210
+ export type ErrorCode = "usage" | "not-found" | "ambiguous" | "unsupported" | "environment" | "unexpected" | "bounce-aborted" | "verify-failed" | "blocked" | `verify-failed:${string}` | `blocked:${string}`;
155
211
  export interface ErrorEnvelope {
156
212
  apiVersion: typeof API_VERSION;
157
213
  ok: false;
158
214
  kind: "error";
159
215
  error: {
160
- /** Stable machine-readable code, mirrors the exit-code family (e.g. "verify-failed", "blocked"). */
161
- code: string;
216
+ /** Stable machine-readable code from the {@link ErrorCode} registry (mirrors the exit-code family, e.g. "verify-failed", "blocked:H-UNKNOWN-TAG"). */
217
+ code: ErrorCode;
162
218
  message: string;
163
219
  /**
164
220
  * Advisory attribution when failure signals point somewhere: e.g.
@@ -168,25 +224,117 @@ export interface ErrorEnvelope {
168
224
  likelyCause?: string;
169
225
  /** Actionable next step for the caller, when one exists. */
170
226
  remediation?: string;
171
- detail?: unknown;
172
227
  /**
173
- * Structured, machine-readable failure context (ADDITIVE). For an
174
- * unresolved show/bare-noun subject it carries `candidates` — the
175
- * did-you-mean title matches (standard item shapes, capped) so an agent
176
- * can self-correct without a second round-trip. For a bare mutation verb
177
- * (`things update <ref>`) it carries `suggestions` — the concrete
178
- * namespaced command(s) to run instead.
228
+ * The SINGLE structured, machine-readable failure-context object (there is
229
+ * no separate `details` field — the two were reconciled into this one). Each
230
+ * key is present only for the failures that produce it:
231
+ * - `expected` / `observed` a verify-failed mutation's expected delta and
232
+ * the observed post-write state.
233
+ * - `candidates` the disambiguation list for a not-found / ambiguous
234
+ * resolution (or a show/bare-noun did-you-mean): the ONE fixed, flag-
235
+ * invariant candidate shape (`CandidateRef`; see docs/contract.md), a
236
+ * LIVE-scoped pool capped at 8, so an agent can self-correct without
237
+ * another round-trip.
238
+ * - `suggestions` — for a bare mutation verb (`things update <ref>`), the
239
+ * concrete namespaced command(s) to run instead.
240
+ * - `considered` — the vectors weighed (and why each was rejected) for an
241
+ * unsupported operation.
242
+ * - `placed` / `remaining` / `cause` — a bounce reorder that aborted
243
+ * part-way: the items already placed, those not yet placed, and the cause.
244
+ * - `failed` / `completed` — a multi-leg move that failed mid-way: the leg
245
+ * that failed and the legs already completed before it.
179
246
  */
180
- details?: {
247
+ detail?: {
248
+ expected?: unknown;
249
+ observed?: Record<string, unknown> | null;
181
250
  candidates?: unknown[];
182
251
  suggestions?: string[];
252
+ considered?: {
253
+ vector: string;
254
+ why: string;
255
+ }[];
256
+ placed?: string[];
257
+ remaining?: string[];
258
+ cause?: unknown;
259
+ failed?: unknown;
260
+ completed?: unknown[];
183
261
  };
184
262
  };
185
263
  meta: EnvelopeMeta;
186
264
  }
187
265
  export type Envelope<T> = OkEnvelope<T> | ErrorEnvelope;
266
+ /**
267
+ * The envelope `kind` discriminators a SUCCESSFUL response carries today (the
268
+ * error envelope's `kind` is always the literal `"error"`). This union is the
269
+ * schema's record of the known payload classes — the flat-list reads
270
+ * (`data.items`), the sectioned reads (`data.sections`), the composite cards
271
+ * (`data.view`), the single-entity detail (`data.item`), the mutations/plans
272
+ * (flat `data` fields), and the diagnostic payloads.
273
+ *
274
+ * Per the compatibility covenant (docs/contract.md) a consumer MUST tolerate an
275
+ * UNKNOWN `kind`: adding a new kind is additive / non-breaking. So this union is
276
+ * "the kinds known to THIS build", not a closed set a generic reader may assume
277
+ * complete — the JSON Schema pins it as an enum so drift is caught, but a
278
+ * forward-compatible consumer routes on the wrapper it finds in `data`.
279
+ */
280
+ export type WireOkKind = "inbox" | "upcoming" | "logbook" | "trash" | "changes" | "search" | "projects" | "areas" | "tags" | "today" | "anytime" | "someday" | "area-view" | "project-view" | "detail" | "mutation-result" | "move-result" | "mutation-plan" | "move-plan" | "project-reopen" | "doctor" | "capabilities" | "config" | "legend" | "setup-shortcuts" | "install-skill";
281
+ /**
282
+ * The `data` payload of a successful envelope. Its CONCRETE shape is
283
+ * command-specific — one of the R1/R2 read wrappers (`item` | `view` | `items` |
284
+ * `sections`) or a mutation's flat result fields — and, crucially, entity
285
+ * payloads are omit-empty pruned on the wire (docs/design/contracts.md).
286
+ *
287
+ * COVERAGE BOUNDARY: this schema does NOT model the per-kind payload shapes. It
288
+ * pins the ENVELOPE layer exactly (`apiVersion`, `ok`, `kind`, `meta`, and the
289
+ * whole `error` object) and treats `data` as an open JSON object. Fully typing
290
+ * every kind's payload against the omit-empty wire shape is a separate, larger
291
+ * effort; until then `src/contracts.ts` and `docs/contract.md` remain the
292
+ * authoritative description of what each `kind`'s `data` contains.
293
+ */
294
+ export type WireData = Record<string, unknown>;
295
+ /** A successful `--json` envelope, exactly as emitted on stdout. */
296
+ export interface WireOkEnvelope {
297
+ apiVersion: typeof API_VERSION;
298
+ ok: true;
299
+ /** The payload class — see {@link WireOkKind}. */
300
+ kind: WireOkKind;
301
+ /** Command-specific payload; open at the schema layer (see {@link WireData}). */
302
+ data: WireData;
303
+ meta: EnvelopeMeta;
304
+ }
305
+ /**
306
+ * The machine-readable ROOT the envelope JSON Schema is generated from
307
+ * (`schema/envelope.schema.json`, produced by `npm run schema:gen`): the
308
+ * discriminated union — on the boolean `ok` — of a successful envelope and an
309
+ * {@link ErrorEnvelope}, exactly as written to stdout by the CLI `--json`
310
+ * surface and inherited by the MCP server. This is the same grammar
311
+ * docs/contract.md describes in prose; the schema is its generated, testable
312
+ * rendering. NB: the streaming commands (`batch`, `undo`) emit JSON Lines, a
313
+ * documented exception NOT covered by this envelope type.
314
+ */
315
+ export type WireEnvelope = WireOkEnvelope | ErrorEnvelope;
188
316
  export declare function okEnvelope<T>(kind: string, data: T, meta: EnvelopeMeta): OkEnvelope<T>;
189
317
  export declare function errorEnvelope(error: ErrorEnvelope["error"], meta: EnvelopeMeta): ErrorEnvelope;
318
+ /**
319
+ * Project a successful mutation/reorder/move outcome to its ENVELOPE `data`
320
+ * shape. The wire's mutation-success discriminator is REDUNDANT and is not
321
+ * emitted: the envelope already carries call success (`ok: true`, and the
322
+ * envelope `kind` names the payload class — `mutation-result` / `move-result`).
323
+ * So the result's internal `kind` discriminator is STRIPPED here — the emitted
324
+ * `data` has no `result` and no `kind`, just the payload fields (`op`, `uuid`,
325
+ * `title`, `undoToken`, `observed`, `vector`, `tier`, `touched`, notes, …).
326
+ * Only the internal tags `"ok"` / `"move-ok"` ever reach this boundary
327
+ * (failures route to error envelopes, dry-runs to the `mutation-plan` /
328
+ * `move-plan` kinds), so nothing is lost by dropping it. The library keeps the
329
+ * idiomatic `kind` discriminator on its in-memory
330
+ * `MutationResult`/`ReorderResult`/`MoveResult` unions; it simply never appears
331
+ * on the wire. BOTH consumer surfaces emit through it: the CLI (`mutation-result`
332
+ * / `move-result` envelopes) and the MCP mutation/move content block (phase-2
333
+ * framing sweep, 2026-07-31) — a dry-run routes to the bare `plan` on both.
334
+ */
335
+ export declare function mutationWireData<T extends {
336
+ kind: string;
337
+ }>(ok: T): Omit<T, "kind">;
190
338
  /**
191
339
  * Canonical machine-readable error CODE for a refused ("blocked") mutation: the
192
340
  * specific hazard id when one is named, else the block reason. Every surface —
package/dist/contracts.js CHANGED
@@ -13,7 +13,7 @@ export const API_VERSION = 1;
13
13
  * Package version, surfaced by `things --version` and the MCP serverInfo.
14
14
  * Kept in lockstep with package.json by a contract test.
15
15
  */
16
- export const PKG_VERSION = "0.11.0";
16
+ export const PKG_VERSION = "0.12.0";
17
17
  /**
18
18
  * Stable exit-code contract for the `things` CLI (mirrored by MCP error
19
19
  * codes). Part of the public API surface consumed by agents and scripts —
@@ -43,6 +43,27 @@ export function okEnvelope(kind, data, meta) {
43
43
  export function errorEnvelope(error, meta) {
44
44
  return { apiVersion: API_VERSION, ok: false, kind: "error", error, meta };
45
45
  }
46
+ /**
47
+ * Project a successful mutation/reorder/move outcome to its ENVELOPE `data`
48
+ * shape. The wire's mutation-success discriminator is REDUNDANT and is not
49
+ * emitted: the envelope already carries call success (`ok: true`, and the
50
+ * envelope `kind` names the payload class — `mutation-result` / `move-result`).
51
+ * So the result's internal `kind` discriminator is STRIPPED here — the emitted
52
+ * `data` has no `result` and no `kind`, just the payload fields (`op`, `uuid`,
53
+ * `title`, `undoToken`, `observed`, `vector`, `tier`, `touched`, notes, …).
54
+ * Only the internal tags `"ok"` / `"move-ok"` ever reach this boundary
55
+ * (failures route to error envelopes, dry-runs to the `mutation-plan` /
56
+ * `move-plan` kinds), so nothing is lost by dropping it. The library keeps the
57
+ * idiomatic `kind` discriminator on its in-memory
58
+ * `MutationResult`/`ReorderResult`/`MoveResult` unions; it simply never appears
59
+ * on the wire. BOTH consumer surfaces emit through it: the CLI (`mutation-result`
60
+ * / `move-result` envelopes) and the MCP mutation/move content block (phase-2
61
+ * framing sweep, 2026-07-31) — a dry-run routes to the bare `plan` on both.
62
+ */
63
+ export function mutationWireData(ok) {
64
+ const { kind: _kind, ...rest } = ok;
65
+ return rest;
66
+ }
46
67
  /**
47
68
  * Canonical machine-readable error CODE for a refused ("blocked") mutation: the
48
69
  * specific hazard id when one is named, else the block reason. Every surface —
@@ -1 +1 @@
1
- {"version":3,"file":"contracts.js","sourceRoot":"","sources":["../src/contracts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,CAAC;AAE7B;;;GAGG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,QAAQ,CAAC;AAEpC;;;;GAIG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,eAAe;IACf,EAAE,EAAE,CAAC;IACL,4DAA4D;IAC5D,UAAU,EAAE,CAAC;IACb,kEAAkE;IAClE,KAAK,EAAE,CAAC;IACR,uGAAuG;IACvG,YAAY,EAAE,CAAC;IACf,wFAAwF;IACxF,OAAO,EAAE,CAAC;IACV,gGAAgG;IAChG,YAAY,EAAE,CAAC;IACf,6DAA6D;IAC7D,WAAW,EAAE,CAAC;IACd,kFAAkF;IAClF,WAAW,EAAE,CAAC;CACN,CAAC;AAyJX,MAAM,UAAU,UAAU,CAAI,IAAY,EAAE,IAAO,EAAE,IAAkB;IACrE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;AACjE,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAA6B,EAAE,IAAkB;IAC7E,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AAC5E,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,WAAW,CAAC,OAA4C;IACtE,OAAO,WAAW,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;AACvD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAmB,OAAsB;IACvE,OAAO,iBAAiB,OAAO,CAAC,MAAM,EAAE,CAAC;AAC3C,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,iBAAiB,CAC/B,QAAsD;IAEtD,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,QAAQ,CAAC,EAAE,CAAC;IAC9C,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACnD,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,SAAS,IAAI,CAAC,CAAC,MAAM,KAAK,OAAO,CAAC,EAAE,CAAC;QACvE,OAAO,QAAQ,CAAC,YAAY,CAAC;IAC/B,CAAC;IACD,IAAI,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC;QAAE,OAAO,QAAQ,CAAC,OAAO,CAAC;IAClD,IAAI,KAAK,CAAC,GAAG,CAAC,aAAa,CAAC;QAAE,OAAO,QAAQ,CAAC,WAAW,CAAC;IAC1D,OAAO,QAAQ,CAAC,YAAY,CAAC;AAC/B,CAAC"}
1
+ {"version":3,"file":"contracts.js","sourceRoot":"","sources":["../src/contracts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,CAAC;AAE7B;;;GAGG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,QAAQ,CAAC;AAEpC;;;;GAIG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,eAAe;IACf,EAAE,EAAE,CAAC;IACL,4DAA4D;IAC5D,UAAU,EAAE,CAAC;IACb,kEAAkE;IAClE,KAAK,EAAE,CAAC;IACR,uGAAuG;IACvG,YAAY,EAAE,CAAC;IACf,wFAAwF;IACxF,OAAO,EAAE,CAAC;IACV,gGAAgG;IAChG,YAAY,EAAE,CAAC;IACf,6DAA6D;IAC7D,WAAW,EAAE,CAAC;IACd,kFAAkF;IAClF,WAAW,EAAE,CAAC;CACN,CAAC;AAmUX,MAAM,UAAU,UAAU,CAAI,IAAY,EAAE,IAAO,EAAE,IAAkB;IACrE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;AACjE,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAA6B,EAAE,IAAkB;IAC7E,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AAC5E,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,gBAAgB,CAA6B,EAAK;IAChE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,CAAC;IACpC,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,WAAW,CAAC,OAA4C;IACtE,OAAO,WAAW,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;AACvD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAmB,OAAsB;IACvE,OAAO,iBAAiB,OAAO,CAAC,MAAM,EAAE,CAAC;AAC3C,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,iBAAiB,CAC/B,QAAsD;IAEtD,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,QAAQ,CAAC,EAAE,CAAC;IAC9C,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACnD,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,SAAS,IAAI,CAAC,CAAC,MAAM,KAAK,OAAO,CAAC,EAAE,CAAC;QACvE,OAAO,QAAQ,CAAC,YAAY,CAAC;IAC/B,CAAC;IACD,IAAI,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC;QAAE,OAAO,QAAQ,CAAC,OAAO,CAAC;IAClD,IAAI,KAAK,CAAC,GAAG,CAAC,aAAa,CAAC;QAAE,OAAO,QAAQ,CAAC,WAAW,CAAC;IAC1D,OAAO,QAAQ,CAAC,YAAY,CAAC;AAC/B,CAAC"}
@@ -4,7 +4,7 @@ import { type CertificationStatus } from "./write/vectors/ui-certification.ts";
4
4
  import { type AvailabilityDeps, type ShortcutsState, type UrlSchemeState } from "./write/availability.ts";
5
5
  import { type EnvironmentChange, type EnvironmentTracker, type EnvironmentTuple } from "./write/environment.ts";
6
6
  import { type SyncHealth, type SyncHealthDeps } from "./sync-health.ts";
7
- import { ExitCode, type EnvelopeMeta } from "./contracts.ts";
7
+ import { ExitCode, type EnvelopeMeta, type ErrorCode } from "./contracts.ts";
8
8
  export interface DiagnoseReport {
9
9
  db: {
10
10
  path: string;
@@ -21,11 +21,43 @@ export interface DiagnoseReport {
21
21
  };
22
22
  app: {
23
23
  installed: boolean;
24
+ /** Installed app version (CFBundleShortVersionString); null when unreadable. */
25
+ version: string | null;
26
+ /**
27
+ * The app version the behavioral LAWS were last certified against
28
+ * (config `certified-app-version`); null when never set.
29
+ */
30
+ certifiedVersion: string | null;
31
+ /**
32
+ * True when the installed version is known, a certified version is set, and
33
+ * they differ — a PASSIVE signal that the behavioral laws
34
+ * (docs/reference/assumption-register.md) may have moved with zero schema
35
+ * delta. Non-blocking: writes stay enabled; doctor emits a notice pointing
36
+ * at the drift suite. False when unset/unknown/matching.
37
+ */
38
+ behavioralDrift: boolean;
24
39
  };
25
40
  writes: {
26
41
  enabled: boolean;
27
42
  reason: string;
28
43
  };
44
+ /**
45
+ * The active container scope (from THINGS_API_SCOPE / the stored `scope` key),
46
+ * so the jail is never silently on. `requested` is the raw ref (null when
47
+ * unscoped); `resolved` names the container it pins to (null when the ref no
48
+ * longer resolves — a fail-closed empty jail). The MCP `--scope` flag is
49
+ * per-process and not visible here.
50
+ */
51
+ scope: {
52
+ requested: string | null;
53
+ source: "env" | "config" | null;
54
+ resolved: {
55
+ kind: "area" | "project";
56
+ uuid: string;
57
+ title: string;
58
+ } | null;
59
+ detail: string;
60
+ };
29
61
  experimental: {
30
62
  /** config allowExperimental (opt-in for private-surface capabilities). */
31
63
  enabled: boolean;
@@ -132,7 +164,7 @@ export interface DiagnoseOptions {
132
164
  export interface DiagnoseResult {
133
165
  report: DiagnoseReport | null;
134
166
  error: {
135
- code: string;
167
+ code: ErrorCode;
136
168
  message: string;
137
169
  remediation?: string;
138
170
  } | null;
package/dist/diagnose.js CHANGED
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import { existsSync } from "node:fs";
7
7
  import { loadConfig } from "./config.js";
8
+ import { resolveScope } from "./read/scope.js";
8
9
  import { auditDir } from "./paths.js";
9
10
  import { readAuditRecords, scanAuditIntegrity } from "./write/undo.js";
10
11
  import { decodeRecurrenceRule } from "./model/recurrence.js";
@@ -128,6 +129,32 @@ export function diagnose(dbPath, options = {}) {
128
129
  if (t.extraColumns.length > 0)
129
130
  extraColumns[t.table] = t.extraColumns;
130
131
  }
132
+ // Resolve the ambient scope (env / stored) for the report — the MCP flag is
133
+ // per-process and not visible here. A stored ref that no longer resolves is
134
+ // a fail-closed empty jail (reads empty, writes refused), reported as such.
135
+ const scopeReport = (() => {
136
+ if (config.scope == null) {
137
+ return { requested: null, source: null, resolved: null, detail: "unscoped" };
138
+ }
139
+ try {
140
+ const s = resolveScope(conn.db, config.scope.ref, config.scope.source);
141
+ return {
142
+ requested: config.scope.ref,
143
+ source: config.scope.source,
144
+ resolved: { kind: s.kind, uuid: s.uuid, title: s.title },
145
+ detail: `scoped to ${s.kind} "${s.title}" (source: ${config.scope.source})`,
146
+ };
147
+ }
148
+ catch {
149
+ return {
150
+ requested: config.scope.ref,
151
+ source: config.scope.source,
152
+ resolved: null,
153
+ detail: `scope "${config.scope.ref}" (source: ${config.scope.source}) resolves to no ` +
154
+ "container — a fail-closed empty jail (reads empty, writes refused). Clear or fix it.",
155
+ };
156
+ }
157
+ })();
131
158
  const report = {
132
159
  db: {
133
160
  path: located.path,
@@ -142,7 +169,14 @@ export function diagnose(dbPath, options = {}) {
142
169
  detail: status.kind === "drift" ? status.detail : [],
143
170
  extraColumns,
144
171
  },
145
- app: { installed: existsSync(THINGS_APP) },
172
+ app: {
173
+ installed: existsSync(THINGS_APP),
174
+ version: currentEnv.thingsVersion,
175
+ certifiedVersion: config.certifiedAppVersion,
176
+ behavioralDrift: currentEnv.thingsVersion !== null &&
177
+ config.certifiedAppVersion !== null &&
178
+ currentEnv.thingsVersion !== config.certifiedAppVersion,
179
+ },
146
180
  writes: {
147
181
  enabled: writesEnabled,
148
182
  reason: accepted
@@ -154,6 +188,7 @@ export function diagnose(dbPath, options = {}) {
154
188
  ? "schema fingerprint deviates from baseline — writes disabled until revalidated"
155
189
  : "unknown databaseVersion — update things-api or revalidate",
156
190
  },
191
+ scope: scopeReport,
157
192
  experimental: {
158
193
  enabled: config.allowExperimental,
159
194
  sdefDeclaresReorder: sdefCanary,