diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index bc8cff8..21c82e0 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -1,7 +1,7 @@ { "$schema": "https://anthropic.com/claude-code/marketplace.schema.json", "name": "make-no-mistakes", - "version": "1.37.0", + "version": "1.38.0", "description": "The disciplined dev lifecycle \u2014 implement issues, review PRs, sync releases, test E2E, manage sessions, and stash secrets via OS-native prompts. One plugin to make no mistakes.", "owner": { "name": "Luis Andres Pena Castillo", @@ -11,7 +11,7 @@ { "name": "make-no-mistakes", "description": "Dev lifecycle orchestrator. Start with /make-no-mistakes:domain-driven-advisor \u2014 the canonical entry point that inspects your repo, asks a few plain-language questions, and routes you across the six-family audit engine (schema-drift, contract-drift, ddd, explicit-architecture, strangler, enforcement-hooks), then runs a premortem on the aggregated remediation plan. Also ships disciplined Linear issue execution with worktree isolation, PR review with Greptile gating, team release sync, E2E test generation/execution, test suite previewer, security pentesting, MoSCoW + RICE prioritization, cross-platform secret stash via OS-native GUI prompts (zenity / kdialog / osascript / Get-Credential), and session management. 37 commands, 11 auto-activating skills, 2 specialized agents.", - "version": "1.37.0", + "version": "1.38.0", "author": { "name": "Luis Andres Pena Castillo", "email": "lapc506@users.noreply.github.com" diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index b1337fb..f2af3c5 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "make-no-mistakes", - "version": "1.37.0", + "version": "1.38.0", "description": "The disciplined dev lifecycle — implement issues, review PRs, sync releases, test E2E, manage sessions, stash secrets, and enforce manifest-driven tool-call hooks. One plugin to make no mistakes.", "author": { "name": "Luis Andres Pena Castillo", diff --git a/CHANGELOG.md b/CHANGELOG.md index 088c9d2..5e48682 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,115 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.38.0] - 2026-07-31 + +### Changed +- **`rebase-advisor` → `sync-advisor`, and it now measures instead of asking.** + The old skill was 43 lines and measured nothing: step 1 was *"Confirm the user + wants a full team sync"* — a question back to the user about something three + git commands answer. Its `description` then over-routed, triggering on + `"align with develop"` and `"branches are behind"` — both `git pull` cases — + and sending them all to `/make-no-mistakes:rebase`, which stashes every + worktree, rebases every local branch and auto-merges PRs. Between `git pull` + and that, the toolkit offered nothing, and nothing read-only at all. + + **Six read-only predicates** now run before anything is named: distance + (`git rev-list --left-right --count`), whether a fast-forward is possible + (`git merge-base --is-ancestor`), a dirty tree split into staged vs unstaged, + untracked files that the base ref already tracks, worktrees behind, and + branches carrying unpushed commits. The fifth is the threshold that decides + between a plain pull and the team command: one branch behind is a pull; + several worktrees behind is what `/rebase` was built for. + + **The fourth predicate is the one nothing else reports.** An untracked local + file at a path the ref tracks aborts the pull outright, and it is invisible + everywhere else — verified on a throwaway pair of repos: `git status` shows + only `?? newfile.txt`, distance reports a clean `0 ahead, 1 behind`, and + `git merge-base --is-ancestor` says a fast-forward is possible. The pull then + exits 1 with *"The following untracked working tree files would be + overwritten by merge … Please move or remove them before you merge"* — the + message names the user's own file and offers deletion as the remedy, which is + the one irreversible move available. The skill reports these by name and + recommends copying them out of the repo; it never recommends deleting them. + + The collision check carries `--full-name -- :/` and `--full-tree` because the + predicate is otherwise silently wrong when run from a subdirectory, in two + independent ways. **Format:** without the flags both commands print + prefix-relative paths, and adding `--full-name` on its own is worse than + adding nothing — `ls-files` emits `sub/newfile.txt` while `ls-tree` emits + `newfile.txt`, they stop matching, and `comm -12` returns empty for a tree + that is about to abort the pull. **Scope:** `--full-name` changes how a path + prints, never which paths are considered, so without `-- :/` a collision at + the repo root is invisible to a run started from `sub/`. Both controls run, + from the repo root and from a subdirectory, with the command extracted + verbatim from the SKILL.md so the test cannot drift from the doc. + + **It never acts.** Every fix is printed for the user to run — `git pull`, + `git stash`, `git rebase`, `/make-no-mistakes:rebase`. The single write is + `git fetch origin --quiet`, which touches remote-tracking refs and nothing + else, and the skill says so out loud when it runs: without it every + measurement is taken against a stale `origin/` and reports a drift that + stopped being true days ago, which is the failure the skill exists to catch. + + `commands/rebase.md` is untouched and stays a real destination. What changed + is who decides when it applies — measured, not asked. + + Step 0 resolves the base ref in a bare form (`develop`, never + `origin/develop`) and normalises with `${BASE#origin/}` whichever branch of + the resolution produced it, because every predicate interpolates + `origin/$BASE` and a value carrying the remote becomes `origin/origin/develop` + — `fatal: ambiguous argument … unknown revision`, exit 128, measured. It also + does **not** resolve the base from `@{upstream}`: on a feature branch that is + the branch's own remote copy (`origin/andres/sync-advisor`), so it answers + "am I pushed?" — predicate 6's question — and reports `0 behind` on a branch + far behind the actual base. Wrong ref, not merely wrong spelling. The + `origin/HEAD` step is a fall-through rather than a requirement: it is + routinely unset (`fatal: ref refs/remotes/origin/HEAD is not a symbolic ref`, + observed in this repo) and the `develop`/`main`/`master`/`trunk` probe covers + it. Both branches of the resolution verified, each against a control that + fails. + + **Minor and not major, decided by reading rather than by habit.** A skill + auto-activates on its `description`; it is not invoked by name the way a + command is, so a renamed `name:` changes no call site. + `grep -rniI "rebase.advisor" . --exclude-dir=node_modules --exclude-dir=.git + --exclude=CHANGELOG.md` returns nothing — no command, no doc, no other skill + named it. That negative is real rather than a broken search: the same grep + for `spike-recommend` returns 10 files, so cross-references of this shape do + get found when they exist. + + One surface does break, and it is named here rather than folded into the + above: a user who typed `/make-no-mistakes:rebase-advisor` explicitly (README + documents that skills can be invoked that way) now gets an unknown skill. It + fails loudly, with the replacement one line away in the same table, and the + installer prunes the old file rather than leaving both live — but anyone who + reads that as breaking should say so on the PR. + +### Added +- **`syncAdvisor.governedPaths` in `make-no-mistakes.config.json`** (see + `commands/make-no-mistakes.config.example.json`) — the paths whose changes + `sync-advisor` reports **by name**, turning "you are 12 commits behind" into + "three hooks changed, two of them fix defects you may be looking at right + now". Repo-relative prefixes, fed to + `git diff --name-only HEAD...origin/ -- ` (three dots, so it + diffs from the merge base and shows what landed on the ref rather than what + the user changed locally). + + **No default, and the degraded path omits the line rather than guessing.** + With the key unset the skill still reports distance and routing and simply + drops the consequence section. A built-in fallback list would be wrong in + every repo but the one it was copied from — and it would read as measured, + which is worse than a missing section. The key lives in the behaviour config + rather than in a domain config for the reason that file's own `_boundary` + note gives: a preference parked in a domain config is invisible to every + command that does not touch that domain. + + Origin (2026-07-31, as reported): a developer filed two bug reports against a + hook, both with clean reproductions. One was a real defect; the other + described behaviour fixed days earlier against a stale checkout, and nothing + in the report separated them. A commit count alone would not have separated + them either — naming the changed hooks would have. + ## [1.37.0] - 2026-07-31 ### Changed @@ -840,7 +949,8 @@ installed caches) but had no representation on `main`; this release lands them. - Product Owner Extension (SPOPC) roadmap section in README ([PR #4](https://github.com/DojoCodingLabs/make-no-mistakes-toolkit/pull/4)). -[Unreleased]: https://github.com/DojoCodingLabs/make-no-mistakes-toolkit/compare/v1.37.0...HEAD +[Unreleased]: https://github.com/DojoCodingLabs/make-no-mistakes-toolkit/compare/v1.38.0...HEAD +[1.38.0]: https://github.com/DojoCodingLabs/make-no-mistakes-toolkit/releases/tag/v1.38.0 [1.37.0]: https://github.com/DojoCodingLabs/make-no-mistakes-toolkit/releases/tag/v1.37.0 [1.36.0]: https://github.com/DojoCodingLabs/make-no-mistakes-toolkit/releases/tag/v1.36.0 [1.35.0]: https://github.com/DojoCodingLabs/make-no-mistakes-toolkit/releases/tag/v1.35.0 diff --git a/README.md b/README.md index 074ad4d..65c594e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # make-no-mistakes -**Version: 1.37.0** · [CHANGELOG](./CHANGELOG.md) · [Marketplace](https://github.com/DojoCodingLabs/make-no-mistakes-toolkit) +**Version: 1.38.0** · [CHANGELOG](./CHANGELOG.md) · [Marketplace](https://github.com/DojoCodingLabs/make-no-mistakes-toolkit) The disciplined dev lifecycle — implement issues, review PRs, sync releases, test E2E, and manage sessions. One plugin to make no mistakes. @@ -128,7 +128,7 @@ Auto-activate by context — you don't need to remember the command name. | [`spike-recommend`](skills/spike-recommend/SKILL.md) | Paste a Linear issue URL or ask to analyze an issue | | [`review-open-prs`](skills/review-open-prs/SKILL.md) | Ask about open PRs, merge readiness, or Greptile scores | | [`review-active-issues`](skills/review-active-issues/SKILL.md) | Ask about your Linear issues, backlog, or issue status | -| [`rebase-advisor`](skills/rebase-advisor/SKILL.md) | Mention needing to sync branches after a release (suggests `/make-no-mistakes:rebase`) | +| [`sync-advisor`](skills/sync-advisor/SKILL.md) | Ask "am I up to date", "is my checkout stale", or mention syncing after a release. Measures the drift with six read-only git predicates, names which **governed** files changed, and routes to the smallest fix — `git pull`, a single-branch rebase, or `/make-no-mistakes:rebase`. Never syncs anything itself | | [`audit-engine`](skills/audit-engine/SKILL.md) | Run any of the six repo-health audits (schema-drift, contract-drift, ddd, explicit-architecture, strangler, enforcement-hooks). Hybrid LLM-first detection + deterministic verification + cure-mapping | | [`domain-driven-advisor`](skills/domain-driven-advisor/SKILL.md) | Ask "which audit do I need?" / "where do I start with repo health?" — routes you to the right audit(s) and runs a premortem | | [`premortem`](skills/premortem/SKILL.md) | Say "premortem this", "what could kill this", "stress test this plan", "what am I missing", or "find the blind spots" on a plan/launch/decision | @@ -262,6 +262,26 @@ The plugin reads `linear-setup.json` at your repo root for project-specific sett If no `linear-setup.json` exists, the plugin auto-detects settings from your environment (GitHub org from current repo, Linear team from MCP, etc.). +Toolkit *behaviour* (as opposed to where things go) is read from an optional +`make-no-mistakes.config.json`, also at the repo root — see +[`commands/make-no-mistakes.config.example.json`](commands/make-no-mistakes.config.example.json) +for every key and its default. Two consumers today: `language` / `diacritics` + +`explain` for `/explain`, and `syncAdvisor.governedPaths` for the `sync-advisor` +skill: + +```json +{ + "syncAdvisor": { + "governedPaths": [".claude/hooks/", "scripts/", ".github/workflows/"] + } +} +``` + +Those are the directories where a stale checkout silently changes behaviour, so +`sync-advisor` names them by file when they have moved on the base ref. The key +has **no default** — unset, the skill reports distance and routing and omits +that line rather than guessing at paths it cannot know. + ## Verify Installation **Claude Code:** diff --git a/commands/make-no-mistakes.config.example.json b/commands/make-no-mistakes.config.example.json index 677fe51..027fb04 100644 --- a/commands/make-no-mistakes.config.example.json +++ b/commands/make-no-mistakes.config.example.json @@ -14,5 +14,10 @@ "actionables": true, "askUserQuestion": "when-open-decisions" }, - "_explain": "Per-command overrides for /explain. `askUserQuestion` accepts 'when-open-decisions' (default — only call it if the explanation actually leaves a decision open), 'always', or 'never'. Setting 'always' is discouraged: a menu invented to satisfy the format trains the reader to ignore the menus that matter." + "_explain": "Per-command overrides for /explain. `askUserQuestion` accepts 'when-open-decisions' (default — only call it if the explanation actually leaves a decision open), 'always', or 'never'. Setting 'always' is discouraged: a menu invented to satisfy the format trains the reader to ignore the menus that matter.", + + "syncAdvisor": { + "governedPaths": [".claude/hooks/", "scripts/", ".github/workflows/"] + }, + "_syncAdvisor": "Paths whose changes the `sync-advisor` skill reports by name when it measures drift, so the answer is 'you are missing these three hook fixes' instead of 'you are 12 commits behind'. Repo-relative path prefixes, passed to `git diff --name-only HEAD...origin/ -- `. No default — with this key unset the skill still reports distance and routing, and omits the consequence line rather than guessing. Guessing is the worse failure: a built-in list would be wrong in every repo but the one it was copied from, and read as measured. Set it to the directories where a stale checkout silently changes behaviour — hooks, CI workflows, scripts, migrations, lockfiles." } diff --git a/package.json b/package.json index d894e6b..55db7af 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lapc506/make-no-mistakes", - "version": "1.37.0", + "version": "1.38.0", "description": "The disciplined dev lifecycle — implement issues, review PRs, sync releases, test E2E, manage sessions, stash secrets, and enforce manifest-driven tool-call hooks (no SSH+DB, no manual prod, no minified build, no secret leaks, Slack format). OpenCode + Claude Code plugin.", "type": "module", "main": "./dist/index.js", diff --git a/skills/rebase-advisor/SKILL.md b/skills/rebase-advisor/SKILL.md deleted file mode 100644 index 22ea592..0000000 --- a/skills/rebase-advisor/SKILL.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -name: rebase-advisor -description: > - Detects when the user needs to sync branches after a release or rebase - their work across multiple branches. Suggests the /make-no-mistakes:rebase command. - Triggers on: "sync with release", "rebase all branches", "align with develop", - "post-release sync", "branches are behind", "need to rebase everything", - "team rebase", "release sync", "sync after merge to main". - Does NOT trigger on: single-branch rebase ("rebase this branch"), - git rebase interactive, cherry-pick, or simple merge operations. ---- - -# Rebase Advisor - -You detected that the user needs a **team-wide branch sync** — not a simple single-branch rebase. - -## When This Applies - -This skill activates when the user describes a situation involving: -- Multiple branches that need to be rebased after a release -- Post-release synchronization across the team -- Branches that have fallen behind `develop` or `main` -- Worktrees that need alignment after merges - -## What To Do - -1. Confirm the user wants a full team sync (not just rebasing one branch) -2. Suggest the dedicated command: - -> This sounds like a team-wide release sync. Use: -> -> `/make-no-mistakes:rebase {repo-name}` -> -> This command handles the full protocol: -> - Syncs `develop` and `main` with remote -> - Stashes uncommitted work in all worktrees -> - Rebases every local branch onto updated `develop` -> - Auto-merges ready PRs (CI green + Greptile approved) -> - Post-merge re-check for cascading effects -> - Restores stashed work -> - Produces a health report - -3. If the user just needs a simple single-branch rebase, do NOT invoke this skill — let them use `git rebase` directly. diff --git a/skills/sync-advisor/SKILL.md b/skills/sync-advisor/SKILL.md new file mode 100644 index 0000000..a09e528 --- /dev/null +++ b/skills/sync-advisor/SKILL.md @@ -0,0 +1,316 @@ +--- +name: sync-advisor +description: > + Measures how far a checkout has drifted from its base ref and routes to the + smallest sync that closes the gap — a plain pull, a single-branch rebase, or + the team-wide /make-no-mistakes:rebase. Reports which governed files changed, + so the answer is "you are missing these three hook fixes", not just "you are + N commits behind". Read-only: it measures and recommends, it never syncs. + Triggers on: "am I up to date", "is my branch behind", "how far behind am I", + "do I need to pull", "is my checkout stale", "what changed on develop", + "sync with develop", "sync with release", "post-release sync", + "rebase all branches", "team rebase", "my worktrees are behind", + "did I miss anything from develop". + Does NOT trigger on: interactive rebase ("git rebase -i", "squash these + commits"), cherry-pick, resolving a named merge conflict, or a request to + actually perform the team sync (that is /make-no-mistakes:rebase itself). +--- + +# Sync Advisor + +A checkout has drifted from its base ref, or may have. **Measure the drift +before naming a fix.** Six read-only predicates answer what would otherwise be +a question back to the user, and the answer separates three very different +routes: `git pull`, a single-branch rebase, and `/make-no-mistakes:rebase` — +which stashes every worktree, rebases every local branch, and auto-merges PRs. + +Between `git pull` and that, there was nothing. Filling that gap is what this +skill is for, and it is why it does not forward every mention of "sync" to the +heaviest command available. + +## This skill never acts + +Every command below either reads or reports. The fixes are **printed for the +user to run**, never executed: + +- `git pull`, `git rebase`, `git stash`, `git merge` — suggested, not run. +- `/make-no-mistakes:rebase` — suggested, not invoked. + +One exception, and name it out loud when you use it: `git fetch origin --quiet` +runs first. It writes remote-tracking refs and nothing else — no local branch, +no index, no working tree. Without it every measurement below is taken against +a stale `origin/` and reports a drift that stopped being true days ago, +which is the failure this skill exists to prevent. + +If the user wants the sync performed, that is the `/make-no-mistakes:rebase` +command's job and they invoke it themselves. + +## Step 0 — Resolve the base ref + +Two things have to be right here, and each one fails silently in its own way: +**which** branch is the base, and what **form** its name is in. + +**`$BASE` is a bare branch name — `develop`, never `origin/develop`.** Every +predicate below interpolates `origin/$BASE`, so a value that already carries +the remote becomes `origin/origin/develop`, which is not a ref and takes the +whole run down: + +``` +$ git rev-list --left-right --count "HEAD...origin/origin/develop" +fatal: ambiguous argument 'HEAD...origin/origin/develop': unknown revision +``` + +**Do not resolve the base from `@{upstream}`.** On any feature branch the +upstream is that branch's *own* remote copy — `origin/andres/sync-advisor`, not +`origin/develop`. Measuring against it answers *"am I pushed?"*, which is +predicate 6's question, and it reports `0 behind` on a branch that is fifty +commits behind the base. It is the wrong ref, not merely the wrong spelling. + +Resolve in this order, and say which one you used: + +```bash +# 1. a base the user named explicitly — take it +# 2. the remote's default branch, when origin/HEAD is set +git symbolic-ref --short refs/remotes/origin/HEAD + +# 3. otherwise probe, in order +for b in develop main master trunk; do + git show-ref --verify --quiet "refs/remotes/origin/$b" && { echo "$b"; break; } +done +``` + +Step 2 is not reliable enough to stand alone — `origin/HEAD` is frequently +unset in a fresh clone or worktree, and then it fails with +`fatal: ref refs/remotes/origin/HEAD is not a symbolic ref`. That is a fall- +through, not an error to report. Repos in this toolkit's audience use +`develop`, `main`, `master` and `trunk`; a hardcoded `develop` produces a +confident measurement against a ref that does not exist. + +Then normalise unconditionally, whichever branch produced the value: + +```bash +BASE=${BASE#refs/remotes/} +BASE=${BASE#origin/} +``` + +Then fetch, so that `origin/$BASE` is the ref and not a memory of it: + +```bash +git fetch origin --quiet +``` + +## Step 1 — The six predicates + +Run all six. Each routes somewhere different, and the fourth is the one nothing +else reports. + +| # | Predicate | Answers | +|---|-----------|---------| +| 1 | Distance | Is anything needed at all? | +| 2 | Fast-forward? | `pull` vs. rebase/merge | +| 3 | Dirty tree | Stash or commit first | +| 4 | Untracked files the ref already tracks | **Back up first — these abort the pull** | +| 5 | Worktrees behind | A one-branch problem or a fleet problem | +| 6 | Unpushed commits | What a team-wide rebase would be touching | + +### 1. Distance + +```bash +git rev-list --left-right --count "HEAD...origin/$BASE" +``` + +Two tab-separated numbers: commits ahead of the ref, then commits behind it. +Two zeros means the work is already done — **say so and stop.** A skill that +always finds something to recommend is not measuring. + +### 2. Is a fast-forward possible? + +```bash +git merge-base --is-ancestor HEAD "origin/$BASE"; echo "exit=$?" +``` + +`exit=0` — HEAD is an ancestor of the ref, so `git pull --ff-only` applies and +nothing can conflict. `exit=1` — the histories diverged and the user holds +local commits the ref does not; a pull will merge or rebase, and can conflict. +Those are different recommendations, and predicate 1 alone cannot tell them +apart. + +### 3. Dirty tree, split by stage + +```bash +git status --porcelain +``` + +Column 1 is the index, column 2 is the working tree. ` M` is unstaged, `M ` is +staged, `MM` is both, `??` is untracked. The split matters because the advice +differs: staged work is one commit from safe, unstaged work needs a stash, and +the untracked rows belong to predicate 4. + +### 4. Untracked files that the ref already tracks + +The one nobody has, and the one worth the most: + +```bash +comm -12 \ + <(git ls-files --others --exclude-standard --full-name -- :/ | sort) \ + <(git ls-tree -r --full-tree --name-only "origin/$BASE" | sort) +``` + +**All three of `--full-name`, `:/` and `--full-tree` are load-bearing, and two +of them fix different problems.** Run from a subdirectory with none of them, +both commands report paths relative to the current prefix *and* restrict +themselves to that subtree — so they still agree with each other, and the +predicate quietly answers for one directory instead of the repo. + +`--full-name` and `--full-tree` fix the **format** halves, and they are a pair: +adding `--full-name` alone is worse than adding nothing, because `ls-files` +starts emitting `sub/newfile.txt` while `ls-tree` still emits `newfile.txt`, +the two stop matching, and `comm -12` returns empty — a clean bill of health +for a tree that is about to abort the pull. + +`-- :/` fixes the **scope** half, which the flags do not touch: `--full-name` +changes how a path is printed, never which paths are considered. Without the +pathspec, a collision at the repo root is invisible to a run started from +`sub/`. + +Every path this prints exists locally as untracked **and** exists on the ref as +tracked. Git refuses to overwrite it, so the pull aborts before doing anything: + +``` +error: The following untracked working tree files would be overwritten by merge: + +Please move or remove them before you merge. +Aborting +``` + +Three things earn this a dedicated predicate. It does not read as a problem in +`git status`, which shows the file as a plain `??` — indistinguishable from +harmless scratch. It is invisible to the distance count, and to the +fast-forward check: a clean `0 ahead, 1 behind` fast-forward aborts exactly the +same way. And the message names the user's own files, then names deletion as a +remedy — so the reflex is to remove the local copy, the one irreversible move +available. + +Report these by name and recommend copying them somewhere outside the repo +first. Never recommend deleting them. + +### 5. Worktrees behind + +```bash +git worktree list --porcelain +``` + +Then, for each `branch refs/heads/` it reports: + +```bash +git rev-list --count "..origin/$BASE" +``` + +This works from any checkout — worktrees share one object store and one ref +namespace, so there is no need to enter each one. **This predicate is the +threshold for `/make-no-mistakes:rebase`**: one branch behind is a `git pull`; +several worktrees and branches behind is what that command was built for. + +### 6. Branches carrying unpushed commits + +```bash +git for-each-ref --format='%(refname:short) | %(upstream:short) | %(upstream:track)' refs/heads +``` + +`[ahead 3]` means three commits exist only locally. An empty upstream column +means the branch was never pushed at all — cross-check with +`git ls-remote --heads origin` when it matters. + +This does not change the recommendation; it changes what the user knows before +accepting it. `/make-no-mistakes:rebase` rebases every local branch, and this +is the list of what has no remote copy if one of those rebases goes wrong. + +## Step 2 — The consequence line + +Distance is a number. **What the user needs is what the distance costs.** Read +`make-no-mistakes.config.json` at the repo root (see +`commands/make-no-mistakes.config.example.json`): + +```json +{ + "syncAdvisor": { + "governedPaths": [".claude/hooks/", "scripts/", ".github/workflows/"] + } +} +``` + +For each configured path: + +```bash +git diff --name-only "HEAD...origin/$BASE" -- "" +git log --oneline "HEAD..origin/$BASE" -- "" +``` + +Three dots on the `diff` is deliberate: it diffs from the merge base, so it +shows what landed on the ref rather than what the user changed locally. + +Report the files with the subject line of the commit that touched each. "You +are 12 behind" is a number nobody can act on. "Three hooks changed, two of them +fix defects you may be looking at right now" is the sentence that ends the +wrong debugging session. + +**Degraded mode — no config file, or no `syncAdvisor` key.** Report distance +and routing exactly as above and **omit the consequence line entirely**. Do not +guess at governed paths and do not fall back to a built-in list: an invented +list is wrong in every repo but the one it was copied from, and a confident +wrong list is worse than a missing section. Mention once, in one clause, that +the key is unset and what setting it would add. + +## Step 3 — Route + +Take the first row that matches, in order: + +| Condition | Recommend | +|-----------|-----------| +| Behind = 0, and no worktree behind | Nothing. The checkout is current — say it in one line and stop, dirty tree or not. Uncommitted work is not a sync problem. | +| Untracked collisions (predicate 4) | Copy those files outside the repo first. Nothing else proceeds — the pull aborts. | +| Behind > 0 and dirty tree (predicate 3) | `git stash` for unstaged work, a commit for staged work — naming the files — then re-read this table. | +| Behind > 0, fast-forward possible, one branch | `git pull --ff-only` | +| Behind > 0, diverged, one branch | `git rebase origin/$BASE` — a single-branch rebase, not the team command. | +| Two or more worktrees/branches behind | `/make-no-mistakes:rebase {repo-name}` | + +`/make-no-mistakes:rebase` is a real destination and stays one. It syncs +`develop` and `main` with the remote, stashes uncommitted work across all +worktrees, rebases every local branch onto the updated base, auto-merges PRs +that are green and approved, re-checks for cascading effects, restores the +stashes, and prints a health report. That is the right tool for a fleet of +branches after a release, and the wrong tool for one branch four commits +behind. What changes here is who decides which case it is — measured, not +asked. + +## Report shape + +Keep it to what was measured: + +``` +origin/develop — 0 ahead, 12 behind. Fast-forward possible. + +Governed files that changed: + .claude/hooks/.sh "refuse a command that discards stderr" + .claude/hooks/.sh "narrow the citation matcher" + .github/workflows/.yml "pin the checkout to the exact SHA" + +Working tree: clean. Worktrees behind: none. Unpushed: none. + + git pull --ff-only +``` + +Two hooks in that list changed behaviour the user may be reporting as a bug. +Name that when it applies — it is the whole point of the consequence line. + +## Why this exists + +On 2026-07-31 a developer filed two bug reports against a hook, both with clean +reproductions. One was a real defect. The other described behaviour that had +been fixed days earlier; the checkout was stale. **Nothing in the report +separated them**, and the wrong half consumed a debugging session before the +checkout's age was ever questioned. + +A commit count alone would not have caught it either. What was needed was the +consequence line: *three hooks changed, two of them fix defects you may be +looking at right now.*