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.*