# isurus-hg Plugin Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Build the `isurus-hg` Claude Code plugin v1: 5 generic Mercurial skills, an Isurus-conventions overlay skill, and a PreToolUse hook that blocks `git` commands inside `.hg` repositories. **Architecture:** A standard Claude Code plugin layout (`.claude-plugin/`, `hooks/`, `skills/`). The hook is a single shell script driven by `jq`-parsed JSON from the tool call. Each skill is a self-contained `SKILL.md` with strict frontmatter (`name`, `description`) plus a body. A small shell lint script validates skill structure before commit. Manual scenarios cover end-to-end behavior because skill activation can't be unit-tested without Claude in the loop. **Tech Stack:** Bash 4+, `jq` (JSON parsing in the hook), Mercurial 7.x, GNU coreutils. No language runtimes, no build step. Source repo is Mercurial; a future `hg-git` mirror is out of scope for v1. **Important:** This plugin is developed in **Mercurial**, not git. Every commit step in this plan uses `hg`. If you instinctively type `git`, stop and use `hg` — that's exactly the failure mode this plugin is designed to prevent. **Reference spec:** `docs/superpowers/specs/2026-05-01-isurus-hg-plugin-design.md` --- ## Task 1: Plugin manifest Establishes the plugin identity. Required by Claude Code to recognize the directory as a plugin. **Files:** - Create: `.claude-plugin/plugin.json` - [ ] **Step 1: Create the manifest directory and file** ```bash mkdir -p .claude-plugin ``` Then create `.claude-plugin/plugin.json` with this exact content: ```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"] } ``` - [ ] **Step 2: Validate the JSON parses** Run: ```bash jq . .claude-plugin/plugin.json ``` Expected: the JSON is echoed back, formatted, with no parse errors. - [ ] **Step 3: Stage and commit** ```bash hg add .claude-plugin/plugin.json hg commit -m "feat: add plugin manifest" ``` Verify with `hg log -l 1` that the commit landed. --- ## Task 2: Skill content lint script Validates `SKILL.md` files have correct frontmatter and stay under the 400-line target. Built before any skills so each skill task can run it as a check. **Files:** - Create: `scripts/check-skills.sh` - [ ] **Step 1: Create the scripts directory and the lint script** ```bash mkdir -p scripts ``` Create `scripts/check-skills.sh` with this exact content: ```bash #!/usr/bin/env bash # Lint SKILL.md files: validates frontmatter and length. # Exit 0 = all good. Exit 1 = at least one error. set -u errors=0 warnings=0 shopt -s nullglob for skill_file in skills/*/SKILL.md; do skill_dir=$(dirname "$skill_file") skill_name=$(basename "$skill_dir") # Frontmatter must start on line 1 with --- first_line=$(head -n 1 "$skill_file") if [[ "$first_line" != "---" ]]; then echo "ERROR: $skill_file does not start with frontmatter ('---' on line 1)" errors=$((errors + 1)) continue fi # Frontmatter must contain 'name:' and 'description:' before the closing --- frontmatter=$(awk '/^---$/{c++; if (c==2) exit} c==1' "$skill_file") if ! grep -q '^name:' <<<"$frontmatter"; then echo "ERROR: $skill_file frontmatter missing 'name:' field" errors=$((errors + 1)) fi if ! grep -q '^description:' <<<"$frontmatter"; then echo "ERROR: $skill_file frontmatter missing 'description:' field" errors=$((errors + 1)) fi # Frontmatter 'name' should match directory name name_value=$(grep '^name:' <<<"$frontmatter" | head -n 1 | sed 's/^name:[[:space:]]*//') if [[ -n "$name_value" && "$name_value" != "$skill_name" ]]; then echo "ERROR: $skill_file name '$name_value' does not match directory '$skill_name'" errors=$((errors + 1)) fi # Length warning at 400 lines line_count=$(wc -l <"$skill_file") if (( line_count > 400 )); then echo "WARN: $skill_file is $line_count lines (target <400)" warnings=$((warnings + 1)) fi done # Summary echo echo "Skill lint complete: $errors error(s), $warnings warning(s)" if (( errors > 0 )); then exit 1 fi exit 0 ``` - [ ] **Step 2: Make it executable** ```bash chmod +x scripts/check-skills.sh ``` - [ ] **Step 3: Run it on the empty skills directory to verify it works** Run: ```bash ./scripts/check-skills.sh ``` Expected output (exact): ``` Skill lint complete: 0 error(s), 0 warning(s) ``` Exit code: 0. (No skills exist yet — the script's loop body is skipped.) - [ ] **Step 4: Stage and commit** ```bash hg add scripts/check-skills.sh hg commit -m "feat: add skill content lint script" ``` --- ## Task 3: Hook test framework Builds the test runner that will exercise the hook in isolation. Runner only — test cases come in tasks 4 and 6. **Files:** - Create: `hooks/test/run.sh` - [ ] **Step 1: Create the directory and the runner** ```bash mkdir -p hooks/test/cases ``` Create `hooks/test/run.sh` with this exact content: ```bash #!/usr/bin/env bash # Runs all hook test cases. Each case is a self-contained shell script # that exits 0 on PASS, non-zero on FAIL. # # Cases live in hooks/test/cases/*.sh, are run alphabetically, and # can use the helper functions/env defined here. set -u # Resolve repo root regardless of where this is invoked from. script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) repo_root=$(cd "$script_dir/../.." && pwd) HOOK_SCRIPT="$repo_root/hooks/block-git-in-hg.sh" export HOOK_SCRIPT if [[ ! -x "$HOOK_SCRIPT" ]]; then echo "FATAL: hook script not found or not executable: $HOOK_SCRIPT" exit 2 fi passed=0 failed=0 failed_cases=() shopt -s nullglob for case_file in "$script_dir"/cases/*.sh; do case_name=$(basename "$case_file" .sh) if bash "$case_file" >/tmp/hook_test_out.$$ 2>&1; then echo "PASS $case_name" passed=$((passed + 1)) else echo "FAIL $case_name" sed 's/^/ | /' /tmp/hook_test_out.$$ failed=$((failed + 1)) failed_cases+=("$case_name") fi rm -f /tmp/hook_test_out.$$ done echo echo "Result: $passed passed, $failed failed" if (( failed > 0 )); then printf ' failed: %s\n' "${failed_cases[@]}" exit 1 fi exit 0 ``` - [ ] **Step 2: Make it executable** ```bash chmod +x hooks/test/run.sh ``` - [ ] **Step 3: Run it against the empty cases directory** Run: ```bash ./hooks/test/run.sh ``` Expected: exits with code 2 because the hook script doesn't exist yet: ``` FATAL: hook script not found or not executable: .../hooks/block-git-in-hg.sh ``` This is correct — the runner is wired up; the hook gets created in Task 5. - [ ] **Step 4: Stage and commit** ```bash hg add hooks/test/run.sh hg commit -m "feat: add hook test framework" ``` --- ## Task 4: Hook test cases — first batch (3 cases) Writes the simplest behavior tests first. These will fail until Task 5 implements the hook. **Files:** - Create: `hooks/test/cases/01-git-in-hg-blocks.sh` - Create: `hooks/test/cases/02-git-outside-repo-allows.sh` - Create: `hooks/test/cases/06-non-git-command-allows.sh` - [ ] **Step 1: Write case 01 — git inside an .hg repo blocks** Create `hooks/test/cases/01-git-in-hg-blocks.sh`: ```bash #!/usr/bin/env bash # Case 01: 'git status' inside an .hg repository must be blocked. set -u tmp=$(mktemp -d) trap 'rm -rf "$tmp"' EXIT mkdir -p "$tmp/repo/.hg" mkdir -p "$tmp/repo/sub" input=$(jq -n --arg cwd "$tmp/repo/sub" --arg cmd "git status" '{ cwd: $cwd, hook_event_name: "PreToolUse", tool_name: "Bash", tool_input: { command: $cmd } }') # Run hook; capture stderr and exit code. err=$(echo "$input" | "$HOOK_SCRIPT" 2>&1 >/dev/null) rc=$? if [[ $rc -ne 2 ]]; then echo "expected exit 2 (blocked), got $rc" echo "stderr was: $err" exit 1 fi if ! grep -q "hg status" <<<"$err"; then echo "expected stderr to suggest 'hg status', got: $err" exit 1 fi ``` - [ ] **Step 2: Write case 02 — git outside any repo allows** Create `hooks/test/cases/02-git-outside-repo-allows.sh`: ```bash #!/usr/bin/env bash # Case 02: 'git --version' outside any .hg/.git context must be allowed. set -u tmp=$(mktemp -d) trap 'rm -rf "$tmp"' EXIT input=$(jq -n --arg cwd "$tmp" --arg cmd "git --version" '{ cwd: $cwd, hook_event_name: "PreToolUse", tool_name: "Bash", tool_input: { command: $cmd } }') if ! echo "$input" | "$HOOK_SCRIPT" >/dev/null 2>&1; then echo "expected exit 0 (allow), got non-zero" exit 1 fi ``` - [ ] **Step 3: Write case 06 — non-git command in .hg repo allows** Create `hooks/test/cases/06-non-git-command-allows.sh`: ```bash #!/usr/bin/env bash # Case 06: 'ls -la' inside an .hg repo must be allowed (not a git command). set -u tmp=$(mktemp -d) trap 'rm -rf "$tmp"' EXIT mkdir -p "$tmp/repo/.hg" input=$(jq -n --arg cwd "$tmp/repo" --arg cmd "ls -la" '{ cwd: $cwd, hook_event_name: "PreToolUse", tool_name: "Bash", tool_input: { command: $cmd } }') if ! echo "$input" | "$HOOK_SCRIPT" >/dev/null 2>&1; then echo "expected exit 0 (allow), got non-zero" exit 1 fi ``` - [ ] **Step 4: Stage and commit (tests fail until Task 5)** ```bash hg add hooks/test/cases/01-git-in-hg-blocks.sh \ hooks/test/cases/02-git-outside-repo-allows.sh \ hooks/test/cases/06-non-git-command-allows.sh hg commit -m "test: hook cases — basic block/allow behavior" ``` --- ## Task 5: Hook implementation v1 (passes first batch) Implements just enough of `block-git-in-hg.sh` to pass cases 01, 02, 06. **Files:** - Create: `hooks/block-git-in-hg.sh` - [ ] **Step 1: Create the hook script** Create `hooks/block-git-in-hg.sh` with this exact content: ```bash #!/usr/bin/env bash # PreToolUse hook: blocks `git` invocations when the working directory # is inside a Mercurial (.hg) repository. # # Reads tool-call JSON from stdin, exits 0 to allow, exits 2 with a # message on stderr to block. set -u # Fail open if jq is missing — better to allow a command than to silently # block all bash for a missing dependency. if ! command -v jq >/dev/null 2>&1; then echo "isurus-hg hook: 'jq' not found; allowing command. Install jq to enable git-blocking." >&2 exit 0 fi input=$(cat) cmd=$(jq -r '.tool_input.command // ""' <<<"$input") cwd=$(jq -r '.cwd // ""' <<<"$input") # Allow if no command extracted. if [[ -z "$cmd" ]]; then exit 0 fi # Strip leading whitespace and any leading VAR=value env assignments. trimmed=$(sed -E 's/^[[:space:]]+//' <<<"$cmd") while [[ "$trimmed" =~ ^[A-Za-z_][A-Za-z0-9_]*=[^[:space:]]*[[:space:]]+ ]]; do trimmed=$(sed -E 's/^[A-Za-z_][A-Za-z0-9_]*=[^[:space:]]*[[:space:]]+//' <<<"$trimmed") done # Extract the leading word. first=$(awk '{print $1}' <<<"$trimmed") if [[ "$first" != "git" ]]; then exit 0 fi # Find VCS context for cwd: the closest .hg or .git ancestor. # If there's no usable cwd, allow. if [[ -z "$cwd" || ! -d "$cwd" ]]; then exit 0 fi dir=$cwd hg_root="" git_root="" while [[ "$dir" != "/" && -n "$dir" ]]; do if [[ -z "$hg_root" && -d "$dir/.hg" ]]; then hg_root=$dir fi if [[ -z "$git_root" && -d "$dir/.git" ]]; then git_root=$dir fi if [[ -n "$hg_root" || -n "$git_root" ]]; then break fi dir=$(dirname "$dir") done # If a .git was found anywhere up the walk, allow. The loop breaks at # the first iteration where either is found, so a non-empty git_root means # .git is at the same level as .hg or closer — either way, the user is in # a git context. if [[ -n "$git_root" ]]; then exit 0 fi # No .hg ancestor => allow (e.g. plain `git --version` outside any repo). if [[ -z "$hg_root" ]]; then exit 0 fi # We are inside an .hg repo and the command starts with `git`. Block. # Build a suggestion based on the next word. sub=$(awk '{print $2}' <<<"$trimmed") case "$sub" in status) suggest="hg status" ;; add) suggest="hg add" ;; commit) suggest="hg commit -m \"...\" (no -u; identity comes from ~/.hgrc)" ;; log) suggest="hg log" ;; diff) suggest="hg diff" ;; branch) suggest="hg bookmarks (in this workflow, bookmarks are feature branches)" ;; checkout) suggest="hg update " ;; pull) suggest="hg pull -u" ;; push) suggest="hg push -B " ;; blame) suggest="hg annotate" ;; *) suggest="See the mercurial-basics skill for the full command map." ;; esac cat >&2 </dev/null 2>&1; then echo "sub-case A (nested .git) expected exit 0, got non-zero" exit 1 fi # Sub-case B: bare git repo (no .git subdir) inside .hg ancestor mkdir -p "$tmp/outer/testdata/repo.git/refs" mkdir -p "$tmp/outer/testdata/repo.git/objects" touch "$tmp/outer/testdata/repo.git/HEAD" input=$(jq -n --arg cwd "$tmp/outer/testdata/repo.git" --arg cmd "git log" '{ cwd: $cwd, hook_event_name: "PreToolUse", tool_name: "Bash", tool_input: { command: $cmd } }') if ! echo "$input" | "$HOOK_SCRIPT" >/dev/null 2>&1; then echo "sub-case B (bare repo) expected exit 0, got non-zero" exit 1 fi ``` - [ ] **Step 2: Write case 04 — hg command in .hg repo allows** Create `hooks/test/cases/04-hg-command-allows.sh`: ```bash #!/usr/bin/env bash # Case 04: 'hg status' inside an .hg repo must be allowed (only git is blocked). set -u tmp=$(mktemp -d) trap 'rm -rf "$tmp"' EXIT mkdir -p "$tmp/repo/.hg" input=$(jq -n --arg cwd "$tmp/repo" --arg cmd "hg status" '{ cwd: $cwd, hook_event_name: "PreToolUse", tool_name: "Bash", tool_input: { command: $cmd } }') if ! echo "$input" | "$HOOK_SCRIPT" >/dev/null 2>&1; then echo "expected exit 0 (allow), got non-zero" exit 1 fi ``` - [ ] **Step 3: Write case 05 — env var prefix stripped before detection** Create `hooks/test/cases/05-env-var-prefix-stripped.sh`: ```bash #!/usr/bin/env bash # Case 05: 'FOO=bar git status' inside an .hg repo must be blocked # (env-var prefix correctly skipped to find the real leading command). set -u tmp=$(mktemp -d) trap 'rm -rf "$tmp"' EXIT mkdir -p "$tmp/repo/.hg" input=$(jq -n --arg cwd "$tmp/repo" --arg cmd "FOO=bar GIT_PAGER=cat git status" '{ cwd: $cwd, hook_event_name: "PreToolUse", tool_name: "Bash", tool_input: { command: $cmd } }') err=$(echo "$input" | "$HOOK_SCRIPT" 2>&1 >/dev/null) rc=$? if [[ $rc -ne 2 ]]; then echo "expected exit 2 (blocked), got $rc" echo "stderr: $err" exit 1 fi ``` - [ ] **Step 4: Write case 07 — cd-then-git allows (documents escape valve)** Create `hooks/test/cases/07-cd-then-git-allows.sh`: ```bash #!/usr/bin/env bash # Case 07: 'cd /tmp && git init' inside an .hg repo must be allowed # (only the leading token is inspected — this is the documented escape valve). set -u tmp=$(mktemp -d) trap 'rm -rf "$tmp"' EXIT mkdir -p "$tmp/repo/.hg" input=$(jq -n --arg cwd "$tmp/repo" --arg cmd "cd /tmp && git init" '{ cwd: $cwd, hook_event_name: "PreToolUse", tool_name: "Bash", tool_input: { command: $cmd } }') if ! echo "$input" | "$HOOK_SCRIPT" >/dev/null 2>&1; then echo "expected exit 0 (allow), got non-zero" exit 1 fi ``` - [ ] **Step 5: Run the test suite — case 03 should fail** ```bash ./hooks/test/run.sh ``` Expected: cases 04, 05, 07 PASS, case 03 FAILs on the bare-repo sub-case (the v1 hook doesn't yet check for the bare-repo signature). Output similar to: ``` PASS 01-git-in-hg-blocks PASS 02-git-outside-repo-allows FAIL 03-git-inside-nested-git-allows | sub-case B (bare repo) expected exit 0, got non-zero PASS 04-hg-command-allows PASS 05-env-var-prefix-stripped PASS 06-non-git-command-allows PASS 07-cd-then-git-allows Result: 6 passed, 1 failed ``` This is expected — Task 7 fixes it. - [ ] **Step 6: Stage and commit** ```bash hg add hooks/test/cases/03-git-inside-nested-git-allows.sh \ hooks/test/cases/04-hg-command-allows.sh \ hooks/test/cases/05-env-var-prefix-stripped.sh \ hooks/test/cases/07-cd-then-git-allows.sh hg commit -m "test: hook cases — bare repos, env prefix, cd escape valve" ``` --- ## Task 7: Hook implementation v2 (bare-repo support) Adds the bare-repo signature check so case 03 sub-case B passes. **Files:** - Modify: `hooks/block-git-in-hg.sh` - [ ] **Step 1: Add the bare-repo check to the hook** Edit `hooks/block-git-in-hg.sh`. Find this block: ```bash # Find VCS context for cwd: the closest .hg or .git ancestor. # If there's no usable cwd, allow. if [[ -z "$cwd" || ! -d "$cwd" ]]; then exit 0 fi dir=$cwd ``` Replace it with: ```bash # Find VCS context for cwd: the closest .hg or .git ancestor. # If there's no usable cwd, allow. if [[ -z "$cwd" || ! -d "$cwd" ]]; then exit 0 fi # Bare git repo check: cwd contains HEAD + refs/ + objects/ at the top level # (the directory itself IS the git metadata; there's no .git subdir). if [[ -e "$cwd/HEAD" && -d "$cwd/refs" && -d "$cwd/objects" ]]; then exit 0 fi dir=$cwd ``` - [ ] **Step 2: Run the full test suite — all 7 must pass** ```bash ./hooks/test/run.sh ``` Expected output: ``` PASS 01-git-in-hg-blocks PASS 02-git-outside-repo-allows PASS 03-git-inside-nested-git-allows PASS 04-hg-command-allows PASS 05-env-var-prefix-stripped PASS 06-non-git-command-allows PASS 07-cd-then-git-allows Result: 7 passed, 0 failed ``` Exit code: 0. - [ ] **Step 3: Stage and commit** ```bash hg commit -m "feat: hook detects bare git repos to allow testdata fixtures" ``` (`hg commit` without explicit paths picks up all modified tracked files.) --- ## Task 8: Wire the hook into `hooks.json` Tells Claude Code to invoke the hook on every Bash tool call. **Files:** - Create: `hooks/hooks.json` - [ ] **Step 1: Create the hook config** Create `hooks/hooks.json` with this exact content: ```json { "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/block-git-in-hg.sh" } ] } ] } } ``` - [ ] **Step 2: Validate the JSON parses** ```bash jq . hooks/hooks.json ``` Expected: the JSON is echoed back, formatted, no parse errors. - [ ] **Step 3: Stage and commit** ```bash hg add hooks/hooks.json hg commit -m "feat: register PreToolUse hook on Bash" ``` --- ## Task 9: Skill — `mercurial-basics` Generic Mercurial skill covering inspection commands and commit hygiene. The foundation skill referenced by all the others. **Files:** - Create: `skills/mercurial-basics/SKILL.md` - [ ] **Step 1: Create the skill directory and file** ```bash mkdir -p skills/mercurial-basics ``` Create `skills/mercurial-basics/SKILL.md`: ```markdown --- name: mercurial-basics description: 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. --- # Mercurial basics ## When this applies Use when: - The working directory contains `.hg/` (or any ancestor does). - About to run any `hg` command. - About to perform an operation that has both git and hg equivalents and the repo is hg. Do not use for: - Pulling, merging, conflict resolution → see `mercurial-sync-and-merge`. - Bookmarks and the PR workflow → see `mercurial-bookmarks-and-prs`. - Amending or rewriting history → see `mercurial-history-rewriting`. ## Identity comes from ~/.hgrc **Never pass `-u` to `hg commit`.** Identity is configured globally in `~/.hgrc`: ```ini [ui] username = Name ``` `hg commit -u "..."` is for impersonation and will create commits with the wrong author. Always: ```sh hg commit -m "your message" ``` ## Inspection commands | Need | Command | |---|---| | What changed in working dir | `hg status` | | Diff of unstaged changes | `hg diff` | | Recent commits | `hg log` (or `hg log -l 10` for limit) | | Working dir parent revision | `hg parents` | | All current heads | `hg heads` | | Named branches | `hg branches` | | Bookmarks (≈ feature branches) | `hg bookmarks` | | Phase of a revision | `hg phase -r ` | | Who wrote each line | `hg annotate ` (git users: `blame`) | ## Reading `hg log` output Default format shows changeset hash, branch (if not `default`), tag, user, date, and summary: ``` changeset: 42:a1b2c3d4e5f6 branch: myfeature tag: tip bookmark: work-in-progress user: Alice date: Mon May 02 09:00:00 2026 -0500 summary: feat: add the thing ``` For terser output: `hg log --template '{node|short} {bookmarks} {branch} {desc|firstline}\n'` ## Commit messages The Isurus convention (and a generally good rule) is: - **First line:** imperative summary, **under 72 characters**. Examples: `feat: add language stats to repo sidebar`, `fix: handle nil reviewer in PR template`, `chore: bump dependency X to v2`. - **Body (optional):** explain *why* the change is being made. The diff already shows *what*. One blank line between summary and body. - **Category prefix** (`feat:`, `fix:`, `docs:`, `chore:`, `test:`, `refactor:`) is convention in this codebase — match what `hg log` shows. Keep each commit focused. If a single change touches unrelated areas, split into separate commits. ## Adding files ```sh hg add path/to/file # explicit hg add # add all untracked files (use carefully) ``` Mercurial does not have a staging area. Files are tracked or not; commits include all changes to tracked files. To commit only some changes, use `hg commit path/to/file1 path/to/file2`. ## .hgignore syntax `.hgignore` lives at the repo root. Default syntax is `glob`: ``` syntax: glob # Build artifacts dist/ *.o # Switch syntax for a section syntax: rootglob binary-at-root-only # Switch back syntax: glob **/*.swp ``` `syntax: rootglob` matches only at the repo root (useful for binary names that must not match files in subdirectories). ## Phase awareness Every changeset has a phase: `draft`, `public`, or `secret`. The phase governs what's safe to rewrite: - **draft** — local or pushed-but-not-merged. Safe to amend, uncommit, rewrite. - **public** — published and considered immutable. - **secret** — never pushed. In a non-publishing repo (like Isurus), pushed changesets stay draft until merged into the canonical branch. This is what makes `hg amend` safe during code review. See `mercurial-history-rewriting` for the rewriting rules. To check: `hg phase -r `. To see your tip: `hg phase -r tip`. ## Common workflow examples Inspect what's changed and commit: ```sh hg status hg diff hg add new-file.go hg commit -m "feat: add new thing" ``` See recent history on a bookmark: ```sh hg log -l 5 -B my-feature ``` Diff between two revisions: ```sh hg diff -r REV1 -r REV2 ``` ``` - [ ] **Step 2: Run the lint script** ```bash ./scripts/check-skills.sh ``` Expected: ``` Skill lint complete: 0 error(s), 0 warning(s) ``` - [ ] **Step 3: Stage and commit** ```bash hg add skills/mercurial-basics/SKILL.md hg commit -m "feat: add mercurial-basics skill" ``` --- ## Task 10: Skill — `mercurial-bookmarks-and-prs` The bookmark workflow that's the unit of feature work and the unit of pull requests in Isurus. **Files:** - Create: `skills/mercurial-bookmarks-and-prs/SKILL.md` - [ ] **Step 1: Create the skill** ```bash mkdir -p skills/mercurial-bookmarks-and-prs ``` Create `skills/mercurial-bookmarks-and-prs/SKILL.md`: ```markdown --- name: mercurial-bookmarks-and-prs description: 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. --- # Mercurial bookmarks and pull requests ## When this applies Use when: - Starting a new feature or fix that will become a pull request. - Switching between multiple in-progress features. - About to push work for review. - A bookmark is in the wrong place and needs fixing. Do not use for: - Long-lived branches like `default`, `stable` — those are named branches, see "Bookmark vs named branch" below for the distinction. - Pulling/merging upstream changes → see `mercurial-sync-and-merge`. - Amending commits before push → see `mercurial-history-rewriting`. ## Bookmark vs named branch | | Bookmark | Named branch | |---|---|---| | Purpose | Feature/PR pointer | Long-lived line of development | | Lifetime | Days to weeks; deleted after merge | Months to years | | Examples | `add-language-stats`, `fix-nil-reviewer` | `default`, `stable`, `release-2.0` | | Moves on commit? | Yes (the active one moves) | The new commit stays on the branch | | Can be deleted? | Yes (`hg bookmark -d`) | Effectively no | **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. ## Starting a feature ```sh hg pull -u # sync to latest default hg bookmark my-feature # create bookmark at current rev # ... edit files ... hg add new-file.go hg commit -m "feat: add new thing" hg push -B my-feature # push the bookmark to the remote ``` After `hg commit`, the bookmark `my-feature` advances to the new changeset automatically (it's the active bookmark). ## Listing bookmarks ```sh hg bookmarks ``` Output marks the active bookmark with `*`: ``` add-language-stats 42:a1b2c3d4e5f6 * my-feature 47:b9c8d7e6f5a4 work-in-progress 39:c5d4e3f2a1b0 ``` ## Switching bookmarks ```sh hg update my-feature # switch to the bookmark and make it active ``` `hg update` checks out the changeset the bookmark points to AND activates the bookmark (so future commits move it forward). If you only want to look at a revision without activating any bookmark there: ```sh hg update -r ``` ## Pushing for review The Isurus convention (non-publishing repo + bookmarks = PRs): ```sh hg push -B my-feature ``` `-B my-feature` pushes the bookmark by name. Without `-B`, `hg push` pushes the current head's changesets but does not push the bookmark itself, which means the remote won't know to associate them with a PR. After pushing, open the PR in the Isurus web UI (or the equivalent forge UI). See `isurus-conventions` for Isurus specifics. ## Updating a PR after review feedback In a non-publishing repo, pushed changesets stay `draft` until the PR is merged. That means amending after a push is **safe**: ```sh # ... make the requested changes ... hg amend # rewrites the tip changeset in place hg push -B my-feature -f # -f because the rewrite changed the hash ``` The `-f` is required because the new amended changeset has a different hash than what's on the remote; without `-f` the push is rejected. This is the *intended* workflow — not a sign you're doing something dangerous, because the changesets are still draft. ## Deleting a bookmark after merge Once the PR is merged into `default`: ```sh hg pull -u # pull in the merged changes hg bookmark -d my-feature # delete locally hg push --delete my-feature # delete on the remote (if not already gone) ``` ## Recovering a misplaced bookmark If the bookmark is on the wrong changeset (e.g., you forgot it was active and committed something unrelated): ```sh hg bookmarks # see where it is hg bookmark my-feature -r # move it ``` The `-r` flag moves an existing bookmark to a specific revision without checking out anything. ## Common pitfalls - **Forgetting `-B` on push:** the changesets land but no bookmark = no PR association. Re-push with `hg push -B `. - **Forgetting `-f` after amend:** push is rejected with "remote has changesets you don't have." Verify it's your own amended changeset (not a real upstream change), then `-f`. - **Active bookmark surprise:** `hg update -r ` *without* a bookmark name leaves no bookmark active; subsequent commits create anonymous heads. Always activate a bookmark you intend to commit to. ``` - [ ] **Step 2: Run the lint script** ```bash ./scripts/check-skills.sh ``` Expected: ``` Skill lint complete: 0 error(s), 0 warning(s) ``` - [ ] **Step 3: Stage and commit** ```bash hg add skills/mercurial-bookmarks-and-prs/SKILL.md hg commit -m "feat: add mercurial-bookmarks-and-prs skill" ``` --- ## Task 11: Skill — `mercurial-sync-and-merge` Pull, update, multiple heads, merge, conflict resolution. **Files:** - Create: `skills/mercurial-sync-and-merge/SKILL.md` - [ ] **Step 1: Create the skill** ```bash mkdir -p skills/mercurial-sync-and-merge ``` Create `skills/mercurial-sync-and-merge/SKILL.md`: ```markdown --- name: mercurial-sync-and-merge description: Use when pulling changes from a remote, updating to a different revision, dealing with multiple heads, or resolving merge conflicts in a Mercurial repo. --- # Mercurial sync and merge ## When this applies Use when: - Pulling changes from a remote. - Updating the working directory to a different revision. - The repo has multiple heads on the same branch. - Resolving merge conflicts. Do not use for: - Local commit hygiene → see `mercurial-basics`. - Moving or deleting bookmarks → see `mercurial-bookmarks-and-prs`. - Amending after a merge → see `mercurial-history-rewriting`. ## Pull then update ```sh hg pull # fetch new changesets, do not touch the working dir hg update # update working dir to tip of the active branch/bookmark ``` Combined: ```sh hg pull -u # pull then update in one step (most common) ``` If a pull brings in changesets that create a new head on the same branch (someone else committed in parallel), `hg update` will move to the new tip *only if* the working dir is at the previous tip. Otherwise it warns about multiple heads. ## Multiple heads A "head" is a changeset with no children on the same branch. Normal repos have one head per branch. Two parallel commits → two heads. See them: ```sh hg heads # all heads hg heads default # heads on the default branch ``` Resolution options: 1. **Merge them** — combine the two lines: ```sh hg update -r hg merge # resolve any conflicts (see below) hg commit -m "merge: combine parallel work" ``` 2. **Rebase your work onto the other head** (preferred for personal feature work): ```sh # If your work is the new head and the other is upstream: hg update -r # then for each of your local commits, replay using rebase or amend ``` Note: vanilla Mercurial without the `rebase` extension can't `hg rebase` directly. Either enable `rebase` in `~/.hgrc` (`[extensions] rebase = `) or fall back to merge. For Isurus today: merge is the safe default; rebase is not part of the v1 workflow. ## Merging two heads ```sh hg update -r # check out the changeset to merge INTO hg merge -r # bring source into the working dir # files with conflicts are listed in the output ``` ## Conflict resolution After `hg merge`, files in conflict are marked. Inspect them: ```sh hg resolve --list # 'U' = unresolved, 'R' = resolved ``` Edit each unresolved file. Conflict markers look like: ``` <<<<<<< local your version ======= their version >>>>>>> other ``` Remove the markers, keep the correct content, save. Then mark resolved: ```sh hg resolve --mark path/to/file ``` Or after editing all of them: ```sh hg resolve --mark --all ``` Verify nothing remains: ```sh hg resolve --list # should print nothing ``` Re-run a merge tool on a specific file (e.g., to retry): ```sh hg resolve --tool internal:merge3 path/to/file ``` Once all files are resolved: ```sh hg commit -m "merge: combine X and Y" ``` A merge commit has two parents. `hg log --graph` (or `hg log -G`) shows the merge visually. ## Aborting a merge If you started a merge and want to back out: ```sh hg update -C -r ``` `-C` means clean — discard the in-progress merge state. Verify the working dir is clean afterward with `hg status`. ## Common pitfalls - **`hg pull` without `-u`:** changesets are in the repo but the working dir is unchanged. You're working against stale code without realizing. - **Two parents after merge with no commit:** if you `hg merge` and then run other commands, the working dir has two parents until you `hg commit`. Until then, `hg parents` shows both. - **Conflict markers committed:** if you `hg commit` without removing all `<<<<<<<`/`=======`/`>>>>>>>` markers, you commit broken files. Always grep before commit: ```sh grep -r '<<<<<<<' . ``` ``` - [ ] **Step 2: Run the lint script** ```bash ./scripts/check-skills.sh ``` Expected: 0 errors, 0 warnings. - [ ] **Step 3: Stage and commit** ```bash hg add skills/mercurial-sync-and-merge/SKILL.md hg commit -m "feat: add mercurial-sync-and-merge skill" ``` --- ## Task 12: Skill — `mercurial-history-rewriting` Amend, uncommit, splitting commits, the draft-vs-public phase rule, and recovery. **Files:** - Create: `skills/mercurial-history-rewriting/SKILL.md` - [ ] **Step 1: Create the skill** ```bash mkdir -p skills/mercurial-history-rewriting ``` Create `skills/mercurial-history-rewriting/SKILL.md`: ```markdown --- name: mercurial-history-rewriting description: 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. --- # Mercurial history rewriting ## When this applies Use when: - About to amend a commit (fix typo, add forgotten file, edit the message). - Need to uncommit / split a changeset. - Recovering from a mistaken commit. Do not use for: - Routine commits → see `mercurial-basics`. - Bookmark/PR push flow → see `mercurial-bookmarks-and-prs`. - Conflicts after merging → see `mercurial-sync-and-merge`. ## The fundamental rule: draft only **Only rewrite changesets in `draft` phase. Never rewrite `public` ones.** Check before rewriting: ```sh hg phase -r tip # or any specific revision ``` If the phase is `public`, **stop and surface the situation to the user.** Rewriting public history breaks every other clone of the repo. Mercurial will refuse by default with: `cannot amend public changesets`. In Isurus the repo is non-publishing, so pushed changesets stay `draft` until merged into the canonical branch. That makes `hg amend` safe during code review. After merge, the changesets become `public` and are off-limits. ## `hg amend` — fix the tip changeset in place The most common rewrite. Edit files, then: ```sh hg amend # fold working-dir changes into tip; keeps message hg amend -m "new message" # also rewrite the message hg amend file1 file2 # only fold those files ``` After amend, the new changeset has a different hash. If it was already pushed, you'll need `-f` next push: ```sh hg push -B my-feature -f ``` This is normal in a non-publishing repo. See `mercurial-bookmarks-and-prs` for the post-amend push pattern. ## `hg uncommit` — undo a commit, keeping changes in working dir ```sh hg uncommit # the entire tip changeset hg uncommit file1 file2 # only those files (rest stay committed) ``` Use cases: - The commit message is wrong and you want to redo it from scratch. - You committed too much and want to split. After uncommit, the files are back in the working dir as uncommitted changes. Re-commit selectively. ## Splitting a commit Goal: turn one commit into two (or more) more focused ones. ```sh hg uncommit # uncommit everything hg commit path/to/group-1 -m "feat: part 1" hg commit path/to/group-2 -m "feat: part 2" ``` If groups overlap by line within a single file, you may need to use a partial-add tool — that's beyond v1 scope. Manually patch and recommit, or use `hg commit -i` (interactive) if available. ## Recovering from a mistake ### "I committed too soon / wrong message" ```sh hg amend -m "the right message" ``` ### "I committed to the wrong bookmark" ```sh hg bookmarks # see what's where hg bookmark wrong-name -r .~1 # move the wrong bookmark back one hg bookmark right-name -r . # put the right bookmark on tip ``` ### "I committed something I shouldn't have" If only the most recent commit is wrong and changes haven't been pushed: ```sh hg uncommit # changes return to working dir # discard them (CAREFUL — destructive): hg revert --all --no-backup # discard all working-dir changes ``` ### "I want to undo the last several local commits" This is harder in vanilla Mercurial without `evolve`. Options: 1. `hg strip ` — removes a changeset and all descendants. Requires the `strip` extension (`[extensions] strip = ` in `~/.hgrc`). Destructive; backups are saved in `.hg/strip-backup/` by default. 2. Re-clone from the remote, lose local changes. In Isurus today, prefer option 1 with the strip extension. For complex multi-commit rewrites, this is exactly the case where `evolve` + `topics` would help (deferred to phase 2). ## What `hg rollback` is and isn't `hg rollback` undoes the most recent transaction (commit, pull, push). It's mostly deprecated because it operates on the *transaction*, not the changeset, and quietly loses information. **Prefer `hg amend` / `hg uncommit` / `hg strip` instead.** The repo's `[ui] rollback = false` is recommended (and is the default in modern Mercurial). ## Public-changeset attempts If an `hg amend` returns: ``` abort: cannot amend public changesets: ``` Do not use `--force` to override. Stop and surface the situation: someone (or the workflow) has marked this changeset public, which means it's been integrated. The right answer is a *new* commit on top, not a rewrite. ## A note on evolve / topics Modern Mercurial (with the `evolve` extension and `topics`) supports rich history rewriting workflows: stable obsolescence markers, topic branches, automatic descendant rebasing. Isurus does not enable these today; if it does in a future version, this skill will grow a sibling (`mercurial-evolve`) or be expanded. ``` - [ ] **Step 2: Run the lint script** ```bash ./scripts/check-skills.sh ``` Expected: 0 errors, 0 warnings. - [ ] **Step 3: Stage and commit** ```bash hg add skills/mercurial-history-rewriting/SKILL.md hg commit -m "feat: add mercurial-history-rewriting skill" ``` --- ## Task 13: Skill — `mercurial-finishing-a-branch` End-to-end pre-PR checklist + push flow. The hg counterpart to superpowers' `finishing-a-development-branch`. **Files:** - Create: `skills/mercurial-finishing-a-branch/SKILL.md` - [ ] **Step 1: Create the skill** ```bash mkdir -p skills/mercurial-finishing-a-branch ``` Create `skills/mercurial-finishing-a-branch/SKILL.md`: ```markdown --- name: mercurial-finishing-a-branch description: 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`. --- # Mercurial: finishing a development branch ## When this applies Use when: - Implementation is complete and the user is ready to propose a PR. - The user says "let's ship this" / "this is done" / "let's open a PR" in an hg repo. Do not use for: - Mid-development commits → see `mercurial-basics`. - Recovering from a bad commit → see `mercurial-history-rewriting`. ## The flow Six steps. Do them in order; do not skip. ### 1. Verify the working directory is clean ```sh hg status ``` Expected: empty output. If anything is shown: - New files you intended to include → `hg add` and amend the tip commit. - New files you didn't mean to track → add to `.hgignore` and commit that change separately. - Modified files → either amend or commit them as a separate change. Do not push with uncommitted changes lingering. ### 2. Verify you're on the right bookmark ```sh hg bookmarks ``` The active bookmark is marked with `*`. Confirm it's the feature bookmark, not e.g. an accidental commit on no bookmark. If a feature bookmark exists but isn't active: ```sh hg update ``` If commits were made without an active bookmark (anonymous head): ```sh hg bookmark -r . ``` (See `mercurial-bookmarks-and-prs` for the bookmark mechanics.) ### 3. Verify history is clean ```sh hg log -l 10 --template '{node|short} {phase} {desc|firstline}\n' ``` Look for: - All commits should be `draft` (in non-publishing repos like Isurus). - Commit messages follow the convention (imperative summary, <72 chars, "category: summary" prefix where the project uses one). - No "wip", "fix typo", "address review" debris — squash via `hg amend` / `hg uncommit`. If cleanup is needed → see `mercurial-history-rewriting`. The non-publishing repo means amend is safe even on already-pushed changesets. ### 4. Run the project's pre-PR checks Detect the project's check command: ```sh ls Makefile 2>/dev/null && grep -E '^(check|test|fmt|vet|lint):' Makefile ``` Common patterns: - **Has `Makefile` with `check` target:** `make check` (full test suite). Then run `make fmt` and `make vet` if those exist. - **Go project, no Makefile:** `go test ./...`, `go vet ./...`, `gofmt -l .` (anything output = unformatted). - **Other languages:** match the project's documented test/lint commands. For the Isurus repository specifically, see `isurus-conventions` — it documents the exact `make` targets used. If checks fail, **fix or surface** before continuing. Do not push code that fails the project's own gate. ### 5. Push the bookmark ```sh hg push -B ``` The `-B ` is critical — without it the changesets push but the remote doesn't know to associate them with a bookmark/PR. If the push is rejected because the bookmark was previously pushed and has since been amended: ```sh hg push -B -f ``` `-f` is the expected workflow in non-publishing repos after `hg amend`. See `mercurial-bookmarks-and-prs` for context. ### 6. Open the pull request In the forge web UI: 1. Navigate to the repository. 2. The forge typically shows a banner offering to open a PR for the just-pushed bookmark — click it. 3. Title the PR using the same format as the commit message summary. 4. PR description: explain *why*, link any related issues. The diff shows *what*; don't repeat it in the description. For Isurus-specific URL patterns and PR conventions → see `isurus-conventions`. ## After the PR is open When reviewer feedback comes in: - Make the requested changes locally. - `hg amend` to fold the fix into the existing changeset (in non-publishing repos). - `hg push -B -f`. - Reply to the review comments confirming the fix is pushed. When the PR is merged: - `hg pull -u` to bring the merged changes locally. - `hg bookmark -d ` to delete the local bookmark. - Optionally `hg push --delete ` if the forge doesn't auto-delete on merge. ``` - [ ] **Step 2: Run the lint script** ```bash ./scripts/check-skills.sh ``` Expected: 0 errors, 0 warnings. - [ ] **Step 3: Stage and commit** ```bash hg add skills/mercurial-finishing-a-branch/SKILL.md hg commit -m "feat: add mercurial-finishing-a-branch skill" ``` --- ## Task 14: Skill — `isurus-conventions` The Isurus-specific overlay. Activates only in the Isurus repo. Supplies the project-specific values that the generic skills' procedures call into. **Files:** - Create: `skills/isurus-conventions/SKILL.md` - [ ] **Step 1: Create the skill** ```bash mkdir -p skills/isurus-conventions ``` Create `skills/isurus-conventions/SKILL.md`: ```markdown --- name: isurus-conventions description: 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. --- # Isurus conventions (Mercurial overlay) ## When this applies Use when working in the Isurus repository. Detect Isurus by: - Repo root contains `cmd/isurus/`, or - Top-level `README.md` contains the word `Isurus`, or - Repo is cloned from `ssh://hg@hg.leafscale.com/leafscale/isurus`. This skill is **purely additive**. The procedures live in `mercurial-basics`, `mercurial-bookmarks-and-prs`, `mercurial-sync-and-merge`, `mercurial-history-rewriting`, and `mercurial-finishing-a-branch`. This file supplies Isurus-specific values those procedures plug into. ## Repository facts | Fact | Value | |---|---| | VCS | Mercurial 7.x (never git) | | Clone URL | `ssh://hg@hg.leafscale.com/leafscale/isurus` | | Long-lived branches | `default`, `stable` | | Phase model | **Non-publishing** — pushed changesets stay `draft` until merged | | Feature/PR mechanism | **Bookmarks** (push with `-B `) | | Forge | Isurus (web UI at `https://hg.leafscale.com/`) | ## Commit messages in this repo - Imperative summary, **under 72 characters**. - Prefix with a category: `feat:`, `fix:`, `chore:`, `docs:`, `test:`, `refactor:`, `scripts:`, `hgignore:`. Match what `hg log` shows for recent commits if uncertain. - Body (optional) explains *why*; the diff shows *what*. - One blank line between summary and body. - **Never pass `-u`** to `hg commit` — identity is in `~/.hgrc`. ## Pre-PR check sequence Run from the repo root, in this order: ```sh make fmt # gofmt — leaves no diff if all files are formatted make vet # go vet — catches suspicious constructs make check # full test suite against PostgreSQL test DB ``` `make check` requires the `isurus-test` database on localhost: - DB name: `isurus-test` - User: `isurus-test` - Password: `isurus-test` Override with `ISURUS_TEST_DSN`. See `make test-setup` for one-time DB creation. Optional but recommended (skip if not installed): ```sh make lint # golangci-lint ``` The PR checklist from `CONTRIBUTING.md` (must all be true before opening PR): - `make check` passes - `go vet ./...` is clean - Code is formatted (`make fmt`) - New public functions have short godoc comments where non-obvious - No new `TODO`/`FIXME` markers (fix or file an issue instead) - Documentation updated if behavior or configuration changed - Commit messages explain *why* ## Pull request flow 1. `hg push -B ` to the canonical remote. 2. Open a PR in the Isurus web UI (`https://hg.leafscale.com//`). 3. Title: same as the commit message summary. 4. Description: *why* + link to related issues. 5. Reviewer feedback → `hg amend` → `hg push -B -f`. The non-publishing repo makes `-f` after amend the **expected** workflow, not a sign of trouble. ## What this overlay does NOT cover - General Mercurial commands → `mercurial-basics`. - Bookmark mechanics → `mercurial-bookmarks-and-prs`. - Pull / merge / conflicts → `mercurial-sync-and-merge`. - Amend / uncommit / phase rules → `mercurial-history-rewriting`. - The end-to-end "finish a branch" sequence → `mercurial-finishing-a-branch`. This file only changes the *values* those skills use (URLs, branch names, check commands). The procedures stay the same. ``` - [ ] **Step 2: Run the lint script** ```bash ./scripts/check-skills.sh ``` Expected: 0 errors, 0 warnings. - [ ] **Step 3: Stage and commit** ```bash hg add skills/isurus-conventions/SKILL.md hg commit -m "feat: add isurus-conventions overlay skill" ``` --- ## Task 15: Manual scenarios doc The acceptance test suite that can't be automated. **Files:** - Create: `tests/MANUAL.md` - [ ] **Step 1: Create the doc** ```bash mkdir -p tests ``` Create `tests/MANUAL.md`: ```markdown # Manual test scenarios These scenarios verify end-to-end plugin behavior — they exercise the parts of the system (skill activation, hook firing in real Claude Code sessions) that can't be unit-tested without Claude in the loop. Walk through them after meaningful changes to any skill or the hook. For each scenario: PASS if the listed outcome matches; FAIL otherwise. Note failures and revisit. --- ## Scenario 1: Fresh empty hg repo, basic commit **Setup:** ```sh cd $(mktemp -d) && hg init && echo "hello" > README.md ``` Open Claude Code in that directory. **Prompt:** "Commit the README.md file with an appropriate message." **Expected:** - Claude loads the `mercurial-basics` skill. - `hg add README.md` followed by `hg commit -m "..."` (no `-u` flag). - Commit message follows the convention: imperative summary <72 chars, category prefix optional but reasonable. **FAIL signals:** - `git` commands appear (the hook should block them, but Claude shouldn't try in the first place). - `-u "..."` appears on `hg commit`. - Multi-line commit message via `-m` without proper quoting. --- ## Scenario 2: Isurus repo, start a feature **Setup:** open Claude Code in `~/repos/isurus-project/isurus`. **Prompt:** "Start a new feature branch called `add-language-stats` and make a small no-op change." **Expected:** - Both `mercurial-bookmarks-and-prs` and `isurus-conventions` activate. - `hg bookmark add-language-stats` is created. - A small change is made and committed (no `-u`, message follows the Isurus convention with a prefix like `feat:`). - No mention of `git` anywhere. - Claude does NOT try to use named branches for the feature. --- ## Scenario 3: Mid-stream conflict **Setup:** in a scratch hg repo, create two heads: ```sh cd $(mktemp -d) && hg init echo "A" > file.txt && hg add file.txt && hg commit -m "init" echo "A1" > file.txt && hg commit -m "branch 1" hg update -r 0 echo "A2" > file.txt && hg commit -m "branch 2" ``` `hg heads` should show two heads. Open Claude Code. **Prompt:** "Merge the two heads." **Expected:** - `mercurial-sync-and-merge` activates. - Claude runs `hg merge`, resolves the conflict in `file.txt` (asks user how to resolve OR resolves obviously), `hg resolve --mark file.txt`, and finally `hg commit -m "merge: ..."`. - No conflict markers (`<<<<<<<`) remain in `file.txt` after commit. --- ## Scenario 4: History rewrite request on a public changeset **Setup:** in any hg repo, find a `public`-phase changeset: ```sh hg log --template '{node|short} {phase} {desc|firstline}\n' | grep ' public ' ``` Note its hash. Open Claude Code. **Prompt:** "Amend changeset `` to fix a typo." **Expected:** - `mercurial-history-rewriting` activates. - Claude **refuses** to amend a public changeset, surfaces the phase rule, and offers an alternative (a new commit on top, or coordinating with the team to demote the phase). **FAIL signals:** - Claude tries `hg amend --force` or any other override. - Claude doesn't notice the phase and just runs `hg amend`. --- ## Scenario 5: git inside Isurus is blocked **Setup:** `cd ~/repos/isurus-project/isurus` (or any `.hg` repo). In Claude Code: **Prompt:** "Run `git status` to see what's changed." **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`. --- ## Scenario 6: git inside testdata bare repo is allowed **Setup:** `cd ~/repos/isurus-project/isurus/internal/import/testdata//repo.git` (these are bare git repos used as import-flow test fixtures). In Claude Code: **Prompt:** "Run `git log --all` here." **Expected:** - The hook does NOT block (bare-repo signature wins over the outer `.hg/`). - `git log --all` runs successfully. If no testdata fixture is available, simulate by hand: ```sh cd $(mktemp -d) mkdir -p outer/.hg cd outer mkdir -p repo.git/refs repo.git/objects touch repo.git/HEAD cd repo.git # now ask Claude to run `git log` from this directory ``` --- ## Acceptance criteria reminder Per the spec, v1 is done when: - `scripts/check-skills.sh` passes (0 errors). - `hooks/test/run.sh` passes (7/7 cases). - All 6 scenarios above PASS against the real Isurus repo. - A second hg repo (any non-Isurus hg repo) demonstrates that `isurus-conventions` does NOT activate but the generic skills do. ``` - [ ] **Step 2: Stage and commit** ```bash hg add tests/MANUAL.md hg commit -m "docs: add manual scenario test checklist" ``` --- ## Task 16: README User-facing documentation for the plugin: what it does, how to install, what the hook does. **Files:** - Create: `README.md` - [ ] **Step 1: Create the README** Create `README.md`: ```markdown # isurus-hg A Claude Code plugin that teaches Claude to work fluently in **Mercurial 7.x**, with an **Isurus-conventions overlay** for the Isurus forge codebase, and a **PreToolUse hook** that prevents accidental `git` commands inside `.hg` repositories. Built because Claude's general competence is uneven on Mercurial — bookmarks, named branches, phases, and Isurus's house conventions are easy to get wrong without explicit guidance — and because "never use `git`" is more reliable as a hook than as a memorized instruction. ## What it includes **Skills** (auto-loaded by Claude when relevant): - `mercurial-basics` — `status`, `diff`, `log`, `add`, `commit`, message format, phase awareness. - `mercurial-bookmarks-and-prs` — bookmark workflow as the unit of feature work and PRs. - `mercurial-sync-and-merge` — pull, update, multiple heads, merge, conflict resolution. - `mercurial-history-rewriting` — `amend`, `uncommit`, the draft-vs-public phase rule, recovery patterns. - `mercurial-finishing-a-branch` — pre-PR checklist + push flow (the hg counterpart to superpowers' `finishing-a-development-branch`). - `isurus-conventions` — overlay skill that activates in the Isurus repo; supplies project-specific values (URLs, branches, `make` targets) that the generic skills' procedures plug into. **Hook:** - `block-git-in-hg.sh` — fires on every `Bash` tool call. If the command starts with `git` and the working directory is inside an `.hg` repository (and not inside a closer `.git` repo or a bare git repo), the call is blocked with a message suggesting the `hg` equivalent. ## Install Two supported mechanisms, depending on whether you want the plugin loaded for one session or persistently across sessions. ### Option 1 — `--plugin-dir` flag (session-only, recommended for development and testing) Launch Claude Code with the plugin directory as an argument: ```sh claude --plugin-dir /absolute/path/to/isurus-claude-plugin ``` The plugin is loaded for that session only. Repeat the flag to load multiple plugins. Takes precedence over installed marketplace plugins, which is what you want while iterating on the plugin itself. ### Option 2 — local marketplace (persistent across sessions) If you want the plugin to load every time Claude Code starts, register it through a local marketplace. 1. Create a `marketplace.json` in any parent directory of the plugin. For this repo's recommended layout, place it at `/.claude-plugin/marketplace.json`: ```json { "name": "local-isurus", "owner": { "name": "Your Name" }, "plugins": [ { "name": "isurus-hg", "source": "./isurus-claude-plugin", "description": "Mercurial fluency for Claude Code, with an Isurus-conventions overlay." } ] } ``` `source` is relative to the marketplace.json's directory. 2. Register the marketplace and install the plugin from inside Claude Code: ``` /plugin marketplace add /absolute/path/to/parent /plugin install isurus-hg@local-isurus ``` The skills become available and the hook is registered automatically via `hooks/hooks.json`. **Requirements:** - Mercurial 7.x (the skills assume modern command behavior). - `jq` on `PATH` (the hook uses it to parse tool-call JSON; if missing, the hook fails open with a warning). - Bash 4+. > **Note for v0.1.0 users:** earlier README revisions described an undocumented `"plugins": { ... }` block in `~/.claude/settings.json`. That block is not a supported Claude Code feature and is silently ignored. Use one of the two mechanisms above instead. ## Hook behavior The hook intercepts `Bash` tool calls. Decision tree: 1. Command doesn't start with `git` → allow. 2. Working directory is a bare git repository (contains `HEAD`, `refs/`, `objects/` at top level) → allow. 3. Closest VCS ancestor is `.git/` → allow. 4. Closest VCS ancestor is `.hg/` → **block** with exit code 2 and a stderr message suggesting the `hg` equivalent. 5. No VCS context → allow (e.g., `git --version` outside any repo). 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`. ## Development This plugin is developed in **Mercurial** (eating its own dogfood). To work on it: ```sh hg clone cd isurus-claude-plugin # Validate skills ./scripts/check-skills.sh # Run hook tests ./hooks/test/run.sh # Walk manual scenarios after meaningful changes cat tests/MANUAL.md ``` Design spec: `docs/superpowers/specs/2026-05-01-isurus-hg-plugin-design.md` Implementation plan: `docs/superpowers/plans/2026-05-02-isurus-hg-plugin.md` ## License MIT — see [LICENSE](LICENSE). ``` - [ ] **Step 2: Stage and commit** ```bash hg add README.md hg commit -m "docs: add README" ``` --- ## Task 17: LICENSE file MIT text. Required for the manifest's `license: MIT` to actually be true. **Files:** - Create: `LICENSE` - [ ] **Step 1: Create the LICENSE file** Create `LICENSE` with the standard MIT text: ``` MIT License Copyright (c) 2026 Leafscale, LLC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ``` - [ ] **Step 2: Stage and commit** ```bash hg add LICENSE hg commit -m "docs: add MIT LICENSE" ``` --- ## Task 18: `.gitignore` for the future git mirror Even though v1 is hg-only, a `.gitignore` makes the future `hg-git` mirror clean. Excludes hg metadata and Claude local state. **Files:** - Create: `.gitignore` - [ ] **Step 1: Create the file** Create `.gitignore`: ``` # Mercurial metadata (the canonical source — git is downstream mirror) .hg/ .hgignore .hgtags # Claude Code local state .claude/ .remember/ .superpowers/ # OS .DS_Store Thumbs.db # Editor *.swp *.swo .idea/ .vscode/ ``` - [ ] **Step 2: Stage and commit** ```bash hg add .gitignore hg commit -m "chore: add .gitignore for future git mirror" ``` --- ## Task 19: Final v1 acceptance check Verifies all the acceptance criteria from the spec are met before tagging. **Files:** none (this is a verification task) - [ ] **Step 1: Run skill content lint — must be clean** ```bash ./scripts/check-skills.sh ``` Expected: ``` Skill lint complete: 0 error(s), 0 warning(s) ``` If any warnings appear (e.g., a skill exceeds 400 lines), decide whether to split or accept; document the decision in a follow-up commit. - [ ] **Step 2: Run hook test suite — all 7 must pass** ```bash ./hooks/test/run.sh ``` Expected: ``` PASS 01-git-in-hg-blocks PASS 02-git-outside-repo-allows PASS 03-git-inside-nested-git-allows PASS 04-hg-command-allows PASS 05-env-var-prefix-stripped PASS 06-non-git-command-allows PASS 07-cd-then-git-allows Result: 7 passed, 0 failed ``` - [ ] **Step 3: Verify the plugin manifest validates** ```bash jq . .claude-plugin/plugin.json && jq . hooks/hooks.json ``` Both files must parse cleanly. - [ ] **Step 4: Verify the file inventory matches the spec** ```bash find .claude-plugin hooks skills scripts tests -type f | sort ``` Expected (one per line, in this order): ``` .claude-plugin/plugin.json hooks/block-git-in-hg.sh hooks/hooks.json hooks/test/cases/01-git-in-hg-blocks.sh hooks/test/cases/02-git-outside-repo-allows.sh hooks/test/cases/03-git-inside-nested-git-allows.sh hooks/test/cases/04-hg-command-allows.sh hooks/test/cases/05-env-var-prefix-stripped.sh hooks/test/cases/06-non-git-command-allows.sh hooks/test/cases/07-cd-then-git-allows.sh hooks/test/run.sh scripts/check-skills.sh skills/isurus-conventions/SKILL.md skills/mercurial-basics/SKILL.md skills/mercurial-bookmarks-and-prs/SKILL.md skills/mercurial-finishing-a-branch/SKILL.md skills/mercurial-history-rewriting/SKILL.md skills/mercurial-sync-and-merge/SKILL.md tests/MANUAL.md ``` If anything is missing or extra, resolve before continuing. - [ ] **Step 5: Walk the manual scenarios** Open `tests/MANUAL.md` and walk all 6 scenarios. Note any failures. If any scenario FAILs, do not tag v0.1.0 — return to the relevant skill or hook, fix, and re-walk. - [ ] **Step 6: Tag v0.1.0** ```bash hg tag v0.1.0 hg log -l 2 --template '{node|short} {tags} {desc|firstline}\n' ``` Expected output: ``` tip Added tag v0.1.0 for changeset v0.1.0 ``` The tag commit is automatic; that's hg's tag mechanism. - [ ] **Step 7: Install locally and smoke-test** Launch a Claude Code session with the plugin loaded via the `--plugin-dir` flag (session-only; preferred for the smoke test): ```sh claude --plugin-dir /home/ctusa/repos/isurus-project/isurus-claude-plugin ``` In the session: 1. `cd` into `~/repos/isurus-project/isurus`. 2. Ask: "Run `git status` here." → confirm the hook blocks. 3. Ask: "What's the working-copy status?" → confirm `mercurial-basics` activates and `hg status` runs. If both work, v0.1.0 is ready for the dogfooding period described in the spec's rollout section. > **Note:** earlier revisions of this plan described an `~/.claude/settings.json` `"plugins"` block as the install method. That key is not a supported Claude Code feature and is silently ignored. The `--plugin-dir` flag (above) and a local marketplace are the two verified mechanisms — see the spec's "Install for v1" section for the local-marketplace recipe. - [ ] **Step 8: Final commit-log review** ```bash hg log --template '{node|short} {desc|firstline}\n' ``` Read through the full commit log. Each commit message should clearly describe its change. If any look unclear, that's information for future-you about commit hygiene — note for next time. Don't rewrite existing public-ish commits at this stage. --- ## Self-review notes This plan was self-reviewed against the spec. Coverage check: | Spec section | Implementing tasks | |---|---| | Plugin manifest | Task 1 | | File layout | Tasks 1-18 (each file in the layout has a creating task) | | 5 generic skills | Tasks 9-13 | | Isurus overlay skill | Task 14 | | Hook config | Task 8 | | Hook script | Tasks 5, 7 | | Hook detection logic incl. bare-repo handling | Task 7 | | Hook test cases (7) | Tasks 4, 6 | | Skill content lint | Task 2 | | Manual scenarios doc | Task 15 | | README | Task 16 | | LICENSE | Task 17 | | .gitignore for future git mirror | Task 18 | | Acceptance criteria | Task 19 | Deferred items from the spec (forge MCP, slash commands, evolve/topics, subagents, formal evals, CI, CHANGELOG/CONTRIBUTING/SECURITY) intentionally have no tasks — they are phase 2 or beyond. `.hgignore` and the spec/plan documents themselves are already committed (pre-plan setup), so no task creates them.