Skip to content

Commit 5efd8e8

Browse files
DOC-6830 Add /park and /pickup skills for parked-PR docs
Add `/park` and `/pickup`, two skills that extend the `/reflect` → `/finalize` experience-notes pipeline to docs written against an unreleased or still-changing external source (an upstream PR, a preview feature). `/park` freezes a branch as a labelled PR carrying a machine-diffable manifest — a source snapshot plus a re-check checklist — and defers `/finalize`; `/pickup` diffs the source's current state against that snapshot, reconciles the docs, and resumes the pipeline to merge (or, with no argument, scans parked PRs for fired triggers). The manifest format is shared via `_shared/park-manifest.md`, mirroring `_shared/commit-trailers.md`. Also carries the assess-comments coverage-ledger updates from the review cycle and a `.gitignore` rule for the in-repo memory store. The design was hardened over roughly seven Bugbot + Codex review rounds, and two structural rules came out of them that a future editor should not relitigate. First: the manifest lives in the PR body as a **pure pointer** to `/pickup`'s closing sequence, never a copy of it — a duplicated step list drifted from the authoritative source on nearly every round (label order, push-before-reflect, missing rebase). Second: `/pickup` Step 5 asserts an **invariant** (before the `gh pr merge` hand-off the pushed remote head must contain everything that must land) plus an ordered checklist of intent, and delegates the exact git commands rather than enumerating them — each command spelled out in prose (commit, push, rebase ordering, force-with-lease) drew the next git edge-case finding in turn. The general lesson, worth more than the skills themselves: when a duplicated summary or an enumerated mechanic keeps drawing correct-but-incremental review findings, collapse it to a single source or assert an invariant, rather than patching the next adjacent gap. Learned: /park + /pickup extend reflect->finalize for docs on unreleased sources; the manifest snapshot is what makes /pickup a diff, not a re-read Constraint: never run /finalize while a PR is parked — it would squash the Recheck/Gaps trailers /pickup depends on Directive: keep /pickup Step 5 an invariant + intent checklist and the park manifest a pure pointer to it — never re-enumerate the git sequence in either; every enumerated version drew a fresh drift finding Rejected: in-repo .claude/parked/ manifest file | chose a PR-body block gh can read and list with no merge-time cleanup Gaps: the /pickup path is unexercised until an upstream trigger fires — spring-data-redis#3390 will be the shakedown Ticket: DOC-6830 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 02b8bfd commit 5efd8e8

5 files changed

Lines changed: 526 additions & 13 deletions

