@rethunk/mcp-multi-root-git 3.1.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/AGENTS.md +21 -15
  2. package/CHANGELOG.md +114 -39
  3. package/HUMANS.md +20 -39
  4. package/README.md +30 -13
  5. package/dist/repo-paths.js +57 -13
  6. package/dist/server/batch-commit-tool.js +187 -46
  7. package/dist/server/error-codes.js +25 -7
  8. package/dist/server/git-blame-tool.js +6 -3
  9. package/dist/server/git-branch-tool.js +151 -0
  10. package/dist/server/git-cherry-pick-tool.js +220 -11
  11. package/dist/server/git-conflicts-tool.js +230 -0
  12. package/dist/server/git-diff-summary-tool.js +36 -25
  13. package/dist/server/git-diff-tool.js +55 -27
  14. package/dist/server/git-grep-tool.js +188 -0
  15. package/dist/server/git-inventory-tool.js +26 -6
  16. package/dist/server/git-log-tool.js +35 -4
  17. package/dist/server/git-merge-tool.js +70 -12
  18. package/dist/server/git-parity-tool.js +13 -4
  19. package/dist/server/git-push-tool.js +3 -1
  20. package/dist/server/git-refs.js +48 -17
  21. package/dist/server/git-revert-tool.js +160 -0
  22. package/dist/server/git-show-tool.js +7 -3
  23. package/dist/server/git-stash-tool.js +110 -67
  24. package/dist/server/git-tag-tool.js +7 -6
  25. package/dist/server/git-worktree-tool.js +65 -53
  26. package/dist/server/git.js +116 -24
  27. package/dist/server/inventory.js +90 -32
  28. package/dist/server/presets-resource.js +37 -20
  29. package/dist/server/presets.js +87 -15
  30. package/dist/server/roots.js +13 -1
  31. package/dist/server/schemas.js +9 -2
  32. package/dist/server/test-harness.js +11 -4
  33. package/dist/server/tool-parameter-schemas.js +44 -55
  34. package/dist/server/tools.js +36 -17
  35. package/dist/server.js +1 -1
  36. package/docs/install.md +52 -5
  37. package/docs/mcp-tools.md +385 -178
  38. package/package.json +6 -6
  39. package/schemas/batch_commit.json +2 -2
  40. package/schemas/git_blame.json +1 -1
  41. package/schemas/git_branch.json +54 -0
  42. package/schemas/git_cherry_pick.json +12 -2
  43. package/schemas/git_cherry_pick_continue.json +34 -0
  44. package/schemas/{git_reflog.json → git_conflicts.json} +12 -12
  45. package/schemas/git_diff.json +11 -3
  46. package/schemas/git_grep.json +85 -0
  47. package/schemas/git_inventory.json +19 -5
  48. package/schemas/git_log.json +7 -6
  49. package/schemas/git_merge.json +2 -2
  50. package/schemas/git_parity.json +0 -5
  51. package/schemas/git_revert.json +47 -0
  52. package/schemas/git_show.json +1 -1
  53. package/schemas/git_stash_push.json +47 -0
  54. package/schemas/git_status.json +0 -5
  55. package/schemas/git_worktree_add.json +1 -1
  56. package/schemas/git_worktree_remove.json +1 -1
  57. package/schemas/index.json +28 -23
  58. package/schemas/list_presets.json +0 -5
  59. package/tool-parameters.schema.json +326 -167
  60. package/dist/server/git-branch-list-tool.js +0 -132
  61. package/dist/server/git-fetch-tool.js +0 -257
  62. package/dist/server/git-reflog-tool.js +0 -120
  63. package/schemas/git_branch_list.json +0 -30
  64. package/schemas/git_fetch.json +0 -46
  65. package/schemas/git_stash_list.json +0 -24
  66. package/schemas/git_worktree_list.json +0 -24
package/AGENTS.md CHANGED
@@ -15,48 +15,54 @@ IDEs injecting this as context: do not re-link from rules.
15
15
 
16
16
  | File | Symbols |
17
17
  |------|---------|
18
- | [`src/server.ts`](src/server.ts) | `FastMCP` + `roots: { enabled: true }`; `readMcpServerVersion()`; `registerRethunkGitTools` |
19
- | [`src/server/json.ts`](src/server/json.ts) | `jsonRespond()` (minified, no envelope), `spreadWhen`, `spreadDefined` |
18
+ | [`src/server.ts`](src/server.ts) | `FastMCP` + `roots: { enabled: true }`; `MCP_JSON_FORMAT_VERSION`; `registerRethunkGitTools` |
19
+ | [`src/server/json.ts`](src/server/json.ts) | `jsonRespond()` (minified, no envelope), `readMcpServerVersion()`, `spreadWhen`, `spreadDefined` |
20
20
  | [`src/server/git.ts`](src/server/git.ts) | `gateGit`, `spawnGitAsync` (optional `{ timeoutMs, signal }`), `asyncPool`, `GIT_SUBPROCESS_PARALLELISM`, `GIT_SUBPROCESS_TIMEOUT_MS`, `gitTopLevel`, `gitRevParseGitDir`, `gitRevParseHead`, `parseGitSubmodulePaths`, `hasGitMetadata`, `gitStatusSnapshotAsync`, `gitStatusShortBranchAsync`, `fetchAheadBehind`, `isSafeGitUpstreamToken` |
21
21
  | [`src/server/roots.ts`](src/server/roots.ts) | `requireGitAndRoots` (fan-out `root` resolution: string / string[] / `"*"`), `requireSingleRepo` (`workspaceRoot`), `resolveRootPathList`, `RootPickArgs` — shared tool preludes; session root resolution |
22
22
  | [`src/server/presets.ts`](src/server/presets.ts) | `PRESET_FILE_PATH`, `loadPresetsFromGitTop`, `presetLoadErrorPayload`, `applyPresetNestedRoots`, `applyPresetParityPairs`; Zod schemas must match [`git-mcp-presets.schema.json`](git-mcp-presets.schema.json) |
23
23
  | [`src/server/schemas.ts`](src/server/schemas.ts) | `WorkspacePickSchema` (single-repo: `workspaceRoot` + `format`), `RootPickSchema` (fan-out: polymorphic `root` + `format`), `MAX_INVENTORY_ROOTS_DEFAULT`, **`MAX_ROOT_PATHS`** (256) |
24
24
  | [`src/server/inventory.ts`](src/server/inventory.ts) | `InventoryEntryJson`, `validateRepoPath`, `makeSkipEntry`, `buildInventorySectionMarkdown`, `collectInventoryEntry` |
25
- | [`src/server/git-refs.ts`](src/server/git-refs.ts) | `isProtectedBranch`, `isSafeGitRefToken`, `isSafeGitRangeToken`, `isSafeGitAncestorRef`; `getCurrentBranch`, `resolveRef`, `isWorkingTreeClean`, `isFullyMergedInto`, `isContentEquivalentlyMergedInto`, `commitListBetween`; `listWorktrees`, `worktreeForBranch`; `inferRemoteFromUpstream`; `conflictPaths` |
25
+ | [`src/server/git-refs.ts`](src/server/git-refs.ts) | `isProtectedBranch`, `isSafeGitRefToken`, `isSafeGitRangeToken`, `isSafeGitAncestorRef`, `isSafeGitCommitIsh`; `getCurrentBranch`, `resolveRef`, `isWorkingTreeClean`, `isFullyMergedInto`, `isContentEquivalentlyMergedInto`, `commitListBetween`; `listWorktrees`, `worktreeForBranch`; `inferRemoteFromUpstream`; `conflictPaths` |
26
+ | [`src/server/error-codes.ts`](src/server/error-codes.ts) | `ERROR_CODES` (centralised error-code registry — the exact `error` field strings on the wire), `ErrorCode` type |
26
27
  | [`src/server/tools.ts`](src/server/tools.ts) | `registerRethunkGitTools` — dispatches to `register*` below; `selectToolRegistrars(envValue, registrars)` — pure parse/filter fn for `RETHUNK_GIT_TOOLS` (reads env inside `registerRethunkGitTools`, not at module scope); `TOOL_REGISTRARS` ordered array |
27
28
  | [`src/server/git-status-tool.ts`](src/server/git-status-tool.ts) | `git_status` |
