|
root / docs / superpowers / specs / 2026-05-01-isurus-hg-plugin-design.md
2026-05-01-isurus-hg-plugin-design.md markdown 331 lines 18.9 KB

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

  1. Teach Claude to do common Mercurial workflows correctly in any hg repo.
  2. 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.
  3. Mechanically prevent the most common failure mode: reaching for git commands inside an hg repo.
  4. 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 + topics workflow support — not enabled in Isurus today.
  • Slash commands for common workflows — easy to add once recurring patterns are visible.
  • Subagents (e.g. an hg-operator for 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, SECURITY files — 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 specific hg commands 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-branch references the other skills it relies on rather than duplicating their content.
  • isurus-conventions is 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

  1. Read tool-call JSON from stdin; extract tool_input.command and cwd.
  2. Normalize the command: trim leading whitespace; skip leading VAR=value env assignments.
  3. If the first token is not git, exit 0 (allow).
  4. Determine VCS context for cwd:
    • Bare git repo check: if cwd itself contains HEAD, refs/, and objects/ (the bare-repo signature), treat as a git context → exit 0 (allow).
    • Otherwise walk upward from cwd looking 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).

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 init are only inspected at the leading token; if intent is to run git in a different directory, prefix with cd and the hook won't block (intended escape valve).
  • Subshells / pipes (git status | grep modified): leading token is still git, 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:

  1. What it does — one paragraph + bulleted list of the 6 skills and the hook.
  2. Install--plugin-dir flag 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.
  3. Hooks behavior — one paragraph explaining the git-blocking hook so a user understands why their git status got 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:

  1. 01-git-in-hg-blocks.shgit status inside an .hg repo blocks with exit 2 and includes the hg status suggestion.
  2. 02-git-outside-repo-allows.shgit --version in /tmp (no .hg or .git ancestor) exits 0.
  3. 03-git-inside-nested-git-allows.shgit log inside a regular .git working dir nested under an .hg directory exits 0 (closer .git wins). Also covers the bare-repo case (HEAD + refs/ + objects/ present in cwd) inside an .hg ancestor.
  4. 04-hg-command-allows.shhg status inside an .hg repo exits 0 (not a git command).
  5. 05-env-var-prefix-stripped.shFOO=bar git status inside an .hg repo blocks (env prefix correctly skipped).
  6. 06-non-git-command-allows.shls inside an .hg repo exits 0.
  7. 07-cd-then-git-allows.shcd /tmp && git init inside an .hg repo 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 hg examples and runs them in --dry-run mode 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-basics activates, no -u flag used, message format follows the rules.
  • Isurus repo: ask Claude to start a feature → verify mercurial-bookmarks-and-prs and isurus-conventions both 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-merge activates with correct hg resolve flow.
  • History rewrite request on public changeset: ask Claude to amend a public commit → verify mercurial-history-rewriting refuses and explains why.
  • git in Isurus: run git status in the Isurus repo → verify hook blocks with the right suggestion.
  • git in testdata: run git log inside internal/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.md files exist with valid frontmatter and pass scripts/check-skills.sh.
  • All 7 hook test cases pass hooks/test/run.sh.
  • All 6 manual scenarios in tests/MANUAL.md pass when walked through against the real Isurus repo.
  • A second hg repo (any non-Isurus hg repo, even a throwaway ~/scratch-hg) demonstrates that isurus-conventions does 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

  1. Day 1: install locally, run hook test suite, walk the 6 manual scenarios. Fix anything obviously broken.
  2. 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.
  3. Week 2: re-walk the manual scenarios. If they all pass clean and Week 1 produced fewer than ~3 issues, tag 0.2.0.
  4. 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)

  1. pip install hg-git (or use OS package).
  2. Add to ~/.hgrc: hggit = under [extensions].
  3. Create a bare git repo to serve as the mirror.
  4. Add to the plugin repo's .hg/hgrc: a [paths] entry like git-mirror = git+ssh://....
  5. hg push git-mirror after each release tag — manually at first, automated via a commit hook later if it sticks.
  6. The git mirror's marketplace.json entry 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.0 etc. 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.