isurus-hg plugin — design
Date: 2026-05-01
Status: approved (pending user review of this document)
Authors: Chris Tusa, with Claude (brainstorming session)
Context
Isurus (Leafscale's Mercurial forge product) is developed in Mercurial 7.x. Claude Code's general competence covers git workflows well but is uneven on hg — bookmarks, named branches, phases, and Isurus's house conventions in particular are easy to get wrong without explicit guidance. The Isurus repo's CLAUDE.md already says "never use git commands," but that instruction is enforced only by the model reading and remembering it; in practice, git status slips out occasionally during long sessions.
This plugin makes Claude fluent in Mercurial (with an Isurus-specific overlay) and adds a hook that prevents accidental git commands inside .hg repos.
Goals
- Teach Claude to do common Mercurial workflows correctly in any hg repo.
- Encode Isurus's house conventions (non-publishing repo, bookmarks-as-PRs, commit message style, pre-PR Makefile checklist) so Claude follows them automatically when working in Isurus.
- Mechanically prevent the most common failure mode: reaching for
gitcommands inside an hg repo. - Keep the generic Mercurial knowledge separable from the Isurus-specific overlay so the plugin is useful for any hg user (and is publishable externally if/when desired).
Non-goals (deferred to phase 2 or beyond)
- Isurus forge integration (PRs, code review, CI, issues) via MCP — would benefit from a stable skill foundation first.
evolve+topicsworkflow support — not enabled in Isurus today.- Slash commands for common workflows — easy to add once recurring patterns are visible.
- Subagents (e.g. an
hg-operatorfor risky multi-step operations) — complexity unjustified until skills prove insufficient. - Formal eval framework via
skill-creator— manual scenarios are sufficient until real drift appears. - CI for the plugin itself — solo project; on-demand shell tests are enough.
CHANGELOG,CONTRIBUTING,SECURITYfiles — YAGNI for solo development.
Architecture
A standard Claude Code plugin layout. Six skills (5 generic + 1 Isurus overlay), one PreToolUse hook scoped to Bash, and a manifest. No build step, no runtime dependencies beyond shell tools and Mercurial 7.x.
File layout
.claude-plugin/
plugin.json
hooks/
hooks.json
block-git-in-hg.sh
test/
run.sh
cases/
01-git-in-hg-blocks.sh
02-git-outside-repo-allows.sh
03-git-inside-nested-git-allows.sh
04-hg-command-allows.sh
05-env-var-prefix-stripped.sh
06-non-git-command-allows.sh
07-cd-then-git-allows.sh
skills/
mercurial-basics/SKILL.md
mercurial-bookmarks-and-prs/SKILL.md
mercurial-sync-and-merge/SKILL.md
mercurial-history-rewriting/SKILL.md
mercurial-finishing-a-branch/SKILL.md
isurus-conventions/SKILL.md
scripts/
check-skills.sh
tests/
MANUAL.md
docs/
superpowers/
specs/
2026-05-01-isurus-hg-plugin-design.md # this document
README.md
.hgignore
.gitignore
Repository hosting
The plugin lives in Mercurial (eating Isurus's own dogfood) with a git mirror via the hg-git extension for Claude Code marketplace consumption. The hg side is canonical; the git side is generated mechanically from hg history. .hgignore excludes .git/ and .gitignore excludes .hg/ so the two metadata directories don't fight.
Components
Skills
Each skill is a single SKILL.md with frontmatter (name, description) followed by instructions. The description is the activation trigger; descriptions are concrete and scoped so skills load only when their domain is in play and don't pollute non-Mercurial work.
Each skill body opens with a short "When this applies / when it doesn't" paragraph to prevent misuse.
Generic Mercurial skills (5)
| Skill | Activation trigger (frontmatter description) | Body covers |
|---|---|---|
| mercurial-basics | "Use when working in any Mercurial repository for inspection or local commits — hg status, diff, log, add, commit, parents, heads, branches, bookmarks, phase. Triggers on any hg ... command or when .hg/ is in the working directory." |
Core inspection commands; commit hygiene including no -u flag (identity from ~/.hgrc); commit message format (imperative summary <72 chars, body explains why, "category: summary" prefix); .hgignore syntax; reading hg log output; phase awareness as a foundational concept. |
| mercurial-bookmarks-and-prs | "Use when starting feature work, switching between in-progress work, or pushing a feature for review in a Mercurial repo. Bookmarks are the unit of feature work and the unit of pull requests in this workflow." | Create/list/delete bookmarks; bookmark vs named branch (when to use which); hg push -B name; switching active bookmarks; what happens to a bookmark when you commit; deleting after merge; recovering when a bookmark is in the wrong place. |
| mercurial-sync-and-merge | "Use when pulling changes from a remote, updating to a different revision, dealing with multiple heads, or resolving merge conflicts in a Mercurial repo." | pull, pull -u, update, what creates multiple heads, how to merge them, hg merge, hg resolve (mark/unmark/list/--all), aborting a merge, fast-forward-style updates vs explicit merges. |
| mercurial-history-rewriting | "Use when rewriting Mercurial history — amending commits, uncommitting, splitting commits, or recovering from a mistake. Critical: only safe on draft changesets, never on public ones." | hg amend, hg uncommit, hg rollback (and why it's mostly deprecated), splitting a commit (hg uncommit + selective hg add), draft-vs-public phase rules, safe recovery patterns; explicit refusal to rewrite public history. Calls out absence of evolve/topics today and notes those would unlock more (deferred to phase 2). |
| mercurial-finishing-a-branch | "Use when an implementation is complete and ready to be proposed as a pull request — runs the pre-PR checklist (build, vet, format, test) and walks through pushing the bookmark and opening a PR. The Mercurial counterpart to superpowers' finishing-a-development-branch." |
End-to-end flow: confirm clean working dir, run project's pre-PR checks (detects a Makefile and uses make check / make fmt / make vet if present, else falls back to language defaults), squash/amend if commits are messy, hg push -B, instructions for opening the PR in the forge UI. References mercurial-history-rewriting for amend cleanup and isurus-conventions if active. |
Isurus overlay skill (1)
| Skill | Activation trigger | Body covers |
|---|---|---|
| isurus-conventions | "Use when working in the Isurus repository (or any repo identified as Isurus by the presence of Isurus in README.md or cmd/isurus/). Encodes Isurus-specific Mercurial conventions: non-publishing repo, bookmarks-as-PRs, commit message style, pre-PR Makefile checklist, opening PRs in the Isurus web UI." |
Conventions distilled from Isurus's CLAUDE.md and CONTRIBUTING.md: non-publishing means amend during review is safe; clone URL pattern (ssh://hg@hg.leafscale.com/...); commit message conventions used in this repo (category: summary); make check / make fmt / make vet are the pre-PR gates; PRs are opened in the Isurus web UI after hg push -B. Short — mostly facts, not procedures. The procedures live in the generic skills; this overlays the project-specific values. |
Skill design principles
- Activation precision: descriptions explicitly mention
.hg/presence or specifichgcommands so skills load only when actually working with Mercurial. - Each skill targets ~150–400 lines. Crossing 400 is a signal to spin out a sub-skill.
- Cross-references are explicit.
mercurial-finishing-a-branchreferences the other skills it relies on rather than duplicating their content. isurus-conventionsis small and additive — it doesn't repeat what the generic skills say; it supplies the project's specific values (URLs, command names, gate commands).
Hook: block-git-in-hg.sh
A PreToolUse hook scoped to the Bash tool. Configuration in hooks/hooks.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/block-git-in-hg.sh" }
]
}
]
}
}
Detection logic
- Read tool-call JSON from stdin; extract
tool_input.commandandcwd. - Normalize the command: trim leading whitespace; skip leading
VAR=valueenv assignments. - If the first token is not
git, exit 0 (allow). - Determine VCS context for
cwd:- Bare git repo check: if
cwditself containsHEAD,refs/, andobjects/(the bare-repo signature), treat as a git context → exit 0 (allow). - Otherwise walk upward from
cwdlooking for the closest.hg/or.git/:.git/found first (or only.git/exists) → exit 0 (allow). Real git repo, real git command..hg/found first → block (exit 2 with stderr message).- Neither found → exit 0 (allow). Outside any repo (e.g.
git --version).
- Bare git repo check: if
This rule handles Isurus's internal/import/testdata/*/repo.git/ fixtures via the bare-repo check: those directories are the git metadata (no .git/ subdirectory), so the bare-repo signature matches and git commands inside them pass through even though an .hg/ exists higher up the tree.
git → hg suggestion map
git |
suggested hg |
|---|---|
status |
hg status |
add |
hg add |
commit -m "..." |
hg commit -m "..." (no -u) |
log |
hg log |
diff |
hg diff |
branch |
hg bookmarks (in this workflow) |
checkout <name> |
hg update <name> |
pull |
hg pull -u |
push |
hg push -B <bookmark> |
blame |
hg annotate |
Anything not in the table gets a generic message: "This is a Mercurial repository. The mercurial-basics skill has the equivalent commands."
Sample blocked-call output
git command blocked: this is a Mercurial repository (.hg/ found at /home/ctusa/repos/isurus-project/isurus).
You ran: git status
Use: hg status
See the mercurial-basics skill for the full command map.
Acknowledged limitations (not addressed in v1)
- Compound commands like
cd /tmp && git initare only inspected at the leading token; if intent is to run git in a different directory, prefix withcdand the hook won't block (intended escape valve). - Subshells / pipes (
git status | grep modified): leading token is stillgit, will be blocked. Correct behavior. - Quoted commands inside
bash -c "...": not inspected. Acceptable hole; rare in practice. - No allowlist mechanism. Override by commenting out the hook entry. YAGNI on building an allowlist before there's a real use case.
Manifest: .claude-plugin/plugin.json
{
"name": "isurus-hg",
"version": "0.1.0",
"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",
"email": "chris.tusa@leafscale.com",
"url": "https://leafscale.com"
},
"homepage": "https://www.leafscale.com/products/isurus",
"repository": "https://leafscale.isurus.dev/isurus/isurus-claude-plugin",
"license": "MIT",
"keywords": ["mercurial", "hg", "isurus", "version-control"]
}
Version starts at 0.1.0 — pre-1.0 signals "stabilizing" and lets skill copy evolve without breaking semver promises.
README.md (root)
Three sections:
- What it does — one paragraph + bulleted list of the 6 skills and the hook.
- Install —
--plugin-dirflag for development/testing, plus a local-marketplace recipe for persistent use, with a "remote marketplace coming soon" note for after the git mirror is set up. - Hooks behavior — one paragraph explaining the git-blocking hook so a user understands why their
git statusgot rejected.
No CHANGELOG, CONTRIBUTING, or SECURITY files for v1.
Licensing
MIT. The plugin is mostly generic Mercurial knowledge plus an overlay describing how to use Isurus; making it freely redistributable supports adoption signal and onboarding for the Isurus forge, and avoids friction for outside contributors who don't have a Leafscale CLA.
Testing & validation
1. Hook script — automated shell tests
The hook is a pure function: JSON in, exit code + message out. Tests live under hooks/test/ and exercise the script in isolation, no Claude in the loop.
Each case file (hooks/test/cases/NN-description.sh) builds a temp directory with the right .hg/ or .git/ skeleton, pipes synthetic tool-call JSON through block-git-in-hg.sh, and asserts on exit code and stderr substring. ~10 lines per case. run.sh (~20 lines) iterates the cases and prints PASS/FAIL.
Initial cases:
01-git-in-hg-blocks.sh—git statusinside an.hgrepo blocks with exit 2 and includes thehg statussuggestion.02-git-outside-repo-allows.sh—git --versionin/tmp(no.hgor.gitancestor) exits 0.03-git-inside-nested-git-allows.sh—git loginside a regular.gitworking dir nested under an.hgdirectory exits 0 (closer.gitwins). Also covers the bare-repo case (HEAD+refs/+objects/present incwd) inside an.hgancestor.04-hg-command-allows.sh—hg statusinside an.hgrepo exits 0 (not a git command).05-env-var-prefix-stripped.sh—FOO=bar git statusinside an.hgrepo blocks (env prefix correctly skipped).06-non-git-command-allows.sh—lsinside an.hgrepo exits 0.07-cd-then-git-allows.sh—cd /tmp && git initinside an.hgrepo exits 0 (documents the escape-valve limitation).
No frameworks or dependencies beyond bash and mktemp.
2. Skill content — lint-style validation
scripts/check-skills.sh walks skills/*/SKILL.md and:
- Validates frontmatter parses and contains
name+description. - Warns if a file exceeds 400 lines.
- Greps for
[link](path)references and confirms the path exists. - Optionally extracts fenced
hgexamples and runs them in--dry-runmode where supported, to catch typos.
Shell + a small Python helper for YAML parsing. Run manually before commits.
3. Skill behavior — manual scenario checklist
tests/MANUAL.md lists scenarios to walk through after meaningful changes:
- Fresh empty hg repo: ask Claude to commit a file → verify
mercurial-basicsactivates, no-uflag used, message format follows the rules. - Isurus repo: ask Claude to start a feature → verify
mercurial-bookmarks-and-prsandisurus-conventionsboth load, bookmark created, no named-branch confusion. - Mid-stream conflict: set up two heads in a scratch repo, ask Claude to merge → verify
mercurial-sync-and-mergeactivates with correcthg resolveflow. - History rewrite request on public changeset: ask Claude to amend a public commit → verify
mercurial-history-rewritingrefuses and explains why. - git in Isurus: run
git statusin the Isurus repo → verify hook blocks with the right suggestion. - git in testdata: run
git loginsideinternal/import/testdata/foo/repo.git/→ verify hook allows (closer.git/).
Each scenario is one paragraph. ~6 scenarios for v1.
Acceptance criteria for v1
- All 6
SKILL.mdfiles exist with valid frontmatter and passscripts/check-skills.sh. - All 7 hook test cases pass
hooks/test/run.sh. - All 6 manual scenarios in
tests/MANUAL.mdpass when walked through against the real Isurus repo. - A second hg repo (any non-Isurus hg repo, even a throwaway
~/scratch-hg) demonstrates thatisurus-conventionsdoes not activate but the generic skills do.
Rollout
Install for v1 (local development)
Correction (post-v0.1.0): earlier revisions of this spec described an
~/.claude/settings.json"plugins"block as the install mechanism. That key is not a documented Claude Code feature and is silently ignored. The verified mechanisms below replace it.
Two supported mechanisms.
1. Session-only (recommended for development and the manual-scenarios walk-through):
claude --plugin-dir /home/ctusa/repos/isurus-project/isurus-claude-plugin
Loads the plugin for that session. Repeat the flag to load multiple plugins. Takes precedence over installed marketplace plugins.
2. Persistent (recommended once dogfooding is stable): create a local marketplace.
Place a marketplace.json in any parent directory of the plugin checkout (suggested: <parent>/.claude-plugin/marketplace.json):
{
"name": "local-isurus",
"owner": { "name": "Chris Tusa" },
"plugins": [
{
"name": "isurus-hg",
"source": "./isurus-claude-plugin",
"description": "Mercurial fluency for Claude Code, with an Isurus-conventions overlay."
}
]
}
Then from inside Claude Code:
/plugin marketplace add /home/ctusa/repos/isurus-project
/plugin install isurus-hg@local-isurus
Either mechanism activates the 6 skills and registers the hook on Bash tool calls.
For other Leafscale devs (once dogfooding is solid): use Option 2 with their own checkout path, or — when the git mirror is set up — point a marketplace at the published git URL instead of a local path.
Initial dogfooding plan
- Day 1: install locally, run hook test suite, walk the 6 manual scenarios. Fix anything obviously broken.
- Week 1: use it in real Isurus work. Note any moment where Claude reaches for the wrong skill, misses a convention, or the hook fires when it shouldn't. Each note → either a skill tweak or a hook test case.
- Week 2: re-walk the manual scenarios. If they all pass clean and Week 1 produced fewer than ~3 issues, tag
0.2.0. - Decision point: continue dogfooding solo, or invite one other Leafscale dev to install via path entry and report.
Git mirror setup (post-v1, before marketplace publication)
pip install hg-git(or use OS package).- Add to
~/.hgrc:hggit =under[extensions]. - Create a bare git repo to serve as the mirror.
- Add to the plugin repo's
.hg/hgrc: a[paths]entry likegit-mirror = git+ssh://.... hg push git-mirrorafter each release tag — manually at first, automated via a commit hook later if it sticks.- The git mirror's
marketplace.jsonentry can then be added to a Claude Code marketplace.
Versioning policy
- 0.x: skill copy and hook behavior may change without notice between versions. Dogfooding phase.
- 1.0.0: declared once skills have been stable for two consecutive weeks of real use with no edits.
- After 1.0: breaking changes (renamed skills, changed hook behavior) bump the major; new skills or expanded coverage bump the minor; copy fixes and bugfixes bump the patch.
- Tags as
hg tag v0.1.0etc. Once the git mirror is up, tags propagate automatically.
Open questions
None blocking. Everything not decided in this spec is explicitly deferred to phase 2 (see Non-goals) and will be revisited based on dogfooding outcomes.