28
- | [`src/server/git-inventory-tool.ts`](src/server/git-inventory-tool.ts) | `git_inventory` |
29
+ | [`src/server/git-inventory-tool.ts`](src/server/git-inventory-tool.ts) | `git_inventory` — optional `compareRefs: { left, right }` ahead/behind between arbitrary local refs |
29
30
  | [`src/server/git-parity-tool.ts`](src/server/git-parity-tool.ts) | `git_parity` |
30
31
  | [`src/server/list-presets-tool.ts`](src/server/list-presets-tool.ts) | `list_presets` |
31
- | [`src/server/git-log-tool.ts`](src/server/git-log-tool.ts) | `git_log` — v3 JSON shape: `sha` (full), `workspaceRoot`, no `sha7`/`ageRelative`, optional `email` |
32
+ | [`src/server/git-log-tool.ts`](src/server/git-log-tool.ts) | `git_log` — v3 JSON shape: `sha` (full), `workspaceRoot`, no `sha7`/`ageRelative`, optional `email`; optional `follow` (exactly one `paths` entry) |
33
+ | [`src/server/git-grep-tool.ts`](src/server/git-grep-tool.ts) | `git_grep` — read-only fan-out pickaxe history search (`git log -S`/`-G` via required `pickaxe`, `commits[]` per root); content mode removed in v6 |
32
34
  | [`src/server/git-diff-summary-tool.ts`](src/server/git-diff-summary-tool.ts) | `git_diff_summary` — structured token-efficient diff viewer; read-only |
33
35
  | [`src/server/git-diff-tool.ts`](src/server/git-diff-tool.ts) | `git_diff` — raw scoped diff text; read-only |
34
36
  | [`src/server/git-show-tool.ts`](src/server/git-show-tool.ts) | `git_show` — inspect commit message + diff or file content at a ref; read-only |
35
- | [`src/server/git-stash-tool.ts`](src/server/git-stash-tool.ts) | `git_stash_list`, `git_stash_apply` |
36
- | [`src/server/git-fetch-tool.ts`](src/server/git-fetch-tool.ts) | `git_fetch` — fetch remote refs without touching the working tree |
37
+ | [`src/server/git-conflicts-tool.ts`](src/server/git-conflicts-tool.ts) | `git_conflicts` — inspect unresolved merge conflicts (state detection + per-file ours/theirs/base hunk parsing) |
38
+ | [`src/server/git-stash-tool.ts`](src/server/git-stash-tool.ts) | `git_stash_apply`, `git_stash_push` |
39
+ | [`src/server/git-blame-tool.ts`](src/server/git-blame-tool.ts) | `git_blame` — file authorship, run-length grouped by contiguous same-commit line runs; read-only |
37
40
  | [`src/server/git-tag-tool.ts`](src/server/git-tag-tool.ts) | `git_tag` — create/delete annotated or lightweight tags |
38
- | [`src/server/git-worktree-tool.ts`](src/server/git-worktree-tool.ts) | `git_worktree_list`, `git_worktree_add`, `git_worktree_remove` |
41
+ | [`src/server/git-branch-tool.ts`](src/server/git-branch-tool.ts) | `git_branch` create/delete/rename local branches; protected-name checks on every action |
42
+ | [`src/server/git-worktree-tool.ts`](src/server/git-worktree-tool.ts) | `git_worktree_add`, `git_worktree_remove` |
39
43
  | [`src/server/batch-commit-tool.ts`](src/server/batch-commit-tool.ts) | `batch_commit` — sequential multi-commit; mutating; exports `PushReport`, `runPushAfter` |
40
44
  | [`src/server/git-push-tool.ts`](src/server/git-push-tool.ts) | `git_push` — standalone push with optional upstream tracking |
41
45
  | [`src/server/git-merge-tool.ts`](src/server/git-merge-tool.ts) | `git_merge` — mutating |
42
- | [`src/server/git-cherry-pick-tool.ts`](src/server/git-cherry-pick-tool.ts) | `git_cherry_pick` — mutating |
46
+ | [`src/server/git-cherry-pick-tool.ts`](src/server/git-cherry-pick-tool.ts) | `git_cherry_pick` (`onConflict?: "abort"\|"pause"`), `git_cherry_pick_continue` (`action?: "continue"\|"abort"`, resumes/aborts a paused cherry-pick) both mutating |
43
47
  | [`src/server/git-reset-soft-tool.ts`](src/server/git-reset-soft-tool.ts) | `git_reset_soft` — soft-reset; mutating |
48
+ | [`src/server/git-revert-tool.ts`](src/server/git-revert-tool.ts) | `git_revert` — inverse-commit revert; mutating, non-history-rewriting |
44
49
  | [`src/server/presets-resource.ts`](src/server/presets-resource.ts) | `rethunk-git://presets` resource |
45
50
  | [`src/server/tool-parameter-schemas.ts`](src/server/tool-parameter-schemas.ts) | `buildToolParameterSchemaDocument`, `captureToolParameterSchemas`; backs `tool-parameters.schema.json` and published `schemas/*.json` snapshots |
51
+ | [`src/server/coverage.ts`](src/server/coverage.ts) | `parseAllFilesLineCoverage` — parses bun test coverage output for `coverage:check` CI gate |
46
52
  | [`src/repo-paths.ts`](src/repo-paths.ts) | `resolvePathForRepo`, `assertRelativePathUnderTop`, `isStrictlyUnderGitTop` |
47
53
 
48
54
  ## Changing contracts
49
55
 
50
56
  - **No banner paragraphs** in shipped docs. Use normal titles + cross-links.
