things-api 0.19.2 → 0.19.4

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 (66) hide show
  1. package/README.md +1 -1
  2. package/deputy/prebuilt/Things API Helper.app/Contents/CodeResources +0 -0
  3. package/deputy/prebuilt/Things API Helper.app/Contents/Helpers/things-reader.app/Contents/MacOS/things-reader +0 -0
  4. package/deputy/prebuilt/Things API Helper.app/Contents/MacOS/things-deputy +0 -0
  5. package/dist/audit/schema.d.ts +14 -0
  6. package/dist/audit/schema.js.map +1 -1
  7. package/dist/cli/commands/op-result.js +13 -1
  8. package/dist/cli/commands/op-result.js.map +1 -1
  9. package/dist/cli/commands/writes.js +51 -7
  10. package/dist/cli/commands/writes.js.map +1 -1
  11. package/dist/contracts.d.ts +1 -1
  12. package/dist/contracts.js +1 -1
  13. package/dist/index.d.ts +1 -1
  14. package/dist/index.js.map +1 -1
  15. package/dist/mcp/server.js +7 -0
  16. package/dist/mcp/server.js.map +1 -1
  17. package/dist/op-result.d.ts +9 -0
  18. package/dist/op-result.js +5 -0
  19. package/dist/op-result.js.map +1 -1
  20. package/dist/ui-state.d.ts +1 -1
  21. package/dist/ui-state.js +33 -8
  22. package/dist/ui-state.js.map +1 -1
  23. package/dist/write/batch.js +4 -1
  24. package/dist/write/batch.js.map +1 -1
  25. package/dist/write/clone.js +12 -6
  26. package/dist/write/clone.js.map +1 -1
  27. package/dist/write/disclosures.d.ts +263 -0
  28. package/dist/write/disclosures.js +265 -0
  29. package/dist/write/disclosures.js.map +1 -0
  30. package/dist/write/heading.js +4 -7
  31. package/dist/write/heading.js.map +1 -1
  32. package/dist/write/make-repeating-project.js +2 -1
  33. package/dist/write/make-repeating-project.js.map +1 -1
  34. package/dist/write/opid.js +5 -3
  35. package/dist/write/opid.js.map +1 -1
  36. package/dist/write/pipeline.d.ts +39 -1
  37. package/dist/write/pipeline.js +49 -23
  38. package/dist/write/pipeline.js.map +1 -1
  39. package/dist/write/promote-clone.js +46 -36
  40. package/dist/write/promote-clone.js.map +1 -1
  41. package/dist/write/reorder.js +45 -39
  42. package/dist/write/reorder.js.map +1 -1
  43. package/dist/write/resolution-timestamps.js +4 -1
  44. package/dist/write/resolution-timestamps.js.map +1 -1
  45. package/dist/write/spawn-expectation.d.ts +135 -0
  46. package/dist/write/spawn-expectation.js +237 -0
  47. package/dist/write/spawn-expectation.js.map +1 -0
  48. package/dist/write/template-mutation.js +20 -33
  49. package/dist/write/template-mutation.js.map +1 -1
  50. package/dist/write/vectors/types.d.ts +12 -0
  51. package/dist/write/vectors/ui-recipes.js +11 -1
  52. package/dist/write/vectors/ui-recipes.js.map +1 -1
  53. package/dist/write/vectors/ui-state.d.ts +136 -6
  54. package/dist/write/vectors/ui-state.js +372 -70
  55. package/dist/write/vectors/ui-state.js.map +1 -1
  56. package/dist/write/vectors/ui.d.ts +62 -2
  57. package/dist/write/vectors/ui.js +401 -151
  58. package/dist/write/vectors/ui.js.map +1 -1
  59. package/dist/write/verify/delta.d.ts +17 -2
  60. package/dist/write/verify/delta.js +46 -16
  61. package/dist/write/verify/delta.js.map +1 -1
  62. package/dist/write/verify/poller.d.ts +3 -2
  63. package/dist/write/verify/poller.js +3 -1
  64. package/dist/write/verify/poller.js.map +1 -1
  65. package/package.json +1 -1
  66. package/skills/things-cli/SKILL.md +1 -1
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  A typed TypeScript library + CLI (`things`) for programmatic interaction with [Things 3](https://culturedcode.com/things/) by Cultured Code.
6
6
 
7
- **Status: read + write + MCP layers live and published to npm (v0.19.2 — see [CHANGELOG.md](CHANGELOG.md)).** Reads go straight to the local SQLite database (UI-exact Today ordering, sidebar-grouped Anytime/Someday with container-status cascade, decoded repeat rules, occurrence projections); writes run a verified pipeline over four write vectors — two lab-validated headless surfaces (the URL scheme + AppleScript) and two opt-in surfaces (Apple Shortcuts and an Accessibility-driven GUI vector) — with hazard guards, disruption-tier policy, a JSONL audit trail, batch mode, audit-replay undo (transactional across compound operations), full project lifecycle (complete/cancel/reopen/restore), heading rename/archive/unarchive with children policies, container detach, granular stateful checklists, tag hierarchy management incl. un-nesting, completion/creation backdating, Logbook imports, tiered fuzzy name resolution (uuid prefixes, `things:///show?id=` share links), and kind-neutral in-place reordering within any container or view (one `things reorder <refs…> [--start|--end|--before|--after] --in <target>` verb — Today/Evening, a project, an area, a heading's children, someday, inbox — plus `project move-heading` for the headings themselves and `area reorder` for the sidebar). The Apple Shortcuts surface is wired for the two headless capabilities nothing else can do: creating a heading in an existing project (`things project add-heading`) and clearing a reminder from a date-scheduled item (`things todo clear-reminder`). Both run through bundled proxy shortcuts (`things setup`) and are gated on their presence. The fourth vector — the Accessibility-driven GUI vector — is off by default and drives the local Things app to reach transforms that exist on no scriptable surface (repeat-rule editing on existing to-dos and projects, convert-to-project, sidebar area reorder); see [Accessibility GUI vector](#accessibility-gui-vector-optional-off-by-default) below. Single-item permanent delete stays interactive-only (its macOS consent has no always-allow) and is out of the headless pipeline. See [docs/design/](docs/design/) for the architecture and VM-lab design, [docs/lab/](docs/lab/harness.md) for the probe harness and campaign results the write layer is grounded in, and [docs/atlas/](docs/atlas/schema-v26.md) for the database↔UI map.
7
+ **Status: read + write + MCP layers live and published to npm (v0.19.4 — see [CHANGELOG.md](CHANGELOG.md)).** Reads go straight to the local SQLite database (UI-exact Today ordering, sidebar-grouped Anytime/Someday with container-status cascade, decoded repeat rules, occurrence projections); writes run a verified pipeline over four write vectors — two lab-validated headless surfaces (the URL scheme + AppleScript) and two opt-in surfaces (Apple Shortcuts and an Accessibility-driven GUI vector) — with hazard guards, disruption-tier policy, a JSONL audit trail, batch mode, audit-replay undo (transactional across compound operations), full project lifecycle (complete/cancel/reopen/restore), heading rename/archive/unarchive with children policies, container detach, granular stateful checklists, tag hierarchy management incl. un-nesting, completion/creation backdating, Logbook imports, tiered fuzzy name resolution (uuid prefixes, `things:///show?id=` share links), and kind-neutral in-place reordering within any container or view (one `things reorder <refs…> [--start|--end|--before|--after] --in <target>` verb — Today/Evening, a project, an area, a heading's children, someday, inbox — plus `project move-heading` for the headings themselves and `area reorder` for the sidebar). The Apple Shortcuts surface is wired for the two headless capabilities nothing else can do: creating a heading in an existing project (`things project add-heading`) and clearing a reminder from a date-scheduled item (`things todo clear-reminder`). Both run through bundled proxy shortcuts (`things setup`) and are gated on their presence. The fourth vector — the Accessibility-driven GUI vector — is off by default and drives the local Things app to reach transforms that exist on no scriptable surface (repeat-rule editing on existing to-dos and projects, convert-to-project, sidebar area reorder); see [Accessibility GUI vector](#accessibility-gui-vector-optional-off-by-default) below. Single-item permanent delete stays interactive-only (its macOS consent has no always-allow) and is out of the headless pipeline. See [docs/design/](docs/design/) for the architecture and VM-lab design, [docs/lab/](docs/lab/harness.md) for the probe harness and campaign results the write layer is grounded in, and [docs/atlas/](docs/atlas/schema-v26.md) for the database↔UI map.
8
8
 
9
9
  ```sh
10
10
  things today --json # read: your Today list, Evening split, UI order
@@ -63,6 +63,20 @@ export interface AuditRecord {
63
63
  * refusal to guess, never an assumption either way.
64
64
  */
65
65
  expected?: DeltaSpec;
66
+ /**
67
+ * The GUI drive's step play-by-play (ADDITIVE), one compact entry per recipe
68
+ * step, in order — recorded on the FINAL record of every ui-vector write,
69
+ * success and failure alike.
70
+ *
71
+ * This record is the APPEND-ONLY DEBUG LOG of the diagnostic ladder (#632).
72
+ * The step list used to ride every successful result and cost a caller context
73
+ * on writes that went fine; now the default success output omits it, a failure
74
+ * carries it, `--verbose` opts a success back into it, and it is ALWAYS here —
75
+ * so `things op-result <op-id>` can hand it back after the fact. Compact
76
+ * labels only (the recipe's own step names); the per-invocation trace file
77
+ * remains the deep tier with the raw payloads and timings.
78
+ */
79
+ steps?: string[];
66
80
  /** Normalized requested delta (params as given, post-normalization). */
67
81
  requested: Record<string, unknown>;
68
82
  /** Asserted-field subset of the pre-state (null when target didn't exist). */
@@ -1 +1 @@
1
- {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/audit/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AA+FzC;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,SAAS,CACvB,MAA0E;IAE1E,IAAI,MAAM,CAAC,GAAG,EAAE,IAAI,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;IACzD,yEAAyE;IACzE,wEAAwE;IACxE,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC;QAC9B,MAAM,CAAC,EAAE;QACT,MAAM,CAAC,EAAE;QACT,MAAM,CAAC,KAAK;QACZ,MAAM,CAAC,IAAI;QACX,MAAM,CAAC,IAAI,IAAI,EAAE;KAClB,CAAC,CAAC;IACH,MAAM,MAAM,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAChF,OAAO,KAAK,MAAM,EAAE,CAAC;AACvB,CAAC"}
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/audit/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AA6GzC;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,SAAS,CACvB,MAA0E;IAE1E,IAAI,MAAM,CAAC,GAAG,EAAE,IAAI,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;IACzD,yEAAyE;IACzE,wEAAwE;IACxE,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC;QAC9B,MAAM,CAAC,EAAE;QACT,MAAM,CAAC,EAAE;QACT,MAAM,CAAC,KAAK;QACZ,MAAM,CAAC,IAAI;QACX,MAAM,CAAC,IAAI,IAAI,EAAE;KAClB,CAAC,CAAC;IACH,MAAM,MAAM,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAChF,OAAO,KAAK,MAAM,EAAE,CAAC;AACvB,CAAC"}
@@ -8,7 +8,9 @@ export function registerOpResult(program) {
8
8
  "your environment killed the command before it printed its result: let the command die, " +
9
9
  "then run this. Reports FOUND (the final result + target + observation), INTENT-ONLY (the " +
10
10
  "op started but no outcome was written — still running or the process died mid-flight, " +
11
- "outcome UNCERTAIN), or UNKNOWN (no such op-id in history). Always exit 0 (a history read).")
11
+ "outcome UNCERTAIN), or UNKNOWN (no such op-id in history). Also reports the step-by-step " +
12
+ "account of how a change was driven through the app, for a change that succeeded as well " +
13
+ "as one that failed. Always exit 0 (a history read).")
12
14
  .option("--json", "emit versioned JSON envelope on stdout")
13
15
  .action((opId, opts) => {
14
16
  const started = Date.now();
@@ -38,6 +40,16 @@ export function registerOpResult(program) {
38
40
  if (data.status === "found" && data.observed !== null) {
39
41
  process.stdout.write(` observed: ${JSON.stringify(data.observed)}\n`);
40
42
  }
43
+ // The step account, read back from the change history (#632). A write
44
+ // that succeeded no longer prints its steps, so this is where they are
45
+ // recovered — which is what makes leaving them off the success output
46
+ // a move rather than a loss.
47
+ if (data.steps !== null && data.steps.length > 0) {
48
+ process.stdout.write(` drove ${data.steps.length} step(s):\n`);
49
+ for (const [i, step] of data.steps.entries()) {
50
+ process.stdout.write(` ${i + 1}. ${step}\n`);
51
+ }
52
+ }
41
53
  }
42
54
  process.exitCode = 0;
43
55
  });
@@ -1 +1 @@
1
- {"version":3,"file":"op-result.js","sourceRoot":"","sources":["../../../src/cli/commands/op-result.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAqB,MAAM,gBAAgB,CAAC;AAEzE,MAAM,UAAU,gBAAgB,CAAC,OAAgB;IAC/C,OAAO;SACJ,OAAO,CAAC,WAAW,CAAC;SACpB,QAAQ,CAAC,SAAS,EAAE,gDAAgD,CAAC;SACrE,WAAW,CACV,sFAAsF;QACpF,wFAAwF;QACxF,yFAAyF;QACzF,2FAA2F;QAC3F,wFAAwF;QACxF,4FAA4F,CAC/F;SACA,MAAM,CAAC,QAAQ,EAAE,wCAAwC,CAAC;SAC1D,MAAM,CAAC,CAAC,IAAY,EAAE,IAAwB,EAAE,EAAE;QACjD,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC3B,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC5B,MAAM,IAAI,GAAiB;YACzB,SAAS,EAAE,IAAI;YACf,WAAW,EAAE,SAAS;YACtB,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO;SAChC,CAAC;QACF,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YACd,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,WAAW,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;QACnF,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,GACR,IAAI,CAAC,MAAM,KAAK,OAAO;gBACrB,CAAC,CAAC,GAAG,IAAI,CAAC,MAAM,MAAM,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;gBAC7E,CAAC,CAAC,IAAI,CAAC,MAAM,KAAK,aAAa;oBAC7B,CAAC,CAAC,iBAAiB,IAAI,CAAC,EAAE,8BAA8B;oBACxD,CAAC,CAAC,eAAe,CAAC;YACxB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,aAAa,IAAI,KAAK,IAAI,IAAI,CAAC,CAAC;YACrD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC;YACzC,IAAI,IAAI,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;gBAClC,MAAM,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC;gBAC1B,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,iBAAiB,CAAC,CAAC,cAAc,GAAG,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,IAAI,EAAE,GAAG;oBAC9E,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,uBAAuB,CAAC,CAAC,CAAC,cAAc,uBAAuB;oBAC9E,GAAG,CAAC,CAAC,YAAY,IAAI,CACxB,CAAC;YACJ,CAAC;YACD,IAAI,IAAI,CAAC,MAAM,KAAK,OAAO,IAAI,IAAI,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAC;gBACtD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,eAAe,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;YACzE,CAAC;QACH,CAAC;QACD,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;IACvB,CAAC,CAAC,CAAC;AACP,CAAC"}
1
+ {"version":3,"file":"op-result.js","sourceRoot":"","sources":["../../../src/cli/commands/op-result.ts"],"names":[],"mappings":"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"}
@@ -25,6 +25,14 @@ const PRESERVE_MODIFIED_HELP = "keep this change off the modification-date timel
25
25
  "Safe with Things Cloud: the restored date syncs to your other devices and stays put, so " +
26
26
  "the item stays off the timeline everywhere — unless another device edits the same item " +
27
27
  "at nearly the same time, which re-dates it.";
28
+ /**
29
+ * `--verbose` help. The flag is the middle rung of the diagnostic ladder: a
30
+ * successful change reports what it did and what to consider doing next, and
31
+ * only says HOW it drove the app when asked. A failure prints the steps without
32
+ * the flag, and `things op-result <key>` prints them after the fact either way.
33
+ */
34
+ const VERBOSE_HELP = "add the step-by-step account of how the change was driven through the app; a failed change " +
35
+ "reports it anyway, and `things op-result` reports it for any change already made";
28
36
  function addWriteFlags(cmd, capability = {}) {
29
37
  const opId = new Option("--op-id <key>", "idempotency key: a resubmission with the same key is recognized as already applied " +
30
38
  "and not re-run (matches [A-Za-z0-9_-], 1-64 chars)");
@@ -40,6 +48,7 @@ function addWriteFlags(cmd, capability = {}) {
40
48
  .option("--allow-very-disruptive", "permit changes that visibly drive the Things UI")
41
49
  .option("--verify-timeout <ms>", "how long to wait for the change to take effect")
42
50
  .option("--actor <name>", "author name recorded for this change (default: from config)")
51
+ .option("--verbose", VERBOSE_HELP)
43
52
  .addOption(opId);
44
53
  }
45
54
  /**
@@ -126,6 +135,7 @@ function writeOptionsFrom(opts, extra = {}) {
126
135
  ...(maxDisruption !== undefined && { maxDisruption }),
127
136
  ...(opts.verifyTimeout !== undefined && { verifyTimeoutMs: Number(opts.verifyTimeout) }),
128
137
  ...(opts.actor !== undefined && { actor: opts.actor }),
138
+ ...(opts.verbose === true && { verbose: true }),
129
139
  ...(opts.dangerouslyDriveGui === true && { dangerouslyDriveGui: true }),
130
140
  ...(opts.opId !== undefined && { opId: opts.opId }),
131
141
  ...(opts.preserveModified === true && { preserveModified: true }),
@@ -396,6 +406,30 @@ function emitContextNote(result) {
396
406
  if (parts.length > 0)
397
407
  process.stdout.write(dim(` ${parts.join("; ")}\n`));
398
408
  }
409
+ /**
410
+ * The TWO TIERS on the human path (#632), plus the step account when it was
411
+ * asked for. Warnings are the actionable half and keep the `warning:` prefix on
412
+ * stderr, where an operator's eye already goes; notes are matter-of-fact and
413
+ * render DIM and unprefixed on stdout alongside the other disclosure lines, so
414
+ * "here is what happened" never wears the costume of "you should do something".
415
+ *
416
+ * The step account is dimmer still and indented under its own heading — present
417
+ * only when `--verbose` put `steps` on the result.
418
+ */
419
+ function emitDisclosures(result) {
420
+ for (const note of result.notes ?? [])
421
+ process.stdout.write(dim(` ${note}\n`));
422
+ for (const warning of result.warnings ?? []) {
423
+ process.stderr.write(`warning: ${warning}\n`);
424
+ }
425
+ const steps = result.steps ?? [];
426
+ if (steps.length === 0)
427
+ return;
428
+ process.stdout.write(dim(` drove ${steps.length} step(s):\n`));
429
+ for (const [i, step] of steps.entries()) {
430
+ process.stdout.write(dim(` ${i + 1}. ${step}\n`));
431
+ }
432
+ }
399
433
  function emitResult(result, opts, meta) {
400
434
  switch (result.kind) {
401
435
  case "bounce-aborted": {
@@ -415,10 +449,11 @@ function emitResult(result, opts, meta) {
415
449
  return;
416
450
  }
417
451
  case "ok": {
418
- for (const warning of result.warnings ?? []) {
419
- process.stderr.write(`warning: ${warning}\n`);
420
- }
421
452
  if (opts.json) {
453
+ // ONE CHANNEL (#632). Under --json the envelope is the WHOLE output:
454
+ // `warnings` and `notes` are already in it, so echoing them to stderr
455
+ // made every consumer pay for the same prose twice — once parsed, once
456
+ // as noise in its log. The human path below keeps the prose.
422
457
  process.stdout.write(`${JSON.stringify(okEnvelope("mutation-result", mutationWireData(result), meta))}\n`);
423
458
  }
424
459
  else {
@@ -430,6 +465,7 @@ function emitResult(result, opts, meta) {
430
465
  emitOccurrenceNote(result);
431
466
  emitPreserveNote(result);
432
467
  emitContextNote(result);
468
+ emitDisclosures(result);
433
469
  }
434
470
  process.exitCode = ExitCode.Ok;
435
471
  return;
@@ -459,7 +495,14 @@ function emitResult(result, opts, meta) {
459
495
  message: result.detail,
460
496
  ...(result.likelyCause !== undefined && { likelyCause: result.likelyCause }),
461
497
  ...(result.hint !== undefined && { remediation: result.hint }),
462
- detail: { expected: result.expected, observed: result.observed },
498
+ detail: {
499
+ expected: result.expected,
500
+ observed: result.observed,
501
+ // A FAILURE always carries the drive's play-by-play (#632) —
502
+ // no flag, no second command. It is what made the field bug
503
+ // reports actionable.
504
+ ...(result.steps !== undefined && { steps: result.steps }),
505
+ },
463
506
  }, meta))}\n`);
464
507
  }
465
508
  else {
@@ -467,6 +510,9 @@ function emitResult(result, opts, meta) {
467
510
  if (result.likelyCause !== undefined) {
468
511
  process.stderr.write(` likely cause: ${result.likelyCause}${result.hint !== undefined ? ` — ${result.hint}` : ""}\n`);
469
512
  }
513
+ for (const [i, step] of (result.steps ?? []).entries()) {
514
+ process.stderr.write(` step ${i + 1}: ${step}\n`);
515
+ }
470
516
  }
471
517
  process.exitCode = ExitCode.VerifyFailed;
472
518
  return;
@@ -1999,12 +2045,10 @@ export function registerWriteCommands(program) {
1999
2045
  // Human path discloses the count inline; --json carries it on
2000
2046
  // `observed.logged`. Everything else defers to the shared emitter.
2001
2047
  if (result.kind === "ok" && o.json !== true) {
2002
- for (const warning of result.warnings ?? []) {
2003
- process.stderr.write(`warning: ${warning}\n`);
2004
- }
2005
2048
  const logged = result.observed?.logged ?? 0;
2006
2049
  process.stdout.write(`ok log-now (logged ${logged} item${logged === 1 ? "" : "s"}, ` +
2007
2050
  `vector=${result.vector}, tier=${result.tier}, verified)\n`);
2051
+ emitDisclosures(result);
2008
2052
  process.exitCode = ExitCode.Ok;
2009
2053
  return;
2010
2054
  }