File tree

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# Park manifest — format & conventions (shared)
2+
3+
Canonical definition of the **park manifest**: the pickup contract embedded in a parked PR.
4+
Shared by **`/park`** (which *writes* it) and **`/pickup`** (which *reads and diffs against*
5+
it), so the format can't drift between the two. Change it here, once.
6+
7+
> Referenced by `.claude/skills/park/SKILL.md` and `.claude/skills/pickup/SKILL.md`. Not a
8+
> skill itself (no `SKILL.md`), so it won't be invoked directly.
9+
10+
## What a parked PR is
11+
12+
A PR whose docs are **written but deliberately not mergeable yet** because they track an
13+
external event that hasn't happened — an upstream release, a feature reaching GA, an API
14+
being finalised. The work is done to the extent it *can* be; what remains is **re-checking it
15+
against the source once the source stops moving.**
16+
17+
The enemy is forgetting: which upstream thing this tracked, what state it was in when we
18+
wrote against it, and what we already knew was likely to change. The manifest fixes all
19+
three in a place a future agent can find with `gh` alone.
20+
21+
## Where it lives
22+
23+
In the **PR body**, as a markdown block delimited by exact marker lines so `/pickup` can
24+
extract it deterministically:
25+
26+
```
27+
<!-- park-manifest -->
28+
## Park manifest
29+
...
30+
<!-- /park-manifest -->
31+
```
32+
33+
PR-body (not an in-repo file) on purpose: `gh` can read/write it and list parked PRs by
34+
label, with nothing to clean up at merge. The block is plain markdown — human-readable in the
35+
PR, machine-locatable by its markers.
36+
37+
## Required sections
38+
39+
1. **Header fields** — flat `Key: value` lines:
40+
- `Ticket:``DOC-NNNN`.
41+
- `Parked at:` — date (pass it in; agents can't read the clock).
42+
- `Trigger to pick up:` — the *condition*, concrete enough to test mechanically, e.g.
43+
"upstream PR X merged **and** in a released version".
44+
- `Labels:` — the labels applied (`parked`, `do not merge yet`).
45+
46+
2. **Pinned sources (state observed at park time)** — a table, one row per external source.
47+
Each row records the URL **and a snapshot of its state at park time**: for a GitHub PR,
48+
`state` / `merged` / `head SHA` / `base` / `milestone` / `updated`. **The snapshot is what
49+
makes pickup a diff rather than a re-read** — without it, `/pickup` sees "now" with nothing
50+
to compare against. Include the exact command to re-fetch each source.
51+
52+
3. **Observed shape the page assumes** — a **confidence-tagged** summary of the API/behaviour
53+
the docs commit to. Tag it (`LOW`/`MEDIUM`/`HIGH`) — preemptive docs written against an
54+
unmerged diff are usually LOW, and saying so tells pickup how hard to scrutinise.
55+
56+
4. **Re-check checklist** — GitHub task-list (`- [ ]`) of everything to verify or finish on
57+
pickup. **Seed it from the branch's commit trailers** — harvest `Recheck:` / `Gaps:` /
58+
`Directive:` from `main..HEAD` (see [`commit-trailers.md`](./commit-trailers.md)); those
59+
*are* the loose ends. Add anything predicted-to-change not already captured. Mark the
60+
highest-risk items.
61+
62+
5. **On pickup, then** — when the trigger fires, run `/pickup <PR>`. It reconciles the docs
63+
against the now-settled source and takes the PR through the normal `/reflect``/finalize`
64+
pipeline to merge, lifting the `parked` / `do not merge yet` labels only after `/finalize`.
65+
**The closing sequence and its ordering live solely in `/pickup` Steps 4–5, which are
66+
authoritative; this manifest deliberately does not restate them — not even as a list.** (A
67+
hand-copied step list here drifted from `/pickup` on every review round, so it was replaced by
68+
this pointer. Don't re-add one, in any order.) The one guarantee this section makes: the `do not merge yet` guard holds until
69+
`/finalize` completes.
70+
71+
## Relationship to commit trailers
72+
73+
No double-bookkeeping. Commit `Recheck:` / `Gaps:` trailers are the *per-change,
74+
location-bound* record (`git log`-reachable). The manifest is the *consolidated, source-pinned*
75+
contract for the PR as a whole. `/park` **harvests** the trailers into the checklist; it does
76+
not restate them as trailers.
77+
78+
## Finalize is deferred while parked
79+
80+
Critical: **do not run `/finalize` at park time.** Finalize squashes away the episodic
81+
`Recheck:` / `Gaps:` trailers — exactly what `/pickup` needs. The durable distill happens
82+
*after* pickup, once the source has settled. The manifest's "On pickup, then" section records
83+
this so it isn't forgotten.
84+
85+
## Labels
86+
87+
- `parked` — "PR speculatively added based on pre-release info. Check validity when release
88+
goes ahead."
89+
- `do not merge yet` — the merge guard.
90+
91+
`/pickup` finds parked PRs with `gh pr list --label parked`.

.claude/skills/park/SKILL.md

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
---
2+
name: park
3+
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."
4+
---
5+
6+
# Park — freeze a PR that's waiting on the world
7+
8+
Some docs are done as far as they *can* be, but can't merge yet: they're written against an
9+
upstream PR that hasn't merged, a feature that hasn't GA'd, an API still in review. Left as a
10+
bare open PR, the context rots — six weeks later nobody remembers what it tracked or what was
11+
known to be shaky. This skill **freezes that context into the PR** so it can be picked up
12+
cleanly when the source settles.
13+
14+
Park is the pipeline's **pause button between `/reflect` and `/finalize`** — it preserves the
15+
episodic layer and adds a forward-looking re-check contract, instead of distilling and
16+
merging.
17+
18+
The manifest format is defined once in
19+
[`../_shared/park-manifest.md`](../_shared/park-manifest.md)**read it first**, and
20+
[`../_shared/commit-trailers.md`](../_shared/commit-trailers.md) for the trailer vocabulary
21+
you'll harvest. This file is *how to build and land the manifest*.
22+
23+
## Where this sits
24+
25+
```
26+
/reflect → WIP commit messages [episodic, provisional]
27+
↓ THIS SKILL — when the work can't merge yet
28+
/park → parked PR + manifest [frozen; /finalize DEFERRED]
29+
↓ (wait for the upstream trigger)
30+
/pickup → reconcile docs vs source [thaw + delta]
31+
32+
/reflect+/finalize → durable squash commit [resume the normal pipeline]
33+
```
34+
35+
## The one principle: snapshot the source, not just a pointer
36+
37+
A note that says "watch upstream PR X" is nearly useless on pickup — it tells you where to
38+
look but not *what changed*. The manifest must capture the source's **state at park time**
39+
(merge status, head SHA, milestone, the observed API shape) so pickup is a **diff**, not a
40+
fresh read. Recording the snapshot is the whole value of parking; everything else is
41+
plumbing.
42+
43+
## Step 1 — Confirm it should be parked (not merged, not abandoned)
44+
45+
Park only when **the docs are as complete as the source allows** and the sole blocker is an
46+
external event. If the work is merely unfinished, keep working. If the source may never ship,
47+
don't park — say so. There must be a concrete, testable **trigger condition**.
48+
49+
## Step 2 — Harvest the loose ends already on the branch
50+
51+
The branch's own commits hold the re-check items, from `/reflect`:
52+
53+
```
54+
git log --reverse main..HEAD --format='%(trailers:only,unfold)'
55+
```
56+
57+
Pull the `Recheck:`, `Gaps:`, and `Directive:` trailers — these seed the checklist. Read the
58+
commit bodies too, for the *why*. Don't restate them as trailers on a new commit; they belong
59+
in the manifest checklist now (see [`../_shared/park-manifest.md`](../_shared/park-manifest.md),
60+
"Relationship to commit trailers").
61+
62+
## Step 3 — Snapshot each pinned source
63+
64+
Identify the external sources the docs depend on (URLs in the ticket, commit bodies, the page's
65+
`bannerText`). For each, capture its live state with the endpoint that matches the source type.
66+
67+
For a GitHub **pull request**:
68+
69+
```
70+
gh api repos/<owner>/<repo>/pulls/<n> \
71+
--jq '{state, merged, head_sha: .head.sha, base: .base.ref, milestone: .milestone.title, updated_at}'
72+
```
73+
74+
For a GitHub **issue** — a feature-request or GA-tracking issue, say — the `pulls` endpoint and
75+
its `merged` / `head` / `base` fields don't exist, so use `issues/<n>`:
76+
77+
```
78+
gh api repos/<owner>/<repo>/issues/<n> \
79+
--jq '{state, state_reason, milestone: .milestone.title, updated_at, closed_at}'
80+
```
81+
82+
Record URL + snapshot in the sources table, and the re-fetch command for pickup. Add a
83+
**confidence-tagged** summary of the API/behaviour the page assumes (LOW for docs written
84+
against an unmerged diff — and say *why* it's low, e.g. "signatures differed between two reads
85+
of the diff").
86+
87+
## Step 4 — Compose the manifest & PR body
88+
89+
Build the PR body: a short human summary, a prominent **do-not-merge warning** linking the
90+
source, then the `<!-- park-manifest -->``<!-- /park-manifest -->` block with all five
91+
required sections (per the shared spec). The page itself should already carry a `bannerText`
92+
"not yet released / subject to change" note — if it doesn't, add one before parking.
93+
94+
## Step 5 — Land it
95+
96+
- **Commit any working-tree edits first** — including a `bannerText` you added in Step 4 — and
97+
confirm a clean tree before pushing. The parked PR must not get its manifest and labels while a
98+
required edit lingers only in the local working tree.
99+
- Push the branch.
100+
- Open the PR, or update an existing one in place. For a new PR:
101+
```
102+
gh pr create --title "DOC-XXXX <summary> [PARKED]" --body-file <body> \
103+
--label parked --label "do not merge yet" --base main
104+
```
105+
When the branch already has a PR (re-parking — `gh pr create` would fail), replace the body
106+
and ensure the labels instead:
107+
```
108+
gh pr edit <n> --body-file <body> --add-label parked --add-label "do not merge yet"
109+
```
110+
- Verify the labels stuck (`gh pr view <n> --json labels`).
111+
- **Do not run `/finalize`.** State explicitly that it's deferred until pickup — the manifest
112+
records this, but say it in your handoff too.
113+
114+
Present the PR URL, the trigger condition, and the checklist, then stop. Opening a PR is
115+
outward-facing — if the branch isn't pushed or the user hasn't asked, confirm first.
116+
117+
## Limits (read honestly)
118+
119+
- **A stale snapshot is worse than none** — if you record a head SHA or API shape you didn't
120+
actually verify, pickup diffs against fiction. Snapshot only what you checked; leave the
121+
rest out and flag it in the checklist.
122+
- Park **cannot judge whether the source will ship.** It records a trigger; it doesn't predict
123+
the future. A parked PR that never triggers is dead weight — `/pickup`'s scan mode is how you
124+
find and close those.
125+
- It **defers, never distills.** If you're tempted to "just finalize while it's fresh," don't:
126+
you'll squash away the very notes pickup needs. Freshness is preserved *in the manifest*, not
127+
in a premature durable commit.
128+
- The manifest is only as good as the checklist. If Step 2's harvest is skipped, the loose ends
129+
live only in scattered trailers and the pickup starts blind.

.claude/skills/pickup/SKILL.md

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
---
2+
name: pickup
3+
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."
4+
---
5+
6+
# Pickup — thaw a parked PR against its moved source
7+
8+
`/park` froze a PR with a snapshot of the source it tracks. Time passed; the source moved —
9+
the upstream PR merged, the API changed, a version shipped. This skill **thaws the PR**: it
10+
diffs the source's *now* against the *then* recorded in the manifest, tells you exactly what
11+
changed, reconciles the docs, and hands back to the normal merge pipeline.
12+
13+
The manifest format is defined in
14+
[`../_shared/park-manifest.md`](../_shared/park-manifest.md)**read it first**. Trailer
15+
vocabulary for the reflect/finalize handoff is in
16+
[`../_shared/commit-trailers.md`](../_shared/commit-trailers.md).
17+
18+
## Where this sits
19+
20+
```
21+
/park → parked PR + manifest snapshot [frozen]
22+
↓ (the trigger fires)
23+
/pickup → diff source now vs snapshot [THIS SKILL: thaw + reconcile]
24+
25+
/reflect+/finalize → durable squash commit [resume: predicted-vs-actual, distill]
26+
```
27+
28+
## The one principle: diff, then reconcile — don't rewrite from scratch
29+
30+
The manifest's value is the snapshot. Use it: fetch the source now, **compare field-by-field**
31+
against what park recorded, and let the *delta* drive the edits. Rewriting the page blind
32+
throws away the park-time reasoning and re-introduces whatever was already got right. Report
33+
the delta first; edit second.
34+
35+
## Mode A — scan (no argument)
36+
37+
List every parked PR and report, for each, whether its trigger has fired — the standing answer
38+
to "what's waiting, and is any of it ready?"
39+
40+
```
41+
gh pr list --label parked --json number,title,url,updatedAt
42+
```
43+
44+
For each, read the manifest's `Trigger to pick up:`, snapshot the current source state (Step 1's
45+
re-fetch only — don't do the full reconcile), then compare that state to the trigger condition to
46+
decide **fired?**. Use only Step 2's *comparison***not** its stop-and-report rule: in scan mode
47+
a not-met trigger is recorded as `fired?=no` and you continue to the next PR, never aborting the
48+
loop. Output a table: PR / trigger / **fired?** / one-line delta. Recommend which to pick up, which are still waiting, and which look abandoned (source
49+
closed-unmerged, or long dead). Then stop — picking one up is Mode B.
50+
51+
## Mode B — pick up one PR (PR number given)
52+
53+
### Step 1 — Extract the manifest & re-fetch sources
54+
55+
Read the PR body, slice out the `<!-- park-manifest -->``<!-- /park-manifest -->` block. For
56+
each pinned source, run its recorded re-fetch command and capture current state.
57+
58+
### Step 2 — Diff against the snapshot
59+
60+
Compare current vs park-time, field by field. Has `merged` flipped true? Has `head SHA` moved
61+
(the diff may have changed under review)? Is a `milestone` / version now set? For a merged PR,
62+
read the **released** source at the merged tag, not the PR branch. Confirm whether the trigger
63+
condition is genuinely met — **if it isn't, stop and report**; picking up early re-freezes bad
64+
info.
65+
66+
### Step 3 — Walk the checklist
67+
68+
Take each `- [ ]` item in the manifest and resolve it against the current source: **confirmed
69+
as-written**, **changed → here's the correction**, or **still open**. This is the core output —
70+
a per-item verdict, highest-risk items first. Verify against the actual released source, never
71+
the park-time assumption (which was often LOW-confidence by design).
72+
73+
### Step 4 — Reconcile the docs
74+
75+
Apply the corrections: fix signatures/config, fill version placeholders, add sections for
76+
pieces that landed after park (e.g. a deferred API), convert provisional inline examples to
77+
tested examples where the tooling now allows, and remove the page's `bannerText` "not yet
78+
released" warning. This step is only *what to change* — the git operations that make those
79+
changes land (including the rebase and link checks) come next, in order.
80+
81+
### Step 5 — Resume the pipeline & hand off
82+
83+
Pickup **reconciles; it does not merge.** `/finalize` prepares a squash that operates on the
84+
**pushed remote PR head**, so the invariant governing this whole step is:
85+
86+
> Before the `gh pr merge` hand-off, the remote PR branch must already contain **everything
87+
> that must land** — the reconciliation commit, the rebased/merged-`main` history, and any
88+
> later amend. The squash sees only what has been pushed; anything left local is silently
89+
> dropped from the merge.
90+
91+
The checklist below is the **intent***what must be true*, in this order — before the
92+
`gh pr merge` hand-off. It is **not** a command script: achieve each state with normal git, minding
93+
the edge cases of an already-pushed, rebased branch (a rebase needs a clean tree; pushing
94+
rewritten history needs `--force-with-lease`). Stating intent rather than commands is deliberate —
95+
enumerating the exact git steps here drifted out of date every review round.
96+
97+
1. **Reconciliation committed**, the branch **up to date with `main`**, link checks and `relref`
98+
targets re-verified.
99+
2. **`/reflect`** recorded on that commit — *predicted-vs-actual*: what the park snapshot got
100+
right, what the source changed (closes the loop; calibrates future preemptive docs).
101+
3. **Remote head == local** — everything above pushed. This is the invariant made true; nothing
102+
reaches the squash until it is.
103+
4. **`/finalize`** run — the durable squash deferred at park time, now that the source has
104+
settled; it reconciles the whole arc (park notes + pickup findings + any review) and prepares
105+
the `gh pr merge … --body-file` command.
106+
5. **Labels dropped last** — remove `parked` / `do not merge yet` and strip the manifest block
107+
only after `/finalize`. The merge guard holds until then.
108+
109+
Before handing off, **confirm the remote head matches local** (working tree clean, branch not
110+
ahead of `origin`). Then present: the delta report, the doc changes, the finalize output + the
111+
`gh pr merge … --body-file` command, and stop. The human triggers the merge.
112+
113+
## Limits (read honestly)
114+
115+
- **A met trigger is not a settled source.** An upstream PR can merge and *still* churn via
116+
follow-ups before release. If `milestone`/version is unset, treat the API as not-yet-final and
117+
lean toward staying parked. Report the ambiguity; don't force it.
118+
- It **trusts the snapshot's honesty.** If `/park` recorded an unverified shape, the diff is
119+
against fiction — so re-verify high-risk checklist items from source regardless of what the
120+
snapshot claimed, especially anything tagged LOW.
121+
- It **can't recover a checklist that was never written.** With a thin manifest, pickup
122+
degrades to a blind re-review — still useful, but the park-time context is gone.
123+
- Reconciliation edits are real doc changes: drive/verify them like any other, don't assume the
124+
released API matches even a HIGH-confidence snapshot.

0 commit comments

Comments
 (0)