Skip to content

feat(toolkit): verify-branch-state skill + /verify + warn-mode hook (DOJ-4872) — v1.31.0 - #44

Open
lapc506 wants to merge 7 commits into
mainfrom
andres/doj-4872-toolkit-verify-branch-state-skill-v1.31.0
Open

feat(toolkit): verify-branch-state skill + /verify + warn-mode hook (DOJ-4872) — v1.31.0#44
lapc506 wants to merge 7 commits into
mainfrom
andres/doj-4872-toolkit-verify-branch-state-skill-v1.31.0

Conversation

@lapc506

@lapc506 lapc506 commented Jun 3, 2026

Copy link
Copy Markdown
Collaborator

Summary

Plugin-layer (Level 1) extension of the working-tree-vs-ref discipline rule. This release operationalizes memory feedback_working_tree_is_not_truth at the toolkit layer so the rule travels with the plugin to every repo it's installed in.

Incident origin (2026-06-03). A re-audit subagent correctly flagged usePathwayViewModel.ts as deleted on develop during DOJ-4849 triage. The orchestrator "corrected" the subagent by running ls src/hooks/path/usePathwayViewModel.ts against a local main checkout that another session had switched to a different branch. The bare-FS read returned a stale projection; authoritative "FRESH — proceed as briefed" comments went to DOJ-4851/4863/4864 and had to be retracted publicly. Root cause: working tree was treated as truth instead of one projection.

