Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions .claude/skills/_shared/park-manifest.md
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`.
129 changes: 129 additions & 0 deletions .claude/skills/park/SKILL.md
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}'
Comment thread
andy-stark-redis marked this conversation as resolved.
```

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
Comment thread
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.
124 changes: 124 additions & 0 deletions .claude/skills/pickup/SKILL.md
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.
Comment thread
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.
Loading
Loading