From 3810fa6dcc6e17b3995b30e05bb42ca9867ee967 Mon Sep 17 00:00:00 2001 From: endolinbot Date: Thu, 14 May 2026 23:06:21 +0000 Subject: [PATCH] retcon: new skill (reset + restage per-package, separate yarn.lock, impl+tests together) Copied from kriscendobot/garden skills/retcon/SKILL.md at commit aa94ebd0877a931ac67e29018b0fd4b62c257ece: https://github.com/kriscendobot/garden/blob/aa94ebd0877a931ac67e29018b0fd4b62c257ece/skills/retcon/SKILL.md --- retcon/SKILL.md | 106 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 retcon/SKILL.md diff --git a/retcon/SKILL.md b/retcon/SKILL.md new file mode 100644 index 0000000..53c8c50 --- /dev/null +++ b/retcon/SKILL.md @@ -0,0 +1,106 @@ +--- +created: 2026-05-14 +updated: 2026-05-14 +author: gardener +--- + +# Skill: 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. + +## 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. + +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. + +## What the retcon produces + +A linear history on the PR branch 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 `(): `; the body (when present) names *why*, not the diff. + +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. + +## 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. + +```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 # 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 -R / --json baseRefName --jq '.baseRefName') + +# 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 + +# 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): " + +git -C project add packages/bar/ +git -C project commit -m "fix(bar): " + +# (... one commit per affected package, implementation+tests bundled ...) + +# 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" + +# 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 ..HEAD # must be empty (no net change) + +# 6. Force-push with --force-with-lease. +git -C project push --force-with-lease origin HEAD: +``` + +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. + +### Save a pre-retcon reference + +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- HEAD` works locally; delete the tag after the push lands. + +### 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: ` or `build: `. +- **Cross-package refactors that have to land atomically.** One commit covering all packages it touches, `refactor(): `. The "area" names the conceptual change, not a single package. +- **Documentation-only files (READMEs, design notes).** One commit, `docs: ` or `docs(): ` when 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 ..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 ` 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. + +## 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.