Defense in depth. Earlier today, DOJ-4868 shipped the repo-layer (Level 2) enforcement via .claude/hooks/ + root CLAUDE.md in dojo-os (PR #2604). DOJ-4872 ships the plugin-layer (Level 1) here: skill + slash command + warn-mode PreToolUse hook. Without it, agents using make-no-mistakes-toolkit in any other repo have zero guidance about the rule.

Linear: DOJ-4872
Sibling: DOJ-4868 (dojo-os, Level 2)
Memory: feedback_working_tree_is_not_truth
Release: v1.30.0 → v1.31.0

What ships

4 new artifacts:

  1. skills/verify-branch-state/SKILL.md — auto-loading skill, tuned description triggers on "verify against develop", "does X exist on <branch>", "what's on origin/…", "did this land on main", "re-validate the subagent", "before I claim", "post-merge state". Body covers the rule statement, canonical ref-explicit commands (git fetch + git show / ls-tree / grep / diff against origin/<ref>), anti-patterns (ls, grep -rn src/, find, git status), pre-flight checklist, multi-worktree context, and the 2026-06-03 origin incident.

  2. commands/verify.md — slash command /verify <ref> <path-or-pattern>. Runs git fetch origin <ref> --quiet then git ls-tree origin/<ref> -- <path> for paths or git grep <pattern> origin/<ref> -- src/ for patterns. Emits a single verdict line citing the ref's short SHA in the shape origin/<ref> @ <sha7> — <path-or-pattern> <PRESENT|NOT PRESENT|N MATCHES> so the claim can be re-checked later. Handles ref-not-found, shell metacharacters in patterns, and squash-merge cache staleness (always re-fetches).

  3. hooks/pre-tool-use-claim-verification.sh — warn-mode PreToolUse hook, repo-agnostic, no per-repo manifest required. Detects bare-FS verification ops (ls, grep -rn src/, find src/, cat src/, head src/) and, when combined with intent keywords in the recent context envelope (or CLAUDE_HOOK_CONTEXT for smoke tests), prints a stderr warning with the local checkout's HEAD ref + short SHA, a suggested ref-explicit replacement specific to the detected op, and pointers to the skill + slash command. Always exit 0. Defensive set +e. Uses cd -P + pwd -P from day 1 (DOJ-4868 PR #2604 lesson). Trailing-newline-safe while read. Honors CLAUDE_DISABLE_PLUGIN_HOOKS=1. Shellcheck clean.

  4. CHANGELOG.md [1.31.0] - 2026-06-03 — Added / Changed / Why sections + reference link added at bottom.

4 edits:

  1. hooks/hooks.json — registers the new hook under the Bash PreToolUse matcher with a 3-second timeout, alongside the existing pre-bash.sh dispatcher.
  2. .claude-plugin/marketplace.json — top-level + plugin version 1.30.0 → 1.31.0; plugin description pair-mentions the new skill; component counts updated to 30 commands / 11 skills / 2 agents.
  3. .claude-plugin/plugin.jsonversion 1.30.0 → 1.31.0.
  4. package.jsonversion 1.30.0 → 1.31.0.
  5. README.md**Version:** header bumped; new "Also start here: verify-branch-state + /verify" peer subsection sits between the domain-driven-advisor "Start here" block and "What's Inside"; Commands table 29 → 30 (adds /verify); Skills table 10 → 11 (adds verify-branch-state).

Cohesive-PR exception (9 files)

9 files instead of the soft ≤8 target. The 4 new artifacts + 4 release-cut edits sum to 8; the 9th is hooks/hooks.json, which is required to make the new hook non-dead code. The whole set is a single cohesive surface (working-tree-vs-ref discipline) plus the standard release-cut shape mirroring v1.30.0's commit shape. Documented per CLAUDE.md rule 7 cohesive-PR exception.

Per-AC ship status

AC Requirement Status
AC1 Skill with frontmatter + tuned auto-trigger description shipped
AC2 Slash command /verify <ref> <path-or-pattern> cites ref SHA shipped
AC3 Hook: set +e, cd -P + pwd -P, exit 0 always, shellcheck clean shipped
AC4 Versions 1.31.0 agree across 4 manifests verified via jq
AC5 CHANGELOG entry under [1.31.0] - 2026-06-03 + reference link shipped
AC6 README "Start here" peer subsection shipped
AC7 Hook smoke tests pass see below
AC8 Skill cites memory feedback_working_tree_is_not_truth shipped
AC9 Cohesive PR ≤8 files 9 files; exception documented
AC10 Tag release v1.31.0 post-merge, manual

Smoke test outputs (AC7)

$ bash hooks/pre-tool-use-claim-verification.sh "ls src/foo.tsx"

[verify-branch-state] WARN: working-tree read used for a branch-level claim.

  Local HEAD: andres/doj-4872-toolkit-verify-branch-state-skill-v1.31.0 @ c513198
  Command:    ls src/foo.tsx

  This reads the working-tree projection, not the ref. The local checkout
  may have drifted (other sessions, worktrees, hooks). The ref is the truth.

  Suggested ref-explicit replacement:
    git fetch origin <branch> --quiet && git ls-tree origin/<branch> -- src/foo.tsx

  Or use the slash command (cites the ref's SHA for you):
    /make-no-mistakes:verify <branch> <path-or-pattern>

  See: skills/verify-branch-state/SKILL.md
  Memory: feedback_working_tree_is_not_truth (origin 2026-06-03)

  (warn-mode only — your command will still run)

$ bash hooks/pre-tool-use-claim-verification.sh "echo hello"
(silent — exit 0)

$ bash hooks/pre-tool-use-claim-verification.sh 'grep -rn "Symbol" src/'
[verify-branch-state] WARN: working-tree read used for a branch-level claim.
  Command:    grep -rn "Symbol" src/
  Suggested: git fetch origin <branch> --quiet && git grep "Symbol" origin/<branch> -- src/
  ...

$ bash hooks/pre-tool-use-claim-verification.sh 'find src/ -name "*.tsx"'
[verify-branch-state] WARN: ...
  Suggested: git fetch origin <branch> --quiet && git ls-tree -r origin/<branch> -- <path> | grep <pattern>

$ echo '{"tool_name":"Bash","tool_input":{"command":"ls src/foo.tsx"}}' | bash hooks/pre-tool-use-claim-verification.sh
(silent — no intent keyword in context envelope; bare ls alone does not trigger)

$ CLAUDE_HOOK_CONTEXT="does X exist on develop" bash -c 'echo "{...}" | bash hooks/pre-tool-use-claim-verification.sh'
[verify-branch-state] WARN: ... (intent ctx present → triggers)

$ shellcheck hooks/pre-tool-use-claim-verification.sh
(0 issues)

$ jq -r '.version' .claude-plugin/marketplace.json       # 1.31.0
$ jq -r '.plugins[0].version' .claude-plugin/marketplace.json   # 1.31.0
$ jq -r '.version' .claude-plugin/plugin.json            # 1.31.0
$ jq -r '.version' package.json                          # 1.31.0
$ grep '^\*\*Version:' README.md                         # **Version: 1.31.0**

Post-merge actions (deferred to maintainer)

These are explicitly out of this PR's scope and will be performed after merge by @lapc506:

  • Tag the release: git tag -a v1.31.0 -m "v1.31.0" + git push origin v1.31.0 (AC10)
  • Flip Linear DOJ-4872 → Done
  • Post a thread reply to the v1.30.0 announcement in #doj-repo-health (Slack thread)

Test plan

  • All four version manifests agree at 1.31.0 (jq -r '.version' … × 4)
  • Hook smoke tests: warn on ls, silent on echo, warn on grep -rn, warn on find
  • Hook stdin-mode: silent without intent ctx, warn with intent ctx
  • shellcheck hooks/pre-tool-use-claim-verification.sh → 0 issues
  • Hook honors CLAUDE_DISABLE_PLUGIN_HOOKS=1 (early exit 0)
  • cd -P + pwd -P used for hook self-location (day 1, per DOJ-4868 PR #2604)
  • Skill description field tuned for auto-trigger on the canonical intent phrases
  • Slash command emits verdict line in citation-ready format
  • CHANGELOG reference link added at bottom ([1.31.0]: …/releases/tag/v1.31.0)
  • Marketplace plugin description pair-mentions the new skill
  • README "Start here" peer subsection drafted to match the domain-driven-advisor style

Created by Claude Code on behalf of @lapc506.

…e hook (DOJ-4872) — v1.31.0

Ships the plugin-layer (Level 1) extension of the working-tree-vs-ref
discipline rule. DOJ-4868 shipped the repo-layer (Level 2) enforcement in
dojo-os; this release makes the same rule travel with the toolkit to every
repo it is installed in.

New artifacts:

- skills/verify-branch-state/SKILL.md — auto-loading skill, tuned description
  triggers on "verify against develop", "does X exist on <branch>", "what's
  on origin/...", "did this land on main", "re-validate the subagent". Body
  covers the rule, canonical ref-explicit commands (git fetch + git show /
  ls-tree / grep / diff against origin/<ref>), anti-patterns (ls, grep -rn
  src/, find, git status), pre-flight checklist, multi-worktree context, and
  the 2026-06-03 DOJ-4851/4863/4864 origin incident. Cites memory
  feedback_working_tree_is_not_truth.

- commands/verify.md — slash command /verify <ref> <path-or-pattern>. Runs
  git fetch origin <ref> --quiet then git ls-tree origin/<ref> -- <path>
  for paths or git grep <pattern> origin/<ref> -- src/ for patterns. Emits
  a single verdict line citing the ref's short SHA in the shape
  "origin/<ref> @ <sha7> — <path-or-pattern> <PRESENT|NOT PRESENT|N MATCHES>"
  so the claim can be re-checked. Handles ref-not-found, shell
  metacharacters in patterns, and squash-merge cache staleness (always
  re-fetches).

- hooks/pre-tool-use-claim-verification.sh — warn-mode PreToolUse hook,
  repo-agnostic, no per-repo manifest. Detects bare-FS verification ops
  combined with intent keywords; prints stderr warning with local HEAD ref
  + SHA, a suggested ref-explicit replacement, and pointers to the skill +
  slash command. Always exit 0. Defensive set +e. Uses cd -P + pwd -P from
  day 1 (DOJ-4868 PR #2604 lesson). Trailing-newline-safe while read.
  Honors CLAUDE_DISABLE_PLUGIN_HOOKS=1. Shellcheck clean. Wired into
  hooks/hooks.json under the Bash PreToolUse matcher with a 3s timeout.

Version bump 1.30.0 -> 1.31.0 across .claude-plugin/marketplace.json
(top-level + plugin version) + .claude-plugin/plugin.json + package.json +
README.md Version header. CHANGELOG entry under [1.31.0] - 2026-06-03 with
Added / Changed / Why sections + reference link at bottom. README "Start
here" grows a peer entry point ("Also start here: verify-branch-state +
/verify") between the domain-driven-advisor section and "What's Inside".
Commands table 29 -> 30 (adds /verify); Skills table 10 -> 11 (adds
verify-branch-state). Marketplace plugin description pair-mentions the new
skill.

Smoke tests pass:
- bash hooks/pre-tool-use-claim-verification.sh "ls src/foo.tsx" -> warn
- bash hooks/pre-tool-use-claim-verification.sh "echo hello" -> silent
- shellcheck hooks/pre-tool-use-claim-verification.sh -> 0 issues
- jq -r .version on all four manifests -> 1.31.0 (agree)

AC9 note (cohesive-PR exception): 9 files instead of the soft ≤8 target
because the hooks.json registration is required to make the new hook
non-dead. The new skill + command + hook are a single cohesive surface
(working-tree-vs-ref discipline) and the version+CHANGELOG+README updates
are the standard release-cut shape, mirroring the v1.30.0 commit shape.

Related: DOJ-4868 (dojo-os Level-2 sibling), v1.30.0 release (today),
memory feedback_working_tree_is_not_truth.

Created by Claude Code on behalf of @lapc506.
@lapc506

lapc506 commented Jun 3, 2026

Copy link
Copy Markdown
Collaborator Author

@dojo-code-reviewer review

@dojo-code-reviewer dojo-code-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Approved

Approved — 0 blockers, 2 P2, 2 P3. Confidence: 3/5.

🟡 P2 — Major

  • commands/verify.md:42 — 🟡 P2 (major) — The slash command currently assumes the user always passes a branch name that needs to be prefixed with 'origin/'. However, if a user passes a remote-tracking branch like 'origin/develop', a tag like 'v1.31.0', or a commit SHA, this leads to failures like 'origin/origin/develop' or 'origin/v1.31.0' during fetch, rev-parse, and ls-tree/grep. Additionally, the hardcoded '-C "$REPO"' directory switches can fail if the environment hasn't defined the '$REPO' variable. Normalize the reference explicitly first to handle all formats seamlessly, and execute directly within the current workspace.

  • hooks/pre-tool-use-claim-verification.sh:116 — 🟡 P2 (major) — The regex matches bare-filesystem operations followed by a space, which means it will fail to detect verification commands that appear at the end of a line or command string without a trailing space (for example, 'ls' alone or the end of a chain like 'cd src && ls'). Use '([[:space:]]|$)' to ensure the hook reliably triggers in these scenarios.

  if printf '%s' "$cmd" | grep -qE '(^|[;&|][[:space:]]*)(ls|find|cat|head)([[:space:]]|$)'; then

🔵 P3 — Minor

  • hooks/pre-tool-use-claim-verification.sh:162 — 🔵 P3 (minor) — To ensure that the suggested replacement helper is chosen correctly when 'ls' is used at the end of a command string without trailing space, replace 'ls[[:space:]]' with 'ls([[:space:]]|$)'.
if printf '%s' "$COMMAND" | grep -qE '(^|[;&|][[:space:]]*)ls([[:space:]]|$)'; then
  • hooks/pre-tool-use-claim-verification.sh:181 — 🔵 P3 (minor) — To ensure that the suggested replacement helper is chosen correctly when 'find' is used at the end of a command string without trailing space, replace 'find[[:space:]]' with 'find([[:space:]]|$)'.
elif printf '%s' "$COMMAND" | grep -qE '(^|[;&|][[:space:]]*)find([[:space:]]|$)'; then

Total findings: 2 compliance, 2 business context (4 total)


ℹ️ Inline anchoring failed for the comments below; they are listed here instead.

  • commands/verify.md:42 — 🟡 P2 (major) — The slash command currently assumes the user always passes a branch name that needs to be prefixed with 'origin/'. However, if a user passes a remote-tracking branch like 'origin/develop', a tag like 'v1.31.0', or a commit SHA, this leads to failures like 'origin/origin/develop' or 'origin/v1.31.0' during fetch, rev-parse, and ls-tree/grep. Additionally, the hardcoded '-C "$REPO"' directory switches can fail if the environment hasn't defined the '$REPO' variable. Normalize the reference explicitly first to handle all formats seamlessly, and execute directly within the current workspace.
  • hooks/pre-tool-use-claim-verification.sh:116 — 🟡 P2 (major) — The regex matches bare-filesystem operations followed by a space, which means it will fail to detect verification commands that appear at the end of a line or command string without a trailing space (for example, 'ls' alone or the end of a chain like 'cd src && ls'). Use '([[:space:]]|$)' to ensure the hook reliably triggers in these scenarios.
  if printf '%s' "$cmd" | grep -qE '(^|[;&|][[:space:]]*)(ls|find|cat|head)([[:space:]]|$)'; then
  • hooks/pre-tool-use-claim-verification.sh:162 — 🔵 P3 (minor) — To ensure that the suggested replacement helper is chosen correctly when 'ls' is used at the end of a command string without trailing space, replace 'ls[[:space:]]' with 'ls([[:space:]]|$)'.
if printf '%s' "$COMMAND" | grep -qE '(^|[;&|][[:space:]]*)ls([[:space:]]|$)'; then
  • hooks/pre-tool-use-claim-verification.sh:181 — 🔵 P3 (minor) — To ensure that the suggested replacement helper is chosen correctly when 'find' is used at the end of a command string without trailing space, replace 'find[[:space:]]' with 'find([[:space:]]|$)'.
elif printf '%s' "$COMMAND" | grep -qE '(^|[;&|][[:space:]]*)find([[:space:]]|$)'; then

Bot review at 3/5 confidence flagged 2 P2 + 2 P3. All four addressed:

P2 — commands/verify.md ref normalization
  - The behavior section assumed every <ref> arg was a bare branch name
    and unconditionally prefixed `origin/`, breaking `origin/develop`
    (→ `origin/origin/develop`), tags (`v1.31.0` → `origin/v1.31.0`),
    and SHAs. Added a normalization step at the top of the pipeline:
      * `refs/tags/<tag>` for tag inputs (fetched via `git fetch origin
        tag <tag> --quiet`)
      * bare SHA for already-local commits (skips fetch entirely;
        commits are immutable)
      * `origin/<branch>` left as-is when user passes `origin/...`
      * `origin/<branch>` only synthesized for bare branch input
  - Dropped the `-C "$REPO"` indirection — the slash command's cwd is
    the repo root, no need for the hardcoded variable that would fail
    when unset.
  - Updated the verdict-line shape to `<resolved-ref> @ <sha7>` so the
    citation reflects whatever normalization produced (tag refs, SHAs,
    branch refs all render correctly). Added tag + SHA examples.
  - Expanded the Edge cases section: `origin/<branch>` input, tag input,
    commit SHA input, ref not found.

P2 — hooks/pre-tool-use-claim-verification.sh line 116
  - The bare-FS detector regex `(ls|find|cat|head)[[:space:]]` failed
    to match the command-as-last-token cases like a bare `ls` invocation
    or `cd src && ls` (no trailing whitespace). Switched the trailing
    class to `([[:space:]]|$)` so end-of-string also matches.

P3 — hooks/pre-tool-use-claim-verification.sh line 162
  - Same fix in the suggester branch for `ls`: `ls[[:space:]]` →
    `ls([[:space:]]|$)` so the ls-specific replacement text is emitted
    even when the command ends in `ls`.

P3 — hooks/pre-tool-use-claim-verification.sh line 181
  - Same fix in the suggester branch for `find`: `find[[:space:]]` →
    `find([[:space:]]|$)`.

Verification:
  - shellcheck hooks/pre-tool-use-claim-verification.sh → 0 issues
  - bash hooks/pre-tool-use-claim-verification.sh "ls"           → warn (new)
  - bash hooks/pre-tool-use-claim-verification.sh "cd src && ls" → warn (new)
  - bash hooks/pre-tool-use-claim-verification.sh "ls src/foo"   → warn (regression)
  - bash hooks/pre-tool-use-claim-verification.sh "echo hello"   → silent (regression)
  - bash hooks/pre-tool-use-claim-verification.sh "find"         → warn (new)

Created by Claude Code on behalf of @lapc506.
@lapc506

lapc506 commented Jun 3, 2026

Copy link
Copy Markdown
Collaborator Author

@dojo-code-reviewer review

Addressed all 4 findings from prior review (3/5 confidence):

  • P2 commands/verify.md:42 — Added ref normalization step at the top of the Behavior pipeline. origin/<branch>, tags (refs/tags/<tag>), and commit SHAs are now resolved explicitly; bare branch names are the only case that synthesizes origin/<branch>. Dropped -C "$REPO" indirection — slash command's cwd is the repo. Verdict line shape updated to <resolved-ref> @ <sha7> with tag + SHA examples.
  • P2 hooks/pre-tool-use-claim-verification.sh:116 — Detector regex trailing class [[:space:]]([[:space:]]|$). Bare ls, cd src && ls, find (no trailing space) now trigger correctly.
  • P3 hooks/pre-tool-use-claim-verification.sh:162 — Same fix in the suggester's ls branch.
  • P3 hooks/pre-tool-use-claim-verification.sh:181 — Same fix in the suggester's find branch.

Smoke tests + shellcheck still green. Fix commit: 31f10c9.

@dojo-code-reviewer dojo-code-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Approved

Approved — 0 blockers, 2 P3. Confidence: 4/5.

🔵 P3 — Minor

  • hooks/pre-tool-use-claim-verification.sh:145 — 🔵 P3 (minor) — Simplify and make the path argument extraction for ls commands more robust. The current sed regular expression assumes the flags always have a trailing space, which fails to strip flags if they are at the end of the command (e.g., ls -la), resulting in path_arg being extracted as -la. Using awk to loop through the tokens and extract the first non-option token after ls is cleaner, shorter, and correctly handles trailing options or extra whitespace.
  path_arg="$(printf '%s' "$COMMAND" \
    | awk '{for(i=1;i<=NF;i++) if ($i == "ls") {for(j=i+1;j<=NF;j++) if ($j !~ /^-/) {print $j; exit}}}')"
  • hooks/pre-tool-use-claim-verification.sh:158 — 🔵 P3 (minor) — Fix the grep pattern extraction when multiple option flags are specified separately. In the current implementation, if a user runs grep -i -r "Symbol" src/, the awk block matches -i and immediately extracts the next token $(i+1) (which is -r) as the pattern. By utilizing a nested loop in awk that skips any subsequent option tokens starting with -, you can correctly isolate the actual search pattern.
    pattern_arg="$(printf '%s' "$COMMAND" \
      | awk '{for(i=1;i<=NF;i++) if ($i ~ /^-[rRnliN]+$/) {for(j=i+1;j<=NF;j++) if ($j !~ /^-/) {print $j; exit}}}' \
      | tr -d '\"')"

Total findings: 2 business context (2 total)


ℹ️ Inline anchoring failed for the comments below; they are listed here instead.

  • hooks/pre-tool-use-claim-verification.sh:145 — 🔵 P3 (minor) — Simplify and make the path argument extraction for ls commands more robust. The current sed regular expression assumes the flags always have a trailing space, which fails to strip flags if they are at the end of the command (e.g., ls -la), resulting in path_arg being extracted as -la. Using awk to loop through the tokens and extract the first non-option token after ls is cleaner, shorter, and correctly handles trailing options or extra whitespace.
  path_arg="$(printf '%s' "$COMMAND" \
    | awk '{for(i=1;i<=NF;i++) if ($i == "ls") {for(j=i+1;j<=NF;j++) if ($j !~ /^-/) {print $j; exit}}}')"
  • hooks/pre-tool-use-claim-verification.sh:158 — 🔵 P3 (minor) — Fix the grep pattern extraction when multiple option flags are specified separately. In the current implementation, if a user runs grep -i -r "Symbol" src/, the awk block matches -i and immediately extracts the next token $(i+1) (which is -r) as the pattern. By utilizing a nested loop in awk that skips any subsequent option tokens starting with -, you can correctly isolate the actual search pattern.
    pattern_arg="$(printf '%s' "$COMMAND" \
      | awk '{for(i=1;i<=NF;i++) if ($i ~ /^-[rRnliN]+$/) {for(j=i+1;j<=NF;j++) if ($j !~ /^-/) {print $j; exit}}}' \
      | tr -d '\"')"

…n hook (DOJ-4872)

Round-2 bot review at 4/5 confidence flagged 2 P3 polish items on the
suggester's argument extraction. Both addressed; suggestions applied
verbatim with explanatory comments.

P3 — hooks/pre-tool-use-claim-verification.sh:145 (ls path extraction)
  - Old: sed prefix-strip + awk first-token, which fails on commands
    like `ls -la` (no path arg, trailing flags) — sed regex `-flags`
    requires a trailing space, so the flags leak through and `-la` ends
    up as the "path arg" in the suggested replacement.
  - New: awk-only nested loop that locates the literal `ls` token,
    then walks forward skipping any token starting with `-`, and
    emits the first non-flag token. Empty when only flags are present
    (correct), so the suggester falls through to the `<path>` placeholder.

P3 — hooks/pre-tool-use-claim-verification.sh:158 (grep pattern extraction)
  - Old: awk fallback matched the first `-FLAGS` token and emitted the
    immediately following token, which on `grep -i -r "Symbol" src/`
    is `-r` — wrong.
  - New: nested awk loop that, after finding the flag token, walks
    forward skipping additional flag tokens before emitting the first
    non-flag one. Correctly isolates `Symbol` for `grep -i -r "Symbol"`,
    still correct for the single-flag-block form `grep -rn "Symbol"`.

Verification:
  - shellcheck → 0 issues
  - "ls -la"                 → suggester emits <path> placeholder (was: `-la`)
  - "ls src/foo.tsx"         → suggester emits `src/foo.tsx` (regression)
  - "grep -i -r \"Symbol\" src/" → emits "Symbol" (was: `-r`)
  - "grep -rn \"Symbol\" src/"   → emits "Symbol" (regression)
  - "echo hello"             → silent (regression)

Created by Claude Code on behalf of @lapc506.
@lapc506

lapc506 commented Jun 3, 2026

Copy link
Copy Markdown
Collaborator Author

@dojo-code-reviewer review

Round-2 feedback (4/5) addressed — both P3 items applied verbatim with explanatory comments. Fix commit: $(git rev-parse --short HEAD).

  • P3 ls path extraction: awk-only nested loop replaces sed prefix-strip, fixes ls -la leaking -la as the path arg.
  • P3 grep pattern extraction: nested awk loop skips additional flag tokens, fixes grep -i -r "Symbol" src/ mis-extracting -r as the pattern.

Smoke tests + shellcheck green.

@dojo-code-reviewer dojo-code-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Approved

Approved — 0 blockers, 1 P3. Confidence: 4/5.

🔵 P3 — Minor

  • hooks/pre-tool-use-claim-verification.sh:185 — 🔵 P3 (minor) — Strip single quotes as well as double quotes in the fallback pattern extractor to handle single-quoted search patterns (e.g. grep -rn 'Symbol').
      | tr -d "\"'")"

Total findings: 1 business context (1 total)


ℹ️ Inline anchoring failed for the comments below; they are listed here instead.

  • hooks/pre-tool-use-claim-verification.sh:185 — 🔵 P3 (minor) — Strip single quotes as well as double quotes in the fallback pattern extractor to handle single-quoted search patterns (e.g. grep -rn 'Symbol').
      | tr -d "\"'")"

…n hook (DOJ-4872)

Round-3 bot review at 4/5 confidence flagged 1 P3: the pattern extractor
only handled double-quoted forms, so `grep -rn 'Symbol' src/` would fall
through to the awk fallback and (until the awk fallback also stripped
single quotes) emit `'Symbol'` with the quotes attached.

Applied:

- Added a second extractor branch that mirrors the double-quote form but
  for single-quoted patterns. Tries double-quote first (cheapest grep),
  then single-quote, then the multi-flag awk fallback.
- Extended `tr -d` in the awk fallback to strip both `"` and `'`, so the
  unquoted-token output is clean regardless of which quote style the
  original command used.

Verification:
  - shellcheck → 0 issues
  - "grep -rn 'Symbol' src/"      → "Symbol"  (was: 'Symbol')
  - "grep -rn \"Symbol\" src/"      → "Symbol"  (regression)
  - "grep -i -r 'Symbol' src/"    → "Symbol"  (new, multi-flag + single quote)
  - "grep -i -r \"Symbol\" src/"    → "Symbol"  (regression)
  - "ls src/foo.tsx"              → src/foo.tsx (regression)
  - "echo hello"                  → silent     (regression)

Created by Claude Code on behalf of @lapc506.
@lapc506

lapc506 commented Jun 3, 2026

Copy link
Copy Markdown
Collaborator Author

@dojo-code-reviewer review

Round-3 feedback (4/5, 1 P3) addressed:

  • P3 single-quoted grep patterns: added a single-quote extractor branch parallel to the existing double-quote branch, and extended the awk-fallback tr -d to strip both " and '. grep -rn 'Symbol' src/, grep -i -r 'Symbol' src/, and the double-quoted equivalents all extract Symbol cleanly now.

Smoke tests + shellcheck green. Fix commit: $(git rev-parse --short HEAD).

@dojo-code-reviewer dojo-code-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Approved

Approved — 0 blockers, 1 P2, 3 P3. Confidence: 3/5.

🟡 P2 — Major

  • hooks/pre-tool-use-claim-verification.sh:81 — 🟡 P2 (major) — The fallback regex "[^\"]*" stops at the first backslash (which JSON uses to escape internal double quotes), resulting in truncated command extraction when the executed bash command contains quotes (e.g. grep -rn \"Symbol\" src/). This causes subsequent command matching and warning suggestions to fail.

Using an escape-aware JSON string regex resolves this.

    | grep -oE '"command"[[:space:]]*:[[:space:]]*"([^"\\]|\\.)*"' \

🔵 P3 — Minor

  • hooks/pre-tool-use-claim-verification.sh:164 — 🔵 P3 (minor) — Redundant backslashes inside a single-quoted string are treated as literal backslashes by some shells and grep implementations, which can cause pattern extraction to fail or behave inconsistently depending on the platform's grep engine (e.g. macOS BSD grep vs GNU grep). Removing the backslashes before the double quotes inside single quotes improves cross-platform reliability.

Please also apply the same cleanup to the double quote backslashes on line 166.

    | grep -oE 'grep[[:space:]]+-[rRnliN]+[[:space:]]+"[^"]+"' \
  • commands/verify.md:112 — 🔵 P3 (minor) — Hardcoding -- src/ restricts pattern verification to the src/ directory. If the toolkit is installed in a repository that uses a different directory structure (e.g., lib/, app/, packages/, or root-level files), git grep will fail with a fatal pathspec error. Removing the -- src/ suffix or falling back to the repo root if src/ is absent makes the command truly generic and repo-agnostic.

  • commands/verify.md:45 — 🔵 P3 (minor) — Remote-only tags will fail to resolve. If a tag (e.g., v1.31.0) exists on the remote but has not been fetched locally yet, git rev-parse will fail, pushing the ref to the else block which resolves it to the nonexistent branch origin/v1.31.0. Pre-fetching tags or querying git ls-remote first would prevent this failure mode.


Total findings: 1 compliance, 3 business context (4 total)


ℹ️ Inline anchoring failed for the comments below; they are listed here instead.

  • hooks/pre-tool-use-claim-verification.sh:81 — 🟡 P2 (major) — The fallback regex "[^\"]*" stops at the first backslash (which JSON uses to escape internal double quotes), resulting in truncated command extraction when the executed bash command contains quotes (e.g. grep -rn \"Symbol\" src/). This causes subsequent command matching and warning suggestions to fail.

Using an escape-aware JSON string regex resolves this.

    | grep -oE '"command"[[:space:]]*:[[:space:]]*"([^"\\]|\\.)*"' \
  • hooks/pre-tool-use-claim-verification.sh:164 — 🔵 P3 (minor) — Redundant backslashes inside a single-quoted string are treated as literal backslashes by some shells and grep implementations, which can cause pattern extraction to fail or behave inconsistently depending on the platform's grep engine (e.g. macOS BSD grep vs GNU grep). Removing the backslashes before the double quotes inside single quotes improves cross-platform reliability.

Please also apply the same cleanup to the double quote backslashes on line 166.

    | grep -oE 'grep[[:space:]]+-[rRnliN]+[[:space:]]+"[^"]+"' \
  • commands/verify.md:112 — 🔵 P3 (minor) — Hardcoding -- src/ restricts pattern verification to the src/ directory. If the toolkit is installed in a repository that uses a different directory structure (e.g., lib/, app/, packages/, or root-level files), git grep will fail with a fatal pathspec error. Removing the -- src/ suffix or falling back to the repo root if src/ is absent makes the command truly generic and repo-agnostic.
  • commands/verify.md:45 — 🔵 P3 (minor) — Remote-only tags will fail to resolve. If a tag (e.g., v1.31.0) exists on the remote but has not been fetched locally yet, git rev-parse will fail, pushing the ref to the else block which resolves it to the nonexistent branch origin/v1.31.0. Pre-fetching tags or querying git ls-remote first would prevent this failure mode.

…y (DOJ-4872)

Round-4 bot review at 3/5 confidence flagged 1 P2 + 3 P3. All four addressed.

P2 — hooks/pre-tool-use-claim-verification.sh JSON command extractor
  - The jq-less fallback regex `"[^"]*"` stops at the first inner quote.
    For a bash command like `grep -rn \"Symbol\" src/`, the JSON envelope
    embeds it as `"command": "grep -rn \\\"Symbol\\\" src/"`, and the
    naive regex truncates at the first `\"`. Subsequent detector / suggester
    logic then sees a partial command and emits the wrong replacement.
  - Switched to the escape-aware JSON-string body
    `([^"\\]|\\.)*` which accepts any non-quote-non-backslash byte OR a
    backslash followed by any byte. Verified with an envelope containing
    `grep -rn \"Symbol\" src/` — the full command extracts cleanly and
    the suggester emits the right line.

P3 — hooks/pre-tool-use-claim-verification.sh cross-platform quote handling
  - The bot flagged that the explicit single-quoted-pattern grep extractor
    (added in round 3) used embedded-quote shell escaping that can drift
    between GNU and BSD grep. Simplified by removing the explicit single-
    quoted branch entirely: the awk fallback already strips both `"` and
    `'` from the extracted token, so it covers both quote styles without
    embedded escapes. Net code reduction; same behavior across the four
    smoke-tested variants (single/double quoted, single/multi flag).

P3 — commands/verify.md repo-agnostic grep path
  - The behavior section hardcoded `-- src/` as the path filter, which
    would fail with a `fatal: pathspec` error in toolkit consumers that
    have a different layout (`lib/`, `app/`, `packages/`, flat). Added a
    detect-or-fallback step: if `src/` exists on the resolved ref, use
    it as the path filter; otherwise pass no path filter and grep the
    whole tree.

P3 — commands/verify.md remote-only tag resolution
  - The normalization step probed `git rev-parse --verify refs/tags/<tag>`
    which only sees LOCAL tags. A remote-only tag (e.g. `v1.31.0` just
    pushed by a teammate but not yet fetched locally) would fall through
    to the bare-branch branch and resolve to a nonexistent
    `origin/v1.31.0`. Added a `git ls-remote --tags --exit-code origin
    refs/tags/<tag>` probe between the local-tag and SHA branches; on hit,
    runs `git fetch origin tag <tag> --quiet` before resolving. Comment
    documents the failure mode.

Verification:
  - shellcheck → 0 issues
  - JSON envelope with escaped quotes:
      `{"tool_input":{"command":"grep -rn \\\"Symbol\\\" src/"}}` →
      Command: `grep -rn "Symbol" src/`  (was: `grep -rn `)
      Suggested: `git grep "Symbol" origin/<branch> -- src/` (was: garbled)
  - "grep -rn 'Symbol' src/"       → Suggested: "Symbol"  (regression)
  - "grep -rn \"Symbol\" src/"       → Suggested: "Symbol"  (regression)
  - "grep -i -r \"Symbol\" src/"     → Suggested: "Symbol"  (regression)
  - "ls src/foo.tsx"               → Suggested: src/foo.tsx (regression)
  - "echo hello"                   → silent             (regression)

Created by Claude Code on behalf of @lapc506.
@lapc506

lapc506 commented Jun 3, 2026

Copy link
Copy Markdown
Collaborator Author

@dojo-code-reviewer review

Round-4 feedback (3/5, 1 P2 + 3 P3) addressed:

  • P2 hook JSON command-extractor: switched the jq-less fallback regex from "[^"]*" to the escape-aware "([^"\\\\]|\\\\.)*" so envelopes containing grep -rn \"Symbol\" src/ extract the full command instead of truncating at the first inner quote. Verified end-to-end.
  • P3 cross-platform grep quote-handling: removed the explicit single-quoted-pattern extractor branch (which used embedded-quote shell escaping fragile across GNU/BSD grep); the awk fallback already strips both " and ' so it covers both styles cleanly. Net code reduction.
  • P3 commands/verify.md repo-agnostic grep: dropped hardcoded -- src/. Detect src/ on the resolved ref; if absent, pass no path filter so the command works in repos with lib/, app/, packages/, or flat layouts.
  • P3 commands/verify.md remote-only tag: added a git ls-remote --tags --exit-code probe between the local-tag and SHA branches; on hit, fetches the tag before resolving so remote-only tags don't fall through to a nonexistent origin/<tag>.

Smoke tests + shellcheck green. Fix commit: $(git rev-parse --short HEAD).

@dojo-code-reviewer dojo-code-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Approved

Approved — 0 blockers, 4 P2. Confidence: 2/5.

🟡 P2 — Major

  • commands/verify.md:44 — 🟡 P2 (major) — The ARG variable is used in later steps (e.g., "$ARG") but is never defined or assigned. This will cause $ARG to resolve to an empty string, breaking file path and pattern verification when Claude follows these instructions. Assigning ARG="$2" next to RAW_REF ensures proper parameter mapping.
   RAW_REF="$1"
   ARG="$2"
  • commands/verify.md:54 — 🟡 P2 (major) — Running git fetch origin "tag $RAW_REF" (with quotes grouping the tag keyword and the tag name together) passes them as a single argument to Git. This will cause the fetch operation to fail with a "does not appear to be a git repository" error. Separating them as distinct arguments ensures the remote-only tag is fetched successfully.
     git fetch origin tag "$RAW_REF" --quiet
  • hooks/pre-tool-use-claim-verification.sh:99 — 🟡 P2 (major) — The unanchored pattern grep[[:space:]]+-[rRnliN]+ matches any occurrence of grep followed by flags, which includes legitimate Git commands like git grep -n. This causes the hook to generate false-positive warnings even when users are running the exact ref-explicit git grep commands recommended by this very skill. Anchoring grep to the start of the command or a separator prevents these false positives.
  if printf '%s' "$cmd" | grep -qE '(^|[;&|][[:space:]]*)grep[[:space:]]+-[rRnliN]+'; then
  • hooks/pre-tool-use-claim-verification.sh:145 — 🟡 P2 (major) — The unanchored pattern grep[[:space:]]+-[rRnliN]+ matches git grep here as well, causing false positives in the suggestion logic. Anchoring grep to the start of the command or a separator keeps the matching logic consistent with the detector.
elif printf '%s' "$COMMAND" | grep -qE '(^|[;&|][[:space:]]*)grep[[:space:]]+-[rRnliN]+'; then

Total findings: 4 compliance (4 total)


ℹ️ Inline anchoring failed for the comments below; they are listed here instead.

  • commands/verify.md:44 — 🟡 P2 (major) — The ARG variable is used in later steps (e.g., "$ARG") but is never defined or assigned. This will cause $ARG to resolve to an empty string, breaking file path and pattern verification when Claude follows these instructions. Assigning ARG="$2" next to RAW_REF ensures proper parameter mapping.
   RAW_REF="$1"
   ARG="$2"
  • commands/verify.md:54 — 🟡 P2 (major) — Running git fetch origin "tag $RAW_REF" (with quotes grouping the tag keyword and the tag name together) passes them as a single argument to Git. This will cause the fetch operation to fail with a "does not appear to be a git repository" error. Separating them as distinct arguments ensures the remote-only tag is fetched successfully.
     git fetch origin tag "$RAW_REF" --quiet
  • hooks/pre-tool-use-claim-verification.sh:99 — 🟡 P2 (major) — The unanchored pattern grep[[:space:]]+-[rRnliN]+ matches any occurrence of grep followed by flags, which includes legitimate Git commands like git grep -n. This causes the hook to generate false-positive warnings even when users are running the exact ref-explicit git grep commands recommended by this very skill. Anchoring grep to the start of the command or a separator prevents these false positives.
  if printf '%s' "$cmd" | grep -qE '(^|[;&|][[:space:]]*)grep[[:space:]]+-[rRnliN]+'; then
  • hooks/pre-tool-use-claim-verification.sh:145 — 🟡 P2 (major) — The unanchored pattern grep[[:space:]]+-[rRnliN]+ matches git grep here as well, causing false positives in the suggestion logic. Anchoring grep to the start of the command or a separator keeps the matching logic consistent with the detector.
elif printf '%s' "$COMMAND" | grep -qE '(^|[;&|][[:space:]]*)grep[[:space:]]+-[rRnliN]+'; then

…OJ-4872)

Round-5 bot review at 2/5 confidence flagged 4 P2. All four are real
correctness bugs (no nits this round); applied verbatim.

P2 — hooks/pre-tool-use-claim-verification.sh detector grep false-positive
  - The unanchored pattern `grep[[:space:]]+-[rRnliN]+` matched any
    grep-followed-by-flags substring, including the EXACT ref-explicit
    replacement this hook recommends: `git grep -n "Symbol" origin/develop
    -- src/`. Result: the hook warned against its own suggested fix,
    creating a self-reinforcing false-positive loop. Anchored with
    `(^|[;&|][[:space:]]*)` so `grep` must be at the start of the command
    or after a separator, never inside `git grep`.

P2 — hooks/pre-tool-use-claim-verification.sh suggester grep false-positive
  - Same unanchored pattern in the suggester branch caused the
    `git grep` suggester to fire on legitimate `git grep` invocations
    that fell through to the suggester (they shouldn't get there at all
    if the detector is right, but defense-in-depth). Applied the same
    `(^|[;&|][[:space:]]*)` anchor.

P2 — commands/verify.md ARG undefined
  - The behavior section referenced `$ARG` in step 5 but never assigned
    it. Following the example verbatim would leave `$ARG` empty and break
    every verification. Added `ARG="$2"` next to `RAW_REF="$1"` at the
    top of step 1.

P2 — commands/verify.md tag-fetch arg gluing
  - `git fetch origin "tag $RAW_REF" --quiet` quotes the `tag` keyword
    together with the tag name as a single arg, which git parses as a
    refspec name → "does not appear to be a git repository" or "fatal:
    couldn't find remote ref" depending on git version. Switched to
    separate `tag` + `"$RAW_REF"` args, and refactored step 3 to use a
    case-statement dispatcher over `$FETCH_KIND` ("tag" | "branch" | "")
    so the tag-vs-branch fetch shapes stay distinct.

Verification:
  - shellcheck → 0 issues
  - "grep -rn \"Symbol\" src/"            → warn (regression)
  - "git grep -n \"Symbol\" origin/develop -- src/" → silent (was: WARN — the hook
                                                              was attacking its
                                                              own recommended fix)
  - "cd src && grep -rn \"Symbol\" ."     → warn (chain-after-separator works)
  - "echo hello"                        → silent (regression)

Created by Claude Code on behalf of @lapc506.
@lapc506

lapc506 commented Jun 3, 2026

Copy link
Copy Markdown
Collaborator Author

@dojo-code-reviewer review

Round-5 feedback (2/5, 4 P2) addressed — all four were real correctness bugs:

  • P2 hook detector: anchored grep -FLAGS so git grep -n no longer false-positives. Critical fix — the hook was attacking its own ref-explicit recommendation, creating a self-defeating warning loop.
  • P2 hook suggester: same anchor applied in the suggester branch.
  • P2 commands/verify.md ARG undefined: added ARG="$2" next to RAW_REF="$1". Without it, following the example verbatim would leave $ARG empty and break every verification.
  • P2 commands/verify.md tag-fetch arg gluing: switched git fetch origin "tag $RAW_REF" to separate tag + "$RAW_REF" args, and refactored step 3 to a case-statement dispatcher over $FETCH_KIND ("tag" | "branch" | "") so the tag vs branch fetch shapes stay distinct.

Smoke tests + shellcheck green. Fix commit: $(git rev-parse --short HEAD).

@dojo-code-reviewer dojo-code-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Approved

Approved — no findings. Confidence: 5/5.

main advanced 6 commits (1.31.0 -> 1.35.0) while this branch sat open, so
every shared manifest conflicted. Resolution keeps main on everything except
this branch's own contribution.

Taken from main:
- package.json / plugin.json / marketplace.json version -> 1.35.0
- marketplace + package descriptions (main's escaped form)
- CHANGELOG entries 1.35.0 / 1.34.0 / 1.33.0 / 1.32.0 and the link footer
- README version line and the /observability-audit + /parallelize table rows

Kept from this branch:
- commands/verify.md, skills/verify-branch-state/SKILL.md,
  hooks/pre-tool-use-claim-verification.sh (merged cleanly into hooks.json)
- the /verify README row and the verify-branch-state skill row
- the marketplace "Pair it with verify-branch-state" sentence
- the 1.31.0 CHANGELOG entry, placed in chronological order after 1.32.0

Counts recomputed from the merged tree rather than carried over: 38 commands
(was 37 on main, 30 on the branch) and 12 skills (was 11 on both).

Verified on the merge result: vitest 60/60, hooks/test-hooks.sh 328/328,
hooks/test-sentinel-walkup.sh 5/5, and `npm run build-rules` leaves
hooks/rules/rules.json unchanged.

NOTE for the PO: the 1.31.0 CHANGELOG entry describes a version that was
never released (main went 1.30.0 -> 1.32.0). Whether this ships as a
back-filled 1.31.0 or gets renumbered is a release decision, left untouched.

@dojo-code-reviewer dojo-code-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Approved

Approved — no findings. Confidence: 5.00/5.00.

Walkthrough

⚠️ GOVERNANCE WARNING: This PR targets the main branch directly. Standard GitFlow workflow expects features to target develop first (featuredevelopmain). Since the code itself is functionally sound and fully backward-compatible, this is an informative warning and does not block approval.

Walkthrough

This PR implements the verify-branch-state skill, the /verify slash command, and the plugin-wide pre-tool-use-claim-verification.sh hook. It enforces the core discipline that the local working tree is only one projection of the repository, while the remote/fetched ref is the absolute truth, preventing agent footguns when answering branch-level questions in multi-worktree/multi-session environments.

Files Reviewed

  • hooks/pre-tool-use-claim-verification.sh (Warn-mode detector, awk suggestions, fail-open logic)
  • commands/verify.md (Ref-explicit command, tag/branch/SHA normalization, remote tag fetching)
  • skills/verify-branch-state/SKILL.md (Auto-triggering skill definition, checklist, incident reference)
  • hooks/hooks.json (Registration under the Bash matcher with 3s timeout)
  • .claude-plugin/marketplace.json, CHANGELOG.md, and README.md (Release manifest updates and documentation additions)

Safety Rationale

The hook operates entirely in warn-mode (set +e and guaranteed exit 0 on all paths) and fails open gracefully if jq or git tools are missing or if any path resolution fails, making it extremely safe to deploy.

Verdict

Approved — no findings.

lapc506 added a commit that referenced this pull request Jul 31, 2026
Measured on origin/main @ ee0ba47: the three version files all read 1.36.0,
and `gh pr diff 55 | grep -E '^\+.*"version"'` shows #55 already bumps to
1.37.0 in package.json, plugin.json and marketplace.json. Taking 1.37.0 here
collides with it in all three. The same probe over #52, #46, #44 and #41
returns nothing, so 1.38.0 is free. Skipping a version costs nothing;
colliding does.

Minor and not major, decided by reading. A skill auto-activates on its
`description` rather than being invoked by name like a command, so a renamed
`name:` changes no call site. The reference search returns nothing:

  grep -rniI "rebase.advisor" . --exclude-dir=node_modules \
    --exclude-dir=.git --exclude=CHANGELOG.md   ->  no hits

That negative is real and not a broken search -- the same grep for
`spike-recommend` returns 10 files, so cross-references of this shape are
found when they exist.

One surface does break and is stated rather than folded in: a user who typed
`/make-no-mistakes:rebase-advisor` explicitly (README:137 documents that
skills can be invoked that way) now gets an unknown skill. It fails loudly,
the replacement is one row away in the same table, and the installer prunes
the old file rather than leaving both live.

The `[1.37.0]:` CHANGELOG reference slot is deliberately left for #55 to fill.

Suite: 337/337 hooks, 60/60 vitest.

Created by Claude Code on behalf of @lapc506
lapc506 added a commit that referenced this pull request Jul 31, 2026
…(v1.38.0) (#56)

* feat(skills): sync-advisor — measure the drift before naming the fix (v1.37.0)

Renames `rebase-advisor` to `sync-advisor` and turns a blind router into an
advisor that measures.

The old skill was 43 lines and measured nothing. Its 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: it triggered on
"align with develop" and "branches are behind", both of which are `git pull`,
and sent them 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,
fast-forward possible, dirty tree split by stage, untracked files the ref
already tracks, worktrees behind, and branches with unpushed commits. The
fifth is the threshold between a plain pull and the team command.

The fourth is the one nothing else reports, and it was verified on a throwaway
pair of repos rather than asserted. An untracked local file at a path the ref
tracks aborts the pull outright while being invisible everywhere else:
`git status` shows a plain `?? newfile.txt`, distance reports a clean 0 ahead
1 behind, and `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, never deleting them. Both controls
were run: the pipeline printed nothing before the collision existed and named
the file after.

It never acts. Every fix is printed for the user to run. The single write is
`git fetch origin --quiet`, which touches remote-tracking refs and nothing
else, and the skill says so out loud -- without it every measurement is taken
against a stale origin/<base> and reports a drift that stopped being true days
ago, which is the failure this skill exists to catch.

Adds `syncAdvisor.governedPaths` to make-no-mistakes.config.json: the paths
whose changes get reported by name, turning "you are 12 behind" into "three
hooks changed, two of them fix defects you may be looking at right now". No
default -- with the key unset the skill drops the consequence line rather than
falling back to a built-in list, which would be wrong in every repo but the one
it was copied from and would read as measured.

commands/rebase.md is untouched and stays a real destination. What changed is
who decides when it applies.

Origin (2026-07-31, as reported): a developer filed two bug reports against a
hook 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.

Suite: 337/337 hooks, 60/60 vitest.

Created by Claude Code on behalf of @lapc506

* chore(release): take 1.38.0 instead of 1.37.0 — #55 owns 1.37.0

Measured on origin/main @ ee0ba47: the three version files all read 1.36.0,
and `gh pr diff 55 | grep -E '^\+.*"version"'` shows #55 already bumps to
1.37.0 in package.json, plugin.json and marketplace.json. Taking 1.37.0 here
collides with it in all three. The same probe over #52, #46, #44 and #41
returns nothing, so 1.38.0 is free. Skipping a version costs nothing;
colliding does.

Minor and not major, decided by reading. A skill auto-activates on its
`description` rather than being invoked by name like a command, so a renamed
`name:` changes no call site. The reference search returns nothing:

  grep -rniI "rebase.advisor" . --exclude-dir=node_modules \
    --exclude-dir=.git --exclude=CHANGELOG.md   ->  no hits

That negative is real and not a broken search -- the same grep for
`spike-recommend` returns 10 files, so cross-references of this shape are
found when they exist.

One surface does break and is stated rather than folded in: a user who typed
`/make-no-mistakes:rebase-advisor` explicitly (README:137 documents that
skills can be invoked that way) now gets an unknown skill. It fails loudly,
the replacement is one row away in the same table, and the installer prunes
the old file rather than leaving both live.

The `[1.37.0]:` CHANGELOG reference slot is deliberately left for #55 to fill.

Suite: 337/337 hooks, 60/60 vitest.

Created by Claude Code on behalf of @lapc506

* fix(sync-advisor): make the collision predicate correct from any subdirectory

Reviewer P3 on `ea8c46e` claimed `git ls-tree` "always outputs
repository-relative paths" while `git ls-files` is prefix-relative, so the two
would fail to match from a subdirectory, and proposed `--full-name` on
`ls-files`.

Measured: the premise is false and the proposed fix introduces the bug it
claims to prevent. From `sub/`, `ls-tree -r --name-only` prints `newfile.txt`,
not `sub/newfile.txt` -- it strips the prefix exactly like `ls-files` does, so
the original command matched fine and found the collision. Adding
`--full-name` alone then makes `ls-files` emit `sub/newfile.txt` against
`ls-tree`'s `newfile.txt`, they stop matching, and `comm -12` returns empty --
a clean bill of health for a tree about to abort the pull.

But it pointed at a real weakness of a different kind. From a subdirectory both
commands are SCOPED to that subtree, so a collision at the repo root is not
seen at all. That is scope, not format, and no combination of format flags
fixes it: `--full-name` changes how a path prints, never which paths are
considered. The pathspec does.

Shipped: `--full-name -- :/` on ls-files, `--full-tree` on ls-tree. Four cases
run, with the command extracted verbatim from SKILL.md so the test cannot drift
from the doc -- negative control from the root and from `sub/` (both empty),
positive from the root and from `sub/` with one collision in each location
(both list `rootfile.txt` and `sub/newfile.txt`).

Suite: 340/340 hooks, 60/60 vitest.

Created by Claude Code on behalf of @lapc506

* fix(sync-advisor): resolve the base ref correctly, and in a bare form

Reviewer P2 on `00e2c08`, and it is right. Step 0 returned values carrying the
remote prefix while every predicate interpolates `origin/$BASE`, so the run
died on `origin/origin/develop`:

  $ git rev-list --left-right --count "HEAD...origin/origin/main"
  fatal: ambiguous argument … unknown revision          exit=128

Fixed by normalising unconditionally (`${BASE#refs/remotes/}`, `${BASE#origin/}`)
whichever branch of the resolution produced the value. Verified both branches
with a control that must fail: where `origin/HEAD` IS set it returns
`origin/main`, normalises to `main`, and `origin/main` resolves; the
un-normalised form exits 128.

The P2 understated it. Step 0's first command resolved the base from
`@{upstream}`, which on a feature branch is that branch's OWN remote copy --
here `origin/andres/sync-advisor`. That answers "am I pushed?", which is
predicate 6's question, and would report 0 behind on a branch far behind the
real base. Stripping a prefix would have left it pointing at the wrong ref, so
the command is gone from base resolution rather than patched.

Also measured while there: `git symbolic-ref --short refs/remotes/origin/HEAD`
fails outright in this repo (`not a symbolic ref`), so it is documented as a
fall-through into the develop/main/master/trunk probe rather than as a step
that is expected to succeed.

P4 (`--abbrev-ref` and `--symbolic-full-name` redundant) is also correct --
both forms return `origin/andres/sync-advisor` here -- and is moot: that
command no longer appears.

Suite: 340/340 hooks, 60/60 vitest.

Created by Claude Code on behalf of @lapc506
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant