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 +36 -21
- package/README.md +44 -2
- package/dist/index.d.ts +9 -31
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +39 -166
- package/dist/index.js.map +1 -1
- package/dist/recipes.d.ts +64 -0
- package/dist/recipes.d.ts.map +1 -0
- package/dist/recipes.js +146 -0
- package/dist/recipes.js.map +1 -0
- package/docs/failure-first-recipes.md +107 -0
- package/docs/sdk-floor.json +570 -0
- package/docs/sdk-floor.md +82 -0
- package/manifest.json +2 -2
- package/package.json +18 -10
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
|
-
|
|
224
|
+
### Closed
|
|
214
225
|
|
|
215
|
-
|
|
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
|
-
|
|
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
|
-
|
|
234
|
+
### Closed
|
|
224
235
|
|
|
225
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
*
|
|
61
|
+
* Read the whole tracker through the host SDK, preserving explicit failure.
|
|
61
62
|
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
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
|
|
69
|
+
export declare function readPmItems(ctx: Pick<CommandHandlerContext, "sdk">): Promise<PmReadOutcome>;
|
|
92
70
|
declare const _default: {
|
|
93
71
|
name: string;
|
|
94
72
|
version: string;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAiDA,cAAc,mBAAmB,CAAC;AAElC,OAAO,KAAK,
|
|
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
|
|
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
|
-
*
|
|
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
|
-
*
|
|
201
|
-
*
|
|
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(
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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
|
-
|
|
263
|
-
|
|
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
|
-
|
|
278
|
-
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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.
|