# 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`: ```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 ` | `hg update ` | | `pull` | `hg pull -u` | | `push` | `hg push -B ` | | `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` ```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.sh` — `git status` inside an `.hg` repo blocks with exit 2 and includes the `hg status` suggestion. 2. `02-git-outside-repo-allows.sh` — `git --version` in `/tmp` (no `.hg` or `.git` ancestor) exits 0. 3. `03-git-inside-nested-git-allows.sh` — `git 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.sh` — `hg status` inside an `.hg` repo exits 0 (not a git command). 5. `05-env-var-prefix-stripped.sh` — `FOO=bar git status` inside an `.hg` repo blocks (env prefix correctly skipped). 6. `06-non-git-command-allows.sh` — `ls` inside an `.hg` repo exits 0. 7. `07-cd-then-git-allows.sh` — `cd /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):** ```sh 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: `/.claude-plugin/marketplace.json`): ```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.