51
- - **JSON format version** (currently `"5"`, exported as `MCP_JSON_FORMAT_VERSION` in `src/server.ts` and surfaced via the FastMCP `instructions` field — discoverable from the MCP `initialize` response): bump on incompatible JSON changes (renamed/nested/omitted fields). Document migration here + [docs/mcp-tools.md](docs/mcp-tools.md). v2 removed the `rethunkGitMcp` envelope; payloads are minified; optional fields omitted when empty/null/false. v3 changes in `git_log`: `sha7` → `sha` (full SHA), `workspace_root` → `workspaceRoot`, `ageRelative` removed, `email` omitted when empty. v4 changes: `git_blame` output run-length grouped (`lines[]` → `groups[]`, commit metadata once per contiguous run, `maxLines` cap with `truncated`/`omittedLines`); `git_diff_summary` per-file `truncated` omitted when false. v5 changes: `batch_commit` successful `results[*]` entries drop the echoed `message`/`files` (the caller already supplied both) — failing entries still carry them for diagnosis.
57
+ - **JSON format version** (currently `"6"`, exported as `MCP_JSON_FORMAT_VERSION` in `src/server.ts` and surfaced via the FastMCP `instructions` field — discoverable from the MCP `initialize` response): bump on incompatible JSON changes (renamed/nested/omitted fields). Document migration here + [docs/mcp-tools.md](docs/mcp-tools.md). v2 removed the `rethunkGitMcp` envelope; payloads are minified; optional fields omitted when empty/null/false. v3 changes in `git_log`: `sha7` → `sha` (full SHA), `workspace_root` → `workspaceRoot`, `ageRelative` removed, `email` omitted when empty. v4 changes: `git_blame` output run-length grouped (`lines[]` → `groups[]`, commit metadata once per contiguous run, `maxLines` cap with `truncated`/`omittedLines`); `git_diff_summary` per-file `truncated` omitted when false. v5 changes: `batch_commit` successful `results[*]` entries drop the echoed `message`/`files` (the caller already supplied both) — failing entries still carry them for diagnosis. v6 changes: tool surface reduced to 24 (seven thin read wrappers removed — see [CONTRIBUTING.md — Tool inclusion criteria](CONTRIBUTING.md#tool-inclusion-criteria)); `git_grep` is pickaxe-only (`pickaxe` required, `pattern`/`filesOnly` removed, results always `commits[]`).
52
58
  - **Preset file:** keep `presets.ts` Zod schemas aligned with [`git-mcp-presets.schema.json`](git-mcp-presets.schema.json).
53
59
  - **Public tool surface:** rename/add → update [docs/mcp-tools.md](docs/mcp-tools.md) + [README.md](README.md) (if mentioned), then regenerate the shipped schema artifacts (`tool-parameters.schema.json`, `schemas/index.json`, `schemas/*.json`). Install/client wiring → [docs/install.md](docs/install.md) only.
54
60
 
55
61
  ## Validate + CI
56
62
 
57
- Local: `bun run build` | `bun run lint` | `bun run schema:tools:check` | `bun run test`. CI ([`ci.yml`](.github/workflows/ci.yml)) runs same on PRs + `main` after `bun install --frozen-lockfile`, then `bun run test:coverage` + `bun run coverage:check`, and uploads prerelease `npm pack` artifact. Tag `v*.*.*` matching `package.json` version → [`release.yml`](.github/workflows/release.yml) publishes to GitHub Packages as `@rethunk-ai/mcp-multi-root-git` + cuts GitHub Release. npmjs publish is manual (see [HUMANS.md](HUMANS.md)).
63
+ Local: `bun run build` | `bun run lint` | `bun run typecheck` | `bun run schema:tools:check` | `bun run schema:individual:check` | `bun run test`. CI ([`ci.yml`](.github/workflows/ci.yml)) runs the same gates once each on PRs + `main` after `bun install --frozen-lockfile` (schema checks, lint, typecheck, `bun run test:coverage` + `bun run coverage:check`, build), and uploads prerelease `npm pack` artifact. Tag `v*.*.*` matching `package.json` version → [`release.yml`](.github/workflows/release.yml) publishes to GitHub Packages as `@rethunk-ai/mcp-multi-root-git` + cuts GitHub Release. npmjs publish is manual (see [HUMANS.md](HUMANS.md)).
58
64
 
59
- Optional [`.githooks/`](.githooks): `bun run setup-hooks` once per clone. pre-commit=`check`; pre-push=frozen install + build + check + test.
65
+ Optional [`.githooks/`](.githooks): `bun run setup-hooks` once per clone. pre-commit=`lint`; pre-push=frozen install + build + lint + test.
60
66
 
61
67
  Path confinement: [`src/repo-paths.ts`](src/repo-paths.ts) — extend tests when changing.
62
68
 
@@ -66,11 +72,11 @@ Rules for LLMs operating in or against this repository.
66
72
 
67
73
  **End-user Git/MCP preference** (status/log/diff/commits): **`~/.claude/CLAUDE.md`** § **Git & GitHub** — same policy when dogfooding this server from a harness.
68
74
 
69
- **One routing param per tool** — the five fan-out read tools (`git_status`, `git_inventory`, `git_parity`, `list_presets`, `git_log`) take polymorphic **`root`** (string = one repo, string[] = explicit repo list, `"*"` = every MCP root). Every other tool — including all mutating tools (`git_fetch`, `batch_commit`, `git_push`, `git_merge`, `git_cherry_pick`, `git_reset_soft`, `git_tag`, `git_stash_apply`, worktree add/remove) — takes only **`workspaceRoot`** (trusted operator input) and resolves via `requireSingleRepo`; multi-repo routing params are not accepted on writes.
75
+ **One routing param per tool** — the six fan-out read tools (`git_status`, `git_inventory`, `git_parity`, `list_presets`, `git_log`, `git_grep`) take polymorphic **`root`** (string = one repo, string[] = explicit repo list, `"*"` = every MCP root). Every other tool — including all mutating tools (`batch_commit`, `git_push`, `git_merge`, `git_cherry_pick`, `git_reset_soft`, `git_revert`, `git_tag`, `git_branch`, `git_stash_apply`, `git_stash_push`, worktree add/remove) — takes only **`workspaceRoot`** (trusted operator input) and resolves via `requireSingleRepo`; multi-repo routing params are not accepted on writes.
70
76
 
71
- **`batch_commit` atomic staging — single call per logical change** — Do NOT attempt incremental staging across multiple `batch_commit` calls. Each call is self-contained: it stages all files in all entries, commits them sequentially, and the moment the call completes, all commits have landed. Include all related files (for all related commit entries) in a single `batch_commit` call. A call cannot be resumed or extended by a later call — each is an independent transaction. If entry N fails, entries before N remain committed; entries after N are skipped (not rolled back).
77
+ **`batch_commit` atomic staging — single call per logical change** — Do NOT attempt incremental staging across multiple `batch_commit` calls. Each call is self-contained: it stages all files in all entries, commits them sequentially, and the moment the call completes, all commits have landed. Include all related files (for all related commit entries) in a single `batch_commit` call. A call cannot be resumed or extended by a later call — each is an independent transaction. If entry N fails, entries before N remain committed; entries after N are skipped (not rolled back). Each commit isolates the index: unrelated pre-staged paths are temporarily unstaged around an index-based `git commit` (pathspec/`--only` is avoided so hunk-level staging stays intact).
72
78
 
73
- **Protected branches are enforced by the server** — do not attempt `git_worktree_add` with a branch name matching `main`, `master`, `dev`, `develop`, `stable`, `trunk`, `prod`, `production`, `release*`, or `hotfix*`. The server rejects such calls.
79
+ **Protected branches are enforced by the server** — do not attempt `git_worktree_add` or `git_branch` (create/delete/rename, in any role) with a branch name in the protected set: exact matches `main`, `master`, `dev`, `develop`, `stable`, `trunk`, `prod`, `production`, `head`; or names matching `^(release|hotfix)[-/].+$` (e.g. `release/1.0`, `hotfix-123` — bare `release`/`hotfix` alone are not protected). The server rejects such calls.
74
80
 
75
81
  **Never force-push** — `git_push` has no force-push mode by design. `git_merge` with `strategy: "ff-only"` will fail cleanly rather than force.
76
82
 
package/CHANGELOG.md CHANGED
@@ -2,6 +2,101 @@
2
2
 
3
3
  All notable changes to `@rethunk/mcp-multi-root-git` are documented here. Format loosely follows [Keep a Changelog](https://keepachangelog.com); the project uses [Semantic Versioning](https://semver.org).
4
4
 
5
+ ## [Unreleased]
6
+
7
+ ## [4.0.0] — 2026-07-18
8
+
9
+ **Breaking release: the tool surface shrinks from 31 to 24.** Seven thin read wrappers are removed and `git_grep` is reduced to pickaxe-only; `MCP_JSON_FORMAT_VERSION` bumps to **`"6"`**. Rationale is codified in [CONTRIBUTING.md — Tool inclusion criteria](CONTRIBUTING.md#tool-inclusion-criteria): a tool must deliver enforced write policy, fan-out call compression, or large-output compression — wrappers over tiny-output git commands whose Bash equivalents are frictionless on modern harnesses no longer earn a schema slot.
10
+
11
+ ### Removed
12
+
13
+ - **`git_fetch`** — its case rested on sandboxed-Bash network friction that no longer exists; raw `git fetch` output is tiny.
14
+ - **`git_remote`, `git_describe`, `git_stash_list`, `git_reflog`, `git_branch_list`, `git_worktree_list`** — thin wrappers over ~50–200-token porcelain commands; zero per-call token savings while costing schema space in every non-deferred client session.
15
+ - **`git_grep` content mode** — `pattern`, `filesOnly`, and ref-tree content search removed; working-tree content search duplicated the native grep/rg tooling every MCP client ships. `pickaxe` is now **required**; results always carry `commits[]` (never `matches`/`files`).
16
+ - **Error codes** — `pattern_or_pickaxe_required`, `remote_list_failed`, `describe_failed`, `no_tag_found`, `unsafe_match_pattern`, `stash_list_failed`, `branch_list_failed`, `reflog_failed` leave the registry with their tools.
17
+
18
+ ### Changed
19
+
20
+ - **`MCP_JSON_FORMAT_VERSION` `"5"` → `"6"`** — migration notes in [AGENTS.md](AGENTS.md) and [docs/mcp-tools.md](docs/mcp-tools.md).
21
+ - **Docs** — all shipped docs (mcp-tools, install, HUMANS, SECURITY, AGENTS, README) aligned with the 24-tool surface; schema artifacts regenerated.
22
+ - **CONTRIBUTING.md** — new **Tool inclusion criteria** section governing future additions and removals; “Adding a git tool” now starts with the criteria gate.
23
+
24
+ ### Added
25
+
26
+ - **`git_cherry_pick` `onConflict`** — `"abort"` (default, unchanged) or `"pause"`: on conflict, leave the conflict and native cherry-pick sequencer state in place instead of rolling back the whole range (`conflict.paused: true`, applied-so-far count derived from the HEAD advance). Closes the abort-only gap that previously forced a shell fallback to resolve conflicts on a multi-commit cherry-pick range.
27
+ - **`git_cherry_pick` guard** — a second `git_cherry_pick` call while one is already in progress (e.g. left paused) now returns a structured `cherry_pick_in_progress` error (with the conflicting `commit`) instead of the generic `working_tree_dirty`.
28
+ - **`git_cherry_pick_continue`** — new tool to resume (`action: "continue"`, default) or roll back (`action: "abort"`) a cherry-pick left in progress. Stateless: probes `CHERRY_PICK_HEAD` live off `.git` rather than depending on the pausing call. `continue` requires no remaining unmerged paths (`cherry_pick_unresolved_paths` otherwise), then runs `git -c core.editor=true cherry-pick --continue`; if a later pick in the same range then conflicts, the response reports it the same shape as a paused `git_cherry_pick` call so the tool can be called again to walk the rest of the range. `abort` reuses `git_cherry_pick`'s hardened abort helper/reporting (`cherry_pick_abort_failed` on abort failure). Errors `no_cherry_pick_in_progress` when nothing is in progress.
29
+
30
+ ## [3.3.0] — 2026-07-18
31
+
32
+ Feature and hardening release: deepen fan-out reads (pickaxe / follow / compareRefs), tighten path and ref safety, isolate `batch_commit` staging, and align CI/release with the local `ci` script. Additive JSON / behavior only — `MCP_JSON_FORMAT_VERSION` stays at `"5"`.
33
+
34
+ ### Added
35
+
36
+ - **`RETHUNK_GIT_TOOLS=*`** — bare `*` is now an all-tools sentinel (registers every tool) instead of an unrecognized name that emptied the allowlist.
37
+ - **`git_grep` `pickaxe`** — optional `{ mode: "S"|"G", term }` history search (`git log -S`/`-G`) returning `commits[]` per root; `pattern` optional when pickaxe is set (`pattern_or_pickaxe_required` when neither is provided).
38
+ - **`git_log` `follow`** — optional rename-aware history (`git log --follow`); requires exactly one `paths` entry else `invalid_paths`.
39
+ - **`git_inventory` `compareRefs`** — optional `{ left, right }` ahead/behind between arbitrary local refs (independent of upstream); bad tokens → `unsafe_ref_token`.
40
+ - **`git_diff` `maxBytes`** — optional UTF-8 byte cap on returned diff text (default `512000`, range 1024–10000000); oversized output sets `truncated: true`.
41
+ - **Subprocess bounds** — `spawnGitAsync` stdout/stderr capped (default 16 MiB, `GIT_SUBPROCESS_MAX_BUFFER_BYTES`); overflow kills the child and returns `truncated: true`. Hung children escalate SIGTERM → SIGKILL after 2s. Sync helpers gain `GIT_SYNC_TIMEOUT_MS` (30s).
42
+ - **`stash_apply_failed`** — registered in the central error-code registry and emitted on failed stash apply/pop.
43
+
44
+ ### Changed
45
+
46
+ - **CI** — `ci.yml` check job runs `bun run ci` so workflow gates stay aligned with the local script (including coverage `pipefail` and schema checks).
47
+ - **CI** — workflow Bun pin aligned to `packageManager` **1.3.14**; GitHub Actions third-party action pins bumped (`5fa33af`).
48
+ - **Release** — tag publish workflow no longer duplicates build or test after `bun run ci`; verifies a `CHANGELOG.md` section exists for the tagged version before publish.
49
+ - **Release / CI pack** — workflows use `npm pack --ignore-scripts` and `npm publish --ignore-scripts` so `prepublishOnly` does not re-run the full CI suite.
50
+ - **Publish preflight** — coverage threshold checks delegate to `bun run coverage:check` instead of duplicating parser logic.
51
+ - **Schema artifacts** — capture now drives `registerRethunkGitTools` (same path as the live server) instead of a parallel `register*` list, so published parameter schemas cannot silently omit a newly added tool; `schema:individual` / `--check` hard-fails on missing capture schemas and rejects orphan `schemas/*.json` files left after renames/removals.
52
+ - **`root: "*"` fan-out** — capped at `MAX_ROOT_PATHS` (256); oversize sessions return `{ error: root_list_too_many, max, count }` like explicit root arrays. `RootPickSchema` no longer Zod-`.max`s the array (execute-path structured error instead of FastMCP `too_big`).
53
+ - **Docs** — align `docs/mcp-tools.md` with wire contracts: fan-out feature params (`pickaxe` / `follow` / `compareRefs`), `git_diff` `maxBytes`, merge/cherry-pick abort-failure codes, stash apply `conflictPaths` + `destructiveHint`, worktree path argv safety, `batch_commit` index isolation, and related error-table cleanups (`MCP_JSON_FORMAT_VERSION` unchanged).
54
+ - **`SECURITY.md`** — refresh threat model to the full 30-tool surface (read + mutate), including previously omitted mutators; disclose trusted-operator `workspaceRoot` / explicit `root` paths (no MCP-session whitelist this release); document shipped controls (realpath confinement, protected-branch enforcement, no force-push, argv-array spawn + ref validators); correct aspirational claims; add `RETHUNK_GIT_TOOLS` hardening, read-tool content-exfil risk, and soft-reset vs revert rewrite matrix.
55
+ - **`git_stash_apply`** — on failure emits `error: stash_apply_failed` plus optional `conflictPaths`; sets `destructiveHint: true` (pop can delete a stash entry).
56
+ - **`git_merge` / `git_cherry_pick`** — abort helpers surface `--abort` failure (`merge_abort_failed` / `rebase_abort_failed` / `cherry_pick_abort_failed`) instead of claiming a cleaned tree when abort itself failed. `git_cherry_pick` hard-caps expanded picks at 100 (`cherry_pick_too_many_commits`). `git_merge` `auto`/`rebase` rewrite the **source** branch tip when rebasing (documented; behavior unchanged). `deleteMergedWorktrees` skips protected **source branch names**.
57
+ - **`git_diff_summary`** — `totalFiles` / `totalAdditions` / `totalDeletions` count the post-filter set; `excludedFiles` includes exclude-pattern hits and `fileFilter` drops; `truncatedFiles` is the `maxFiles` omit count.
58
+ - **`git_show`** — `git_show_failed` includes `detail` (stderr/stdout trim).
59
+ - **`git_tag`** — tag names use `isSafeGitRefToken`; create `ref` uses `isSafeGitCommitIsh`; tool description is create/delete only.
60
+
61
+ ### Fixed
62
+
63
+ - **Path confinement** — fail closed when `realpath` cannot resolve a path used for repo bounds checks (no ENOENT false-accept via intermediate symlinks outside the git toplevel); missing leaves under a symlink alias of the same toplevel still resolve correctly for deletion staging.
64
+ - **`isSafeGitAncestorRef`** — now delegates to `isSafeGitCommitIsh`, rejecting `..` ranges, `.lock` suffixes, `//`, trailing `/`/`.`, mid-name `~`/`^`, and leading `+` (previously accepted by a looser charset-only check).
65
+ - **`isSafeGitRefToken`** — rejects leading `+` (git force-update refspec); closes force-push/fetch via `branch=+name` when callers use this validator.
66
+ - **`listWorktrees`** — returns `{ ok:false, detail }` on git failure instead of an empty array.
67
+ - **`batch_commit` index isolation** — unrelated pre-staged paths are temporarily unstaged around an index-based `git commit` (avoids pathspec/`--only`, which would squash hunk staging); mid-entry `stage_failed` unstages that entry's paths; dryRun restores the pre-call index via `write-tree`/`read-tree`; fails closed when index snapshot `write-tree` fails; rejects `.` / repo-root / directory pathspecs (`invalid_paths`); line-range staging uses `git diff --` / `--no-index` for untracked and rejects `from > to` (`invalid_line_range`).
68
+ - **`git_log` path confinement** — each `paths` entry gated with `resolvePathForRepo` / `assertRelativePathUnderTop`; escaping paths yield `path_escapes_repo` per root.
69
+ - **`git_parity` / `git_inventory` non-git wording** — `pairs[*].error` and inventory `skipReason` use plain description strings (no nested minified JSON).
70
+ - **`git_diff` / `git_diff_summary` / `git_conflicts`** — honor docs precedence (`base` over `staged`; ignore `head` without `base`); emit bare `unsafe_range_token`; fix rename numstat counts; report `path_escapes_repo` on escaped conflict paths; flag incomplete conflict markers as `truncated`.
71
+ - **`git_push` / `git_fetch`** — reject leading `+` force-update refspecs on branch args.
72
+ - **`git_worktree_add` / `git_worktree_remove`** — reject leading-dash and NUL paths; pass path after `--`; trim `branch`/`baseRef` before spawn; sibling worktrees outside toplevel remain allowed.
73
+
74
+ ## [3.2.0] — 2026-07-10
75
+
76
+ Feature release: seven new tools (23 → 30 registered; content search, conflict inspection, remote/describe read access, branch lifecycle, revert, and stash push), plus argv-safety and range-validation fixes across the diff/blame/show/diff-summary family. The **`root`** fan-out routing param now applies to **six** read tools (see 3.0.0 migration — `git_grep` joins `git_status`, `git_inventory`, `git_parity`, `list_presets`, and `git_log`). Additive JSON changes only — `MCP_JSON_FORMAT_VERSION` stays at `"5"`.
77
+
78
+ ### Added
79
+
80
+ - **`git_grep`** — read-only fan-out content search (`git grep -n -e <pattern>`) across one or more roots. Optional `ref` searches the tree at a commit/branch instead of the working tree; `paths` scopes the search (confined to the repo root); `ignoreCase`/`filesOnly`/`maxMatches` tune output. A clean "no matches" `git grep` exit is treated as success with an empty result set, not an error. Pickaxe/history content search (`-S`/`-G`) is out of scope.
81
+ - `git_conflicts` — read-only inspection of unresolved merge conflicts after `git_merge`/`git_cherry_pick` reports them: detects the in-progress operation (merge/cherry-pick/revert/rebase) and parses each conflicted file's `<<<<<<</|||||||/=======/>>>>>>>` markers into structured ours/theirs/base hunks with line numbers and branch labels, so an agent can resolve conflicts without a raw file read. `withHunks`/`maxLinesPerFile` control cost.
82
+ - `git_remote` — list configured git remotes (`git remote -v`), returning name/fetchUrl/pushUrl (pushUrl omitted when identical to fetchUrl).
83
+ - `git_describe` — describe a commit relative to the nearest reachable tag (`git describe --long`), returning parsed tag/distance/sha.
84
+ - `git_branch` — create, delete, or rename a local branch (`action: "create"|"delete"|"rename"`), with protected-branch rejection on every action (source, target, and rename endpoints) and `force` for `-D` deletes of unmerged branches.
85
+ - `git_revert` — creates new commit(s) that undo one or more source commits (`git revert`), applied in listed order; never rewrites history, unlike `git_reset_soft`. Refuses on a dirty tree; on conflict aborts and leaves the tree clean, reporting structured conflict paths. `noCommit` stages the revert(s) without committing; `mainline` selects the parent when reverting a merge commit.
86
+ - **`git_stash_push`** — stash working-tree changes (`git stash push`). Optional `message`, `includeUntracked` (-u), `keepIndex` (--keep-index), and `paths` to scope the stash. Returns the new stash ref/SHA/subject, or `{ stashed: false, reason: "no_local_changes" }` when there is nothing to stash.
87
+
88
+ ### Changed
89
+
90
+ - PR CI (`ci.yml`) now runs each gate exactly once (build/lint/typecheck/schema-check/test were previously duplicated between `bun run ci` and separate CI steps); the local `ci` script gained the `schema:individual:check` step CI already ran, so the two stay in sync. The tag release workflow (`release.yml`) still duplicated build and tests until the fixes documented under `[Unreleased]`.
91
+ - `git_fetch` moved from the read-only registrar group to the head of the mutating group (it updates refs) — the registration order now matches `tool-parameter-schemas.ts` and the docs table, which already classified it as mutating.
92
+ - Added test coverage for `presets-resource.ts`'s `rethunk-git://presets` resource load handler (valid preset, invalid JSON, missing file) — it previously had none.
93
+
94
+ ### Fixed
95
+
96
+ - **`git_diff`/`git_blame`/`git_show`** — `base`/`head`/`ref` now accept ancestor notation (`HEAD~3`, `main^2`, `v1.0.0~2^1`) consistently across all three tools via a shared `isSafeGitCommitIsh` validator. Previously `git_diff` and `git_blame` rejected the documented `HEAD~3` example outright; `git_show` accepted ancestor notation but via a looser validator that lacked the `..`/`.lock`/`//`/`@{` guards the other tools had (now hardened to match).
97
+ - **`git_diff_summary`/`git_cherry_pick` range endpoints** — `isSafeGitRangeToken` validated each side of an `A..B`/`A...B` range with the base ref-token check (no `~`/`^` support), so `git_diff_summary`'s own documented example `"HEAD~3..HEAD"` was rejected with `unsafe_range_token`. Endpoints (and the no-range single-ref fallthrough) now validate with `isSafeGitCommitIsh`; `git_diff_summary` also dropped its separate ad hoc range-parsing regex in favor of the shared validator.
98
+ - `git_fetch` — corrected a stale comment describing the non-porcelain fallback path as pending work; no behavior change.
99
+
5
100
  ## [3.1.0] — 2026-07-04
6
101
 
7
102
  Token-cost reduction in `batch_commit` output. JSON format version bumped **4 → 5**.
@@ -133,14 +228,11 @@ Bug-fix and documentation release; includes one new `git_log` output format.
133
228
  - **`git_cherry_pick`**: branch deletion after cherry-pick now uses patch-id equivalence by default so source branches with differing SHAs but identical content are correctly detected as merged. Pass `strictMergedRefEquality: true` for strict SHA-reachability semantics.
134
229
  - **`parseGitSubmodulePaths`**: non-regular files at `.gitmodules` (character devices, sockets — common in Claude Code / sandbox environments) are now rejected via `lstatSync().isFile()` before `readFileSync`, preventing `EACCES` errors.
135
230
 
136
- ### Tests
231
+ ### Changed
137
232
 
138
233
  - Integration tests added for `git_stash_list`, `git_stash_apply`, `git_fetch`, `git_status`, `git_inventory`, and `git_parity` execute paths.
139
234
  - `isContentEquivalentlyMergedInto` integration tests via real git repos with cherry-picked and diverged histories.
140
235
  - `batch_commit` dryRun tests covering deleted-file unstaging.
141
-
142
- ### Documentation
143
-
144
236
  - **`git_status`, `git_inventory`, `git_parity`, `list_presets`** now have complete parameter tables, JSON shape examples, and error code tables in `docs/mcp-tools.md`.
145
237
  - **`git_cherry_pick`** `strictMergedRefEquality` parameter added to docs; `deleteMergedBranches` description corrected (default is patch-id equivalence, not SHA-reachability).
146
238
  - **`batch_commit`** `files` parameter and `stage_failed` error code updated to document deleted-file staging path.
@@ -160,28 +252,22 @@ New git MCP tools, better `batch_commit` ergonomics, published schema coverage f
160
252
  - **`GIT_SUBPROCESS_PARALLELISM`** is now configurable via environment and clamps to a safe `2×CPU` maximum.
161
253
  - **`git_show`**, **`git_fetch`**, and **`git_tag`** now use the standard single-repo workspace pick (`workspaceRoot` / `rootIndex`) and omit multi-root-only parameters.
162
254
  - **Build / release tooling** now aligns on Bun `1.3.13`, updated dev dependencies, and the current prerelease tarball flow.
163
-
164
- ### Fixed
165
-
166
- - **MCP roots** — workspace root collection now scans active MCP sessions and dedupes `file://` roots instead of relying on a fixed server `cwd`.
167
- - **`git_push`** and **`batch_commit`** now surface raw git stdout/stderr on failure for easier recovery.
168
- - **Published schema snapshots** are now complete and in sync with the registered tool surface, including `schema:tools:check`.
169
- - **`publish:preflight`** now writes temporary coverage output under the platform temp directory instead of a hard-coded `/tmp` path.
170
-
171
- ### Documentation
172
-
173
255
  - **README / HUMANS / AGENTS / CONTRIBUTING / install docs** refreshed for the current tool surface, shipped schema artifacts, and contributor workflow.
174
256
  - **`SECURITY.md`** added with repository access, git-operation risk, and disclosure guidance.
175
257
  - **`docs/mcp-tools.md`** now documents the full tool surface, `batch_commit` atomic staging semantics, and the shipped schema artifacts.
176
258
  - **`TODO.md`** backlog entries now reflect genuine remaining gaps instead of listing already-implemented tools as missing.
177
259
  - **`specs/` scaffold** added with standard `active`, `done`, and `parked` layout for repo planning.
178
260
  - **CHANGELOG references** for `v2.3.2`–`v2.3.4` were restored.
179
-
180
- ### Tests
181
-
182
261
  - Coverage expanded across `list_presets`, `git_parity`, `git_cherry_pick`, `git_merge`, `git_show`, schema generation, and roots handling.
183
262
  - The shared git test harness now reuses repo-init / commit helpers and speeds up fixture setup.
184
263
 
264
+ ### Fixed
265
+
266
+ - **MCP roots** — workspace root collection now scans active MCP sessions and dedupes `file://` roots instead of relying on a fixed server `cwd`.
267
+ - **`git_push`** and **`batch_commit`** now surface raw git stdout/stderr on failure for easier recovery.
268
+ - **Published schema snapshots** are now complete and in sync with the registered tool surface, including `schema:tools:check`.
269
+ - **`publish:preflight`** now writes temporary coverage output under the platform temp directory instead of a hard-coded `/tmp` path.
270
+
185
271
  ## [2.3.4] — 2026-04-26
186
272
 
187
273
  Publication-prep patch for the `absoluteGitRoots` line.
@@ -195,7 +281,7 @@ Publication-prep patch for the `absoluteGitRoots` line.
195
281
 
196
282
  - **CI coverage gate** now checks `% Lines` from Bun's coverage table instead of accidentally reading `% Funcs`.
197
283
 
198
- ### Documentation
284
+ ### Changed
199
285
 
200
286
  - **`HUMANS.md`** — added sibling-clone `absoluteGitRoots` examples for `git_status` and `git_parity`.
201
287
  - **`docs/mcp-tools.md`** — clarified direct `git_push` use for already-committed work and `git_parity` sibling-clone batches.
@@ -212,12 +298,9 @@ Publication-prep patch for the `absoluteGitRoots` line.
212
298
 
213
299
  ## [2.3.2] — 2026-04-21
214
300
 
215
- ### CI
301
+ ### Changed
216
302
 
217
303
  - Coverage gate added: `bun run test:coverage` enforces an 80% line-coverage minimum in CI (`check` job).
218
-
219
- ### Documentation
220
-
221
304
  - **`CONTRIBUTING.md`** — new file; consolidates dev setup, hook table, commit conventions, CI description, PR checklist, path-confinement guidance, and how-to-add-a-tool guidance for mutating tools.
222
305
  - **`HUMANS.md`** — Development section replaced with a pointer to `CONTRIBUTING.md`; preset file, `git_not_found`, install reference, and publishing steps remain.
223
306
  - **`AGENTS.md`** — corrected pre-push hook description (missing `test` step) and updated canonical-docs link for dev/CI content.
@@ -227,7 +310,7 @@ Publication-prep patch for the `absoluteGitRoots` line.
227
310
 
228
311
  Documentation-only patch following the 2.3.0 release.
229
312
 
230
- ### Documentation
313
+ ### Changed
231
314
 
232
315
  - **`README.md`** — one-liner description updated to include `git_log`, `git_push`, `git_worktree_*`, and `git_reset_soft`.
233
316
  - **`HUMANS.md`** — opening line corrected from "Read-only MCP git tools" to "MCP git tools" (mutating operations have been present since v2.2.0).
@@ -260,22 +343,13 @@ Consumers using `format: "json"` with `git_log` must update field names. All oth
260
343
  - **Token efficiency:** `readOnlyHint: true` added to `git_status`, `git_inventory`, `git_parity`, and `list_presets`. "See docs/mcp-tools.md" suffix dropped from all 9 tool descriptions; descriptions are now self-contained.
261
344
  - **`WorkspacePickSchema`** — `rootIndex` and `allWorkspaceRoots` parameters carry inline descriptions so LLMs can pick them without consulting external docs.
262
345
  - **`git_merge`** — protected-branch list in the description collapsed to a single canonical reference (was duplicated inline).
263
-
264
- ### Internal
265
-
266
346
  - `requireSingleRepo` helper extracted to `roots.ts`; replaces copy-paste preludes across `batch_commit`, `git_diff_summary`, `git_merge`, `git_cherry_pick`, `git_push`, `git_reset_soft`, and all `git_worktree_*`.
267
347
  - `conflictPaths` extracted from `git-merge-tool.ts` to `git-refs.ts`; shared by `git_merge` and `git_cherry_pick`.
268
348
  - `inferRemoteFromUpstream` extracted to `git-refs.ts`; shared by `runPushAfter` (`batch_commit`) and `git_push`.
269
349
  - `isWorkingTreeClean` used consistently everywhere (was inlined in `git_reset_soft`).
270
-
271
- ### Tests
272
-
273
350
  - Coverage: **88.6% lines / 92.8% functions** (up from 69.9% / 71.4%).
274
351
  - 262 tests across 15 files (up from 134 across 7 files).
275
352
  - New test files: `presets.test.ts`, `inventory.test.ts`, `git-utils.test.ts`, `json.test.ts`, `git-reset-soft-tool.test.ts`, `git-push-tool.test.ts`, `git-worktree-tool.test.ts`, `roots.test.ts`. `git-refs.test.ts` extended with `isSafeGitAncestorRef` cases.
276
-
277
- ### Documentation
278
-
279
353
  - `docs/mcp-tools.md` — all new tools documented with parameter tables, JSON shapes, and error-code tables.
280
354
  - `AGENTS.md` — implementation map updated with all new and refactored modules.
281
355
 
@@ -293,31 +367,32 @@ Mutating git operations: merge, cherry-pick, and optional push-after for `batch_
293
367
 
294
368
  - **Internal** — shared ref/branch helpers extracted to a common module; merge/cherry-pick/batch-commit implementations now reuse the same dirty-tree guards, protected-name checks, and upstream detection.
295
369
  - **Tooling** — Biome upgraded to 2.4.11; dev dependency refresh.
296
-
297
- ### Documentation
298
-
299
370
  - `docs/mcp-tools.md` — `git_merge` and `git_cherry_pick` sections with parameters, JSON shape, and error codes; `batch_commit` `push: "after"` option documented.
300
371
 
301
- ## [2.1.0] — prior
372
+ ## [2.1.0] — 2026-04-12
302
373
 
303
374
  - Added `git_log` — path-filtered, time-windowed log across workspace roots.
304
375
  - Added `git_diff_summary` — structured, token-efficient diff viewer with per-file truncation and default exclusions (lock files, `dist`, vendor).
305
376
  - Added `batch_commit` — sequential multi-commit tool. Mutating, not idempotent.
306
377
  - Test harness: fake-server duck-type lets unit tests drive the full FastMCP execute path without a live transport.
307
378
 
308
- ## [2.0.1] — prior
379
+ ## [2.0.1] — 2026-04-11
309
380
 
310
381
  - Bug fixes and internal cleanup; no public tool-surface change.
311
382
 
312
- ## [2.0.0] — prior
383
+ ## [2.0.0] — 2026-04-11
313
384
 
314
385
  - **Breaking:** JSON response envelope removed. `MCP_JSON_FORMAT_VERSION` now `"2"`; payloads are minified, and optional fields are omitted when empty / `null` / `false` (consumers test for presence, not equality to `null`).
315
386
  - Initial preset file schema (`.rethunk/git-mcp-presets.json`) with wrapped and legacy-map layouts.
316
387
 
317
- ## [1.0.0] — prior
388
+ ## [1.0.0] — 2026-04-05
318
389
 
319
390
  - Initial release: `git_status`, `git_inventory`, `git_parity`, `list_presets`.
320
391
 
392
+ [3.3.0]: https://github.com/Rethunk-AI/mcp-multi-root-git/releases/tag/v3.3.0
393
+ [3.2.0]: https://github.com/Rethunk-AI/mcp-multi-root-git/releases/tag/v3.2.0
394
+ [3.1.0]: https://github.com/Rethunk-AI/mcp-multi-root-git/releases/tag/v3.1.0
395
+ [3.0.0]: https://github.com/Rethunk-AI/mcp-multi-root-git/releases/tag/v3.0.0
321
396
  [2.9.1]: https://github.com/Rethunk-AI/mcp-multi-root-git/releases/tag/v2.9.1
322
397
  [2.9.0]: https://github.com/Rethunk-AI/mcp-multi-root-git/releases/tag/v2.9.0
323
398
  [2.8.1]: https://github.com/Rethunk-AI/mcp-multi-root-git/releases/tag/v2.8.1
package/HUMANS.md CHANGED
@@ -53,30 +53,12 @@ With **multiple MCP file roots**, the server picks a root whose git toplevel def
53
53
 
54
54
  If you installed from **GitHub Packages**, use **`./node_modules/@rethunk-ai/mcp-multi-root-git/git-mcp-presets.schema.json`** in **`$schema`** instead (see [docs/install.md](docs/install.md#github-packages)).
55
55
 
56
- ## Tool parameter schema artifact
57
-
58
- The package ships **`tool-parameters.schema.json`**, a generated JSON Schema snapshot of every registered tool's parameter schema, plus the published **`schemas/`** directory with **`schemas/index.json`** and one per-tool JSON Schema file. Maintainers regenerate them with:
59
-
60
- ```bash
61
- bun run schema:tools
62
- bun run schema:individual
63
- ```
64
-
65
- CI and `prepublishOnly` use:
66
-
67
- ```bash
68
- bun run schema:tools:check
69
- bun run schema:individual
70
- ```
71
-
72
- These artifacts are for inspection, drift checks, code generation, and clients that want offline schema snapshots. Runtime MCP schema discovery remains the source of truth for connected clients.
73
-
74
56
  **Layouts:**
75
57
 
76
58
  1. **Wrapped (recommended):** `{ "schemaVersion": "1", "presets": { "<name>": { ... } } }`.
77
59
  2. **Legacy map:** `{ "<preset-name>": { ... } }` with optional top-level `"schemaVersion"` and `"$schema"`.
78
60
 
79
- Invalid JSON or schema errors return **`preset_file_invalid`** (not a silent empty result).
61
+ Invalid JSON returns **`invalid_json`**; schema violations return **`invalid_schema`** (not a silent empty result).
80
62
 
81
63
  ### Preset entry (quick reference)
82
64
 
@@ -92,9 +74,15 @@ Invalid JSON or schema errors return **`preset_file_invalid`** (not a silent emp
92
74
 
93
75
  Relative preset paths must stay inside the git toplevel; escapes are rejected.
94
76
 
77
+ ## Tool parameter schema artifact
78
+
79
+ The package ships **`tool-parameters.schema.json`**, a generated JSON Schema snapshot of every registered tool's parameter schema, plus the published **`schemas/`** directory with **`schemas/index.json`** and one per-tool JSON Schema file. These artifacts are for inspection, drift checks, code generation, and clients that want offline schema snapshots. Runtime MCP schema discovery remains the source of truth for connected clients.
80
+
81
+ Maintainers regenerate and verify them as part of [CONTRIBUTING.md](CONTRIBUTING.md) development setup.
82
+
95
83
  ## Sibling clone batches
96
84
 
97
- Pass a **`root` array** on the fan-out read tools (`git_status`, `git_inventory`, `git_parity`, `list_presets`, `git_log`) when you want one read-only call to inspect independent sibling clones that are not all exposed as MCP workspace roots. This is most useful from agent workflows rooted at a parent directory or when an MCP client exposes only one repo root.
85
+ Pass a **`root` array** on the fan-out read tools (`git_status`, `git_inventory`, `git_parity`, `list_presets`, `git_log`, `git_grep`) when you want one read-only call to inspect independent sibling clones that are not all exposed as MCP workspace roots. This is most useful from agent workflows rooted at a parent directory or when an MCP client exposes only one repo root.
98
86
 
99
87
  Example `git_status` batch:
100
88
 
@@ -125,23 +113,15 @@ Multi-repo routing is read-only by design. Mutating tools such as **`batch_commi
125
113
 
126
114
  ## Prerequisites
127
115
 
128
- - **Git** on `PATH` (`git --version`). Missing git → all tools return `git_not_found`.
129
- - **Node.js ≥ 22** (for `npx`) or **Bun** (for `bunx`). See `engines` in `package.json`.
130
- - No other runtime dependencies; the server is a self-contained MCP stdio process.
116
+ Git, Node.js, and Bun version requirements: **[docs/install.md](docs/install.md#prerequisites)**.
131
117
 
132
118
  ## Running the server
133
119
 
134
- The server is a **stdio** MCP process — your MCP client starts it. You do not run it directly. Quick start:
120
+ The server is a **stdio** MCP process — your MCP client starts it. You do not run it directly.
135
121
 
136
- ```bash
137
- # npmjs (public, may lag tags)
138
- npx -y @rethunk/mcp-multi-root-git
139
-
140
- # GitHub Packages (CI-aligned, every tag)
141
- npx -y @rethunk-ai/mcp-multi-root-git # requires ~/.npmrc with @rethunk-ai registry token
142
- ```
122
+ Install, per-client MCP wiring, and environment variables (`RETHUNK_GIT_TOOLS`, `GIT_SUBPROCESS_PARALLELISM`, `GIT_SUBPROCESS_TIMEOUT_MS`): **[docs/install.md](docs/install.md)** — see [Environment variables](docs/install.md#environment-variables).
143
123
 
144
- Add the server to your MCP client config under a stable name (e.g. `rethunk-git`). Full per-client config (Cursor, VS Code, Claude Desktop, Zed): **[docs/install.md](docs/install.md)**.
124
+ Add the server to your MCP client config under a stable name (e.g. `rethunk-git`).
145
125
 
146
126
  ## Common operations
147
127
 
@@ -154,26 +134,27 @@ Call tools by their registered id (prefix depends on client config name):
154
134
  | Compare HEAD between path pairs | `git_parity` |
155
135
  | List preset names | `list_presets` |
156
136
  | View commit log | `git_log` |
137
+ | Pickaxe history search (which commits changed a term) | `git_grep` |
157
138
  | View structured diff | `git_diff_summary` |
158
139
  | View raw diff text | `git_diff` |
159
140
  | Inspect commit content | `git_show` |
160
- | List stashes | `git_stash_list` |
141
+ | Blame lines in a file | `git_blame` |
142
+ | Inspect unresolved merge conflicts | `git_conflicts` |
143
+ | Stash working-tree changes | `git_stash_push` |
161
144
  | Apply or pop a stash | `git_stash_apply` |
162
- | Fetch remote refs | `git_fetch` |
163
145
  | Create commits | `batch_commit` |
164
146
  | Push a branch | `git_push` |
165
147
  | Create or delete tags | `git_tag` |
148
+ | Create, delete, or rename branches | `git_branch` |
166
149
  | Merge branches | `git_merge` |
167
150
  | Cherry-pick commits | `git_cherry_pick` |
151
+ | Resume/abort a paused cherry-pick | `git_cherry_pick_continue` |
152
+ | Revert commits | `git_revert` |
168
153
  | Soft-reset HEAD | `git_reset_soft` |
169
- | Manage worktrees | `git_worktree_list` / `git_worktree_add` / `git_worktree_remove` |
154
+ | Manage worktrees | `git_worktree_add` / `git_worktree_remove` |
170
155
 
171
156
  Full parameter tables and JSON shapes: **[docs/mcp-tools.md](docs/mcp-tools.md)**.
172
157
 
173
- ## `git_not_found`
174
-
175
- If **`git`** is missing or not runnable, tools and the presets resource respond with **`git_not_found`**. Ensure `git` is on `PATH` in the environment that launches the MCP server.
176
-
177
158
  ## Installation
178
159
 
179
160
  **Full install instructions and MCP client wiring:** **[docs/install.md](docs/install.md)**.
package/README.md CHANGED
@@ -1,12 +1,35 @@
1
- # @rethunk/mcp-multi-root-git
1
+ <h1 align="center">@rethunk/mcp-multi-root-git</h1>
2
+
3
+ <div align="center">
2
4
 
3
5
  [![CI](https://github.com/Rethunk-AI/mcp-multi-root-git/actions/workflows/ci.yml/badge.svg)](https://github.com/Rethunk-AI/mcp-multi-root-git/actions/workflows/ci.yml)
4
6
  [![npm version](https://img.shields.io/npm/v/%40rethunk%2Fmcp-multi-root-git.svg)](https://www.npmjs.com/package/@rethunk/mcp-multi-root-git)
5
7
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
6
8
 
7
- **git** tools over MCP: status, multi-root inventory, `HEAD` parity, presets, logs, structured and raw diff views, commit inspection, fetch, stash, tag, batch commit, push, merge, cherry-pick, worktrees, soft-reset, blame, branch listing, and reflog. **Install and MCP client wiring:** **[docs/install.md](docs/install.md)** only — do not duplicate those steps elsewhere.
9
+ </div>
10
+
11
+ ---
12
+
13
+ MCP **stdio** server exposing **git** tools to AI clients — status, multi-root inventory, parity checks, presets, logs, pickaxe history search, diffs, commits, push, merge, worktrees, and more. One server process spans every workspace root the client registers at `initialize`.
14
+
15
+ **Repository:** [github.com/Rethunk-AI/mcp-multi-root-git](https://github.com/Rethunk-AI/mcp-multi-root-git) · **npmjs (manual releases):** [`@rethunk/mcp-multi-root-git`](https://www.npmjs.com/package/@rethunk/mcp-multi-root-git) · **GitHub Packages (CI on each tag):** [`@rethunk-ai/mcp-multi-root-git`](https://github.com/Rethunk-AI/mcp-multi-root-git/packages) — see [docs/install.md](docs/install.md) and [HUMANS.md#publishing](HUMANS.md#publishing).
16
+
17
+ ## Quick start
18
+
19
+ ```bash
20
+ npx -y @rethunk/mcp-multi-root-git
21
+ ```
8
22
 
9
- **Repository:** [github.com/Rethunk-AI/mcp-multi-root-git](https://github.com/Rethunk-AI/mcp-multi-root-git) · **npmjs (manual releases):** [`@rethunk/mcp-multi-root-git`](https://www.npmjs.com/package/@rethunk/mcp-multi-root-git) · **GitHub Packages (CI on each tag):** [`@rethunk-ai/mcp-multi-root-git`](https://github.com/Rethunk-AI/mcp-multi-root-git/packages) see [docs/install.md](docs/install.md) and [HUMANS.md](HUMANS.md) Publishing.
23
+ Full install, prerequisites, and MCP client wiring: [HUMANS.md](HUMANS.md) and [docs/install.md](docs/install.md).
24
+
25
+ ## Highlights
26
+
27
+ - **Multi-root fan-out** — `git_status`, `git_inventory`, `git_parity`, `git_log`, and `git_grep` route across one repo, an explicit list, or every MCP root (`"*"`)
28
+ - **Structured JSON** — minified tool payloads (format version 5); optional fields omitted when empty
29
+ - **Presets** — `.rethunk/git-mcp-presets.json` at the git toplevel for nested roots and parity pairs
30
+ - **Read and write git** — diffs, blame, conflicts, stash, `batch_commit`, push, merge, cherry-pick, revert, branches, tags, and worktrees (writes take a single `workspaceRoot`)
31
+ - **Deliberately small surface** — 24 tools; every tool must clear the [inclusion criteria](CONTRIBUTING.md#tool-inclusion-criteria) (enforced policy on writes, fan-out call compression, or large-output compression)
32
+ - **Published schemas** — `schemas/index.json` and per-tool JSON Schema files for offline validation
10
33
 
11
34
  ## Documentation
12
35
 
@@ -19,16 +42,10 @@
19
42
  | **[AGENTS.md](AGENTS.md)** | Contributors: implementation map ([`src/server/`](src/server/) + entry [`src/server.ts`](src/server.ts)), contract bumps, CI |
20
43
  | **[CONTRIBUTING.md](CONTRIBUTING.md)** | Dev setup, hooks, commit conventions, release checks, and how to add tools |
21
44
  | **[SECURITY.md](SECURITY.md)** | Disclosure policy, threat model, and repository safety guidance |
45
+ | **[CHANGELOG.md](CHANGELOG.md)** | Release notes (Keep a Changelog style) |
46
+ | **[TODO.md](TODO.md)** | Future backlog and planned work |
22
47
  | **[specs/README.md](specs/README.md)** | Active / done / parked specification layout used for repo planning |
23
48
 
24
- **Tools at a glance:** see the table in **[docs/mcp-tools.md](docs/mcp-tools.md)**.
25
-
26
- ## JSON Schemas
27
-
28
- All MCP tool parameters are published as JSON Schema (draft 2020-12) documents in the **[`schemas/`](schemas/)** directory. Tools and validators can use these schemas to validate tool input arguments without reading source code.
29
-
30
- **Schema files:**
31
- - `schemas/index.json` — Index of all 20 tool schemas with file paths
32
- - `schemas/{tool_name}.json` — Individual schema for each tool (e.g., `schemas/batch_commit.json`, `schemas/git_status.json`)
49
+ ## License
33
50
 
34
- For programmatic use, read `schemas/index.json` to discover available tools and their schema files.
51
+ Copyright (c) 2026 Rethunk Tech. Licensed under the MIT License see [LICENSE](LICENSE) for details.
@@ -1,26 +1,70 @@
1
1
  import { realpathSync } from "node:fs";
2
- import { isAbsolute, relative, resolve } from "node:path";
3
- function realPathOrSelf(p) {
4
- try {
5
- return realpathSync(p);
6
- }
7
- catch {
8
- return p;
2
+ import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
3
+ function isErrno(err, codes) {
4
+ const code = err?.code;
5
+ return typeof code === "string" && codes.includes(code);
6
+ }
7
+ /**
8
+ * Canonicalize for confinement: realpath the longest existing prefix, then
9
+ * append any missing trailing segments lexically.
10
+ *
11
+ * Fail closed (`null`) when realpath fails for a reason other than a missing
12
+ * path component (ENOENT/ENOTDIR), or when nothing on the chain resolves —
13
+ * never return the unresolved input (avoids symlink+missing-leaf false-accept).
14
+ */
15
+ function realpathForConfinement(absPath) {
16
+ const abs = resolve(absPath);
17
+ const missing = [];
18
+ let cur = abs;
19
+ for (;;) {
20
+ try {
21
+ const resolved = realpathSync(cur);
22
+ return missing.length === 0 ? resolved : join(resolved, ...missing);
23
+ }
24
+ catch (err) {
25
+ if (!isErrno(err, ["ENOENT", "ENOTDIR"])) {
26
+ return null;
27
+ }
28
+ const parent = dirname(cur);
29
+ if (parent === cur) {
30
+ return null;
31
+ }
32
+ missing.unshift(basename(cur));
33
+ cur = parent;
34
+ }
9
35
  }
10
36
  }
11
- export function isStrictlyUnderGitTop(absPath, gitTop) {
12
- const absR = realPathOrSelf(resolve(absPath));
13
- const topR = realPathOrSelf(resolve(gitTop));
14
- const rel = relative(topR, absR);
37
+ /** True when `relative(top, path)` is inside or equal to top (not `..` / `../…`). */
38
+ function isRelativeInsideTop(rel) {
15
39
  if (rel === "")
16
40
  return true;
17
- return !rel.startsWith("..") && !isAbsolute(rel);
41
+ // Exact `..` or `../` / `..\` only — do not treat a segment named `..foo` as escape.
42
+ if (rel === ".." || rel.startsWith(`..${sep}`))
43
+ return false;
44
+ return !isAbsolute(rel);
45
+ }
46
+ export function isStrictlyUnderGitTop(absPath, gitTop) {
47
+ let topR;
48
+ try {
49
+ topR = realpathSync(resolve(gitTop));
50
+ }
51
+ catch {
52
+ return false;
53
+ }
54
+ const absR = realpathForConfinement(absPath);
55
+ if (absR === null)
56
+ return false;
57
+ return isRelativeInsideTop(relative(topR, absR));
18
58
  }
19
59
  export function resolvePathForRepo(p, gitTop) {
20
60
  const t = p.trim();
21
61
  return isAbsolute(t) ? resolve(t) : resolve(gitTop, t);
22
62
  }
23
63
  /** Resolved path must lie inside git toplevel (relative or absolute user input). */
24
- export function assertRelativePathUnderTop(_relPath, absResolved, gitTop) {
64
+ export function assertRelativePathUnderTop(userPath, absResolved, gitTop) {
65
+ // Use the caller-supplied path: reject mismatched abs vs resolvePathForRepo.
66
+ if (resolvePathForRepo(userPath, gitTop) !== absResolved) {
67
+ return false;
68
+ }
25
69
  return isStrictlyUnderGitTop(absResolved, gitTop);
26
70
  }