pm-github 2026.10.19 → 2026.10.23
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 -5
- package/README.md +201 -7
- package/dist/closeout.d.ts +5 -3
- package/dist/closeout.d.ts.map +1 -1
- package/dist/closeout.js +27 -28
- package/dist/closeout.js.map +1 -1
- package/dist/gate.d.ts +10 -5
- package/dist/gate.d.ts.map +1 -1
- package/dist/gate.js +151 -14
- package/dist/gate.js.map +1 -1
- package/dist/identity.d.ts +62 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +178 -0
- package/dist/identity.js.map +1 -0
- package/dist/import-fidelity.d.ts +28 -2
- package/dist/import-fidelity.d.ts.map +1 -1
- package/dist/import-fidelity.js +260 -28
- package/dist/import-fidelity.js.map +1 -1
- package/dist/index.d.ts +169 -76
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +389 -71
- package/dist/index.js.map +1 -1
- package/dist/refresh.d.ts +41 -0
- package/dist/refresh.d.ts.map +1 -0
- package/dist/refresh.js +223 -0
- package/dist/refresh.js.map +1 -0
- package/dist/webhooks.d.ts +55 -0
- package/dist/webhooks.d.ts.map +1 -0
- package/dist/webhooks.js +188 -0
- package/dist/webhooks.js.map +1 -0
- package/manifest.json +1 -1
- package/package.json +9 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,18 +1,49 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 2026.10.
|
|
3
|
+
## 2026.10.23 - 2026-10-23
|
|
4
4
|
|
|
5
5
|
### Added
|
|
6
6
|
|
|
7
|
+
- Require opt-in before adopting cited GitHub issue items ([pm-github-cited-issue-adoption](https://github.com/unbraind/pm-github/blob/main/.agents/pm/features/pm-github-cited-issue-adoption.toon))
|
|
8
|
+
- Explore provider identity collisions and approve scoped provenance repairs ([pm-github-identity-collisions](https://github.com/unbraind/pm-github/blob/main/.agents/pm/features/pm-github-identity-collisions.toon))
|
|
9
|
+
- Reconcile authenticated GitHub webhooks through a durable deduplicating inbox ([pm-github-webhook-inbox](https://github.com/unbraind/pm-github/blob/main/.agents/pm/features/pm-github-webhook-inbox.toon))
|
|
10
|
+
- Let reusable workflows carry their own release version ([pm-github-znig](https://github.com/unbraind/pm-github/blob/main/.agents/pm/features/pm-github-znig.toon))
|
|
7
11
|
- Preserve imported GitHub lifetimes, closure evidence and release attribution while linking cross-provider items ([pm-github-c093](https://github.com/unbraind/pm-github/blob/main/.agents/pm/features/pm-github-c093.toon))
|
|
12
|
+
- Refresh only already-linked GitHub items without importing new ones ([pm-github-ullj](https://github.com/unbraind/pm-github/blob/main/.agents/pm/features/pm-github-ullj.toon))
|
|
8
13
|
|
|
9
14
|
### Fixed
|
|
10
15
|
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
16
|
+
- 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))
|
|
17
|
+
- 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))
|
|
18
|
+
- 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))
|
|
19
|
+
- 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))
|
|
20
|
+
- 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))
|
|
21
|
+
- 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))
|
|
22
|
+
- 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))
|
|
23
|
+
- Closeout workflow tests race the detached telemetry flush when removing their temporary global root ([pm-github-aplb](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-aplb.toon))
|
|
24
|
+
- Keep reusable workflow operands out of executable positions in attestation scans ([pm-github-7c95](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-7c95.toon))
|
|
25
|
+
- Keep automated sync and closeout PRs current and dispatch caller checks ([pm-github-8r7c](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-8r7c.toon))
|
|
15
26
|
- Reject malformed effective-rule discriminators before closeout ([pm-github-b141](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-b141.toon))
|
|
27
|
+
- Preserve legacy GitHub ownership across copied provider examples ([pm-github-b143](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-b143.toon))
|
|
28
|
+
- Release timeout must report current workflow approval state ([pm-github-b145](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-b145.toon))
|
|
29
|
+
- The daily release regenerates the changelog with a different status filter than release:check, so an open changelog-unreleased item delivered since the last tag fails the release ([pm-github-l1ot](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-l1ot.toon))
|
|
30
|
+
- Require explicit delivery attribution before merged-PR closeout ([pm-github-uqjf](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-uqjf.toon))
|
|
31
|
+
- 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))
|
|
32
|
+
- 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))
|
|
33
|
+
- 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))
|
|
34
|
+
- 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))
|
|
35
|
+
|
|
36
|
+
### Security
|
|
37
|
+
|
|
38
|
+
- 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))
|
|
39
|
+
|
|
40
|
+
### Other
|
|
41
|
+
|
|
42
|
+
- Enable GitHub CodeQL code scanning ([pm-github-sx18](https://github.com/unbraind/pm-github/blob/main/.agents/pm/chores/pm-github-sx18.toon))
|
|
43
|
+
- Certify pm-github with PM CLI SDK 2026.9.26 ([pm-github-6vcq](https://github.com/unbraind/pm-github/blob/main/.agents/pm/chores/pm-github-6vcq.toon))
|
|
44
|
+
- Certify pm-github on PM CLI/SDK 2026.10.4 and consolidate pending dependency updates ([pm-github-julo](https://github.com/unbraind/pm-github/blob/main/.agents/pm/chores/pm-github-julo.toon))
|
|
45
|
+
- 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))
|
|
46
|
+
- 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))
|
|
16
47
|
|
|
17
48
|
## 2026.10.16 - 2026-10-16
|
|
18
49
|
|
package/README.md
CHANGED
|
@@ -39,11 +39,20 @@ Only `https://github.com/owner/repo/blob/main/.agents/pm/<folder>/<id>.toon`
|
|
|
39
39
|
links in PR bodies are recognized. Percent-encoded names and nested SDK tracker
|
|
40
40
|
folders are supported; malformed encodings and traversal segments are ignored. Repository comparisons ignore case. Links to
|
|
41
41
|
other repositories, unknown ids, closed/canceled items and `--skip` ids produce
|
|
42
|
-
explicit skip rows.
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
explicit skip rows. A whole-line `Closes <item-id>` or `pm-github: <item-id>`
|
|
43
|
+
declaration, a closing keyword before the canonical item link, or a structured
|
|
44
|
+
`PM item: <item-id-or-canonical-link>` line declares delivery. Spaces and tabs
|
|
45
|
+
are allowed; declarations cannot span lines. Matching uses the complete item ID,
|
|
46
|
+
so `Closes pm-x-extra` cannot deliver `pm-x`. Undeclared canonical links in the
|
|
47
|
+
PR body, including links in follow-up lists (`PM:`), can produce `not_delivery`
|
|
48
|
+
rows for otherwise eligible items with the reason "Mentioned, not delivered".
|
|
49
|
+
Tracker PR URL metadata with no body reference or declaration produces no row.
|
|
50
|
+
|
|
51
|
+
When several merged PRs declare delivery, closeout selects the latest merge with
|
|
52
|
+
complete merge facts and passing head checks. Equal merge times use the higher
|
|
53
|
+
PR number. Other qualifying claims receive `duplicate_selection`; mention-only
|
|
54
|
+
PRs retain `not_delivery`. All completion fields and the appended close event use
|
|
55
|
+
the selected PR's merge time, merge SHA, head SHA and check evidence.
|
|
47
56
|
|
|
48
57
|
**A mention is not delivery.** PR bodies routinely link follow-ups, epics and
|
|
49
58
|
related work for context, so a link alone never closes an item. An item is
|
|
@@ -134,6 +143,39 @@ only the tracker; commit it with its history and regenerate the caller's
|
|
|
134
143
|
changelog in a closeout PR. The [automated closeout workflow](#automated-closeout-workflow)
|
|
135
144
|
performs that daily review-PR preparation for fleet repositories.
|
|
136
145
|
|
|
146
|
+
## Authenticated webhook reconciliation
|
|
147
|
+
|
|
148
|
+
`pm github webhook receive <owner/repo> --payload delivery.json --delivery <id>
|
|
149
|
+
--event issues --signature <sha256=...>` accepts explicitly supplied deliveries.
|
|
150
|
+
Set `PM_GITHUB_WEBHOOK_SECRET` through your secret manager. Exact payload bytes
|
|
151
|
+
are authenticated before any write; a durable inbox deduplicates delivery IDs.
|
|
152
|
+
|
|
153
|
+
`pm github webhook reconcile <owner/repo> --limit 20` reads current GitHub state
|
|
154
|
+
for already-linked issues and returns compact proposals with lifecycle deltas.
|
|
155
|
+
It never applies the event's lifecycle or changes items. Out-of-order events
|
|
156
|
+
therefore cannot reopen a closed item. Output defaults to TOON; add `--json`
|
|
157
|
+
for JSON. Signature and storage refusals include typed codes and remedies.
|
|
158
|
+
|
|
159
|
+
Use `pm github refresh <owner/repo> --dry-run` to review full changes and
|
|
160
|
+
`--atomic` to apply a fresh polling read. Polling remains the recovery route
|
|
161
|
+
for missed deliveries or unavailable webhooks. Back up the ignored durable
|
|
162
|
+
`github-webhooks` directory inside your tracker. See the
|
|
163
|
+
[CLI and typed SDK example](docs/webhook-inbox.md) for deployment, crash recovery,
|
|
164
|
+
retention and supported-event details.
|
|
165
|
+
|
|
166
|
+
## Without git
|
|
167
|
+
|
|
168
|
+
With an explicit repository, GitHub import, export, refresh, sync, project commands,
|
|
169
|
+
blocker checks, closeout and webhook receive/reconcile work in a plain pm tracker
|
|
170
|
+
without a git executable.
|
|
171
|
+
`pm github identity` also works there, entirely offline and without credentials.
|
|
172
|
+
The provider commands still require their normal GitHub access and credentials. Privacy scanning
|
|
173
|
+
with `github gate --diff <file>` also works there. Reading a proposed tracker diff
|
|
174
|
+
with `github gate` requires a git repository; otherwise pass an explicit diff.
|
|
175
|
+
Gated imports (`github import --gate`, including the scheduled sync workflow)
|
|
176
|
+
also require a git repository so the proposed publication can be checked. They
|
|
177
|
+
refuse before writing when the tracker is outside one; plain imports still work.
|
|
178
|
+
|
|
137
179
|
## Revalidate GitHub blockers
|
|
138
180
|
|
|
139
181
|
**Compatibility:** pm-github now requires PM CLI 2026.10.5 or newer for complete
|
|
@@ -186,7 +228,7 @@ containing this command before adopting the updated workflow.
|
|
|
186
228
|
|---|---|
|
|
187
229
|
| `importers` | `pm github import <owner/repo>` — idempotent native import pipeline |
|
|
188
230
|
| `importers` (exporter) | `pm github export` — pm items → GitHub issues (dry-run by default; upsert) |
|
|
189
|
-
| `commands` | `pm gh-issues import` (legacy import alias), `pm github sync` (push status), `pm github gate` (fail-closed pre-push privacy gate), `pm github validate` (diagnostics), `pm github closeout` (verified merged-PR delivery), `pm github project list\|fields\|import\|sync` (Projects v2) |
|
|
231
|
+
| `commands` | `pm github refresh <owner/repo>` (refresh linked items only), `pm gh-issues import` (legacy import alias), `pm github sync` (push status), `pm github gate` (fail-closed pre-push privacy gate), `pm github validate` (diagnostics), `pm github closeout` (verified merged-PR delivery), `pm github project list\|fields\|import\|sync` (Projects v2) |
|
|
190
232
|
| `schema` | declares `github_url`, `github_number`, `github_state`, `github_author`, `github_created_at`, `github_updated_at` item fields |
|
|
191
233
|
| `hooks` | `afterCommand` — opt-in sync reminder (`PM_GITHUB_SYNC`) for linked items |
|
|
192
234
|
| `preflight` | early warning when a mutating github command lacks a token |
|
|
@@ -223,6 +265,67 @@ checks.
|
|
|
223
265
|
|
|
224
266
|
## Import
|
|
225
267
|
|
|
268
|
+
### Keeping a mirror current
|
|
269
|
+
|
|
270
|
+
Use `refresh` when your tracker deliberately mirrors only a few issues from a busy
|
|
271
|
+
repository. It selects existing `gh:owner/repo#N` tags or explicit `github_url`
|
|
272
|
+
links (also `github_repo` with `github_number`), fetches those numbers in GraphQL
|
|
273
|
+
batches of at most 100, and **never creates items**. Only `/issues/N` URLs are
|
|
274
|
+
refreshable links: the batch query reads issue nodes, so a `/pull/N` URL is not
|
|
275
|
+
selected. Ordinary references in text
|
|
276
|
+
are not links. Conflicting identities or multiple owners fail before any write.
|
|
277
|
+
|
|
278
|
+
```sh
|
|
279
|
+
pm github refresh owner/repo --dry-run --json
|
|
280
|
+
pm github refresh owner/repo --atomic
|
|
281
|
+
pm github refresh owner/repo --since 7d --limit 50 --dry-run
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
`--limit` bounds the selected issue numbers, in ascending order, before fetching.
|
|
285
|
+
`--since` filters by the fetched upstream update time, including closed issues;
|
|
286
|
+
filtered items are counted separately. Omit it for a complete refresh. A GitHub
|
|
287
|
+
token is required for GraphQL (`GITHUB_TOKEN`, `GH_TOKEN`, or `gh auth login`).
|
|
288
|
+
Closure evidence uses the importer's timeline reader; label pages are fetched in
|
|
289
|
+
full. API retries and rate limits use the shared transport.
|
|
290
|
+
|
|
291
|
+
Refresh reuses import's title, labels and source lifecycle mapping, including
|
|
292
|
+
real closure/completion dates and reopening. Body, local item type, assignee and
|
|
293
|
+
milestone are preserved. A `release` already present on the item is only replaced
|
|
294
|
+
when GitHub supplies calendar-tag attribution for a closure; open issues and
|
|
295
|
+
closures without a matching tag keep the existing release, so user- or tool-set
|
|
296
|
+
attribution survives. Existing cross-provider link-only owners retain their
|
|
297
|
+
native lifecycle and content, as on import. TOON (default) or `--json` returns a
|
|
298
|
+
`plan` of `{id, number, changes: [{field, old, new}]}` plus `updated`, `unchanged`,
|
|
299
|
+
`missing`, `filtered`, `selected`, `resumed` and `created: 0`. On dry-run, `updated` means
|
|
300
|
+
would update; nothing is written. Missing rows carry a typed `reason` and remain
|
|
301
|
+
unchanged: `not_found_or_inaccessible` (GitHub cannot distinguish deleted/hidden),
|
|
302
|
+
`inaccessible`, `transferred`, `api_error`, `incomplete_response`, or `invalid_issue`.
|
|
303
|
+
A request-wide failure aborts before new refresh writes. Recovery of unfinished
|
|
304
|
+
transactions happens first and may already have committed earlier writes when
|
|
305
|
+
a subsequent request fails.
|
|
306
|
+
|
|
307
|
+
Each changed item receives an audit comment. `--atomic` applies the batch in one
|
|
308
|
+
SDK workspace writer transaction with rollback; otherwise each changed item gets
|
|
309
|
+
its own transaction. Every update transaction persists its exact plan beside the
|
|
310
|
+
SDK journal before writing. If a run dies mid-transaction, the next refresh
|
|
311
|
+
detects unfinished journals for the selected items, validates every saved plan
|
|
312
|
+
before any replay, and refuses recovery from another repository. It replays the original
|
|
313
|
+
plan — committing the journal without duplicating the audit comment — before
|
|
314
|
+
unchanged items are skipped, and reports it in `resumed`. A preview
|
|
315
|
+
(`--dry-run`) refuses while such a journal is open; a missing plan fails closed
|
|
316
|
+
naming the transaction id instead of silently reporting a no-op. Review the preview before publishing tracker changes and
|
|
317
|
+
apply the existing privacy gate to any proposed commit.
|
|
318
|
+
|
|
319
|
+
For a weekly refresh, run this in a checkout with the extension installed, token
|
|
320
|
+
available and `PM_PATH` set to the intended tracker. For example, a cron entry:
|
|
321
|
+
|
|
322
|
+
```cron
|
|
323
|
+
0 6 * * 1 cd "$MIRROR_CHECKOUT" && pm github refresh owner/repo --atomic --json >> "$MIRROR_LOG" 2>&1
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
Configure those environment variables in the crontab. The command updates the
|
|
327
|
+
tracker locally; review and commit changes using your normal workflow.
|
|
328
|
+
|
|
226
329
|
### `pm github import <owner/repo>` (or `pm gh-issues import`)
|
|
227
330
|
|
|
228
331
|
```bash
|
|
@@ -258,6 +361,7 @@ pm github import owner/repo --dry-run
|
|
|
258
361
|
| `--with-comments` | boolean | Fetch issue comments and append them to the item body |
|
|
259
362
|
| `--comments-mode <mode>` | `body`\|`annotations`\|`both` | How fetched GitHub comments are persisted (default `body`). `annotations` syncs comments into the pm item's native comments collection via the SDK; `both` writes the body section AND native comments. `annotations`/`both` are idempotent on re-import (dedupe by GitHub comment id) |
|
|
260
363
|
| `--atomic` | boolean | Commit every create, update, close, and reopen in one workspace-writer-locked, crash-resumable transaction (requires pm CLI/SDK >=2026.7.20). Normal failure compensation restores updated/closed items and deletes newly created items; an incomplete compensation is reported explicitly for retry or repair. |
|
|
364
|
+
| `--adopt-cited` | boolean | Explicitly opt in to conservative citation adoption (default: off). Tag new items `gh:owner/repo#N` instead. Default imports only report citation candidates. |
|
|
261
365
|
| `--link-deps` | boolean | After import, map dependency references in issue **bodies** (`Blocked by #N`, `Depends on owner/repo#N`, `Blocks #N`) to pm dependency edges between the linked items. Idempotent, best-effort, and re-runnable; see [Dependency linking](#dependency-linking---link-deps) below. |
|
|
262
366
|
| `--gate` | boolean | Fail-closed privacy gate: verify plan completeness (every fetched issue accounted for exactly once) and provenance idempotency (every re-import lands on exactly one `gh:owner/repo#N`-tagged item) BEFORE the write, then scan the proposed tracker change with [`pm github gate`](#pm-github-gate) AFTER it. Any finding — or any unreadable scan input — exits non-zero so an automated pipeline can never commit or push the change. Embeds `gate` receipts in the result. |
|
|
263
367
|
| `--dry-run` | boolean | Preview without writing |
|
|
@@ -265,6 +369,74 @@ pm github import owner/repo --dry-run
|
|
|
265
369
|
|
|
266
370
|
Each imported item records GitHub provenance: the `gh:owner/repo#N` idempotency tag, a `github_author:<login>` tag, and an enriched description (`author @<login> · state reason <reason> · created <iso> · updated <iso>`). GitHub issues closed as `not_planned` import as pm `canceled` instead of `closed`, preserving the difference between completed work and deliberately dropped work. The integration declares the `github_url`, `github_number`, `github_state`, `github_author`, `github_created_at`, and `github_updated_at` schema fields.
|
|
267
371
|
|
|
372
|
+
The primary way to prevent duplicate imports is to tag an item
|
|
373
|
+
`gh:owner/repo#N` **when it is created**, for example
|
|
374
|
+
`pm create issue "Fix widget behavior" --tags "gh:acme/widgets#30"`.
|
|
375
|
+
For an existing item, use `pm update <id> --add-tags "gh:acme/widgets#30"`. A prose URL is context, not an
|
|
376
|
+
ownership declaration. Existing tag/structured URL/provider matching takes
|
|
377
|
+
precedence and is unchanged.
|
|
378
|
+
|
|
379
|
+
**Citation adoption is off by default.** Manual imports, the legacy alias and
|
|
380
|
+
scheduled gated syncs retain normal create behavior when explicit ownership is
|
|
381
|
+
absent. They report read-only `citation_candidates` so an agent can tag the
|
|
382
|
+
correct item, including when several candidates exist. No candidate is adopted
|
|
383
|
+
or ambiguity-blocked by default.
|
|
384
|
+
|
|
385
|
+
Use `--adopt-cited` on the CLI, `adoptCited: true` in SDK import options, or
|
|
386
|
+
`adopt-cited: true` on the reusable sync workflow to enable the fallback. The
|
|
387
|
+
fallback deliberately favors refusal over overwriting an unrelated item:
|
|
388
|
+
|
|
389
|
+
1. **Normalize before checking.** Decode complete named/numeric HTML entities and
|
|
390
|
+
percent-encoded bytes repeatedly until stable, bounded to eight passes.
|
|
391
|
+
Apply NFKC and remove zero-width/bidi control characters. Unwrap Markdown
|
|
392
|
+
link destinations and reference definitions and join soft line breaks for
|
|
393
|
+
intent matching. Retain normalized source as well as the unwrapped view for
|
|
394
|
+
exclusion, so a link label cannot hide a second reference. Joined-line views
|
|
395
|
+
also expose split references. A normalization bound exhaustion is a refusal.
|
|
396
|
+
2. **Allowlisted ownership.** A qualified target issue reference must appear in
|
|
397
|
+
the title, directly follow `Fix`, `Fixes`, `Fixed`, `Close`, `Closes`, `Closed`,
|
|
398
|
+
`Resolve`, `Resolves` or `Resolved` at the start of the description, or be a
|
|
399
|
+
URL on a whole body line `Issue: <url>` or `Tracks: <url>`. Matching ignores
|
|
400
|
+
case. Body fix phrases, `Related:` mentions, unanchored phrases and bare
|
|
401
|
+
numbers cannot authorize adoption.
|
|
402
|
+
3. **Exclude every other reference.** Across all normalized fields, any other
|
|
403
|
+
`github.com` URL, foreign repository, different `#<digits>` or
|
|
404
|
+
`issues`/`pull` token followed by another number vetoes adoption. Unattributed
|
|
405
|
+
paths/bare hashes also refuse adoption: this item projection has no verified
|
|
406
|
+
owning-repository context. PR URLs, including a same-number PR URL, are other
|
|
407
|
+
URLs. The scan over-counts code and incidental text deliberately. These
|
|
408
|
+
checks reduce risk; they do not make arbitrary prose an ownership contract.
|
|
409
|
+
|
|
410
|
+
| Existing prose | Default | With `--adopt-cited` |
|
|
411
|
+
| --- | --- | --- |
|
|
412
|
+
| Description: `Fixes acme/widgets#30` | Create + `adoption_disabled` hint | Eligible if no other reference |
|
|
413
|
+
| Body line: `Issue: https://github.com/acme/widgets/issues/30` | Create + hint | Eligible if no other reference |
|
|
414
|
+
| Title: `Handle acme/widgets#30` | Create + hint | Eligible if no other reference |
|
|
415
|
+
| Body: `Fixes acme/widgets#30` | Create + `missing_intent` hint | Create + hint |
|
|
416
|
+
| Body: `Related: acme/widgets#30` | Create + `missing_intent` hint | Create + hint |
|
|
417
|
+
| Title cites issues 30 and 31 | Create + `multiple_issues` hint | Create + hint |
|
|
418
|
+
| Description fixes issue 30; body has `**https://github.com/other/widgets/issues/30**` | Create + hint | Create + `multiple_issues` hint |
|
|
419
|
+
| Description fixes issue 30; body has `[other](https://github.com/other/widgets/issues/31)` or `issues%2F31` | Create + hint | Create + `multiple_issues` hint |
|
|
420
|
+
|
|
421
|
+
Plans include `citation_candidates: [{id, reason}]`; aggregate dry-run and write
|
|
422
|
+
receipts include `citation_candidates: [{issue, id, reason}]`. Reasons are
|
|
423
|
+
`adoption_disabled`, `missing_intent`, `multiple_issues`, `normalization_limit` or `structured_owner` (the item already carries a `github_*` field, a core `external_ref` or `source_owner`, a `linear_*`/`jira_*`/`ado_*` identity field, provider provenance, a GitHub issue URL in its links or a native declaration, so prose citing another issue never re-owns it).
|
|
424
|
+
Candidate reporting never changes the referenced item's status, content or
|
|
425
|
+
fields, including a closed item's completion state.
|
|
426
|
+
|
|
427
|
+
With explicit opt-in, one eligible untagged candidate produces an `adopt` plan:
|
|
428
|
+
its ID receives the provenance tag and `github_*` fields through normal upsert.
|
|
429
|
+
Closed/canceled owners remain eligible. Two or more eligible owners refuse the
|
|
430
|
+
batch with typed `ambiguous_citation`, IDs and a remedy to tag the correct item.
|
|
431
|
+
The default path never applies that fallback ambiguity refusal. Existing
|
|
432
|
+
structured-identity collision checks still apply in both modes.
|
|
433
|
+
|
|
434
|
+
Dry runs show `adopt` and `wouldAdopt`; write receipts report `adopted` separately
|
|
435
|
+
from `imported`/`updated`. Gated idempotency counts an opt-in adopt as one landing
|
|
436
|
+
item. Status push, export and Projects matching retain their scope. Citation
|
|
437
|
+
reporting and opt-in adoption work in non-git trackers; actual `--gate` writes
|
|
438
|
+
still require a Git worktree for the proposed-change privacy scan.
|
|
439
|
+
|
|
268
440
|
`--issue` deduplicates references and fetches each selected issue directly, including closed issues, without listing the repository. Every selected number is fetched and checked before any tracker write. A missing number fails the whole import; `--dry-run` instead reports `missingIssues` and previews the available selection. Pull request numbers require `--include-prs`. Selection combines with comment modes, dependency linking, atomic imports, gates, dry runs, and custom item types, preserving provenance-based re-import idempotency.
|
|
269
441
|
|
|
270
442
|
`--limit` bounds the matching list after PR, draft, and milestone filters. Pagination stops once one extra matching issue establishes truncation, so at most `n + 1` eligible issues are retained; GitHub pages may also contain excluded entries. Without a limit, existing list ordering is unchanged.
|
|
@@ -462,7 +634,9 @@ Unreadable input, malformed or truncated diffs, binary input, invalid UTF-8, Git
|
|
|
462
634
|
|
|
463
635
|
Rendered values are also scanned in memory before mutation, so a known finding cannot be written to disk. After mutation, the importer verifies that every planned provenance tag exists exactly once, then scans the tracker diff. A completed SDK journal cannot substitute for missing item files.
|
|
464
636
|
|
|
465
|
-
The `gate` receipt reports the verdict, scanned file count, findings, and allowlisted count.
|
|
637
|
+
The `gate` receipt reports the verdict, scanned file count, findings, and allowlisted count. Each finding includes `match_offset` (the starting character offset of the hashed match within the scanned field value, or the serialized added line for tracker text) and `excerpt`. Host-path excerpts show only the root initial, such as `/h***/…`; other matches use `[redacted]`. Human-readable workflow diagnostics include the same evidence. Full paths and secrets never appear. Recognizable bare-root JavaScript regex literals immediately used with `.test()` or `.exec()` are distinguished from paths; quoted host paths and slash-separated host paths remain refused, including inside code and regex source. Ambiguous quote contexts remain refused. Whole JSON-serialized fields retain the same distinction after decoding; TOON wrappers are recognized only in tracker text.
|
|
638
|
+
|
|
639
|
+
With `--json`, successful reports go to stdout; failures use the host CLI's JSON error envelope on stderr, with the redacted gate report serialized in `detail` (`jq ' .detail | fromjson ' gate-error.json`). Unreadable inputs produce a nonzero JSON refusal.
|
|
466
640
|
|
|
467
641
|
## Self-versioned workflows and Dependabot
|
|
468
642
|
|
|
@@ -676,6 +850,7 @@ Optional inputs:
|
|
|
676
850
|
|
|
677
851
|
| Input | Default | Behavior |
|
|
678
852
|
| --- | --- | --- |
|
|
853
|
+
| `adopt-cited` | `false` | Explicitly enable conservative normalized citation adoption for preview, import and repeat verification. Primary prevention is creating items with `gh:owner/repo#N` tags. Leave false to retain normal creates with read-only candidate hints. |
|
|
679
854
|
| `npm-project-dir` | `.` | Relative npm project directory for `npm ci --ignore-scripts`, the CLI `node_modules/.bin` PATH entry, and `post-import-script`. Must have `package.json`, contain no `..` segments, and resolve inside the workspace (including symlinks); invalid paths fail before caching/install. The tracker and publication scope stay at the repository root. |
|
|
680
855
|
| `post-import-script` | `changelog:full` | Caller npm script name invoked with `npm run --if-present` after import and before the final scan. Override for a different package script, or set `""` to disable. Missing scripts are skipped. Raw shell commands are not accepted. Generated `CHANGELOG.md` is staged with the tracker. Changing any file outside the documented scope fails the run, whether staged, unstaged, or untracked. |
|
|
681
856
|
| `automation-app-client-id` | `""` | Optional GitHub App client ID, paired with the `automation_app_private_key` workflow-call secret. Enables eligible native PR runs through a fresh repo-scoped token; absent credentials retain GITHUB_TOKEN with a notice. |
|
|
@@ -701,6 +876,7 @@ jobs:
|
|
|
701
876
|
uses: unbraind/pm-github/.github/workflows/pm-github-sync.yml@RELEASE_COMMIT_SHA # vRELEASE_VERSION
|
|
702
877
|
with:
|
|
703
878
|
automation-app-client-id: ${{ vars.PM_GITHUB_APP_CLIENT_ID }}
|
|
879
|
+
adopt-cited: false # Tag new items gh:owner/repo#N; opt in only for deliberate adoption.
|
|
704
880
|
secrets:
|
|
705
881
|
automation_app_private_key: ${{ secrets.PM_GITHUB_APP_PRIVATE_KEY }}
|
|
706
882
|
```
|
|
@@ -765,6 +941,10 @@ Release checks require type checking, docstrings, ESLint, zero source duplicatio
|
|
|
765
941
|
|
|
766
942
|
`npm run changelog:full` reads the complete tracker through the SDK and feeds pm-changelog. Closed work supplies release history; the explicit `changelog-unreleased` tag includes a pending candidate without closing its PM item. Other open work stays excluded.
|
|
767
943
|
|
|
944
|
+
Daily Release measures the successful `test (22)` and `test (26)` jobs from the last 20 successful main push CI runs. Its merge wait is the nearest-rank p95 duration plus the larger of 15 minutes or 25%, bounded to 60–120 minutes. Missing history uses the 60-minute floor. The log and job summary record the samples, p95, margin and bounds; the job timeout is 360 minutes.
|
|
945
|
+
|
|
946
|
+
A main advance during preparation or waiting can update the release branch once. Every intervening commit must preserve `package.json` and lockfile release versions, `CHANGELOG.md`, `manifest.json`, and release notes, including changes later reverted. Dependency-only changes are eligible. A conflict, metadata change, non-ancestor main, or second advance aborts. A successful update uses a head-specific push lease, restarts the full measured wait, and requires protected checks on the new head. Publication still verifies the merged commit and runs the release gate again.
|
|
947
|
+
|
|
768
948
|
## Multi-agent merge safety
|
|
769
949
|
|
|
770
950
|
This repo tracks its project management in `.agents/pm/` and ships a committed `.gitattributes`
|
|
@@ -814,3 +994,17 @@ Before creating an item, import checks the complete tracker for its provenance t
|
|
|
814
994
|
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.
|
|
815
995
|
|
|
816
996
|
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.
|
|
997
|
+
|
|
998
|
+
## Provider identity collisions
|
|
999
|
+
|
|
1000
|
+
Use `pm github identity --json` for a bounded offline graph of conflicting
|
|
1001
|
+
`external_ref`, GitHub provenance tags and native/provider ownership. Copied
|
|
1002
|
+
prose appears as a weaker mention; genuine provider links retain distinct
|
|
1003
|
+
ownership evidence. Inspect one group with
|
|
1004
|
+
`pm github identity --identity 'gh:example/project#7'`.
|
|
1005
|
+
|
|
1006
|
+
Preview a narrowly scoped repair with `--identity`, `--owner` and `--detach`;
|
|
1007
|
+
apply only after reviewing its exact `--approve` digest. Ambiguous identities
|
|
1008
|
+
refuse with typed remedies. The SDK supplies complete reads, semantic preview,
|
|
1009
|
+
writer transactions, immutable audit history and idempotent replay. See the
|
|
1010
|
+
[worked example and SDK contracts](docs/identity-collisions.md).
|
package/dist/closeout.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ export interface CloseoutRow {
|
|
|
16
16
|
code: CloseoutCode;
|
|
17
17
|
}
|
|
18
18
|
/** Stable decisions used by automation and compact skip counts. */
|
|
19
|
-
export type CloseoutCode = "already_terminal" | "other_repository" | "duplicate_selection" | "explicit_skip" | "unknown_item" | "epic" | "active_children" | "not_delivery" | "close_failed" | "
|
|
19
|
+
export type CloseoutCode = "already_terminal" | "other_repository" | "duplicate_selection" | "explicit_skip" | "unknown_item" | "epic" | "active_children" | "not_delivery" | "close_failed" | "head_checks_passed" | "head_checks_failed" | "head_evidence_unavailable" | "required_checks_unverified" | "incomplete_merge";
|
|
20
20
|
/** Which head-check policy was evaluated for one selected pull request. */
|
|
21
21
|
interface CheckGate {
|
|
22
22
|
pr: number;
|
|
@@ -47,13 +47,15 @@ interface PullRequest {
|
|
|
47
47
|
/** Verify metadata-backed or caller-declared requirements before considering observed head runs. */
|
|
48
48
|
export declare function checkCloseoutHead(api: CloseoutApi, repo: string, pr: PullRequest, declared?: string[]): Promise<CheckGate>;
|
|
49
49
|
/**
|
|
50
|
-
* Whether a closing keyword
|
|
50
|
+
* Whether a closing keyword or standalone structured item declaration identifies a link.
|
|
51
|
+
* @param prefix - Same-line text preceding the canonical link.
|
|
52
|
+
* @param suffix - Same-line text after the link, including a possible markdown closing parenthesis.
|
|
51
53
|
*
|
|
52
54
|
* Only earlier links (markdown or bare), commas and "and" may sit between the keyword and the link, so
|
|
53
55
|
* `Delivers [a](…) and [b](…)` declares both while `Not fixed here; see <link>` declares nothing.
|
|
54
56
|
* Scans backwards with plain string operations; no backtracking pattern touches PR text.
|
|
55
57
|
*/
|
|
56
|
-
export declare function declaresDelivery(prefix: string): boolean;
|
|
58
|
+
export declare function declaresDelivery(prefix: string, suffix?: string): boolean;
|
|
57
59
|
/** Extract only canonical tracker links, retaining foreign references solely for skip rows. */
|
|
58
60
|
export declare function extractCloseoutItems(body: string): {
|
|
59
61
|
item: string;
|
package/dist/closeout.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"closeout.d.ts","sourceRoot":"","sources":["../closeout.ts"],"names":[],"mappings":"AAGA,6EAA6E;AAC7E,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjE,IAAI,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;CACzC;AAED,gFAAgF;AAChF,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,OAAO,GAAG,MAAM,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,YAAY,CAAC;CACpB;AAED,mEAAmE;AACnE,MAAM,MAAM,YAAY,GAAG,kBAAkB,GAAG,kBAAkB,GAAG,qBAAqB,GAAG,eAAe,GACxG,cAAc,GAAG,MAAM,GAAG,iBAAiB,GAAG,cAAc,GAAG,cAAc,GAC7E,oBAAoB,GAAG,oBAAoB,GAAG,
|
|
1
|
+
{"version":3,"file":"closeout.d.ts","sourceRoot":"","sources":["../closeout.ts"],"names":[],"mappings":"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"}
|
package/dist/closeout.js
CHANGED
|
@@ -236,17 +236,21 @@ function declarationKey(id) {
|
|
|
236
236
|
});
|
|
237
237
|
}
|
|
238
238
|
/**
|
|
239
|
-
* Whether a closing keyword
|
|
239
|
+
* Whether a closing keyword or standalone structured item declaration identifies a link.
|
|
240
|
+
* @param prefix - Same-line text preceding the canonical link.
|
|
241
|
+
* @param suffix - Same-line text after the link, including a possible markdown closing parenthesis.
|
|
240
242
|
*
|
|
241
243
|
* Only earlier links (markdown or bare), commas and "and" may sit between the keyword and the link, so
|
|
242
244
|
* `Delivers [a](…) and [b](…)` declares both while `Not fixed here; see <link>` declares nothing.
|
|
243
245
|
* Scans backwards with plain string operations; no backtracking pattern touches PR text.
|
|
244
246
|
*/
|
|
245
|
-
export function declaresDelivery(prefix) {
|
|
247
|
+
export function declaresDelivery(prefix, suffix = "") {
|
|
246
248
|
let rest = prefix;
|
|
247
249
|
// The link may be the target of a markdown label: `Closes [pm-x](`.
|
|
248
250
|
if (rest.endsWith("]("))
|
|
249
251
|
rest = rest.slice(0, Math.max(0, rest.lastIndexOf("[")));
|
|
252
|
+
if (/^(?:[-*][ \t]+)?PM item:[ \t]*$/i.test(rest.trimStart()))
|
|
253
|
+
return /^\)?$/.test(suffix.trim());
|
|
250
254
|
for (;;) {
|
|
251
255
|
rest = rest.trimEnd();
|
|
252
256
|
if (rest.endsWith(",")) {
|
|
@@ -293,7 +297,8 @@ export function extractCloseoutItems(body) {
|
|
|
293
297
|
return [];
|
|
294
298
|
const item = components.at(-1);
|
|
295
299
|
const key = `${match[1].toLowerCase()}/${item}`;
|
|
296
|
-
const
|
|
300
|
+
const lineEnd = body.indexOf("\n", match.index);
|
|
301
|
+
const declared = declaresDelivery(body.slice(body.lastIndexOf("\n", match.index) + 1, match.index), body.slice(match.index + match[0].length, lineEnd < 0 ? body.length : lineEnd));
|
|
297
302
|
if (seen.has(key)) {
|
|
298
303
|
// A later "Closes <link>" line still declares an item an earlier line only mentioned.
|
|
299
304
|
if (declared)
|
|
@@ -390,8 +395,6 @@ export async function runCloseout(pmRoot, repo, options, api) {
|
|
|
390
395
|
if (typeof parent === "string" && !TERMINAL_STATUSES.includes(item.status))
|
|
391
396
|
activeChildren.set(parent, (activeChildren.get(parent) ?? 0) + 1);
|
|
392
397
|
}
|
|
393
|
-
const selected = new Set();
|
|
394
|
-
const candidates = new Map();
|
|
395
398
|
const plan = [];
|
|
396
399
|
const checks = [];
|
|
397
400
|
const closures = [];
|
|
@@ -404,9 +407,9 @@ export async function runCloseout(pmRoot, repo, options, api) {
|
|
|
404
407
|
const refs = extractCloseoutItems(pr.body ?? "");
|
|
405
408
|
// Scan declarations once per PR, retaining case-insensitive IDs and optional punctuation.
|
|
406
409
|
const declarations = new Set();
|
|
407
|
-
for (const match of (pr.body ?? "").matchAll(/^(?:[-*][ \t]+)?([a-z]+|pm-github:)[ \t]+(\S+)[ \t]*$/gim)) {
|
|
410
|
+
for (const match of (pr.body ?? "").matchAll(/^(?:[-*][ \t]+)?([a-z]+|pm-github:|PM item:)[ \t]+(\S+)[ \t]*$/gim)) {
|
|
408
411
|
const keyword = match[1].toLowerCase();
|
|
409
|
-
if (!CLOSING_KEYWORDS.has(keyword) && keyword !== "pm-github:")
|
|
412
|
+
if (!CLOSING_KEYWORDS.has(keyword) && keyword !== "pm-github:" && keyword !== "pm item:")
|
|
410
413
|
continue;
|
|
411
414
|
const id = declarationKey(match[2]);
|
|
412
415
|
declarations.add(id);
|
|
@@ -414,9 +417,8 @@ export async function runCloseout(pmRoot, repo, options, api) {
|
|
|
414
417
|
declarations.add(id.slice(0, -1));
|
|
415
418
|
}
|
|
416
419
|
for (const item of items.values()) {
|
|
417
|
-
//
|
|
418
|
-
|
|
419
|
-
if (linked || declarations.has(declarationKey(item.id))) {
|
|
420
|
+
// Delivery must be declared in this PR body, never inferred from tracker metadata.
|
|
421
|
+
if (declarations.has(declarationKey(item.id))) {
|
|
420
422
|
const ref = refs.find(ref => ref.item === item.id && ref.repo.toLowerCase() === repo.toLowerCase());
|
|
421
423
|
if (ref)
|
|
422
424
|
ref.declared = true;
|
|
@@ -425,11 +427,6 @@ export async function runCloseout(pmRoot, repo, options, api) {
|
|
|
425
427
|
}
|
|
426
428
|
}
|
|
427
429
|
for (const ref of refs) {
|
|
428
|
-
if (ref.declared && pr.merged && ref.repo.toLowerCase() === repo.toLowerCase()) {
|
|
429
|
-
const choices = candidates.get(ref.item) ?? new Set();
|
|
430
|
-
choices.add(number);
|
|
431
|
-
candidates.set(ref.item, choices);
|
|
432
|
-
}
|
|
433
430
|
let reason;
|
|
434
431
|
let code;
|
|
435
432
|
if (ref.repo.toLowerCase() !== repo.toLowerCase()) {
|
|
@@ -462,15 +459,10 @@ export async function runCloseout(pmRoot, repo, options, api) {
|
|
|
462
459
|
}
|
|
463
460
|
else if (!ref.declared) {
|
|
464
461
|
code = "not_delivery";
|
|
465
|
-
reason = "A mention is not delivery
|
|
466
|
-
}
|
|
467
|
-
else if (selected.has(ref.item)) {
|
|
468
|
-
code = "duplicate_selection";
|
|
469
|
-
reason = "Item already selected by another pull request.";
|
|
462
|
+
reason = "Mentioned, not delivered. A mention is not delivery. Declare Closes, Fixes, Resolves or Delivers, or use a structured PM item: or pm-github: declaration.";
|
|
470
463
|
}
|
|
471
464
|
else {
|
|
472
465
|
reason = gate.reason;
|
|
473
|
-
selected.add(ref.item);
|
|
474
466
|
const row = { item: ref.item, pr: number, action: "close", code: gate.code, reason };
|
|
475
467
|
const criteria = items.get(ref.item).acceptance_criteria;
|
|
476
468
|
closures.push({ row, reason: closeReason(pr, gate.passed), at: pr.merged_at,
|
|
@@ -485,13 +477,20 @@ export async function runCloseout(pmRoot, repo, options, api) {
|
|
|
485
477
|
plan.push({ item: ref.item, pr: number, action: "skip", code, reason });
|
|
486
478
|
}
|
|
487
479
|
}
|
|
488
|
-
//
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
row.
|
|
480
|
+
// Choose one complete, passing delivery before any SDK write. Merge chronology, not PR number,
|
|
481
|
+
// determines the winner; an equal timestamp uses the higher PR number for stable selection.
|
|
482
|
+
const deliveries = new Map();
|
|
483
|
+
for (const entry of closures) {
|
|
484
|
+
const previous = deliveries.get(entry.row.item);
|
|
485
|
+
if (!previous || Date.parse(entry.at) >= Date.parse(previous.at)) {
|
|
486
|
+
deliveries.set(entry.row.item, entry);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
for (const entry of closures) {
|
|
490
|
+
if (deliveries.get(entry.row.item) !== entry) {
|
|
491
|
+
entry.row.action = "skip";
|
|
492
|
+
entry.row.code = "duplicate_selection";
|
|
493
|
+
entry.row.reason = "A later qualifying merged PR declares delivery for this item.";
|
|
495
494
|
}
|
|
496
495
|
}
|
|
497
496
|
let closed = 0;
|