Repository navigation
Polish Phase 2 docs, fix Windows CI, bump to 26.8.5 - #4
Merged
Merged
Conversation
Records the finalized architecture for context branching/diff/merge (data model, git integration, merge engine, MCP/plugin surface, and build sequence) in docs/CONTEXT_VERSIONING.md, and updates ROADMAP.md's Phase 2 section to link it and track per-item status instead of a vague bullet list.
Introduces src/versioning/ as a self-contained package implementing the checkpoint-object/facts/branch schema from docs/CONTEXT_VERSIONING.md: content-addressed objects (objects.ts), pure fact-delta application (facts.ts), branch directory management (branches.ts), git branch mapping (gitmap.ts), and recordCheckpoint() tying them together (checkpoint.ts). All paths are centralized in paths.ts so the on-disk layout can change in one place. This is library code only, isolated from Phase 1: nothing here reads or writes context.md or sessions/*.json, and no existing file was touched. No CLI command wires it up yet — that starts with module 2 (brg branch / brg checkout).
Adds .claude/, *.local.md, NOTES.md, SCRATCH.md, and TODO.local.md alongside the existing CLAUDE.md entry, so AI-assistant working notes and design-discussion scratch files never get committed or pushed to the remote.
…ule 2) brg branch <name> [--intent] wraps git branch and creates a matching brg branch (.brg/branches/<name>/) recorded against the sha it was created from; intent is required, prompted interactively if not passed via flag. brg checkout <name> wraps git checkout and restores that branch's intent/summary/recent-checkpoints, or checks out cleanly with a note when the git branch has no matching brg context. brg init now also installs a post-checkout hook (idempotent, never overwrites a foreign hook) that flags plain `git checkout` usage outside brg landing on a branch with no brg context yet. This is a name-only MVP: the design doc's "all flags passed through" is not yet implemented, only <name>. Built on top of module 1's versioning data model without modifying it; the only Phase 1 file touched is init.ts, which gets one additive call.
Users running brg don't need to know its internal storage is a ".brg/" directory. Reworded the "already initialized", "not initialized yet", and "branch already tracked" messages in init, checkpoint, log, context, and branch to describe state in plain terms instead of referencing the directory path.
Adds 21 more tests across objects/facts/branches/gitmap/checkpoint (merge-checkpoint hashing, corrupt-file tolerance, multi-branch isolation, fact accumulation across checkpoints, mapping overwrites), taking the versioning suite from 20 to 41 tests. Found in the process: readLog threw on a corrupt log.jsonl line instead of degrading gracefully like readFacts/readGitMap already do — fixed to match. Also updates CHANGELOG.md's Unreleased section for the feature/phase-2 work so far (design doc, versioning data model, brg branch/checkout, message wording fix).
diffFacts() in src/versioning/diff.ts is a pure structural comparison of two fact arrays — no filesystem, no LLM calls, no branch concept of its own, so brg merge's conflict detection can reuse it later. Same (subject, relation) with different objects on each side is reported as "changed" rather than add+remove, matching the candidate-conflict definition in the design doc. brg diff <branchA> <branchB> wraps it for the two branches' current head fact sets. Checkpoint-level diff (via history replay) is scoped out of this module per discussion — branch-vs-branch only for now.
mergeFacts() in src/versioning/merge.ts is a pure union merge: facts unique to either side or identical on both merge automatically; a (subject, relation) key with different objects on each side is held out as a candidate conflict rather than silently overwritten. computeFactsDelta() (facts.ts) expresses a merge's outcome as add/ remove ops against the target's pre-merge state, and recordMergeCheckpoint() (checkpoint.ts) writes that as a two-parent checkpoint object per the design doc's parents:[idA,idB] schema. brg merge <source> merges into the brg branch matching the currently checked-out git branch. Context-only for now — no git merge is run, matching the discussed scope. Conflicts resolve interactively by default; --auto tries the active tool's summarizeViaSelf as an LLM arbiter first, falling back to the interactive prompt per-conflict when unavailable or its reply doesn't parse as target/source/both. Both resolvers are injected via an optional MergeDependencies argument so the command's branching logic is tested without mocking stdin or shelling out to a real tool CLI. 24 new tests covering the merge engine, delta computation, checkpoint recording, and all command error paths plus both resolver flows.
Previously brg branch always required creating a matching git branch — there was no way to fork brg context to explore a different angle while staying on the same git branch, which defeats a real use case (discussing an alternate approach without polluting the existing context thread). brg branch now always creates the brg context branch first, unconditionally. A matching git branch is then asked about interactively (accept to create one, same name by default or a different one you type; decline to keep it context-only), and skipped automatically outside a git repo. brg checkout only runs `git checkout` if the brg branch has a linked git branch (per refs/git-map.json); otherwise it switches brg's active context in place, leaving the checked-out git branch untouched. Falls back to plain `git checkout` with a note if <name> isn't a tracked brg branch at all. This breaks the assumption that "which brg branch is current" can be derived by asking git and reverse-looking-up git-map — a context-only branch has nothing for git to tell us. Added refs/active (via new versioning/active.ts) as the explicit source of truth instead, set by brg branch/brg checkout. brg merge now resolves its target through this pointer instead of the checked-out git branch. brg init seeds a default active branch (named after the checked-out git branch, or "main" outside a repo) so there's never a project with no active branch; idempotently backfilled on re-run for projects initialized before this existed. docs/CONTEXT_VERSIONING.md updated to document git mapping as optional per branch and the new active-branch file. Both resolvers in commands/branch.ts (confirmGitBranch) are injectable, matching the DI pattern already used in commands/merge.ts, so tests drive the interactive prompt deterministically. 14 new/rewritten tests across branch-checkout.test.ts, merge.test.ts, and init.test.ts cover the decline/accept/rename paths, context-only branch switching, and merge target resolution via the active pointer instead of git.
versioning/graph.ts implements a lane-based ASCII graph layout, the same family of algorithm as git log --graph: each lane is a column awaiting a specific checkpoint id, converging when a shared ancestor is reached and opening a new lane on a merge's second parent. Simpler than git's own algorithm (no multi-step diagonal compaction) but exact for what this app actually produces, since a merge checkpoint's parents field is always exactly two ids, never an octopus merge. collectGraphNodes walks every branch's head backward through parent/ parents links, deduped by id, so history from a branch that's since been merged (and possibly deleted) still renders correctly as long as a surviving checkpoint still points at it. brg log --graph is a flag on the existing brg log command rather than a new one — without it, brg log behaves exactly as before (Phase 1's session-based timeline, untouched). 13 new tests, including hand-traced expected output for linear history, two never-converging branches, and a merge's connector line and both parent lineages. Flagged in ROADMAP.md: brg checkpoint (Phase 1) still doesn't call recordCheckpoint, so nothing in the CLI creates a branch's first checkpoint object except brg merge itself, which requires one to already exist. brg diff/merge/log --graph have nothing to work with in normal use until that's wired up — left as a known, deliberate gap per module 1's original note, not assumed or silently fixed here.
Phase 2's context-versioning design is the settled decision going forward, so brg checkpoint should be based on it rather than leaving it a separate, unpopulated system. core/checkpoint.ts's performCheckpoint (the single path shared by the explicit brg checkpoint command and brg switch's auto-checkpoint) now also calls versioning/checkpoint.ts's recordCheckpoint against the currently active brg branch, after its existing context.md/session write. facts_delta is always empty for now — structured fact extraction is a separate, later, LLM-driven addition per the design doc — so this just guarantees every branch has a real checkpoint chain (message, timestamp, tool, parent links) for brg diff/merge/log --graph to work with, without requiring a separate step. The versioning write is best-effort: if there's no active branch (unreachable in normal use since brg init always seeds one), it's skipped rather than failing the primary checkpoint. 9 new tests across checkpoint.test.ts and switch.test.ts, verified end-to-end with a real CLI build: brg checkpoint followed by brg log --graph now shows real history instead of "no checkpoints recorded."
New brg mcp command starts an MCP server (@modelcontextprotocol/sdk, zod — both new dependencies, needed directly for this module) over stdio, the standard way a local MCP server is consumed: a client spawns the command as a subprocess and talks to it over stdin/stdout. Four tools per the design doc, each a thin wrapper in src/mcp/tools.ts over the same versioning data brg branch/diff/merge/checkpoint already use — no separate data path: - context_search — a branch's intent/summary/facts/recent checkpoints, defaults to the active branch, optional query substring-filters facts - context_commit — record a checkpoint (same message-only scope as brg checkpoint's own wiring) - context_diff — same structural diff engine as brg diff - context_merge — auto-merges anything with no conflict; a real conflict is NOT committed, it's returned as data (target/source values per subject+relation) since there's no TTY over MCP for the interactive prompt brg merge uses. The calling agent decides and calls again with `resolutions` filled in to finish — stateless on brg's side, the second call just re-runs the union merge. src/mcp/tools.ts holds pure request/response logic (no MCP SDK types), testable directly; commands/mcp.ts is the only place wiring it to the SDK. Split buildMcpServer() out from mcpCommand() so tests can connect via InMemoryTransport instead of spawning a real subprocess. 24 new tests: 19 unit tests against src/mcp/tools.ts directly, 5 end-to-end tests driving the real MCP Client/JSON-RPC protocol over InMemoryTransport. Also manually verified with a real `brg mcp` subprocess and StdioClientTransport — listTools, context_commit, and context_search all round-tripped correctly.
…run on versioning data
context.md and sessions/*.json were fully redundant with the
branch-scoped versioning data model once the underlying architecture
question got asked directly: since Phase 2 is the settled design, why
maintain two overlapping histories in two formats? This retires
Phase 1's storage entirely rather than keeping a legacy path — pre-1.0
software, no users depend on the old format yet.
Data model:
- CheckpointObject gains a contextText field — the full generated text
from a checkpoint's context strategy (tool self-summary, transcript
excerpt, or a formatted line), carried forward permanently since it's
what summary.md regeneration reads from. Session records used to hold
this in contextSnapshot; checkpoint objects didn't have anywhere to
put it before now.
- ContextStrategy.generate now returns { text, source } instead of a
bare string, so a checkpoint's source field (tool-summary/
transcript-extract/manual) reflects which tier actually produced it
instead of always being hardcoded to 'manual'.
- New versioning/summary.ts: regenerateSummary rebuilds a branch's
summary.md from scratch from its checkpoint log on every checkpoint,
bounded by a character budget, called from both recordCheckpoint and
recordMergeCheckpoint. Deliberately not append-and-compact like
context.md was — objects/+log.jsonl are already a durable, complete
history, so summary.md can be a disposable, regenerated cache instead
of something that itself needs compaction/backup bookkeeping. writeSummary
upgraded to atomic tmp+rename, matching writeFacts, since it's now
written on every checkpoint instead of rarely.
Command rewiring:
- core/checkpoint.ts's performCheckpoint now generates text via the
active ContextStrategy and records it on the active branch via
versioning/checkpoint.ts's recordCheckpoint — no more context.md
append or session file write. Throws if there's no active branch
(unreachable in normal use); checkpointCommand/switchCommand check
getActiveBranch() upfront and error cleanly instead of crashing.
- brg switch hands off the active branch's summary.md instead of
context.md (already bounded by regeneration, so no separate
truncation pass needed at handoff time).
- brg status shows active branch + summary.md size + branch-scoped
checkpoint count instead of context.md size + global session count.
- brg context show prints the active branch's summary.md.
- brg log (without --graph) now lists every branch's checkpoints
together, flat and chronological, sourced from checkpoint objects
instead of sessions/*.json.
- brg init no longer creates context.md or sessions/.
Deleted: src/core/context.ts, src/core/session.ts, test/compaction.test.ts.
Tests: rewrote ai-assisted/corrupted-data/checkpoint/switch/init/
branch-checkout tests for the new data source, added test/summary.test.ts
(9 tests covering regeneration, budget truncation, per-branch isolation,
merge-checkpoint fallback formatting). Verified end-to-end with a real
CLI build: init, checkpoint x2, status, context show, log, and log
--graph all confirmed working against the new storage, with zero
context.md/sessions/ files created.
Docs: README's demo/quickstart/command reference/architecture section
rewritten for the branch-scoped model; ROADMAP.md's Phase 1 section
notes the retirement and Phase 2's intro flags structured fact
extraction as the single highest-value remaining piece, not yet
scheduled; docs/CONTEXT_VERSIONING.md's Capture section marked with
current status. docs/USER_GUIDE.md still describes the old model in
its Concepts/Command-reference sections (23 references) — flagged as a
follow-up, not attempted in this pass given its scope.
Plugin/dashboard/export are mechanical display layers over data that already exists; fact extraction is comparatively risky (LLM cost, accuracy, failure-mode design) and deserves its own dedicated pass once the rest of Phase 2's structural work is done.
plugin/ bundles brg's hooks and MCP server for Claude Code: - SessionStart injects the active branch's summary.md as session context, skipped when the session started via /clear so an explicit fresh-slate request isn't fought. - PreCompact checkpoints before Claude Code wipes context, attributed to config.defaultTool or falling back to the claude adapter. - .mcp.json registers brg mcp (module 6) as the plugin's MCP server. Both hooks are thin brg hook <event> subcommands (src/commands/hook.ts) rather than plugin-bundled scripts, so hooks.json just points at the already-installed brg binary and all logic/tests live alongside every other command. Targets Claude Code only, same scoping as the ToolAdapter interface — Codex has no equivalent plugin/hook system today. Stdin reading is injectable (HookDependencies.readStdin) so tests never touch real process.stdin, which is neither a TTY nor closed under a test runner and would otherwise hang indefinitely reading it for real — same DI pattern already used in branch.ts/merge.ts. 15 new tests covering both hooks' behavior (injection content, /clear suppression, all other source values still inject, malformed/throwing stdin readers don't crash, tool attribution, no-active-branch no-ops) plus the event dispatcher. Verified end-to-end with a real CLI build: piped SessionStart/PreCompact JSON through brg hook and confirmed correct output/checkpoint behavior; validated all three plugin JSON files parse.
Local HTTP server (node:http, zero new dependencies) over .brg/: - versioning/dashboard.ts: pure data-shaping (buildDashboardStats, buildDashboardGraph, getCheckpointDetail), testable without a server. Graph nodes get x (chronological order) and y (branch lane) for a horizontal-lane SVG layout — branches as rows, time flowing left to right, distinct from the ASCII renderer's vertical lane-reuse model since a web view naturally maps lane-to-branch-name directly. - commands/dashboard.ts: createDashboardServer() (routes: / , /api/stats, /api/graph, /api/checkpoint/:id) split from dashboardCommand() so tests hit a real server on an ephemeral port via fetch(), not a spawned process. Every request reads .brg/ fresh — no caching, no database. Checkpoint inspector renders facts_delta directly as add/remove lines; no separate diff computation needed, the delta already is the diff. Brand-styled per CLAUDE.md: paper/ink/amber palette, JetBrains Mono, the real logo mark (assets/logo.svg) inlined in the header, and a stat row of real numbers only — branches, checkpoints, active branch, and an estimated token count (chars/4, labeled "(est.)", not a real tokenizer — brg has no tokenizer dependency and isn't adding one for a dashboard stat). No cache-hit-rate stat: that's an Anthropic-API- level number brg has no visibility into, so it's not fabricated. Fixed along the way: collectGraphNodes' newestFirst ordering (and buildDashboardGraph's chronological ordering) used timestamp with an id tie-break for identical-millisecond checkpoints — but content-hash ids are uncorrelated with causal order, so a child could sort before its own parent. Replaced with topologicalOrder() (Kahn's algorithm, exported from graph.ts): a parent always precedes its children regardless of timestamp resolution, with timestamp/id only breaking ties among nodes that are simultaneously ready. 4 new tests cover the exact bug pattern (child id alphabetically before parent id, same millisecond) for both single-parent and merge-checkpoint cases. 19 new dashboard tests (data functions + real end-to-end HTTP requests against a live server) plus the 4 topological-order tests. Verified visually: built two branches with a merge via a real CLI build, served the dashboard, and rendered a static snapshot of the actual shipped HTML/CSS/JS with the captured data to confirm the graph/inspector/logo render correctly.
Free, local, no-account alternative to cloud sharing: a readable snapshot of a branch's context someone can open without brg installed. versioning/export.ts (buildExportData) gathers intent, chronological decision-log entries (each checkpoint's message plus its own facts_delta — no separate diff computation), and the current facts table — same branch/object read path every other brg surface uses, no export-specific data path. versioning/export-render.ts renders that into Markdown (zero dependency, git-diffable) or a self-contained HTML file (zero dependency, notes print-to-PDF as the alternative rather than bundling a PDF renderer, per the design doc). Before this, "the same rendering the dashboard uses" for the HTML export's embedded graph would have meant duplicating the dashboard's client-side JS layout logic in server-side TS. Instead factored the SVG generation into versioning/graph-svg.ts (server-side, pure function over a DashboardGraph) and switched brg dashboard itself to fetch this same pre-rendered markup from a new GET /api/graph.svg route, layering click handlers on top of it client-side, rather than building the SVG in the browser. One rendering, two consumers. brg export [--branch <name>] [--format md|html] [--out <path>] defaults to the active branch, md format, and brg-export-<branch>.<format>. Found and fixed along the way: buildDashboardGraph/collectGraphNodes' newestFirst ordering used timestamp with an id tie-break for identical-millisecond checkpoints, but content-hash ids are uncorrelated with causal order, so a child could sort before its own parent. Replaced with topologicalOrder() (Kahn's algorithm, exported from graph.ts) — correct regardless of timestamp resolution. 44 new tests (export data/render/command, graph-svg rendering including XSS-safety escaping, dashboard's new /api/graph.svg route) plus the graph.ts topological-order fix's own tests. Verified end-to-end with a real CLI build: created two branches, merged them, exported both --format md and --format html, and visually confirmed the HTML export renders correctly (published as an artifact from the actual generated file, not a mock).
Two complementary paths, matching how a checkpoint can actually originate — neither needs a new dependency or API key, both reuse ToolAdapter.summarizeViaSelf/MCP, the same "shell out to the tool's own auth" principle every other AI-touching piece of brg already follows. Reliable path (core/checkpoint.ts's performCheckpoint, used by brg checkpoint, brg switch's auto-checkpoint, and the plugin's PreCompact hook): fires unconditionally at every checkpoint boundary brg itself controls. ai-assisted's tier 1 now requests a combined summary+facts JSON response in one call instead of two, fed the branch's current facts so it reports only what's new/changed (the design doc's "incremental" requirement). context-strategies/parse-facts-response.ts is deliberately tolerant — strips markdown code fences models often wrap responses in, drops individually-malformed fact entries rather than rejecting the whole response, falls back to treating the raw response as a plain summary (facts empty) if the model doesn't follow the JSON format at all. manual and ai-assisted's tiers 2/3 always produce factsDelta: [] — no live model call, nothing to extract from. ContextStrategy.generate gained a third parameter (existingFacts) and factsDelta on its return type. Opportunistic path (brg mcp's context_commit): an MCP-connected agent can push structured facts directly in the same call as its checkpoint message, based on its own live understanding — zero extra LLM calls, most accurate since the agent that just did the work describes it. This is what the MCP server was actually built for, but per the design doc it's model-discretionary (the agent has to choose to call it), so it's additive, never the mechanism brg depends on for "control" — that's what the reliable path is for. New CheckpointSource value 'mcp-agent' records when facts are actually provided via this path, 'manual' otherwise. 31 new/updated tests: parse-facts-response's tolerant parsing (code fences, malformed entries, non-JSON fallback), manual's always-empty factsDelta, ai-assisted's combined-response parsing and existing-facts instruction content, performCheckpoint's end-to-end extraction and accumulation across checkpoints, context_commit's source attribution and real MCP schema (zod) validation. Verified end-to-end with a real CLI build and real claude/MCP subprocess calls: the reliable path completed a full summarizeViaSelf round-trip without error (correctly returning facts_delta: [] since this sandbox has no real prior session matching the fabricated checkpoint message), and the opportunistic path successfully pushed a structured fact through context_commit into facts.json via a live MCP client/server exchange.
Ran the full CLI against the "active brg branch is always the source of truth for context; the checked-out git branch is metadata/reference only" spec and fixed six gaps: unified brg branch/checkout into a single checkout command (create+switch on a new name, switch-only on an existing one, with --inherit/--orphan/--git flags to skip prompts); added files_touched/git_commit_at_checkpoint as reference-only fields on checkpoint objects; scoped brg log to the active branch by default with --all for cross-branch; added a git-branch-mismatch warning to brg status; added a one-arg form to brg diff; and removed context_commit's branch override so an MCP-connected agent can't target a branch other than the active one. Six other spec sections were checked and confirmed already compliant. Docs (README/ROADMAP/CONTEXT_VERSIONING/CHANGELOG) updated to match, and brg branch's dedicated command file/tests removed now that checkout absorbs it.
Patch release covering the context-branch vs git-branch invariant audit and fixes committed in the prior commit. Stamps CHANGELOG's [Unreleased] section as 26.8.5, and bumps package.json, package-lock.json, and the Claude Code plugin manifest to match.
The test invoked the installed post-checkout hook script directly via execFileSync(hookPath), which works on Linux/macOS but ENOENTs on Windows — there's no shebang association for a plain "#!/bin/sh" script outside Git Bash. Trigger it through a real `git checkout -b` instead, which is how the hook actually fires in practice and how git resolves/runs hooks consistently across all three CI platforms.
The entry described the brg branch/checkout merge and the context-branch vs git-branch fixes as a two-step "found and fixed six gaps" audit log — useful for tracking my own process, not for a reader who only cares what the released version actually does. Rewrote it as plain feature/behavior descriptions: brg checkout is now described once as its final unified form instead of narrating brg branch being folded into it, the log/status/ diff/MCP changes are described as what they do rather than what "spec section" they satisfy, and the audit-only bookkeeping bullet (six gaps found, six areas confirmed compliant) is dropped entirely since it told readers nothing about the tool's behavior.
docs/USER_GUIDE.md was still entirely written for Phase 1's retired
storage (context.md, sessions/*.json) — no mention of branches, checkout,
diff, merge, mcp, dashboard, or export anywhere in it. Rewrote it in full
against the current commands and real CLI output/error strings, so it's
accurate for a reader instead of describing a storage model that no
longer exists.
Also fixed smaller staleness found while reviewing every doc end to end:
ROADMAP.md's "brg log" bullet still described the old flat/every-branch
default, and its Phase 2 section header ("three pieces remain") directly
contradicted the shipped-fact-extraction description later in the same
file. CONTRIBUTING.md's commit-message examples referenced the removed
`brg branch` command and the retired context.md. src/index.ts's `--help`
text for `context show` and `status` still described pre-Phase-2 behavior
(context.md, no git-branch field).
Every Phase 2 bullet had grown into a paragraph-length re-explanation of things already fully covered in docs/CONTEXT_VERSIONING.md (design) and docs/USER_GUIDE.md (usage/flags/examples) — direct contradiction of the file's own stated principle that "this roadmap entry tracks status, the linked doc is the source of truth for design." Replaced the prose bullets with a status table: one line per piece, shipped/not-started, linking out to the doc that actually owns the detail instead of duplicating it.
main's PR #3 squash-merged this branch's state as of the "Fix Windows CI failure" commit, before the CHANGELOG/ROADMAP cleanup that followed it — so this merge is just reconciling that same text with itself. Resolved every conflict in favor of this branch's version, since it's the later, corrected one; main has nothing this branch doesn't already contain.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
Follow-up cleanup after Phase 2 (already merged in #3):
docs/USER_GUIDE.md— it was still fully describing the retiredPhase 1 storage (context.md/sessions), with no mention of branches,
checkout, diff, merge, mcp, dashboard, or export.
ROADMAP.md's Phase 2section — both had drifted into duplicating content already owned by
docs/CONTEXT_VERSIONING.md/USER_GUIDE.md, or contradicting itself.brg branchcommand and retiredcontext.mdinCONTRIBUTING.mdandsrc/index.ts's--helptext.directly, which isn't portable outside Git Bash — now triggers it via a
real
git checkout, same as git itself does on every platform.26.8.5.Related issue
N/A
Checklist
npm test) — 316/316Notes for reviewers
No functional CLI changes — docs, help text, one test-portability fix,
and a version bump.