pm-starter 2026.10.25 → 2026.10.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 2026.10.27 - 2026-10-27
4
+
5
+ ### Other
6
+
7
+ - Use SDK-certified complete reads in starter commands and exports ([pm-starter-sdk-adoption-1025](https://github.com/unbraind/pm-starter/blob/main/.agents/pm/tasks/pm-starter-sdk-adoption-1025.toon))
8
+
3
9
  ## 2026.10.25 - 2026-10-25
4
10
 
5
11
  ### Added
@@ -19,7 +25,6 @@
19
25
 
20
26
  ### Fixed
21
27
 
22
- - Fix release publish ordering before protected main push ([pm-starter-nf9d](https://github.com/unbraind/pm-starter/blob/main/.agents/pm/issues/pm-starter-nf9d.toon))
23
28
  - The changelog gate stamps an untagged version with the current date, so its verdict flips every midnight with no commit ([pm-starter-4n5o](https://github.com/unbraind/pm-starter/blob/main/.agents/pm/issues/pm-starter-4n5o.toon))
24
29
  - The release job authenticated with a stored npm token that expired, so publishing stopped while every other gate stayed green ([pm-starter-fllw](https://github.com/unbraind/pm-starter/blob/main/.agents/pm/issues/pm-starter-fllw.toon))
25
30
  - A release run bumped and committed a new version every night while publishing was impossible, because the only externally-failable step runs last ([pm-starter-kjvk](https://github.com/unbraind/pm-starter/blob/main/.agents/pm/issues/pm-starter-kjvk.toon))
@@ -28,6 +33,10 @@
28
33
 
29
34
  - Certify pm-starter on PM CLI/SDK 2026.10.4 and consolidate pending dependency updates ([pm-starter-cz6b](https://github.com/unbraind/pm-starter/blob/main/.agents/pm/chores/pm-starter-cz6b.toon))
30
35
 
36
+ ### Closed
37
+
38
+ - Closed: Fix release publish ordering before protected main push ([pm-starter-nf9d](https://github.com/unbraind/pm-starter/blob/main/.agents/pm/issues/pm-starter-nf9d.toon))
39
+
31
40
  ## 2026.10.5 - 2026-10-05
32
41
 
33
42
  ### Other
@@ -89,32 +98,38 @@
89
98
 
90
99
  ### Fixed
91
100
 
92
- - Pin pm-changelog 2026.8.30 before the next release ([pm-starter-pjdl](https://github.com/unbraind/pm-starter/blob/main/.agents/pm/issues/pm-starter-pjdl.toon))
93
101
  - Regenerate the changelog after the release tag exists ([pm-starter-b41g](https://github.com/unbraind/pm-starter/blob/main/.agents/pm/issues/pm-starter-b41g.toon))
94
102
 
103
+ ### Closed
104
+
105
+ - Closed: Pin pm-changelog 2026.8.30 before the next release ([pm-starter-pjdl](https://github.com/unbraind/pm-starter/blob/main/.agents/pm/issues/pm-starter-pjdl.toon))
106
+
95
107
  ## 2026.8.29 - 2026-08-29
96
108
 
97
109
  ### Fixed
98
110
 
99
111
  - A failed provenance publish silently falls back to an unattested one ([pm-starter-uxmy](https://github.com/unbraind/pm-starter/blob/main/.agents/pm/issues/pm-starter-uxmy.toon))
100
- - Close PR 82 attestation audit bypasses ([pm-starter-83vb](https://github.com/unbraind/pm-starter/blob/main/.agents/pm/issues/pm-starter-83vb.toon))
101
112
  - The publish-attestation gate misses a publish routed through an unquoted shell variable ([pm-starter-74p9](https://github.com/unbraind/pm-starter/blob/main/.agents/pm/issues/pm-starter-74p9.toon))
102
113
  - Fix same-line scalar assignment bypass in publish attestation scanner ([pm-starter-2899](https://github.com/unbraind/pm-starter/blob/main/.agents/pm/issues/pm-starter-2899.toon))
103
114
 
115
+ ### Closed
116
+
117
+ - Closed: Close PR 82 attestation audit bypasses ([pm-starter-83vb](https://github.com/unbraind/pm-starter/blob/main/.agents/pm/issues/pm-starter-83vb.toon))
118
+
104
119
  ## 2026.8.28 - 2026-08-28
105
120
 
106
121
  ### Fixed
107
122
 
108
123
  - changelog scripts read the workspace with default budgets, so pm-changelog refuses complete-read checks once the tracker exceeds one page ([pm-starter-ibm7](https://github.com/unbraind/pm-starter/blob/main/.agents/pm/issues/pm-starter-ibm7.toon))
109
124
 
110
- ### Security
111
-
112
- - The identity gate deadlocks the one remediation its own failure message prescribes ([pm-starter-tf19](https://github.com/unbraind/pm-starter/blob/main/.agents/pm/issues/pm-starter-tf19.toon))
113
-
114
125
  ### Other
115
126
 
116
127
  - Retire stale PR 65 evidence note after revert 67 ([pm-starter-pmnm](https://github.com/unbraind/pm-starter/blob/main/.agents/pm/tasks/pm-starter-pmnm.toon))
117
128
 
129
+ ### Closed
130
+
131
+ - Closed: The identity gate deadlocks the one remediation its own failure message prescribes ([pm-starter-tf19](https://github.com/unbraind/pm-starter/blob/main/.agents/pm/issues/pm-starter-tf19.toon))
132
+
118
133
  ## 2026.8.17 - 2026-08-17
119
134
 
120
135
  ### Fixed
@@ -186,14 +201,14 @@
186
201
 
187
202
  ## 2026.7.28 - 2026-07-28
188
203
 
189
- ### Fixed
190
-
191
- - Extension reports a stale runtime version after the 2026.7.27 bump ([pm-starter-1atg](https://github.com/unbraind/pm-starter/blob/main/.agents/pm/issues/pm-starter-1atg.toon))
192
-
193
204
  ### Other
194
205
 
195
206
  - Adopt pm-cli 2026.7.28 ([pm-starter-evym](https://github.com/unbraind/pm-starter/blob/main/.agents/pm/chores/pm-starter-evym.toon))
196
207
 
208
+ ### Closed
209
+
210
+ - Closed: Extension reports a stale runtime version after the 2026.7.27 bump ([pm-starter-1atg](https://github.com/unbraind/pm-starter/blob/main/.agents/pm/issues/pm-starter-1atg.toon))
211
+
197
212
  ## 2026.7.27 - 2026-07-27
198
213
 
199
214
  ### Removed
@@ -202,34 +217,34 @@
202
217
 
203
218
  ## 2026.7.26 - 2026-07-26
204
219
 
205
- ### Fixed
206
-
207
- - Starter template used any throughout, hiding three wrong SDK contracts and three unguarded pm reads ([pm-starter-kre3](https://github.com/unbraind/pm-starter/blob/main/.agents/pm/issues/pm-starter-kre3.toon))
208
-
209
220
  ### Other
210
221
 
211
222
  - Enable governance duplicate-detection advisory mode and adopt pm-cli 2026.7.25 ([pm-starter-tfh8](https://github.com/unbraind/pm-starter/blob/main/.agents/pm/chores/pm-starter-tfh8.toon))
212
223
 
213
- ## 2026.7.25 - 2026-07-25
224
+ ### Closed
214
225
 
215
- ### Fixed
226
+ - Closed: Starter template used any throughout, hiding three wrong SDK contracts and three unguarded pm reads ([pm-starter-kre3](https://github.com/unbraind/pm-starter/blob/main/.agents/pm/issues/pm-starter-kre3.toon))
216
227
 
217
- - pm item reads are capped at Node's 1 MiB spawnSync default, so a mature tracker fails with no diagnosis ([pm-starter-y8hb](https://github.com/unbraind/pm-starter/blob/main/.agents/pm/issues/pm-starter-y8hb.toon))
228
+ ## 2026.7.25 - 2026-07-25
218
229
 
219
230
  ### Other
220
231
 
221
232
  - Adopt --respect-item-release in changelog scripts and bump pm-changelog to 2026.7.24 ([pm-starter-toz4](https://github.com/unbraind/pm-starter/blob/main/.agents/pm/chores/pm-starter-toz4.toon))
222
233
 
223
- ## 2026.7.23 - 2026-07-23
234
+ ### Closed
224
235
 
225
- ### Fixed
236
+ - Closed: pm item reads are capped at Node's 1 MiB spawnSync default, so a mature tracker fails with no diagnosis ([pm-starter-y8hb](https://github.com/unbraind/pm-starter/blob/main/.agents/pm/issues/pm-starter-y8hb.toon))
226
237
 
227
- - Recommend pm merge reconcile (2026.7.22) over raw history-repair in Multi-agent merge safety docs ([pm-starter-ll3f](https://github.com/unbraind/pm-starter/blob/main/.agents/pm/issues/pm-starter-ll3f.toon))
238
+ ## 2026.7.23 - 2026-07-23
228
239
 
229
240
  ### Other
230
241
 
231
242
  - Adopt pm field-aware merge driver for multi-agent branch-merge safety ([pm-starter-ey52](https://github.com/unbraind/pm-starter/blob/main/.agents/pm/chores/pm-starter-ey52.toon))
232
243
 
244
+ ### Closed
245
+
246
+ - Closed: Recommend pm merge reconcile (2026.7.22) over raw history-repair in Multi-agent merge safety docs ([pm-starter-ll3f](https://github.com/unbraind/pm-starter/blob/main/.agents/pm/issues/pm-starter-ll3f.toon))
247
+
233
248
  ## 2026.7.19 - 2026-07-19
234
249
 
235
250
  ### Added
package/README.md CHANGED
@@ -117,8 +117,8 @@ acceptance is a separate concern. A changing source fails parity explicitly.
117
117
 
118
118
  ## Without git
119
119
 
120
- All starter commands, the read-only exporters, and both laboratories work in a plain
121
- initialized tracker with no Git executable. Creating or running a laboratory needs
120
+ All starter commands, the read-only exporters, both laboratories and the failure-first
121
+ recovery recipes work in a plain initialized tracker with no Git executable. Creating or running a laboratory needs
122
122
  no repository. Cloning the starter and repository release/changelog automation need
123
123
  Git. Laboratory commands require a host that injects the SDK (tested with 2026.10.20)
124
124
  and return an actionable refusal when it is absent.
@@ -148,6 +148,32 @@ implementation; the real CLI supplies the live `api` object at activation time.
148
148
  For the same reason the `EXIT_CODE` / `CommandError` error contract is
149
149
  re-implemented locally rather than imported from the SDK.
150
150
 
151
+ ## Complete SDK reads
152
+
153
+ `starter demo` and `starter-demo export` use `ctx.sdk.client.listAllComplete`
154
+ with full bodies, including closed and canceled items. The SDK calls
155
+ `certifyCompleteListResult` itself: there is no external JSON envelope parser
156
+ or local completeness policy left in this package. Both commands then call the
157
+ SDK history action with `verify: true` for each item. That action reads fresh
158
+ history and uses `verifyHistoryEntries`, including comparison with the current
159
+ item, before returning a verification receipt. A cached event index is never
160
+ accepted as integrity evidence. These reads work without `pm` or `git` on PATH.
161
+
162
+ Install and activation require pm-cli >=2026.8.18, the first published stable
163
+ release with `PmClient.listAllComplete`. The development SDK remains pinned to
164
+ 2026.10.20. See the [published API inventory](docs/sdk-floor.md) for every used
165
+ capability and its first-release artifact evidence. Hosts without SDK injection
166
+ receive an explicit failed-read outcome. Failed or incomplete reads
167
+ continue to fail with exit code 1, including exports; they never become an empty
168
+ workspace. SDK certification is intentionally stricter about counts, unique IDs,
169
+ full projections, unbounded delivery and omission receipts. Invalid history now
170
+ fails explicitly, even if a previous cached read succeeded. `PM_JSON_MAX_BUFFER`
171
+ continues to apply to the other subprocess examples; SDK reads do not use it.
172
+ The exported `readPmItems` helper now accepts the host SDK context and returns a
173
+ Promise. The former `describeListAllIncompleteness` helper was removed with its
174
+ local policy. SDK rows can retain optional properties with value `undefined`
175
+ that CLI JSON omitted; their serialized values are identical. Renderer ownership and callback guards retain their existing scope.
176
+
151
177
  ## SDK 2026.7.6 patterns
152
178
 
153
179
  The starter demonstrates several patterns introduced or stabilized in the
@@ -224,3 +250,19 @@ npx pm github sync --repo unbraind/pm-starter --dry-run
224
250
  ```
225
251
 
226
252
  This does not enable scheduled sync. See `docs/certification-2026.10.4.md` for validation and copied-real-tracker evidence. Whole-executable and statement coverage remains tracked in `pm-starter-puy5`.
253
+
254
+ ## Failure-first task recipes
255
+
256
+ `pm starter recover --json` deliberately encounters three native refusals and
257
+ finishes each read through a structured recovery contract. Use
258
+ `--recipe missing-item|unsupported-capability|insufficient-budget` for one loop,
259
+ or `--output-format toon` for compact TOON receipts. The typed
260
+ `runFailureFirstRecipes(ctx.sdk, { recipe: 'all' })` API uses the same code path.
261
+
262
+ The workflows discover a valid item ID, select an advertised graph read, and
263
+ recover an infeasible output budget using the SDK's certified complete-list
264
+ helper. They read existing trackers without Git and never execute suggested
265
+ shell text or write item data. Each receipt asserts its native discriminator,
266
+ remedy and successful retry; the missing-item SDK classifier is labeled
267
+ explicitly. See the [runnable CLI and SDK examples](docs/failure-first-recipes.md)
268
+ for contracts, refusal codes and concurrency limits.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from "./laboratories.ts";
2
- import type { ExtensionApi } from "@unbrained/pm-cli/sdk";
2
+ export * from "./recipes.ts";
3
+ import type { CommandHandlerContext, ExtensionApi } from "@unbrained/pm-cli/sdk";
3
4
  /**
4
5
  * Semantic exit codes this package throws via {@link CommandError}.
5
6
  *
@@ -57,38 +58,15 @@ export type PmReadOutcome = {
57
58
  readonly reason: string;
58
59
  };
59
60
  /**
60
- * Safely read all items from the workspace by shelling out to `pm`.
61
+ * Read the whole tracker through the host SDK, preserving explicit failure.
61
62
  *
62
- * Never throws, so demos cannot blow up at activation/read time — but it also
63
- * never reports a failed read as an empty workspace. Every failure path returns
64
- * `{ ok: false, reason }` naming what went wrong, and callers turn that into a
65
- * {@link CommandError} rather than rendering it as "no items". This is the SAFE
66
- * read pattern every demo reuses.
67
- *
68
- * @param pmRoot - Workspace root passed through to `pm --path`.
69
- * @returns The rows on a proven-complete read, or the reason the read failed.
70
- */
71
- export declare function readPmItems(pmRoot: string): PmReadOutcome;
72
- /**
73
- * Name the reason a `pm list --status all` envelope is not the whole workspace, or
74
- * return `null` when it is complete.
75
- *
76
- * The envelope has carried a completeness receipt since 2026.8.15, and reading
77
- * `.items` without consulting it is how a partial answer becomes a
78
- * successful-looking result. That is not hypothetical: pm-cli 2026.8.14 returned
79
- * 10 items of a 682-item workspace with `truncated: true`, and every consumer
80
- * that ignored the receipt reported success on 1.5% of the data.
81
- *
82
- * Four independent signals each mean "the rows you got are not all the rows".
83
- * A missing `completeness` object counts as incomplete rather than complete: an
84
- * answer that cannot be verified is not a verified answer, and treating absence
85
- * as success is the same mistake one level up.
86
- *
87
- * @param envelope - Parsed `pm list --status all --json` output.
88
- * @returns A human-readable reason naming the tripped signal and the
89
- * count-versus-total figures, or `null` if the answer is complete.
63
+ * listAllComplete uses certifyCompleteListResult for strict, full, unbounded
64
+ * delivery. A fresh verified history read checks each item against its durable
65
+ * stream before any row reaches a command or exporter. History's verification
66
+ * delegates to verifyHistoryEntries, rather than trusting the cached event index.
67
+ * No runtime SDK import or pm/git subprocess is required.
90
68
  */
91
- export declare function describeListAllIncompleteness(envelope: unknown): string | null;
69
+ export declare function readPmItems(ctx: Pick<CommandHandlerContext, "sdk">): Promise<PmReadOutcome>;
92
70
  declare const _default: {
93
71
  name: string;
94
72
  version: string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAiDA,cAAc,mBAAmB,CAAC;AAElC,OAAO,KAAK,EAIV,YAAY,EAab,MAAM,uBAAuB,CAAC;AAoB/B;;;;;;;;GAQG;AACH,eAAO,MAAM,SAAS;aACpB,eAAe,EAAE,CAAC;aAClB,KAAK,EAAE,CAAC;aACR,SAAS,EAAE,CAAC;CACJ,CAAC;AAKX;;;;;;;;GAQG;AACH,qBAAa,YAAa,SAAQ,KAAK;IACrC,yFAAyF;IACzF,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,OAAO,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAkC,EAIxE;CACF;AAMD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAK1F;AAED,wFAAwF;AACxF,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAMpG;AAED,mFAAmF;AACnF,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,QAAQ,EAAE,MAAM,EAChB,GAAG,IAAI,EAAE,MAAM,EAAE,GAChB,MAAM,CAWR;AA4DD;;;;;;;;;;GAUG;AACH,MAAM,MAAM,aAAa,GACrB;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;CAAE,GACrE;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpD;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,CA0EzD;AA2BD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,6BAA6B,CAAC,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CA2B9E;;;;kBA+3Be,YAAY"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAiDA,cAAc,mBAAmB,CAAC;AAElC,cAAc,cAAc,CAAC;AAE7B,OAAO,KAAK,EAGV,qBAAqB,EACrB,YAAY,EAab,MAAM,uBAAuB,CAAC;AAoB/B;;;;;;;;GAQG;AACH,eAAO,MAAM,SAAS;aACpB,eAAe,EAAE,CAAC;aAClB,KAAK,EAAE,CAAC;aACR,SAAS,EAAE,CAAC;CACJ,CAAC;AAKX;;;;;;;;GAQG;AACH,qBAAa,YAAa,SAAQ,KAAK;IACrC,yFAAyF;IACzF,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,OAAO,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAkC,EAIxE;CACF;AAMD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAK1F;AAED,wFAAwF;AACxF,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAMpG;AAED,mFAAmF;AACnF,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,QAAQ,EAAE,MAAM,EAChB,GAAG,IAAI,EAAE,MAAM,EAAE,GAChB,MAAM,CAWR;AA8CD;;;;;;;;;;GAUG;AACH,MAAM,MAAM,aAAa,GACrB;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;CAAE,GACrE;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpD;;;;;;;;GAQG;AACH,wBAAsB,WAAW,CAAC,GAAG,EAAE,IAAI,CAAC,qBAAqB,EAAE,KAAK,CAAC,GAAG,OAAO,CAAC,aAAa,CAAC,CAcjG;;;;kBAg5Be,YAAY"}
package/dist/index.js CHANGED
@@ -6,8 +6,8 @@
6
6
  //
7
7
  // Each demonstration is labelled in comments as "DEMO: <capability>" and is
8
8
  // deliberately INERT — no destructive behavior and no network access. The demos
9
- // only read (via `pm ... --json`), print, or return data; they never delete or
10
- // mutate your workspace on their own.
9
+ // only read (via the host SDK or `pm ... --json`), print, or return data; they
10
+ // never delete or mutate your workspace on their own.
11
11
  //
12
12
  // ---------------------------------------------------------------------------
13
13
  // CAPABILITY → setup function → manifest capability (see README for the full
@@ -46,6 +46,8 @@
46
46
  import { spawnSync } from "node:child_process";
47
47
  import { runExportLaboratory, runLaboratoryCommand } from "./laboratories.js";
48
48
  export * from "./laboratories.js";
49
+ import { runFailureFirstCommand } from "./recipes.js";
50
+ export * from "./recipes.js";
49
51
  // Standalone-installed extensions load ONLY their own `dist/` at runtime, so
50
52
  // `@unbrained/pm-cli` is not resolvable as a runtime value. We therefore use the
51
53
  // zero-runtime-coupling pattern: import `defineExtension` as a TYPE only and
@@ -175,177 +177,40 @@ function pmJsonMaxBuffer() {
175
177
  const raw = Number(process.env.PM_JSON_MAX_BUFFER);
176
178
  return Number.isSafeInteger(raw) && raw > 0 ? raw : 64 * 1024 * 1024;
177
179
  }
178
- /** Name the real cause of a failed `pm` read. A stdout overrun kills the child
179
- * with `status: null` and EMPTY stderr, so without this the failure surfaces as
180
- * an unexplained error (or, worse, as an empty result set). */
181
- function describePmReadFailure(error, limitBytes) {
182
- const code = error.code;
183
- if (code === "ENOBUFS") {
184
- // This read is always the full workspace, so "narrow the operation" would be a
185
- // dead instruction here — name only the lever the reader actually has.
186
- return `pm output exceeded the ${limitBytes} byte read buffer. `
187
- + "Raise PM_JSON_MAX_BUFFER (in bytes) to increase the read limit for this workspace.";
188
- }
189
- return `pm read failed: ${error.message}`;
190
- }
191
180
  /**
192
- * Safely read all items from the workspace by shelling out to `pm`.
193
- *
194
- * Never throws, so demos cannot blow up at activation/read time — but it also
195
- * never reports a failed read as an empty workspace. Every failure path returns
196
- * `{ ok: false, reason }` naming what went wrong, and callers turn that into a
197
- * {@link CommandError} rather than rendering it as "no items". This is the SAFE
198
- * read pattern every demo reuses.
181
+ * Read the whole tracker through the host SDK, preserving explicit failure.
199
182
  *
200
- * @param pmRoot - Workspace root passed through to `pm --path`.
201
- * @returns The rows on a proven-complete read, or the reason the read failed.
183
+ * listAllComplete uses certifyCompleteListResult for strict, full, unbounded
184
+ * delivery. A fresh verified history read checks each item against its durable
185
+ * stream before any row reaches a command or exporter. History's verification
186
+ * delegates to verifyHistoryEntries, rather than trusting the cached event index.
187
+ * No runtime SDK import or pm/git subprocess is required.
202
188
  */
203
- export function readPmItems(pmRoot) {
204
- const maxBuffer = pmJsonMaxBuffer();
205
- const result = spawnSync("pm", ["--path", pmRoot, "list", "--status", "all", "--full", "--include-body", "--no-truncate", "--strict-read", "--json"], { encoding: "utf-8", maxBuffer });
206
- if (result.error) {
207
- return { ok: false, reason: describePmReadFailure(result.error, maxBuffer) };
208
- }
209
- if (result.status !== 0) {
210
- return {
211
- ok: false,
212
- reason: `pm exited ${result.status} — ${result.stderr?.trim() || "no stderr output"}`,
213
- };
214
- }
215
- let parsed;
189
+ export async function readPmItems(ctx) {
190
+ if (!ctx.sdk)
191
+ return { ok: false, reason: "This read requires a pm host with ctx.sdk (pm-cli >=2026.10.20)." };
216
192
  try {
217
- parsed = JSON.parse(result.stdout);
218
- }
219
- catch (err) {
220
- // Kept separate from the incompleteness check below so the diagnostic names
221
- // the actual failure: unparseable output is a different problem from a
222
- // parsed envelope that admits it is partial.
223
- return {
224
- ok: false,
225
- reason: `could not parse \`pm list --status all --json\` output: ${err instanceof Error ? err.message : String(err)}`,
226
- };
227
- }
228
- // A bare array carries no completeness receipt, so it cannot prove it is the
229
- // whole workspace — and an unprovable answer is exactly what this reader
230
- // exists to refuse. Accepting it would leave a legacy-shaped partial response
231
- // as an open bypass around every check below.
232
- if (Array.isArray(parsed)) {
233
- return {
234
- ok: false,
235
- reason: "`pm list --status all --json` returned a bare array, which carries no completeness receipt to verify",
236
- };
237
- }
238
- // Anything that is not an object carries no receipt either, and
239
- // `describeListAllIncompleteness` answers `null` for every non-object — so
240
- // without this check a payload of `null`, `false`, `42` or `"text"` would
241
- // fall through to an absent rows field and be reported as a successful empty
242
- // workspace. That is the bare-array fail-open one shape over.
243
- if (!isObject(parsed)) {
244
- return {
245
- ok: false,
246
- reason: `\`pm list --status all --json\` returned ${parsed === null ? "null" : typeof parsed}, which carries no completeness receipt to verify`,
247
- };
248
- }
249
- const incomplete = describeListAllIncompleteness(parsed);
250
- if (incomplete) {
251
- // A truncated or degraded envelope is a FAILED read that looks like a
252
- // successful one: `items` is present and parses, so without this check the
253
- // demo renders a partial workspace and reports success.
254
- return { ok: false, reason: `refusing an incomplete pm read — ${incomplete}` };
255
- }
256
- const rows = readPath(parsed, "items") ?? readPath(parsed, "results");
257
- if (rows === undefined)
258
- return { ok: true, items: [] };
259
- if (!Array.isArray(rows)) {
260
- return { ok: false, reason: "`pm list --status all --json` returned a non-array rows field" };
193
+ const result = await ctx.sdk.client.listAllComplete({ includeBody: true });
194
+ for (const item of result.items) {
195
+ const history = await ctx.sdk.client.run("history", { id: item.id, options: { verify: true, compact: true } });
196
+ if (readPath(history, "verification", "ok") !== true) {
197
+ return { ok: false, reason: `history integrity verification failed for ${item.id}` };
198
+ }
199
+ }
200
+ return { ok: true, items: result.items };
261
201
  }
262
- // Dropping unusable rows would contradict everything above: the receipt said
263
- // this answer is the whole workspace, so silently returning fewer rows than it
264
- // contains is a partial read reported as a complete one — the exact failure
265
- // this reader exists to refuse, arriving through the row payload instead of
266
- // through the receipt.
267
- const unusable = rows.findIndex((row) => !isObject(row));
268
- if (unusable !== -1) {
269
- return {
270
- ok: false,
271
- reason: `\`pm list --status all --json\` returned an unusable row at index ${unusable} (${rows[unusable] === null ? "null" : typeof rows[unusable]}); the receipt claimed a complete answer, so dropping it would report a shortened workspace as complete`,
272
- };
202
+ catch (error) {
203
+ return { ok: false, reason: String(error) };
273
204
  }
274
- return { ok: true, items: rows };
275
205
  }
276
- /**
277
- * Read the workspace or fail the command, so a failed read never renders as an
278
- * empty one.
279
- *
280
- * @param pmRoot - Workspace root passed through to `pm --path`.
281
- * @returns The rows of a proven-complete read.
282
- * @throws {CommandError} With {@link EXIT_CODE.GENERIC_FAILURE} when the read failed.
283
- */
284
- function readPmItemsOrFail(pmRoot) {
285
- const outcome = readPmItems(pmRoot);
206
+ /** Read verified complete rows or fail with the command runtime's semantic exit code. */
207
+ async function readPmItemsOrFail(ctx) {
208
+ const outcome = await readPmItems(ctx);
286
209
  if (!outcome.ok) {
287
210
  throw new CommandError(`pm-starter: could not read pm items — ${outcome.reason}`, EXIT_CODE.GENERIC_FAILURE);
288
211
  }
289
212
  return outcome.items;
290
213
  }
291
- /**
292
- * Render one count field for the scale suffix, tolerating a field the installed
293
- * CLI does not emit or emits as something other than a number.
294
- */
295
- function readCount(envelope, key) {
296
- const value = readPath(envelope, key);
297
- return typeof value === "number" ? String(value) : "?";
298
- }
299
- /**
300
- * Name the reason a `pm list --status all` envelope is not the whole workspace, or
301
- * return `null` when it is complete.
302
- *
303
- * The envelope has carried a completeness receipt since 2026.8.15, and reading
304
- * `.items` without consulting it is how a partial answer becomes a
305
- * successful-looking result. That is not hypothetical: pm-cli 2026.8.14 returned
306
- * 10 items of a 682-item workspace with `truncated: true`, and every consumer
307
- * that ignored the receipt reported success on 1.5% of the data.
308
- *
309
- * Four independent signals each mean "the rows you got are not all the rows".
310
- * A missing `completeness` object counts as incomplete rather than complete: an
311
- * answer that cannot be verified is not a verified answer, and treating absence
312
- * as success is the same mistake one level up.
313
- *
314
- * @param envelope - Parsed `pm list --status all --json` output.
315
- * @returns A human-readable reason naming the tripped signal and the
316
- * count-versus-total figures, or `null` if the answer is complete.
317
- */
318
- export function describeListAllIncompleteness(envelope) {
319
- // A non-object, or a bare ARRAY, carries no receipt to contradict — and an
320
- // array is `typeof "object"`, so it must be excluded explicitly or the check
321
- // reports every array as "completeness absent". `readPmItems` already handles
322
- // the array shape on its own line above; only an ENVELOPE can claim to be
323
- // incomplete.
324
- if (!isObject(envelope))
325
- return null;
326
- // Every field below is read through `readPath` rather than asserted with a
327
- // cast. These values come from whichever pm build the user has installed, so
328
- // their types are an assumption, not a guarantee — and a cast that turns out
329
- // to be wrong fails the comparison silently, which for a completeness receipt
330
- // means reporting "complete" for an answer that never claimed to be.
331
- const scale = `${readCount(envelope, "count")} of ${readCount(envelope, "total")} item(s) returned`;
332
- if (readPath(envelope, "truncated") === true)
333
- return `the row list was truncated (${scale})`;
334
- if (readPath(envelope, "has_more") === true)
335
- return `more rows exist past the returned page (${scale})`;
336
- const status = readPath(envelope, "completeness", "status");
337
- if (status !== "complete") {
338
- // Anything that is not the literal string "complete" is treated as
339
- // incomplete, including a non-string the cast would previously have let
340
- // through untyped.
341
- const described = status === undefined ? "absent" : JSON.stringify(status);
342
- return `completeness.status is ${described}, not "complete" (${scale})`;
343
- }
344
- if (readPath(envelope, "omission_receipt", "has_omissions") === true) {
345
- return `field groups were omitted from the projection (${scale})`;
346
- }
347
- return null;
348
- }
349
214
  // ---------------------------------------------------------------------------
350
215
  // DEMO: commands (registerCommand)
351
216
  //
@@ -365,6 +230,14 @@ export function describeListAllIncompleteness(envelope) {
365
230
  * @param api - The extension API surface passed to `activate`.
366
231
  */
367
232
  function setupCommands(api) {
233
+ api.registerCommand({
234
+ name: "starter recover",
235
+ description: "Encounter native refusals and finish read-only tasks through their structured remedies.",
236
+ examples: ["pm starter recover --json", "pm starter recover --recipe insufficient-budget"],
237
+ flags: [{ long: "--recipe", value_name: "recipe", type: "string", description: "all (default), missing-item, unsupported-capability or insufficient-budget." }],
238
+ failure_hints: ["Select an initialized tracker with at least one item and a host with ctx.sdk."],
239
+ run: runFailureFirstCommand,
240
+ });
368
241
  api.registerCommand({
369
242
  name: "starter lab",
370
243
  description: "Run one read-only capability laboratory on directory and synthetic remote adapters.",
@@ -471,10 +344,10 @@ function setupCommands(api) {
471
344
  intent: "demonstrate a command result flowing through a custom renderer",
472
345
  examples: ["pm starter demo", "pm starter demo --json"],
473
346
  failure_hints: [
474
- "The demo reads items via `pm list --status all --json`; ensure the workspace is initialized.",
347
+ "The demo uses host SDK complete reads and verified history; initialize the workspace and use pm-cli >=2026.10.20.",
475
348
  ],
476
349
  async run(ctx) {
477
- const items = readPmItemsOrFail(ctx.pm_root);
350
+ const items = await readPmItemsOrFail(ctx);
478
351
  // Return a small, predictable shape the renderer override can recognize.
479
352
  return {
480
353
  starter_demo: true,
@@ -937,7 +810,7 @@ function setupImportExport(api) {
937
810
  // Read-only: serializes the current items to a compact JSON payload and
938
811
  // prints it (or returns it for --json hosts). Never writes to disk.
939
812
  api.registerExporter("starter-demo", async (ctx) => {
940
- const items = readPmItemsOrFail(ctx.pm_root);
813
+ const items = await readPmItemsOrFail(ctx);
941
814
  const payload = items.map((i) => ({
942
815
  id: i.id,
943
816
  title: i.title,
@@ -1067,7 +940,7 @@ function setupPreflight(api) {
1067
940
  return {
1068
941
  enforce_item_format_gate: d.enforce_item_format_gate ?? true,
1069
942
  run_preflight_item_format_sync: d.run_preflight_item_format_sync ?? false,
1070
- run_extension_migrations: ctx.command === "starter lab" || ctx.command === "starter-lab export"
943
+ run_extension_migrations: ctx.command === "starter lab" || ctx.command === "starter-lab export" || ctx.command === "starter recover"
1071
944
  ? false : d.run_extension_migrations ?? true,
1072
945
  enforce_mandatory_migration_gate: d.enforce_mandatory_migration_gate ?? false,
1073
946
  };
@@ -1150,7 +1023,7 @@ function setupFlags(api) {
1150
1023
  const defineExtension = (module) => module;
1151
1024
  export default defineExtension({
1152
1025
  name: "pm-starter",
1153
- version: "2026.10.25",
1026
+ version: "2026.10.27",
1154
1027
  activate(api) {
1155
1028
  // Register every capability group. Each helper is defensive enough to be
1156
1029
  // safely deleted when you fork this scaffold for a real extension.