# 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 <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`

```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** — local path entry for now (`~/.claude/settings.json` plugin path), with a "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)

Until the plugin is published, install via a path entry in `~/.claude/settings.json`:

```json
{
  "plugins": {
    "isurus-hg": {
      "path": "/home/ctusa/repos/isurus-project/isurus-claude-plugin"
    }
  }
}
```

Restart Claude Code. The 6 skills become available; the hook fires on `Bash` tool calls.

For other Leafscale devs (once dogfooding is solid): same path-entry pattern with their checkout location, or — when the git mirror is set up — install via the marketplace mechanism.

### 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.
