-
Notifications
You must be signed in to change notification settings - Fork 340
DOC-6830 Add /park and /pickup skills for parked-PR lifecycle #3612
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
andy-stark-redis
merged 13 commits into
main
from
DOC-6830-claude-park-and-pickup-skills
Jul 10, 2026
Merged
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
255a627
DOC-6830 Add /park and /pickup skills for parked-PR lifecycle
andy-stark-redis d5b6c86
DOC-6830 Fix cross-file consistency issues in park/pickup skills
andy-stark-redis 7044aef
DOC-6830 Consolidate pickup closing sequence to require a commit befo…
andy-stark-redis d715290
DOC-6830 Rewrite pickup closing sequence around a remote-head invariant
andy-stark-redis 42afebc
DOC-6830 Update assess-comments coverage ledger from #3612 review cycle
andy-stark-redis 23f6025
DOC-6830 Ignore in-repo .claude/projects/ memory store
andy-stark-redis ffd1868
DOC-6830 Collapse manifest pickup summary to a pointer, ending the drift
andy-stark-redis 03ec1e1
DOC-6830 Record churn resolution in assess-comments coverage ledger
andy-stark-redis 7e59358
DOC-6830 Consolidate pickup git sequence and split park PR/issue snap…
andy-stark-redis 91d0c20
DOC-6830 Record r6 churn resolution and fix calibration count in ledger
andy-stark-redis 46dca5c
DOC-6830 Guard park's clean tree and make the manifest a pure pointer
andy-stark-redis 847af35
DOC-6830 Record pickup git-sequence de-prescription in coverage ledger
andy-stark-redis d9263a6
DOC-6830 De-prescribe pickup Step 5 git mechanics; decouple scan-mode…
andy-stark-redis File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| # Park manifest — format & conventions (shared) | ||
|
|
||
| Canonical definition of the **park manifest**: the pickup contract embedded in a parked PR. | ||
| Shared by **`/park`** (which *writes* it) and **`/pickup`** (which *reads and diffs against* | ||
| it), so the format can't drift between the two. Change it here, once. | ||
|
|
||
| > Referenced by `.claude/skills/park/SKILL.md` and `.claude/skills/pickup/SKILL.md`. Not a | ||
| > skill itself (no `SKILL.md`), so it won't be invoked directly. | ||
|
|
||
| ## What a parked PR is | ||
|
|
||
| A PR whose docs are **written but deliberately not mergeable yet** because they track an | ||
| external event that hasn't happened — an upstream release, a feature reaching GA, an API | ||
| being finalised. The work is done to the extent it *can* be; what remains is **re-checking it | ||
| against the source once the source stops moving.** | ||
|
|
||
| The enemy is forgetting: which upstream thing this tracked, what state it was in when we | ||
| wrote against it, and what we already knew was likely to change. The manifest fixes all | ||
| three in a place a future agent can find with `gh` alone. | ||
|
|
||
| ## Where it lives | ||
|
|
||
| In the **PR body**, as a markdown block delimited by exact marker lines so `/pickup` can | ||
| extract it deterministically: | ||
|
|
||
| ``` | ||
| <!-- park-manifest --> | ||
| ## Park manifest | ||
| ... | ||
| <!-- /park-manifest --> | ||
| ``` | ||
|
|
||
| PR-body (not an in-repo file) on purpose: `gh` can read/write it and list parked PRs by | ||
| label, with nothing to clean up at merge. The block is plain markdown — human-readable in the | ||
| PR, machine-locatable by its markers. | ||
|
|
||
| ## Required sections | ||
|
|
||
| 1. **Header fields** — flat `Key: value` lines: | ||
| - `Ticket:` — `DOC-NNNN`. | ||
| - `Parked at:` — date (pass it in; agents can't read the clock). | ||
| - `Trigger to pick up:` — the *condition*, concrete enough to test mechanically, e.g. | ||
| "upstream PR X merged **and** in a released version". | ||
| - `Labels:` — the labels applied (`parked`, `do not merge yet`). | ||
|
|
||
| 2. **Pinned sources (state observed at park time)** — a table, one row per external source. | ||
| Each row records the URL **and a snapshot of its state at park time**: for a GitHub PR, | ||
| `state` / `merged` / `head SHA` / `base` / `milestone` / `updated`. **The snapshot is what | ||
| makes pickup a diff rather than a re-read** — without it, `/pickup` sees "now" with nothing | ||
| to compare against. Include the exact command to re-fetch each source. | ||
|
|
||
| 3. **Observed shape the page assumes** — a **confidence-tagged** summary of the API/behaviour | ||
| the docs commit to. Tag it (`LOW`/`MEDIUM`/`HIGH`) — preemptive docs written against an | ||
| unmerged diff are usually LOW, and saying so tells pickup how hard to scrutinise. | ||
|
|
||
| 4. **Re-check checklist** — GitHub task-list (`- [ ]`) of everything to verify or finish on | ||
| pickup. **Seed it from the branch's commit trailers** — harvest `Recheck:` / `Gaps:` / | ||
| `Directive:` from `main..HEAD` (see [`commit-trailers.md`](./commit-trailers.md)); those | ||
| *are* the loose ends. Add anything predicted-to-change not already captured. Mark the | ||
| highest-risk items. | ||
|
|
||
| 5. **On pickup, then** — when the trigger fires, run `/pickup <PR>`. It reconciles the docs | ||
| against the now-settled source and takes the PR through the normal `/reflect` → `/finalize` | ||
| pipeline to merge, lifting the `parked` / `do not merge yet` labels only after `/finalize`. | ||
| **The closing sequence and its ordering live solely in `/pickup` Steps 4–5, which are | ||
| authoritative; this manifest deliberately does not restate them — not even as a list.** (A | ||
| hand-copied step list here drifted from `/pickup` on every review round, so it was replaced by | ||
| this pointer. Don't re-add one, in any order.) The one guarantee this section makes: the `do not merge yet` guard holds until | ||
| `/finalize` completes. | ||
|
|
||
| ## Relationship to commit trailers | ||
|
|
||
| No double-bookkeeping. Commit `Recheck:` / `Gaps:` trailers are the *per-change, | ||
| location-bound* record (`git log`-reachable). The manifest is the *consolidated, source-pinned* | ||
| contract for the PR as a whole. `/park` **harvests** the trailers into the checklist; it does | ||
| not restate them as trailers. | ||
|
|
||
| ## Finalize is deferred while parked | ||
|
|
||
| Critical: **do not run `/finalize` at park time.** Finalize squashes away the episodic | ||
| `Recheck:` / `Gaps:` trailers — exactly what `/pickup` needs. The durable distill happens | ||
| *after* pickup, once the source has settled. The manifest's "On pickup, then" section records | ||
| this so it isn't forgotten. | ||
|
|
||
| ## Labels | ||
|
|
||
| - `parked` — "PR speculatively added based on pre-release info. Check validity when release | ||
| goes ahead." | ||
| - `do not merge yet` — the merge guard. | ||
|
|
||
| `/pickup` finds parked PRs with `gh pr list --label parked`. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,129 @@ | ||
| --- | ||
| name: park | ||
| description: "Freeze a release-ready-pending-upstream branch as a parked PR — open (or update) the PR with a machine-diffable pickup manifest, apply the parked / do-not-merge labels, and deliberately defer /finalize so the episodic re-check notes survive. Use when docs are written against an unreleased or still-changing external source (an upstream PR, a preview feature) and must wait for it to ship before merging. Pairs with /pickup." | ||
| --- | ||
|
|
||
| # Park — freeze a PR that's waiting on the world | ||
|
|
||
| Some docs are done as far as they *can* be, but can't merge yet: they're written against an | ||
| upstream PR that hasn't merged, a feature that hasn't GA'd, an API still in review. Left as a | ||
| bare open PR, the context rots — six weeks later nobody remembers what it tracked or what was | ||
| known to be shaky. This skill **freezes that context into the PR** so it can be picked up | ||
| cleanly when the source settles. | ||
|
|
||
| Park is the pipeline's **pause button between `/reflect` and `/finalize`** — it preserves the | ||
| episodic layer and adds a forward-looking re-check contract, instead of distilling and | ||
| merging. | ||
|
|
||
| The manifest format is defined once in | ||
| [`../_shared/park-manifest.md`](../_shared/park-manifest.md) — **read it first**, and | ||
| [`../_shared/commit-trailers.md`](../_shared/commit-trailers.md) for the trailer vocabulary | ||
| you'll harvest. This file is *how to build and land the manifest*. | ||
|
|
||
| ## Where this sits | ||
|
|
||
| ``` | ||
| /reflect → WIP commit messages [episodic, provisional] | ||
| ↓ THIS SKILL — when the work can't merge yet | ||
| /park → parked PR + manifest [frozen; /finalize DEFERRED] | ||
| ↓ (wait for the upstream trigger) | ||
| /pickup → reconcile docs vs source [thaw + delta] | ||
| ↓ | ||
| /reflect+/finalize → durable squash commit [resume the normal pipeline] | ||
| ``` | ||
|
|
||
| ## The one principle: snapshot the source, not just a pointer | ||
|
|
||
| A note that says "watch upstream PR X" is nearly useless on pickup — it tells you where to | ||
| look but not *what changed*. The manifest must capture the source's **state at park time** | ||
| (merge status, head SHA, milestone, the observed API shape) so pickup is a **diff**, not a | ||
| fresh read. Recording the snapshot is the whole value of parking; everything else is | ||
| plumbing. | ||
|
|
||
| ## Step 1 — Confirm it should be parked (not merged, not abandoned) | ||
|
|
||
| Park only when **the docs are as complete as the source allows** and the sole blocker is an | ||
| external event. If the work is merely unfinished, keep working. If the source may never ship, | ||
| don't park — say so. There must be a concrete, testable **trigger condition**. | ||
|
|
||
| ## Step 2 — Harvest the loose ends already on the branch | ||
|
|
||
| The branch's own commits hold the re-check items, from `/reflect`: | ||
|
|
||
| ``` | ||
| git log --reverse main..HEAD --format='%(trailers:only,unfold)' | ||
| ``` | ||
|
|
||
| Pull the `Recheck:`, `Gaps:`, and `Directive:` trailers — these seed the checklist. Read the | ||
| commit bodies too, for the *why*. Don't restate them as trailers on a new commit; they belong | ||
| in the manifest checklist now (see [`../_shared/park-manifest.md`](../_shared/park-manifest.md), | ||
| "Relationship to commit trailers"). | ||
|
|
||
| ## Step 3 — Snapshot each pinned source | ||
|
|
||
| Identify the external sources the docs depend on (URLs in the ticket, commit bodies, the page's | ||
| `bannerText`). For each, capture its live state with the endpoint that matches the source type. | ||
|
|
||
| For a GitHub **pull request**: | ||
|
|
||
| ``` | ||
| gh api repos/<owner>/<repo>/pulls/<n> \ | ||
| --jq '{state, merged, head_sha: .head.sha, base: .base.ref, milestone: .milestone.title, updated_at}' | ||
| ``` | ||
|
|
||
| For a GitHub **issue** — a feature-request or GA-tracking issue, say — the `pulls` endpoint and | ||
| its `merged` / `head` / `base` fields don't exist, so use `issues/<n>`: | ||
|
|
||
| ``` | ||
| gh api repos/<owner>/<repo>/issues/<n> \ | ||
| --jq '{state, state_reason, milestone: .milestone.title, updated_at, closed_at}' | ||
| ``` | ||
|
|
||
| Record URL + snapshot in the sources table, and the re-fetch command for pickup. Add a | ||
| **confidence-tagged** summary of the API/behaviour the page assumes (LOW for docs written | ||
| against an unmerged diff — and say *why* it's low, e.g. "signatures differed between two reads | ||
| of the diff"). | ||
|
|
||
| ## Step 4 — Compose the manifest & PR body | ||
|
|
||
| Build the PR body: a short human summary, a prominent **do-not-merge warning** linking the | ||
| source, then the `<!-- park-manifest -->`…`<!-- /park-manifest -->` block with all five | ||
| required sections (per the shared spec). The page itself should already carry a `bannerText` | ||
| "not yet released / subject to change" note — if it doesn't, add one before parking. | ||
|
|
||
| ## Step 5 — Land it | ||
|
|
||
| - **Commit any working-tree edits first** — including a `bannerText` you added in Step 4 — and | ||
| confirm a clean tree before pushing. The parked PR must not get its manifest and labels while a | ||
| required edit lingers only in the local working tree. | ||
| - Push the branch. | ||
| - Open the PR, or update an existing one in place. For a new PR: | ||
| ``` | ||
| gh pr create --title "DOC-XXXX <summary> [PARKED]" --body-file <body> \ | ||
| --label parked --label "do not merge yet" --base main | ||
|
cursor[bot] marked this conversation as resolved.
|
||
| ``` | ||
| When the branch already has a PR (re-parking — `gh pr create` would fail), replace the body | ||
| and ensure the labels instead: | ||
| ``` | ||
| gh pr edit <n> --body-file <body> --add-label parked --add-label "do not merge yet" | ||
| ``` | ||
| - Verify the labels stuck (`gh pr view <n> --json labels`). | ||
| - **Do not run `/finalize`.** State explicitly that it's deferred until pickup — the manifest | ||
| records this, but say it in your handoff too. | ||
|
|
||
| Present the PR URL, the trigger condition, and the checklist, then stop. Opening a PR is | ||
| outward-facing — if the branch isn't pushed or the user hasn't asked, confirm first. | ||
|
|
||
| ## Limits (read honestly) | ||
|
|
||
| - **A stale snapshot is worse than none** — if you record a head SHA or API shape you didn't | ||
| actually verify, pickup diffs against fiction. Snapshot only what you checked; leave the | ||
| rest out and flag it in the checklist. | ||
| - Park **cannot judge whether the source will ship.** It records a trigger; it doesn't predict | ||
| the future. A parked PR that never triggers is dead weight — `/pickup`'s scan mode is how you | ||
| find and close those. | ||
| - It **defers, never distills.** If you're tempted to "just finalize while it's fresh," don't: | ||
| you'll squash away the very notes pickup needs. Freshness is preserved *in the manifest*, not | ||
| in a premature durable commit. | ||
| - The manifest is only as good as the checklist. If Step 2's harvest is skipped, the loose ends | ||
| live only in scattered trailers and the pickup starts blind. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,124 @@ | ||
| --- | ||
| name: pickup | ||
| description: "Thaw a parked PR — re-fetch each pinned source, diff it against the manifest snapshot taken at park time, report what changed vs what was predicted, then reconcile the docs and resume the normal pipeline (/reflect → /finalize) toward merge. Run with a PR number, or with no argument to scan all parked PRs and report which triggers have fired. Pairs with /park." | ||
| --- | ||
|
|
||
| # Pickup — thaw a parked PR against its moved source | ||
|
|
||
| `/park` froze a PR with a snapshot of the source it tracks. Time passed; the source moved — | ||
| the upstream PR merged, the API changed, a version shipped. This skill **thaws the PR**: it | ||
| diffs the source's *now* against the *then* recorded in the manifest, tells you exactly what | ||
| changed, reconciles the docs, and hands back to the normal merge pipeline. | ||
|
|
||
| The manifest format is defined in | ||
| [`../_shared/park-manifest.md`](../_shared/park-manifest.md) — **read it first**. Trailer | ||
| vocabulary for the reflect/finalize handoff is in | ||
| [`../_shared/commit-trailers.md`](../_shared/commit-trailers.md). | ||
|
|
||
| ## Where this sits | ||
|
|
||
| ``` | ||
| /park → parked PR + manifest snapshot [frozen] | ||
| ↓ (the trigger fires) | ||
| /pickup → diff source now vs snapshot [THIS SKILL: thaw + reconcile] | ||
| ↓ | ||
| /reflect+/finalize → durable squash commit [resume: predicted-vs-actual, distill] | ||
| ``` | ||
|
|
||
| ## The one principle: diff, then reconcile — don't rewrite from scratch | ||
|
|
||
| The manifest's value is the snapshot. Use it: fetch the source now, **compare field-by-field** | ||
| against what park recorded, and let the *delta* drive the edits. Rewriting the page blind | ||
| throws away the park-time reasoning and re-introduces whatever was already got right. Report | ||
| the delta first; edit second. | ||
|
|
||
| ## Mode A — scan (no argument) | ||
|
|
||
| List every parked PR and report, for each, whether its trigger has fired — the standing answer | ||
| to "what's waiting, and is any of it ready?" | ||
|
|
||
| ``` | ||
| gh pr list --label parked --json number,title,url,updatedAt | ||
| ``` | ||
|
|
||
| For each, read the manifest's `Trigger to pick up:`, snapshot the current source state (Step 1's | ||
| re-fetch only — don't do the full reconcile), then compare that state to the trigger condition to | ||
| decide **fired?**. Use only Step 2's *comparison* — **not** its stop-and-report rule: in scan mode | ||
| a not-met trigger is recorded as `fired?=no` and you continue to the next PR, never aborting the | ||
| loop. Output a table: PR / trigger / **fired?** / one-line delta. Recommend which to pick up, which are still waiting, and which look abandoned (source | ||
| closed-unmerged, or long dead). Then stop — picking one up is Mode B. | ||
|
|
||
| ## Mode B — pick up one PR (PR number given) | ||
|
|
||
| ### Step 1 — Extract the manifest & re-fetch sources | ||
|
|
||
| Read the PR body, slice out the `<!-- park-manifest -->`…`<!-- /park-manifest -->` block. For | ||
| each pinned source, run its recorded re-fetch command and capture current state. | ||
|
|
||
| ### Step 2 — Diff against the snapshot | ||
|
|
||
| Compare current vs park-time, field by field. Has `merged` flipped true? Has `head SHA` moved | ||
| (the diff may have changed under review)? Is a `milestone` / version now set? For a merged PR, | ||
| read the **released** source at the merged tag, not the PR branch. Confirm whether the trigger | ||
| condition is genuinely met — **if it isn't, stop and report**; picking up early re-freezes bad | ||
| info. | ||
|
cursor[bot] marked this conversation as resolved.
|
||
|
|
||
| ### Step 3 — Walk the checklist | ||
|
|
||
| Take each `- [ ]` item in the manifest and resolve it against the current source: **confirmed | ||
| as-written**, **changed → here's the correction**, or **still open**. This is the core output — | ||
| a per-item verdict, highest-risk items first. Verify against the actual released source, never | ||
| the park-time assumption (which was often LOW-confidence by design). | ||
|
|
||
| ### Step 4 — Reconcile the docs | ||
|
|
||
| Apply the corrections: fix signatures/config, fill version placeholders, add sections for | ||
| pieces that landed after park (e.g. a deferred API), convert provisional inline examples to | ||
| tested examples where the tooling now allows, and remove the page's `bannerText` "not yet | ||
| released" warning. This step is only *what to change* — the git operations that make those | ||
| changes land (including the rebase and link checks) come next, in order. | ||
|
|
||
| ### Step 5 — Resume the pipeline & hand off | ||
|
|
||
| Pickup **reconciles; it does not merge.** `/finalize` prepares a squash that operates on the | ||
| **pushed remote PR head**, so the invariant governing this whole step is: | ||
|
|
||
| > Before the `gh pr merge` hand-off, the remote PR branch must already contain **everything | ||
| > that must land** — the reconciliation commit, the rebased/merged-`main` history, and any | ||
| > later amend. The squash sees only what has been pushed; anything left local is silently | ||
| > dropped from the merge. | ||
|
|
||
| The checklist below is the **intent** — *what must be true*, in this order — before the | ||
| `gh pr merge` hand-off. It is **not** a command script: achieve each state with normal git, minding | ||
| the edge cases of an already-pushed, rebased branch (a rebase needs a clean tree; pushing | ||
| rewritten history needs `--force-with-lease`). Stating intent rather than commands is deliberate — | ||
| enumerating the exact git steps here drifted out of date every review round. | ||
|
|
||
| 1. **Reconciliation committed**, the branch **up to date with `main`**, link checks and `relref` | ||
| targets re-verified. | ||
| 2. **`/reflect`** recorded on that commit — *predicted-vs-actual*: what the park snapshot got | ||
| right, what the source changed (closes the loop; calibrates future preemptive docs). | ||
| 3. **Remote head == local** — everything above pushed. This is the invariant made true; nothing | ||
| reaches the squash until it is. | ||
| 4. **`/finalize`** run — the durable squash deferred at park time, now that the source has | ||
| settled; it reconciles the whole arc (park notes + pickup findings + any review) and prepares | ||
| the `gh pr merge … --body-file` command. | ||
| 5. **Labels dropped last** — remove `parked` / `do not merge yet` and strip the manifest block | ||
| only after `/finalize`. The merge guard holds until then. | ||
|
|
||
| Before handing off, **confirm the remote head matches local** (working tree clean, branch not | ||
| ahead of `origin`). Then present: the delta report, the doc changes, the finalize output + the | ||
| `gh pr merge … --body-file` command, and stop. The human triggers the merge. | ||
|
|
||
| ## Limits (read honestly) | ||
|
|
||
| - **A met trigger is not a settled source.** An upstream PR can merge and *still* churn via | ||
| follow-ups before release. If `milestone`/version is unset, treat the API as not-yet-final and | ||
| lean toward staying parked. Report the ambiguity; don't force it. | ||
| - It **trusts the snapshot's honesty.** If `/park` recorded an unverified shape, the diff is | ||
| against fiction — so re-verify high-risk checklist items from source regardless of what the | ||
| snapshot claimed, especially anything tagged LOW. | ||
| - It **can't recover a checklist that was never written.** With a thin manifest, pickup | ||
| degrades to a blind re-review — still useful, but the park-time context is gone. | ||
| - Reconciliation edits are real doc changes: drive/verify them like any other, don't assume the | ||
| released API matches even a HIGH-confidence snapshot. | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.