Polish retcon and yarn-lock-separate-commit for this workspace
Prune references to other kriscendobot/garden skills and roles (fixer, weaver, judge's panel, boatman/ferry, journal entries, references/endo-but-for-bots) and per-PR session lore. Replace the garden's created/updated/author frontmatter with name + a trigger-first description. Keep the two skills cross-linked, since both now live here, and credit upstream with links pinned to the source commits. retcon: tag pre-retcon tip as a numbered step; verify with a single empty `git diff pre-retcon HEAD`; drop `git -C project`. yarn-lock-separate-commit: promote the rebase --skip recipe from field notes to its own section. Generated-by: Claude Opus 5.5
This commit is contained in:
1 parent
ae2949253c
commit
792e535a56
2 files changed
+139
-98
No files matched your search
+94
-71
@@ -1,106 +1,129 @@
|
||||
---
|
||||
created: 2026-05-14
|
||||
updated: 2026-05-14
|
||||
author: gardener
|
||||
name: retcon
|
||||
description: Retcon a PR branch — use when a maintainer says "retcon", or asks to clean up, regroup, squash, or reorganize a PR's commits into sensibly grouped commits; one commit per affected package with implementation and tests together, conventional-commit messages, and yarn.lock isolated in its own commit. Resets to the base and restages the same net diff; the merge result is byte-identical. Not a rebase onto a new base.
|
||||
---
|
||||
|
||||
# Skill: retcon
|
||||
# Retcon
|
||||
|
||||
Reset a PR's branch to its base and restage the same net diff as a sensibly grouped commit history. The PR's diff is unchanged; only the commit grouping changes. The verb is the maintainer's: *"retcon"* = retroactively continuity-fix the branch's commit history.
|
||||
|
||||
The retcon's typical executor is the [fixer](../../roles/fixer/AGENT.md), dispatched against an open PR whose substance is right but whose commit grouping is not. The [weaver](../../roles/weaver/AGENT.md) handles rebase-onto-current-base; the retcon is the orthogonal operation of regrouping commits without moving the base.
|
||||
Reset a PR's branch to its base and restage the same net diff as a
|
||||
sensibly grouped commit history. The PR's diff doesn't change. Only the
|
||||
commit grouping changes. The name is short for *retroactive continuity*:
|
||||
fixing the story the branch's history tells.
|
||||
|
||||
## When to use
|
||||
|
||||
- A maintainer says "retcon #N" or "please retcon this branch". The verb names this procedure.
|
||||
- A PR's commit history grew sprawling during the jury-fixer loop (one commit per fixer round, intermediate work-in-progress commits, lockfile churn interleaved with code).
|
||||
- Pre-ferry: the boatman's upstream is reviewing commit-by-commit and the bot-side history is not in shape for that. Retcon, then ferry.
|
||||
- A maintainer says "retcon #N" or "please retcon this branch".
|
||||
- A PR's history sprawled during review: one commit per round of
|
||||
feedback, work-in-progress commits, lockfile churn mixed in with code.
|
||||
- Reviewers upstream read commit by commit, and the history isn't ready
|
||||
for that.
|
||||
|
||||
The retcon is **not** a rebase onto a new base. If the branch lags its base, run the weaver first (or chain "weave then retcon"). Retcon assumes the base is current.
|
||||
A retcon is **not** a rebase onto a new base. If the branch lags its
|
||||
base, rebase first, then retcon. A retcon assumes the base is current.
|
||||
|
||||
## What the retcon produces
|
||||
## What a retcon produces
|
||||
|
||||
A linear history on the PR branch with commits grouped as:
|
||||
A linear history with commits grouped as:
|
||||
|
||||
- **One commit per affected package.** Changes under `packages/foo/` ship in one commit; changes under `packages/bar/` ship in another. Conventional-commit message scoped to the package (`feat(foo): ...`, `fix(bar): ...`, etc.).
|
||||
- **One `chore: Update yarn.lock` commit** for the lockfile, separate from any `package.json` commit that caused it. Cite [yarn-lock-separate-commit](../yarn-lock-separate-commit/SKILL.md) for the discipline; the retcon is the place that discipline is *applied retroactively* across the whole branch, not commit-by-commit at write time.
|
||||
- **Implementation and tests in the same commit.** A package's behavior change and the tests covering that change ship together. This is a deliberate departure from the "test, then implement" or "implement, then test" patterns that produce two commits per feature; the retcon wants one.
|
||||
- **Conventional-commit messages** on every commit. The first line is `<type>(<scope>): <imperative summary>`; the body (when present) names *why*, not the diff.
|
||||
- **One commit per affected package.** Changes under `packages/foo/` go
|
||||
in one commit, and changes under `packages/bar/` go in another. Each
|
||||
message is scoped to its package: `feat(foo): ...`, `fix(bar): ...`.
|
||||
- **One `chore: Update yarn.lock` commit**, separate from the
|
||||
`package.json` change that caused it. This applies the
|
||||
[yarn.lock separate-commit rule](../yarn-lock-separate-commit/SKILL.md)
|
||||
to the whole branch at once.
|
||||
- **Implementation and tests in the same commit.** A package's behavior
|
||||
change and the tests covering it ship together, not as a "test" commit
|
||||
plus an "implement" commit.
|
||||
- **Conventional-commit messages.** The first line is
|
||||
`<type>(<scope>): <imperative summary>`. The body, if any, says *why*,
|
||||
not what the diff shows.
|
||||
|
||||
The PR's net diff (the merge result) is byte-identical before and after the retcon. The judge's panel re-runs against the new commit shape: each per-package commit gets its own scope of attention rather than the panel having to mentally re-group sprawling history.
|
||||
The PR's net diff (its merge result) is byte-identical before and
|
||||
after.
|
||||
|
||||
## Procedure
|
||||
|
||||
Run inside the PR's project worktree on its head branch. The branch must be up to date with its base; if not, run a weaver first.
|
||||
Run on the PR's head branch, in a worktree that is up to date with the
|
||||
base.
|
||||
|
||||
```sh
|
||||
# 0. Sanity: confirm the working tree is clean and the branch tip matches the
|
||||
# remote we'll push to.
|
||||
git -C project status --porcelain # must be empty
|
||||
git -C project log --oneline origin/<head-branch>..HEAD # must be empty
|
||||
# 0. Sanity: clean tree, and the tip matches the remote we'll push to.
|
||||
git status --porcelain # must be empty
|
||||
git log --oneline origin/<head-branch>..HEAD # must be empty
|
||||
|
||||
# 1. Identify the base. For a PR, this is the base branch named in the PR's
|
||||
# metadata (typically master or main, or llm for design-roadmap PRs).
|
||||
BASE=$(gh pr view <N> -R <owner>/<repo> --json baseRefName --jq '.baseRefName')
|
||||
# 1. Save the pre-retcon tip for the no-net-change check.
|
||||
git tag pre-retcon HEAD
|
||||
|
||||
# 2. Reset to the base, keeping the net diff as a working-tree state.
|
||||
# --mixed (the default) unstages everything; --soft keeps the index. Use
|
||||
# --mixed so the next step's `git add` is deliberate.
|
||||
git -C project reset --mixed origin/$BASE
|
||||
# 2. Find the base branch.
|
||||
BASE=$(gh pr view <N> --json baseRefName --jq .baseRefName)
|
||||
|
||||
# 3. Restage by package. For each affected package, add the package's files
|
||||
# and commit with a conventional-commit message.
|
||||
git -C project add packages/foo/
|
||||
git -C project commit -m "feat(foo): <one-line summary>"
|
||||
# 3. Reset to the base, keeping the net diff as unstaged changes.
|
||||
# --mixed, so each `git add` that follows is deliberate.
|
||||
git reset --mixed origin/$BASE
|
||||
|
||||
git -C project add packages/bar/
|
||||
git -C project commit -m "fix(bar): <one-line summary>"
|
||||
# 4. Restage by package: implementation + tests together.
|
||||
git add packages/foo/
|
||||
git commit -m "feat(foo): <summary>"
|
||||
git add packages/bar/
|
||||
git commit -m "fix(bar): <summary>"
|
||||
# ... one commit per affected package ...
|
||||
|
||||
# (... one commit per affected package, implementation+tests bundled ...)
|
||||
# 5. Lockfile last.
|
||||
git add yarn.lock
|
||||
git commit -m "chore: Update yarn.lock"
|
||||
|
||||
# 4. Stage and commit the lockfile last, per yarn-lock-separate-commit.
|
||||
git -C project add yarn.lock
|
||||
git -C project commit -m "chore: Update yarn.lock"
|
||||
# 6. Verify: no net change.
|
||||
git diff pre-retcon HEAD # must be empty
|
||||
|
||||
# 5. Confirm the net diff is unchanged.
|
||||
git -C project diff origin/$BASE..HEAD --stat # compare to the pre-retcon diff
|
||||
git -C project diff <pre-retcon-sha>..HEAD # must be empty (no net change)
|
||||
|
||||
# 6. Force-push with --force-with-lease.
|
||||
git -C project push --force-with-lease origin HEAD:<head-branch>
|
||||
# 7. Push without clobbering concurrent work, then clean up.
|
||||
git push --force-with-lease origin HEAD:<head-branch>
|
||||
git tag -d pre-retcon
|
||||
```
|
||||
|
||||
The `--force-with-lease` (not `--force`) refuses to overwrite work pushed since the agent's last fetch; if the lease is rejected, fetch again and abort the retcon rather than clobbering concurrent work.
|
||||
Run `git status` after each `git add` to confirm that only the intended
|
||||
files are staged.
|
||||
|
||||
### Save a pre-retcon reference
|
||||
## Changes that don't fit one package
|
||||
|
||||
Before step 2, tag (or note) the pre-retcon tip so the no-net-change verification in step 5 has something to diff against. `git tag pre-retcon-<short-id> HEAD` works locally; delete the tag after the push lands.
|
||||
Name these groupings when you report the retcon, so the reviewer reads
|
||||
the new history as a deliberate shape:
|
||||
|
||||
### Capture grouping decisions
|
||||
|
||||
When the branch's changes do not map cleanly to a per-package commit (a cross-package refactor, a touch in a top-level config file), name the groupings in the retcon's report so the panel and the maintainer can read the new history as a deliberate shape rather than guessing. Typical buckets when a per-package split does not exhaust the diff:
|
||||
|
||||
- **Top-level config or build changes.** One commit, `chore: <summary>` or `build: <summary>`.
|
||||
- **Cross-package refactors that have to land atomically.** One commit covering all packages it touches, `refactor(<area>): <summary>`. The "area" names the conceptual change, not a single package.
|
||||
- **Documentation-only files (READMEs, design notes).** One commit, `docs: <summary>` or `docs(<scope>): <summary>` when scoped.
|
||||
- **Top-level config or build:** one `chore:` or `build:` commit.
|
||||
- **Cross-package refactors that must land atomically:** one
|
||||
`refactor(<area>):` commit, where the area names the concept, not a
|
||||
package.
|
||||
- **Docs-only files** (READMEs, design notes): one `docs:` commit, or
|
||||
`docs(<scope>):` if it's scoped.
|
||||
|
||||
## Net diff is invariant
|
||||
|
||||
The single load-bearing property of a retcon: the PR's net diff (base ... new head) is byte-identical to the PR's pre-retcon diff (base ... pre-retcon head). The retcon changes commit grouping; it does not change what the PR proposes to merge. Step 5's two diffs verify this:
|
||||
|
||||
- `git diff origin/$BASE..HEAD --stat` against the pre-retcon stat: same files, same line counts.
|
||||
- `git diff <pre-retcon-sha>..HEAD`: empty. The two heads have the same tree.
|
||||
|
||||
If either check fails, the retcon went wrong; do not push. Roll back with `git reset --hard <pre-retcon-sha>` and start over.
|
||||
|
||||
## Notes from the field
|
||||
|
||||
- _2026-05-14_: the verb landed when kriskowal said *"A new verb I would like to use is 'retcon' meaning 'Please reset this branch and restage the changes in sensibly grouped commits. This should generally have a single commit for each affected package, a separate commit for chore: Update yarn.lock, conventional-commit messages, and a single implementation and test commit.'"* The directive bundles three disciplines (per-package grouping, lockfile separation, implementation+tests together) that the garden had as separate skills or norms; the retcon names the procedure that applies all three at once to a branch's full history.
|
||||
This is the property everything else rests on. `git diff pre-retcon
|
||||
HEAD` must be empty, because the two heads have the same tree. If it
|
||||
isn't empty, **don't push.** Run `git reset --hard pre-retcon` and start
|
||||
over.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- **Forgetting the pre-retcon tag.** Without it, step 5's no-net-change check has no reference. Tag before resetting.
|
||||
- **`git reset --hard` instead of `--mixed`.** `--hard` discards the working tree and loses the net diff. Use `--mixed` (or `--soft` if you want the index preserved) so the diff stays available as unstaged changes.
|
||||
- **Lockfile interleaved with a package commit.** If `git add packages/foo/` accidentally stages a lockfile that lives under that package (rare; typically lockfiles are root-level), the lockfile leaks into the package commit. Run `git status` after each `git add` and verify only the intended package's files are staged.
|
||||
- **Implementation without tests, or tests without implementation.** The discipline is one commit per package containing both. A package commit with no test changes when the implementation is new behavior is wrong; either the tests live elsewhere (a cross-package test commit, separately) or the implementation is genuinely untested (a panel finding for the next round, not a retcon target).
|
||||
- **Conflicting with concurrent fixer pushes.** A retcon force-pushes; if a fixer pushed since the agent's last fetch, `--force-with-lease` rejects the push. Do not override with `--force`; abort and rebase the retcon onto the new tip.
|
||||
- **No pre-retcon tag:** the no-net-change check has nothing to compare
|
||||
against. Tag before you reset.
|
||||
- **`git reset --hard` instead of `--mixed`:** it throws away the net
|
||||
diff. Use `--mixed`, or `--soft` to keep the index.
|
||||
- **Lockfile in a package commit:** `git add packages/foo/` can stage a
|
||||
lockfile that lives under that package. Check `git status`.
|
||||
- **Implementation without tests:** if a package commit adds behavior
|
||||
and changes no tests, either the tests live in a separate cross-package
|
||||
commit, or the code is untested. That's a review finding, not
|
||||
something the retcon should fix.
|
||||
- **The lease is rejected:** someone pushed since your last fetch. Don't
|
||||
fall back to `--force`. Fetch, then redo the retcon on the new tip.
|
||||
|
||||
## Origin
|
||||
|
||||
Kris Kowal coined the verb: *"Please reset this branch and restage the
|
||||
changes in sensibly grouped commits. This should generally have a single
|
||||
commit for each affected package, a separate commit for chore: Update
|
||||
yarn.lock, conventional-commit messages, and a single implementation and
|
||||
test commit."* Adapted from
|
||||
[the retcon skill in kriscendobot/garden](https://github.com/kriscendobot/garden/blob/aa94ebd0877a931ac67e29018b0fd4b62c257ece/skills/retcon/SKILL.md)
|
||||
(May 2026).
|
||||
Reference in new issue
Block a user