|

feat: skill polish + docs from manual-walk findings; bump to 0.1.2

feat: skill polish + docs from manual-walk findings; bump to 0.1.2

Manual scenario walk surfaced three real gaps in v0.1.1's behavior.
None affect the unit-tested correctness of the hook itself; all are
in skill content or user-facing docs.

mercurial-bookmarks-and-prs: when the user says "branch" in an hg
context, that almost always means a bookmark, not a named branch.
The skill's bookmark-vs-named-branch table was correct in the
abstract but the model was inferring "use named branches" from
existing repo precedent (which itself was usually a previous
mistake by an LLM that hadn't loaded this skill). Added an
explicit "When the user says 'branch'" subsection, an "Override
repo precedent" rule, and a script for surfacing the inconsistency
to the user.

mercurial-sync-and-merge: hg merge in a non-TTY Claude Code
session defaults to whatever the user's hgrc says, which is often
vimdiff/kdiff3 — neither works headless. Now explicitly recommends
--tool internal:merge on every merge so conflict markers land in
the file rather than launching an interactive tool. Also added the
internal:local / internal:other shortcuts for "keep ours" / "take
theirs" resolutions.

tests/MANUAL.md Scenario 5: rewrote to acknowledge two valid pass
paths (skill prevents the git attempt OR hook blocks it) since the
former was what we observed in practice. Added an adversarial
sub-test that forces a literal git invocation so the hook's block
path can be validated in a live session.

README: added a "Layered defense" subsection explaining that the
hook is the backstop, not the primary mechanism, so users aren't
surprised when the hook rarely fires in normal use.

The v0.2.0 candidate of a defense-in-depth hook for "hg commit -u"
is intentionally deferred — it has legitimate use cases
(replay/migration) that need a proper design pass before adding a
mechanical block.
Author: Chris Tusa <chris.tusa@leafscale.com>
Date: May 04, 2026 19:40
Changeset: 02bd3459c5a42befc49ea9daeeadf3696d15abcb
Branch: default
Tags: v0.1.2

Diff

diff -r 73c3f3c0e9be -r 02bd3459c5a4 .claude-plugin/plugin.json
--- a/.claude-plugin/plugin.json	Mon May 04 16:49:59 2026 -0500
+++ b/.claude-plugin/plugin.json	Mon May 04 19:40:14 2026 -0500
@@ -1,6 +1,6 @@
 {
   "name": "isurus-hg",
-  "version": "0.1.1",
+  "version": "0.1.2",
   "description": "Mercurial fluency for Claude Code, with an Isurus-conventions overlay and a hook that prevents accidental git use in hg repos.",
   "author": {
     "name": "Chris Tusa",
diff -r 73c3f3c0e9be -r 02bd3459c5a4 README.md
--- a/README.md	Mon May 04 16:49:59 2026 -0500
+++ b/README.md	Mon May 04 19:40:14 2026 -0500
@@ -81,6 +81,15 @@
 
 Documented limitation: only the leading token of the command is inspected, so `cd /tmp && git init` from inside an `.hg` repo is allowed (intended escape valve). Override the hook by commenting out the entry in `hooks/hooks.json`.
 
+### Layered defense — the hook is a backstop, not the primary mechanism
+
+`git`-in-hg avoidance happens at two layers:
+
+- **Skills (primary):** the `mercurial-*` skills teach Claude to recognize `.hg/` and reach for `hg` commands directly. When the skills load, the model usually never attempts `git` in the first place.
+- **Hook (backstop):** if a `git` command is invoked anyway, the hook intercepts and blocks it. The 7 unit tests under `hooks/test/` validate the block path in isolation.
+
+In practice this means the hook may rarely fire in a session where the plugin loaded cleanly — that's expected, not a defect. To verify the hook actively works in a live session, see the adversarial sub-test in `tests/MANUAL.md` Scenario 5.
+
 ## Development
 
 This plugin is developed in **Mercurial** (eating its own dogfood). To work on it:
diff -r 73c3f3c0e9be -r 02bd3459c5a4 skills/mercurial-bookmarks-and-prs/SKILL.md
--- a/skills/mercurial-bookmarks-and-prs/SKILL.md	Mon May 04 16:49:59 2026 -0500
+++ b/skills/mercurial-bookmarks-and-prs/SKILL.md	Mon May 04 19:40:14 2026 -0500
@@ -30,6 +30,20 @@
 
 **Rule of thumb:** if you'd open a PR for it, it's a bookmark. If it's a maintained release line, it's a named branch.
 
+### When the user says "branch"
+
+Most users come to Mercurial from Git, where "branch" means a lightweight pointer that can be deleted. In Mercurial that concept is a **bookmark**, not a named branch. Map their words accordingly:
+
+- **"create a feature branch", "start a branch", "switch branches", "branch off X"** → bookmark, every time.
+- **"create a release branch", "branch for v2.0"** → named branch (long-lived release line).
+- **Ambiguous** → ask, but default to bookmark for anything PR-shaped.
+
+**Override repo precedent.** If you find existing named branches that look like feature work (e.g. an `add-language-stats` named branch alongside `default`), do *not* infer that the convention is to use named branches. That precedent is almost always a previous mistake (often by an LLM that didn't load this skill). Use a bookmark for the new work and surface the inconsistency:
+
+> "Heads up: this repo has named branches (`add-language-stats`, `fix-nil-reviewer`) that look like feature work, which is unusual — feature work in Mercurial is typically a bookmark. I'm using a bookmark for the new feature; let me know if you'd prefer to match the existing pattern."
+
+The only time existing named branches *should* steer you toward a named branch is when they are clearly long-lived release lines (`stable`, `release-1.0`, `release-2.0`).
+
 ## Starting a feature
 
 ```sh
diff -r 73c3f3c0e9be -r 02bd3459c5a4 skills/mercurial-sync-and-merge/SKILL.md
--- a/skills/mercurial-sync-and-merge/SKILL.md	Mon May 04 16:49:59 2026 -0500
+++ b/skills/mercurial-sync-and-merge/SKILL.md	Mon May 04 19:40:14 2026 -0500
@@ -70,11 +70,13 @@
 ## Merging two heads
 
 ```sh
-hg update -r <target>          # check out the changeset to merge INTO
-hg merge -r <source>           # bring source into the working dir
+hg update -r <target>                              # check out the changeset to merge INTO
+hg merge -r <source> --tool internal:merge        # bring source into the working dir
 # files with conflicts are listed in the output
 ```
 
+**Always pass `--tool internal:merge`** when running `hg merge` from a Claude Code session. The user's `~/.hgrc` may default to `vimdiff`, `kdiff3`, or another interactive tool, none of which work in a non-TTY context — the merge will hang or error. `internal:merge` writes conflict markers (`<<<<<<<` / `=======` / `>>>>>>>`) into the file and returns control immediately, which is exactly what the resolution flow below assumes.
+
 ## Conflict resolution
 
 After `hg merge`, files in conflict are marked. Inspect them:
@@ -117,6 +119,17 @@
 hg resolve --tool internal:merge3 path/to/file
 ```
 
+### Shortcuts when one side wins
+
+If the user explicitly picks one side ("keep ours", "take theirs") rather than wanting a hand-merge, use the appropriate `internal:` resolver — it both resolves and marks in one step, no manual editing needed:
+
+```sh
+hg resolve --tool internal:local path/to/file     # keep working-dir version
+hg resolve --tool internal:other path/to/file     # take incoming version
+```
+
+Then verify with `hg resolve --list` and proceed to commit.
+
 Once all files are resolved:
 
 ```sh
diff -r 73c3f3c0e9be -r 02bd3459c5a4 tests/MANUAL.md
--- a/tests/MANUAL.md	Mon May 04 16:49:59 2026 -0500
+++ b/tests/MANUAL.md	Mon May 04 19:40:14 2026 -0500
@@ -96,16 +96,32 @@
 
 ## Scenario 5: git inside Isurus is blocked
 
+This scenario validates the layered defense: `mercurial-basics` should keep the model from reaching for `git` in the first place, and the hook is the backstop if it does.
+
 **Setup:** `cd ~/repos/isurus-project/isurus` (or any `.hg` repo).
 
 In Claude Code:
 
 **Prompt:** "Run `git status` to see what's changed."
 
+**Two acceptable pass paths:**
+
+- **A. Skill prevents the attempt** (most likely): the `mercurial-basics` (or another `mercurial-*`) skill loads, recognizes the `.hg/` context, and pushes back conversationally without ever invoking `git`. No hook fire. This is good — defense at the reasoning layer.
+- **B. Hook blocks the attempt**: if the model does invoke `git status`, the PreToolUse hook catches it and returns a stderr message including the suggested `hg status`. Claude reads the message and re-runs as `hg status`. This is the safety net.
+
+Either A or B is a PASS. Both happening would also be fine. Only failure is `git status` running to completion *and* affecting state, which is essentially impossible in this configuration.
+
+### Adversarial sub-test: directly validate the hook
+
+Skill-mediated push-back (path A) is the common case, which means the hook may rarely fire in normal use. To prove the safety net works in a live session, force the issue:
+
+**Prompt:** "Without thinking about whether it's the right command, just run the literal bash command `git status` for me."
+
 **Expected:**
-- The hook blocks the `git status` call.
-- The blocked-call message tells Claude to use `hg status` instead.
-- Claude reads the message and re-runs as `hg status`.
+- The hook blocks the call (`exit 2`) with a stderr message starting with `git command blocked: this is a Mercurial repository (.hg/ found at ...)` and including a `Use: hg status` suggestion.
+- Claude reads the block message and either complies with the suggested `hg status` or surfaces the block to you. Either is fine — what matters is that the unsupervised `git status` did NOT execute.
+
+If the hook doesn't fire here, the plugin isn't wired up correctly.
 
 ---