pm-github 2026.10.27 → 2026.10.29

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/CHANGELOG.md +52 -39
  2. package/README.md +75 -3
  3. package/dist/closeout.d.ts.map +1 -1
  4. package/dist/closeout.js +71 -30
  5. package/dist/closeout.js.map +1 -1
  6. package/dist/discussion-schema.d.ts +110 -0
  7. package/dist/discussion-schema.d.ts.map +1 -0
  8. package/dist/discussion-schema.js +41 -0
  9. package/dist/discussion-schema.js.map +1 -0
  10. package/dist/discussions.d.ts +26 -0
  11. package/dist/discussions.d.ts.map +1 -0
  12. package/dist/discussions.js +215 -0
  13. package/dist/discussions.js.map +1 -0
  14. package/dist/gate.d.ts.map +1 -1
  15. package/dist/gate.js +10 -4
  16. package/dist/gate.js.map +1 -1
  17. package/dist/identity.d.ts.map +1 -1
  18. package/dist/identity.js +2 -1
  19. package/dist/identity.js.map +1 -1
  20. package/dist/import-fidelity.d.ts +8 -3
  21. package/dist/import-fidelity.d.ts.map +1 -1
  22. package/dist/import-fidelity.js +39 -10
  23. package/dist/import-fidelity.js.map +1 -1
  24. package/dist/import-tags.d.ts +15 -0
  25. package/dist/import-tags.d.ts.map +1 -0
  26. package/dist/import-tags.js +35 -0
  27. package/dist/import-tags.js.map +1 -0
  28. package/dist/index.d.ts +52 -41
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +188 -89
  31. package/dist/index.js.map +1 -1
  32. package/dist/issue-forms.d.ts +1 -1
  33. package/dist/issue-forms.d.ts.map +1 -1
  34. package/dist/issue-forms.js +8 -5
  35. package/dist/issue-forms.js.map +1 -1
  36. package/dist/label-names.d.ts +6 -0
  37. package/dist/label-names.d.ts.map +1 -0
  38. package/dist/label-names.js +15 -0
  39. package/dist/label-names.js.map +1 -0
  40. package/dist/labels.d.ts +161 -0
  41. package/dist/labels.d.ts.map +1 -0
  42. package/dist/labels.js +235 -0
  43. package/dist/labels.js.map +1 -0
  44. package/dist/projects.d.ts +6 -4
  45. package/dist/projects.d.ts.map +1 -1
  46. package/dist/projects.js +25 -24
  47. package/dist/projects.js.map +1 -1
  48. package/dist/refresh.d.ts +2 -1
  49. package/dist/refresh.d.ts.map +1 -1
  50. package/dist/refresh.js +19 -9
  51. package/dist/refresh.js.map +1 -1
  52. package/dist/webhooks.d.ts.map +1 -1
  53. package/dist/webhooks.js +5 -2
  54. package/dist/webhooks.js.map +1 -1
  55. package/dist/workflow-diagnostic.d.ts +9 -0
  56. package/dist/workflow-diagnostic.d.ts.map +1 -0
  57. package/dist/workflow-diagnostic.js +62 -0
  58. package/dist/workflow-diagnostic.js.map +1 -0
  59. package/dist/workspace.d.ts +18 -0
  60. package/dist/workspace.d.ts.map +1 -0
  61. package/dist/workspace.js +39 -0
  62. package/dist/workspace.js.map +1 -0
  63. package/docs/discussion-capture-example.json +14 -0
  64. package/docs/discussion-capture.md +44 -0
  65. package/manifest.json +2 -2
  66. package/package.json +15 -10
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 2026.10.29 - 2026-10-29
4
+
5
+ ### Added
6
+
7
+ - Capture selected GitHub discussion passages as reviewed native Decisions ([pm-github-selected-discussion-decisions](https://github.com/unbraind/pm-github/blob/main/.agents/pm/features/pm-github-selected-discussion-decisions.toon))
8
+
9
+ ## 2026.10.28 - 2026-10-28
10
+
11
+ ### Added
12
+
13
+ - Preview exact GitHub label migration impacts (BREAKING: PM CLI 2026.10.17 minimum) ([pm-github-2e38](https://github.com/unbraind/pm-github/blob/main/.agents/pm/features/pm-github-2e38.toon))
14
+
3
15
  ## 2026.10.26 - 2026-10-26
4
16
 
5
17
  ### Changed
@@ -34,7 +46,6 @@
34
46
  - Derive release merge wait from CI duration and refresh a safe main advance once ([pm-github-release-merge-wait](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-release-merge-wait.toon))
35
47
  - A publish spelled through a package runner was invisible to the attestation gate, so an unattested one read as clean ([pm-github-5igz](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-5igz.toon))
36
48
  - PM CLI filtered native Bun tests falsely report an empty run ([pm-github-2ao1](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-2ao1.toon))
37
- - Track pm-changelog rendering closed historical incidents as Fixed ([pm-github-n0mm](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-n0mm.toon))
38
49
  - Stabilize the watchdog startup probe under concurrent coverage ([pm-github-sah1](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-sah1.toon))
39
50
  - Explain privacy matches and distinguish bare-root regex literals during gated sync ([pm-github-xhtp](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-xhtp.toon))
40
51
  - The changelog gate stamps an untagged version with the current date, so its verdict flips every midnight with no commit ([pm-github-m8vj](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-m8vj.toon))
@@ -49,11 +60,6 @@
49
60
  - Closeout changelog generation attributes new deliveries to an old package release ([pm-github-lfcf](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-lfcf.toon))
50
61
  - The release job authenticated with a stored npm token that expired, so publishing stopped while every other gate stayed green ([pm-github-m8u2](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-m8u2.toon))
51
62
  - A release run bumped and committed a new version every night while publishing was impossible, because the only externally-failable step runs last ([pm-github-yq1d](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-yq1d.toon))
52
- - The daily release cannot build a changelog once the tracker outgrows the default output budget ([pm-github-ypi5](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-ypi5.toon))
53
-
54
- ### Security
55
-
56
- - Gate historical identities and host-path privacy across every Git object ([pm-github-zqad](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-zqad.toon))
57
63
 
58
64
  ### Other
59
65
 
@@ -63,6 +69,11 @@
63
69
  - Audit copied fleet release workflows for canonical generation and one daily publication ([pm-github-sagl](https://github.com/unbraind/pm-github/blob/main/.agents/pm/tasks/pm-github-sagl.toon))
64
70
  - Adopt the released gated sync caller in fleet repositories ([pm-github-aiok](https://github.com/unbraind/pm-github/blob/main/.agents/pm/tasks/pm-github-aiok.toon))
65
71
 
72
+ ### Closed
73
+
74
+ - Closed: Gate historical identities and host-path privacy across every Git object ([pm-github-zqad](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-zqad.toon))
75
+ - Closed: The daily release cannot build a changelog once the tracker outgrows the default output budget ([pm-github-ypi5](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-ypi5.toon))
76
+
66
77
  ## 2026.10.16 - 2026-10-16
67
78
 
68
79
  ### Added
@@ -97,13 +108,16 @@
97
108
  ### Fixed
98
109
 
99
110
  - Bound native Bun CLI repeats of a completed public atomic import ([pm-github-hptv](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-hptv.toon))
100
- - Root CI runs skip the read-only-directory comment-sync fixtures visibly instead of failing them ([pm-github-rap0](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-rap0.toon))
101
111
 
102
112
  ### Other
103
113
 
104
114
  - Reach exact all-source coverage and include every operational script ([pm-github-9cjx](https://github.com/unbraind/pm-github/blob/main/.agents/pm/tasks/pm-github-9cjx.toon))
105
115
  - Auto-merge green Dependabot updates and group the pm toolchain into one daily PR ([pm-github-u9df](https://github.com/unbraind/pm-github/blob/main/.agents/pm/tasks/pm-github-u9df.toon))
106
116
 
117
+ ### Closed
118
+
119
+ - Closed: Root CI runs skip the read-only-directory comment-sync fixtures visibly instead of failing them ([pm-github-rap0](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-rap0.toon))
120
+
107
121
  ## 2026.9.26 - 2026-09-26
108
122
 
109
123
  ### Other
@@ -178,22 +192,24 @@
178
192
 
179
193
  ## 2026.8.31 - 2026-08-31
180
194
 
181
- ### Fixed
195
+ ### Closed
182
196
 
183
- - Pin pm-changelog 2026.8.30 before the next release ([pm-github-g9g0](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-g9g0.toon))
184
- - Regenerate the changelog after the release tag was created ([pm-github-5wzr](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-5wzr.toon))
197
+ - Closed: Pin pm-changelog 2026.8.30 before the next release ([pm-github-g9g0](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-g9g0.toon))
198
+ - Closed: Regenerate the changelog after the release tag was created ([pm-github-5wzr](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-5wzr.toon))
185
199
 
186
200
  ## 2026.8.29 - 2026-08-29
187
201
 
188
202
  ### Fixed
189
203
 
190
- - Validate pm CLI development dependency bump ([pm-github-j8zh](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-j8zh.toon))
191
- - The publish-attestation gate misses a publish routed through an unquoted shell variable ([pm-github-tko1](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-tko1.toon))
192
- - True round-trip GitHub sync: search provider, validate diagnostics, safe-by-default export, fix activation ([pm-github-9dqy](https://github.com/unbraind/pm-github/blob/main/.agents/pm/features/pm-github-9dqy.toon))
193
204
  - A failed provenance publish silently falls back to an unattested one ([pm-github-i5b8](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-i5b8.toon))
194
205
  - Fix release publish ordering ahead of protected main push ([pm-github-v2kt](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-v2kt.toon))
195
206
  - BREAKING: pm-github now requires pm CLI 2026.8.20 or newer; older hosts may fail installation or runtime validation ([pm-github-iswq](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-iswq.toon))
196
207
 
208
+ ### Closed
209
+
210
+ - Closed: Validate pm CLI development dependency bump ([pm-github-j8zh](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-j8zh.toon))
211
+ - Closed: The publish-attestation gate misses a publish routed through an unquoted shell variable ([pm-github-tko1](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-tko1.toon))
212
+
197
213
  ## 2026.8.18 - 2026-08-18
198
214
 
199
215
  ### Fixed
@@ -223,13 +239,16 @@
223
239
  ### Fixed
224
240
 
225
241
  - Propagate the docstring gate entry guard fix ([pm-github-wb4q](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-wb4q.toon))
226
- - Converge changelog generation and verification on replace mode ([pm-github-8f60](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-8f60.toon))
227
242
  - The mandatory docstring gate could skip its own scan and still exit zero ([pm-github-wxob](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-wxob.toon))
228
243
 
229
244
  ### Other
230
245
 
231
246
  - Adopt the canonical pm-ops docstring gate ([pm-github-pbxd](https://github.com/unbraind/pm-github/blob/main/.agents/pm/tasks/pm-github-pbxd.toon))
232
247
 
248
+ ### Closed
249
+
250
+ - Closed: Converge changelog generation and verification on replace mode ([pm-github-8f60](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-8f60.toon))
251
+
233
252
  ## 2026.8.7 - 2026-08-07
234
253
 
235
254
  ### Other
@@ -238,14 +257,14 @@
238
257
 
239
258
  ## 2026.8.4 - 2026-08-04
240
259
 
241
- ### Fixed
242
-
243
- - Fix terminal transitions for pm-cli 2026.8.3 close_reason enforcement ([pm-github-rwq9](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-rwq9.toon))
244
-
245
260
  ### Other
246
261
 
247
262
  - Resolve pm-changelog to the release that derives release dates in UTC ([pm-github-9m6j](https://github.com/unbraind/pm-github/blob/main/.agents/pm/chores/pm-github-9m6j.toon))
248
263
 
264
+ ### Closed
265
+
266
+ - Closed: Fix terminal transitions for pm-cli 2026.8.3 close_reason enforcement ([pm-github-rwq9](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-rwq9.toon))
267
+
249
268
  ## 2026.7.30 - 2026-07-30
250
269
 
251
270
  ### Other
@@ -278,38 +297,34 @@
278
297
 
279
298
  ## 2026.7.26 - 2026-07-26
280
299
 
281
- ### Fixed
282
-
283
- - Documented install command fails: pm install github.com/unbraind/pm-github cannot resolve an entry file ([pm-github-q6ql](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-q6ql.toon))
284
-
285
300
  ### Other
286
301
 
287
302
  - Enable governance duplicate-detection advisory mode and adopt pm-cli 2026.7.25 ([pm-github-suxt](https://github.com/unbraind/pm-github/blob/main/.agents/pm/chores/pm-github-suxt.toon))
288
303
 
289
- ## 2026.7.25 - 2026-07-25
304
+ ### Closed
290
305
 
291
- ### Added
306
+ - Closed: Documented install command fails: pm install github.com/unbraind/pm-github cannot resolve an entry file ([pm-github-q6ql](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-q6ql.toon))
292
307
 
293
- - Search pm items in any public GitHub repo (gh-authenticated, semantic search) ([pm-github-r8y0](https://github.com/unbraind/pm-github/blob/main/.agents/pm/features/pm-github-r8y0.toon))
294
-
295
- ### Fixed
296
-
297
- - Import dies with an unexplained pm list-all failed on any tracker over 1 MiB, and non-atomic --dry-run previews every issue as a create ([pm-github-tn92](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-tn92.toon))
298
- - CHANGELOG omits the shipped --link-deps feature and mislabels 2026.7.23 work as Unreleased ([pm-github-px45](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-px45.toon))
308
+ ## 2026.7.25 - 2026-07-25
299
309
 
300
310
  ### Other
301
311
 
302
312
  - Adopt --respect-item-release in changelog scripts and bump pm-changelog to 2026.7.24 ([pm-github-w7m7](https://github.com/unbraind/pm-github/blob/main/.agents/pm/chores/pm-github-w7m7.toon))
303
313
 
314
+ ### Closed
315
+
316
+ - Closed: Import dies with an unexplained pm list-all failed on any tracker over 1 MiB, and non-atomic --dry-run previews every issue as a create ([pm-github-tn92](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-tn92.toon))
317
+ - Closed: CHANGELOG omits the shipped --link-deps feature and mislabels 2026.7.23 work as Unreleased ([pm-github-px45](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-px45.toon))
318
+
304
319
  ## 2026.7.23 - 2026-07-23
305
320
 
306
321
  ### Added
307
322
 
308
323
  - Import --link-deps: map GitHub issue-body dependency references (Blocked by/Depends on/Blocks \#N) into pm dependency edges + emit ordering-cycle advisory via collectNewOrderingCycleWarnings ([pm-github-hdai](https://github.com/unbraind/pm-github/blob/main/.agents/pm/features/pm-github-hdai.toon))
309
324
 
310
- ### Fixed
325
+ ### Closed
311
326
 
312
- - Recommend pm merge reconcile (2026.7.22) over raw history-repair in Multi-agent merge safety docs ([pm-github-wpit](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-wpit.toon))
327
+ - Closed: Recommend pm merge reconcile (2026.7.22) over raw history-repair in Multi-agent merge safety docs ([pm-github-wpit](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-wpit.toon))
313
328
 
314
329
  ## 2026.7.21 - 2026-07-21
315
330
 
@@ -348,11 +363,6 @@
348
363
  - GitHub Projects v2 bidirectional sync (import/sync/list/fields) ([pm-github-y27r](https://github.com/unbraind/pm-github/blob/main/.agents/pm/features/pm-github-y27r.toon))
349
364
  - Full pm ecosystem production pass for pm-github ([pm-github-9eei](https://github.com/unbraind/pm-github/blob/main/.agents/pm/features/pm-github-9eei.toon))
350
365
 
351
- ### Fixed
352
-
353
- - Export --apply aborts the whole batch on the first failed item ([pm-github-q3lk](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-q3lk.toon))
354
- - export --apply exits 0 even when every item fails to write ([pm-github-77wj](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-77wj.toon))
355
-
356
366
  ### Security
357
367
 
358
368
  - Resolve final Projects v2 review findings ([pm-github-0g3p](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-0g3p.toon))
@@ -361,6 +371,11 @@
361
371
 
362
372
  - Full-cycle hardening wave: pm-github ([pm-github-mdcw](https://github.com/unbraind/pm-github/blob/main/.agents/pm/tasks/pm-github-mdcw.toon))
363
373
 
374
+ ### Closed
375
+
376
+ - Closed: Export --apply aborts the whole batch on the first failed item ([pm-github-q3lk](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-q3lk.toon))
377
+ - Closed: export --apply exits 0 even when every item fails to write ([pm-github-77wj](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-77wj.toon))
378
+
364
379
  ## 2026.7.11 - 2026-07-11
365
380
 
366
381
  ### Other
@@ -413,7 +428,6 @@
413
428
 
414
429
  ### Fixed
415
430
 
416
- - True round-trip GitHub sync: search provider, validate diagnostics, safe-by-default export, fix activation ([pm-github-4elt](https://github.com/unbraind/pm-github/blob/main/.agents/pm/features/pm-github-4elt.toon))
417
431
  - FIX: add 'preflight' to manifest capabilities (activation-breaking bug) ([pm-github-qndb](https://github.com/unbraind/pm-github/blob/main/.agents/pm/tasks/pm-github-qndb.toon))
418
432
 
419
433
  ### Other
@@ -425,7 +439,6 @@
425
439
  - Export: dry-run default + upsert existing issues by provenance ([pm-github-fv0x](https://github.com/unbraind/pm-github/blob/main/.agents/pm/tasks/pm-github-fv0x.toon))
426
440
  - github search provider (search capability) ([pm-github-5sbx](https://github.com/unbraind/pm-github/blob/main/.agents/pm/tasks/pm-github-5sbx.toon))
427
441
  - github validate diagnostics command ([pm-github-rroh](https://github.com/unbraind/pm-github/blob/main/.agents/pm/tasks/pm-github-rroh.toon))
428
- - preflight capability: validate token/gh auth + repo reachability before mutating github commands ([pm-github-jget](https://github.com/unbraind/pm-github/blob/main/.agents/pm/tasks/pm-github-jget.toon))
429
442
  - pm github sync: push pm status -\> GitHub close/reopen, guarded by token+--repo+--dry-run ([pm-github-yted](https://github.com/unbraind/pm-github/blob/main/.agents/pm/tasks/pm-github-yted.toon))
430
443
  - renderers capability: register 'github' output format (pm items as GitHub-issue markdown) ([pm-github-vvov](https://github.com/unbraind/pm-github/blob/main/.agents/pm/tasks/pm-github-vvov.toon))
431
444
  - Rate-limit/backoff handling (Retry-After / X-RateLimit-Reset) + useful afterCommand hook ([pm-github-rx1q](https://github.com/unbraind/pm-github/blob/main/.agents/pm/tasks/pm-github-rx1q.toon))
package/README.md CHANGED
@@ -273,12 +273,63 @@ There are no saved plans, leases, idempotency keys, or retries in the completion
273
273
  adapter. Every call re-reads native state, and the semantic form digest is only
274
274
  a source identifier. Imports and sync do not use this opt-in export adapter.
275
275
 
276
+ ## Label migration impact plans
277
+
278
+ Preview an exact GitHub label rename or explicit consolidation across stored
279
+ provider tag occurrences, declared import filters and local export mappings:
280
+
281
+ ```sh
282
+ pm github labels plan --input label-migration.json --limit 10 --json
283
+ pm github labels plan --input label-migration.json --approve <approval>
284
+ ```
285
+
286
+ Start with the [JSON example](docs/label-migration.json) and
287
+ [SDK/CLI contract](docs/label-migrations.md). Provider node IDs stay distinct even
288
+ when their names normalize to the same native tag. Complete counts accompany a
289
+ bounded page of exact affected item/policy identities and explicit choices.
290
+ Checking an application plan revalidates current label IDs and SDK revisions;
291
+ both commands are read-only. Filters/mappings must be explicitly inventoried.
292
+ Provider policy names match without case (`bug`, `BUG`, `BuG`); node IDs stay
293
+ distinct. Exports and issue-form label composition use the same comparison,
294
+ keeping the first spelling of each effective provider label.
295
+ Provider label names use the importer's exported `githubLabelsToTags` conversion:
296
+ a label named `one,two` produces tags `one` and `two`. Stored native tag elements
297
+ use only SDK array-tag normalization: a literal `one,two` remains one tag and
298
+ matches neither `one` nor `two`. Tag rows retain the destination label
299
+ in `after` and expose its complete native expansion in `after_tags`, including
300
+ an empty array when the destination produces no tags. Approval checks use the
301
+ same conversion and refuse unresolved tag choices across every affected row.
302
+
303
+ ## Selected discussion decision capture
304
+
305
+ Turn explicitly selected GitHub **issue-comment** passages into reviewable native
306
+ Decisions with author attribution, source revision and exact quoted spans. Start
307
+ with [the selection example](docs/discussion-capture-example.json), then review:
308
+
309
+ ```sh
310
+ pm github discussion capture --input selection.json --json
311
+ pm github discussion capture --input selection.json --approve REVIEWED_DIGEST --json
312
+ ```
313
+
314
+ Only spans carrying `marked: true` are eligible. Empty selections write nothing;
315
+ unmarked prose is never interpreted as a decision. Apply re-reads current source
316
+ and native history, refuses stale approvals, and preserves existing captures on
317
+ retry. Quotes remain inert JSON data. Independent invalid records have bounded
318
+ refusal/remedy rows. TOON and JSON expose the same compact report.
319
+
320
+ The typed SDK surface is `captureGithubDiscussion(pmRoot, input, hostSdk, options)`.
321
+ It uses the host-injected SDK client; no local git repository is needed. Read the
322
+ [contract and SDK example](docs/discussion-capture.md) for exact offsets, bounds,
323
+ retry semantics and the remaining optimistic read-to-create concurrency window.
324
+
276
325
  ## Without git
277
326
 
278
327
  With an explicit repository, GitHub import, export, refresh, sync, project commands,
279
328
  blocker checks, issue-form compilation/completion, closeout and webhook receive/reconcile work in a plain pm tracker
280
329
  without a git executable.
281
330
  `pm github identity` also works there, entirely offline and without credentials.
331
+ `pm github labels plan` works on read-only non-git trackers; `--catalog` supplies
332
+ an offline complete label snapshot, otherwise it reads current GitHub labels.
282
333
  The provider commands still require their normal GitHub access and credentials. Privacy scanning
283
334
  with `github gate --diff <file>` also works there. Reading a proposed tracker diff
284
335
  with `github gate` requires a git repository; otherwise pass an explicit diff.
@@ -296,9 +347,15 @@ See [SDK adoption evidence](docs/sdk-adoption-1025.md).
296
347
 
297
348
  ## Revalidate GitHub blockers
298
349
 
299
- **Compatibility:** pm-github now requires PM CLI 2026.10.5 or newer for complete
300
- SDK reads and structured command exit statuses. Older hosts refuse installation
301
- or activation through the matching peer dependency and manifest floor.
350
+ **Compatibility:** pm-github requires PM CLI 2026.10.17 or newer. Lossless table
351
+ decoding first appeared in 2026.10.9, while the transaction `match` guard used
352
+ by identity repair first appeared in 2026.10.17. The declared minimum must
353
+ support both. Older hosts refuse installation or activation through matching
354
+ peer/development minimum ranges and the top-level manifest floor. Development
355
+ keeps an exact, integrity-locked SDK resolution. CI separately installs the exact
356
+ minimum from npm, type-checks all production and operational SDK imports, then
357
+ loads and exercises the packed artifact in a non-git tracker. See the
358
+ [published SDK evidence](docs/sdk-minimum.md).
302
359
 
303
360
  `pm github blockers` reads a certified complete tracker and checks every blocked
304
361
  item, plus non-terminal items with an external GitHub `blocked_by` dependency.
@@ -832,6 +889,13 @@ for schedules, groups, commit messages and labels.
832
889
 
833
890
  ## Automated closeout workflow
834
891
 
892
+ For scheduled failures, see the [run evidence and refusal guide](docs/scheduled-workflow-refusals.md).
893
+ Both maintenance workflows select one actionable reason from private command
894
+ receipts and privacy-scan it before emitting an error annotation. Closeout
895
+ accepts GitHub's numeric pagination aliases only after verifying repository
896
+ identity; denied reads identify the endpoint and status, and close validation
897
+ identifies the item and required fields without exposing raw exception prose.
898
+
835
899
  [`.github/workflows/pm-github-closeout.yml`](.github/workflows/pm-github-closeout.yml)
836
900
  is a reusable `workflow_call` that opens or updates **one** PR titled
837
901
  `chore(pm): close items delivered by merged pull requests` from `pm-github/closeout`.
@@ -1113,6 +1177,14 @@ Before creating an item, import checks the complete tracker for its provenance t
1113
1177
 
1114
1178
  With `--gate` and a nonempty filtered issue list, both preview and apply results include a `plan` with source dates, closure reason, release, target identity, link/create/update action and state-conflict decision. An empty filtered issue list returns without a `plan`, including with `--gate --dry-run`. GitHub wins lifecycle conflicts for GitHub-owned imports. A canceled item that GitHub later reports as completed uses the SDK's explicit repeat-closure transition with the source reason. A linked external owner keeps its local status, and the plan records `preserve-provider-owner`; `github_state` still records the source state. Atomic transaction identity includes the latest external edit or different lifecycle generation in history. Local state edits and repeated state cycles force reconciliation, while staging and compensation keep crash retries on the same journal. Finalization runs before the SDK commits and is compensated with the batch on failure. It is idempotent and runs again after a recovered atomic journal, so an interrupted finalization can be repaired by retrying the same import before the gate permits a push.
1115
1179
 
1180
+ An atomic apply whose complete normalized source projection already matches every item
1181
+ reports `unchanged: true`, zero imports/updates, and no transaction ID. It leaves
1182
+ native timestamps and item history untouched, including after a mixed batch of
1183
+ new and existing items. This shortcut is disabled while any GitHub SDK journal
1184
+ is unfinished; normal recovery still runs. Native annotation synchronization,
1185
+ dependency linking, and the post-write privacy gate still run on an unchanged
1186
+ item projection.
1187
+
1116
1188
  SDK limitation: native `updated_at` remains the local mutation clock because the bulk SDK uses it to address rollback history. The GitHub update date is retained exactly in `github_updated_at`, and the gated plan discloses this policy. Backdating native `updated_at` safely requires an SDK import mutation whose version identity is independent of source time. Native creation, closure and completion dates still preserve GitHub lifetimes for momentum and cycle-time readers.
1117
1189
 
1118
1190
  ## Provider identity collisions
@@ -1 +1 @@
1
- {"version":3,"file":"closeout.d.ts","sourceRoot":"","sources":["../closeout.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"closeout.d.ts","sourceRoot":"","sources":["../closeout.ts"],"names":[],"mappings":"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"}
package/dist/closeout.js CHANGED
@@ -1,6 +1,8 @@
1
+ import { readGithubWorkspace, githubStatusIsTerminal } from "./workspace.js";
1
2
  import { ownValue } from "./own-record.js";
3
+ import { normalizeImportValue } from "./import-fidelity.js";
2
4
  /** Close delivered tracker items using merged pull requests and head-check evidence. */
3
- import { close, hashDocument, locateItem, readLocatedItem, readSettings, resolveItemTypeRegistry, runWithActiveExtensions, setActiveExtensionHooks, listAllComplete, certifyCompleteListResult, getHistoryPath, readHistoryEntries, verifyHistoryEntries, PmCliError, EXIT_CODE } from "@unbrained/pm-cli/sdk";
5
+ import { close, hashDocument, locateItem, readLocatedItem, runWithActiveExtensions, setActiveExtensionHooks, listAllComplete, certifyCompleteListResult, getHistoryPath, readHistoryEntries, verifyHistoryEntries, PmCliError, EXIT_CODE } from "@unbrained/pm-cli/sdk";
4
6
  /** Reject non-object API payloads before reading safety-critical fields. */
5
7
  function record(value) {
6
8
  if (value === null || typeof value !== "object" || Array.isArray(value))
@@ -41,9 +43,22 @@ function requiredContexts(value) {
41
43
  throw new Error("--required-checks requires newline-separated context names.");
42
44
  return values.flatMap(entry => entry.split("\n")).map(line => line.trim()).filter(Boolean);
43
45
  }
46
+ /** A safe read diagnostic contains a known endpoint and no response or exception prose. */
47
+ class CloseoutReadError extends Error {
48
+ }
49
+ /** Identify a failed read without copying tokens, API bodies or transport errors. */
50
+ async function evidenceRead(api, url) {
51
+ try {
52
+ return await api.get(url);
53
+ }
54
+ catch (error) {
55
+ const status = error instanceof Error ? /HTTP (\d{3})\b/.exec(error.message)?.[1] : undefined;
56
+ throw new CloseoutReadError(`GitHub GET ${new URL(url).pathname} failed; verify token read access and retry${status ? `: HTTP ${status}` : "."}`);
57
+ }
58
+ }
44
59
  /** Read one payload with the shared authenticated retry/redirect transport. */
45
60
  async function payload(api, endpoint) {
46
- return JSON.parse((await api.get(`${api.base}${endpoint}`)).body);
61
+ return JSON.parse((await evidenceRead(api, `${api.base}${endpoint}`)).body);
47
62
  }
48
63
  /** Drain same-endpoint pagination; reject cycles, foreign links and truncated totals. */
49
64
  async function pages(api, endpoint, checks = false, done) {
@@ -52,12 +67,26 @@ async function pages(api, endpoint, checks = false, done) {
52
67
  const seen = new Set();
53
68
  const rows = [];
54
69
  let total;
70
+ let numericPath;
55
71
  while (next) {
56
72
  const url = new URL(next);
57
- if (url.origin !== initial.origin || url.pathname !== initial.pathname || seen.has(next))
58
- throw new Error("Unverifiable GitHub pagination.");
73
+ if (url.origin !== initial.origin || seen.has(next))
74
+ throw new CloseoutReadError(`Unverifiable GitHub pagination for GET ${initial.pathname}; retry with a same-repository next-page link.`);
75
+ if (url.pathname !== initial.pathname && url.pathname !== numericPath) {
76
+ // GitHub emits numeric repository aliases in Link headers even when the
77
+ // first request used owner/name. Verify the alias before following it.
78
+ const named = /^\/repos\/([^/]+\/[^/]+)(\/.*)$/.exec(initial.pathname);
79
+ const numeric = /^\/repositories\/([1-9]\d*)(\/.*)$/.exec(url.pathname);
80
+ if (!named || !numeric || named[2] !== numeric[2])
81
+ throw new CloseoutReadError(`Unverifiable GitHub pagination for GET ${initial.pathname}; next-page endpoint changed.`);
82
+ const identity = record(await payload(api, `/repos/${named[1]}`));
83
+ if (!Number.isSafeInteger(identity.id) || String(identity.id) !== numeric[1]
84
+ || text(identity.full_name).toLowerCase() !== named[1].toLowerCase())
85
+ throw new CloseoutReadError(`Unverifiable GitHub pagination for GET ${initial.pathname}; repository ID does not match.`);
86
+ numericPath = url.pathname;
87
+ }
59
88
  seen.add(next);
60
- const response = await api.get(next);
89
+ const response = await evidenceRead(api, next);
61
90
  const value = JSON.parse(response.body);
62
91
  let page = value;
63
92
  if (checks) {
@@ -120,20 +149,23 @@ function mergeEvidence(pr) {
120
149
  return mergeEvidenceGaps(pr).length === 0;
121
150
  }
122
151
  /** Treat only permission and not-found responses as protection metadata gaps. */
123
- async function readProtectionSource(read) {
152
+ async function readProtectionSource(read, gaps) {
124
153
  try {
125
154
  return await read();
126
155
  }
127
156
  catch (error) {
128
- if (error instanceof Error && /HTTP (403|404)$/.test(error.message))
157
+ if (error instanceof Error && /HTTP (403|404)$/.test(error.message)) {
158
+ gaps.push(error.message);
129
159
  return null;
160
+ }
130
161
  throw error;
131
162
  }
132
163
  }
133
164
  /** Combine all readable app-bound contexts without discarding known requirements. */
134
165
  async function requirements(api, repo, branch) {
135
- const classic = await readProtectionSource(async () => record(await payload(api, `/repos/${repo}/branches/${encodeURIComponent(branch)}/protection/required_status_checks`)));
136
- const rules = await readProtectionSource(() => pages(api, `/repos/${repo}/rules/branches/${encodeURIComponent(branch)}?per_page=100`));
166
+ const gaps = [];
167
+ const classic = await readProtectionSource(async () => record(await payload(api, `/repos/${repo}/branches/${encodeURIComponent(branch)}/protection/required_status_checks`)), gaps);
168
+ const rules = await readProtectionSource(() => pages(api, `/repos/${repo}/rules/branches/${encodeURIComponent(branch)}?per_page=100`), gaps);
137
169
  const checks = [];
138
170
  if (classic !== null) {
139
171
  if (!Array.isArray(classic.contexts) || !Array.isArray(classic.checks))
@@ -167,7 +199,7 @@ async function requirements(api, repo, branch) {
167
199
  checks.push({ name: text(check.context), app: typeof app === "number" ? app : null });
168
200
  }
169
201
  }
170
- return { checks, fallback: checks.length === 0, unavailable: classic === null || rules === null };
202
+ return { checks, fallback: checks.length === 0, unavailable: classic === null || rules === null, gaps };
171
203
  }
172
204
  /** Verify metadata-backed or caller-declared requirements before considering observed head runs. */
173
205
  export async function checkCloseoutHead(api, repo, pr, declared = []) {
@@ -177,7 +209,7 @@ export async function checkCloseoutHead(api, repo, pr, declared = []) {
177
209
  if (policy.unavailable) {
178
210
  if (declared.length === 0) {
179
211
  result.code = "required_checks_unverified";
180
- result.reason = "Required-check metadata unavailable; declare --required-checks to verify the required contexts.";
212
+ result.reason = `${policy.gaps[0]}. Required-check metadata unavailable; declare --required-checks (workflow input required-checks) to verify the required contexts. GITHUB_TOKEN cannot read administration-only branch protection.`;
181
213
  return result;
182
214
  }
183
215
  policy.checks.push(...declared.map(name => ({ name, app: null })));
@@ -233,14 +265,14 @@ export async function checkCloseoutHead(api, repo, pr, declared = []) {
233
265
  result.code = "head_checks_passed";
234
266
  result.ok = true;
235
267
  }
236
- catch {
268
+ catch (error) {
237
269
  result.code = "head_evidence_unavailable";
270
+ if (error instanceof CloseoutReadError)
271
+ result.reason = error.message;
238
272
  // Never echo an API body, exception payload, PR body or comment into the report.
239
273
  }
240
274
  return result;
241
275
  }
242
- /** Statuses that already ended an item's lifecycle. */
243
- const TERMINAL_STATUSES = ["closed", "canceled"];
244
276
  /** GitHub-style closing keywords that declare delivery of the link they directly precede. */
245
277
  const CLOSING_KEYWORDS = new Set(["close", "closes", "closed", "fix", "fixes", "fixed", "resolve", "resolves", "resolved", "deliver", "delivers", "delivered"]);
246
278
  /**
@@ -389,8 +421,12 @@ function closeReason(pr, passed) {
389
421
  export function closeoutFailureReason(error) {
390
422
  if (error instanceof PmCliError) {
391
423
  const code = error.code;
392
- if (typeof code === "string" && /^[a-z][a-z0-9_]{0,79}$/.test(code))
393
- return `SDK close refused: ${code}.`;
424
+ if (typeof code === "string" && /^[a-z][a-z0-9_]{0,79}$/.test(code)) {
425
+ const fields = [...new Set([...(error.context.recovery?.missing_required_fields ?? []), ...(error.context.field ? [error.context.field] : []),
426
+ ...(error.context.policy_violations ?? []).flatMap(violation => violation.missing_fields)]
427
+ .filter(field => /^[a-z][a-z0-9_]{0,79}$/.test(field)))];
428
+ return `SDK close refused: ${code}.${fields.length ? ` Supply required fields: ${fields.join(", ")}.` : ""}`;
429
+ }
394
430
  return `SDK close refused (exit ${error.exitCode}).`;
395
431
  }
396
432
  return "SDK close failed (unclassified error).";
@@ -446,7 +482,7 @@ export function runCloseout(pmRoot, repo, options, api, observer) {
446
482
  return closeoutAttempt(pmRoot, repo, options, api, true, observer);
447
483
  }
448
484
  /** Re-select a conflicted item once; each attempt installs a lock-scoped SDK precondition. */
449
- async function closeoutAttempt(pmRoot, repo, options, api, retry, observer, onlyItem) {
485
+ async function closeoutAttempt(pmPath, repo, options, api, retry, observer, onlyItem) {
450
486
  if (!repo || !/^(?!\.{1,2}\/)[A-Za-z0-9_.-]+\/(?!\.{1,2}$)[A-Za-z0-9_.-]+$/.test(repo))
451
487
  throw new CloseoutUsageError("closeout requires --repo owner/repo or PM_GITHUB_REPO.");
452
488
  const numbers = usage(() => repeated(options, "pr")).map(value => {
@@ -475,10 +511,15 @@ async function closeoutAttempt(pmRoot, repo, options, api, retry, observer, only
475
511
  }
476
512
  }
477
513
  }
514
+ const workspace = await readGithubWorkspace(pmPath);
515
+ const pmRoot = workspace.pmRoot;
478
516
  const corpus = certifyCompleteListResult(await listAllComplete({}, { pmRoot, noExtensions: true }));
479
517
  await observer?.("listed");
480
- const settings = await readSettings(pmRoot);
481
- const types = resolveItemTypeRegistry(settings).type_to_folder;
518
+ const { settings, types: registry } = workspace;
519
+ const types = registry.type_to_folder;
520
+ const typeNames = new Map(Object.entries(registry.alias_to_type));
521
+ /** Container eligibility uses the same registry as persisted item types. */
522
+ const isEpic = (type) => normalizeImportValue("type", type, typeNames) === normalizeImportValue("type", "Epic", typeNames);
482
523
  const snapshots = new Map();
483
524
  const documents = new Map();
484
525
  const items = new Map(corpus.items.map(item => [item.id, item]));
@@ -497,21 +538,21 @@ async function closeoutAttempt(pmRoot, repo, options, api, retry, observer, only
497
538
  const activeChildren = new Map();
498
539
  for (const item of items.values()) {
499
540
  const parent = item.parent;
500
- if (typeof parent === "string" && !TERMINAL_STATUSES.includes(item.status))
541
+ if (typeof parent === "string" && !githubStatusIsTerminal(item.status, workspace.statuses))
501
542
  activeChildren.set(parent, (activeChildren.get(parent) ?? 0) + 1);
502
543
  }
503
544
  const plan = [];
504
545
  const checks = [];
505
546
  const closures = [];
506
547
  const candidates = new Map();
507
- const unreadable = [];
548
+ const unreadable = new Map();
508
549
  for (const number of [...new Set(numbers)].sort((a, b) => a - b)) {
509
550
  let evidence;
510
551
  try {
511
552
  evidence = await payload(api, `/repos/${repo}/pulls/${number}`);
512
553
  }
513
- catch {
514
- unreadable.push(number);
554
+ catch (error) {
555
+ unreadable.set(number, error instanceof CloseoutReadError ? error.message : "Pull-request evidence unavailable or incomplete.");
515
556
  continue;
516
557
  }
517
558
  const pr = pullRequest(evidence, repo, number);
@@ -562,7 +603,7 @@ async function closeoutAttempt(pmRoot, repo, options, api, retry, observer, only
562
603
  code = "explicit_skip";
563
604
  reason = "Explicitly excluded by --skip.";
564
605
  }
565
- else if (TERMINAL_STATUSES.includes(items.get(ref.item).status)) {
606
+ else if (githubStatusIsTerminal(items.get(ref.item).status, workspace.statuses)) {
566
607
  code = "already_terminal";
567
608
  reason = "Item is already closed or canceled.";
568
609
  }
@@ -570,7 +611,7 @@ async function closeoutAttempt(pmRoot, repo, options, api, retry, observer, only
570
611
  code = "not_merged";
571
612
  reason = "Pull request is confirmed unmerged and cannot deliver this item.";
572
613
  }
573
- else if (items.get(ref.item).type === "Epic") {
614
+ else if (isEpic(items.get(ref.item).type)) {
574
615
  code = "epic";
575
616
  reason = "Epics close when their children are done, not by a pull request.";
576
617
  }
@@ -609,8 +650,8 @@ async function closeoutAttempt(pmRoot, repo, options, api, retry, observer, only
609
650
  const row = { item: ref.item, pr: number, action: "skip", code, reason };
610
651
  plan.push(row);
611
652
  if (role !== "context" && pr.merged !== false && items.has(ref.item) && ref.repo.toLowerCase() === repo.toLowerCase()
612
- && !skip.has(ref.item) && !TERMINAL_STATUSES.includes(items.get(ref.item).status)
613
- && items.get(ref.item).type !== "Epic" && !activeChildren.has(ref.item)) {
653
+ && !skip.has(ref.item) && !githubStatusIsTerminal(items.get(ref.item).status, workspace.statuses)
654
+ && !isEpic(items.get(ref.item).type) && !activeChildren.has(ref.item)) {
614
655
  const group = candidates.get(ref.item) ?? [];
615
656
  group.push({ row, pr, declared: ref.declared, commitsReadable });
616
657
  candidates.set(ref.item, group);
@@ -619,12 +660,12 @@ async function closeoutAttempt(pmRoot, repo, options, api, retry, observer, only
619
660
  }
620
661
  // A failed PR read can hide linkage and a competing claim. Retain its number for
621
662
  // every active eligible item; without the PR evidence no item can be proved unrelated.
622
- for (const number of unreadable) {
663
+ for (const [number, reason] of unreadable) {
623
664
  const url = `https://github.com/${repo}/pull/${number}`;
624
665
  for (const item of items.values()) {
625
- if (skip.has(item.id) || TERMINAL_STATUSES.includes(item.status) || item.type === "Epic" || activeChildren.has(item.id))
666
+ if (skip.has(item.id) || githubStatusIsTerminal(item.status, workspace.statuses) || isEpic(item.type) || activeChildren.has(item.id))
626
667
  continue;
627
- const row = { item: item.id, pr: number, action: "skip", code: "head_evidence_unavailable", reason: "Pull-request fetch failed; merge, linkage and delivery evidence unavailable." };
668
+ const row = { item: item.id, pr: number, action: "skip", code: "head_evidence_unavailable", reason };
628
669
  plan.push(row);
629
670
  const group = candidates.get(item.id) ?? [];
630
671
  group.push({ row, pr: { number, html_url: url, body: null, merged: true, merged_at: null, merge_commit_sha: null, commits: null,