Feature-complete Plastic SCM integration for VS Code — staging, diffs, branches, code reviews, and AI agent support via MCP.
- Git-like staging — Stage/unstage individual files, then check in only what you want
- Two resource groups — "Staged Changes" and "Changes" with inline actions
- Quick diff — Click any changed file to see inline diffs with gutter decorations
- Revert changes — Discard individual file modifications
- Clean Stale Changes — One-click cleanup of files cm reports as changed but whose working copy is byte-identical to the base revision (the Unity reimport "stale CH" pattern). Confirms before reverting, backs up everything to
~/.plastic-scm-backups/, and warns if Unity-critical files (.meta,.unity,.prefab,.asset,.asmdef) are about to be touched.
- Branch explorer — View all branches in the SCM sidebar
- Create/switch/delete — Full branch lifecycle from the command palette
- Status bar — Current branch always visible, click to switch
- Interactive history graph — SVG-based changeset visualization with branch lines
- File-scoped history filter — Filter the graph to changesets that touched a specific file; auto-follows the active editor
- File history — View revision history for any file
- Annotate (Blame) — Line-by-line blame with changeset, author, and date
- Full review lifecycle — Create, comment, change status, manage reviewers
- Threaded comments — Nested discussions on review items
- Tree view — Filterable list (all, assigned to me, pending)
- Merge branches — Headless-safe merge with structured conflict reporting. Pick a strategy (
threeWayAuto/keepSource/keepDestination/manual); the wrapper runs preview-first and refuses to execute when a conflict would force Plastic's mergetool to spawn. Auto-resolved conflicts surface separately from unresolved ones so destination-bias drops are never silent. - Conflict surface —
bpscm_list_pending_merges,bpscm_get_merge_contributors, andbpscm_abort_mergeaudit and revert the most recent merge from a persisted.plastic/bpscm/last-merge.jsonrecord. Abort uses a non-destructive subtractive-merge changeset. - protectedPaths guard — pass glob patterns to
bpscm_mergeand the wrapper will warn (or abort, per policy) when the merge would delete a matching path. - Labels — Create and manage labels on changesets
- Lock rules — Create file-pattern lock rules for binary assets
- Release locks — Manage and release file locks
- Auto-detection — Reads
.plastic/folder to auto-configure server, org, repo, and branch - Workspace update — Pull latest changes with conflict handling
- SSO auto-login — Picks up Unity SSO tokens from the Plastic desktop client
- 23 MCP tools — Status (with optional merge-touched augmentation), stage, checkin, diff, branches (with filters), merge (with strategy + protectedPaths), conflict surface (list pending merges / get contributors / abort merge), workcopy diff, file history, blame, code reviews, review audit, changeset range query, and more.
- 3 resources — Live workspace status, branch, and staged files.
- 2 prompts — Commit message generation and code review summaries.
- Standalone mode — Run
node dist/mcp-server.js --workspace /pathwithout VS Code. - Headless safety guard — Every
cminvocation from the MCP server routes through a centralised guard that injects machine-readable flags, rewrites barecm diff <path>to text-mode, and rejects GUI-spawning subcommands (gui,wkgui,showmergetool) outright. A companion Claude Code PreToolUse hook applies the same rules to Bash tool calls so direct agent shellouts tocm/cm.execan't stall the session either. See Headless safety hook below for the install command.
- Plastic SCM client tools installed (
cmCLI available in PATH), or - Unity Version Control cloud workspace with REST API access
| Setting | Default | Description |
|---|---|---|
bpscm.serverUrl |
"" |
Plastic SCM server URL |
bpscm.organizationName |
"" |
Organization name |
bpscm.repositoryName |
"" |
Repository name |
bpscm.workspaceGuid |
"" |
Workspace GUID (auto-detected) |
bpscm.pollInterval |
3000 |
Status polling interval (ms) |
bpscm.showPrivateFiles |
true |
Show unversioned files |
bpscm.mcp.enabled |
false |
Enable MCP server for AI agents |
Most settings are auto-detected from the .plastic/ folder when you open a Plastic SCM workspace.
To use the MCP server with Claude Code or other MCP clients, add to your MCP config:
{
"mcpServers": {
"betterpscm": {
"command": "node",
"args": ["/path/to/dist/mcp-server.js", "--workspace", "/path/to/workspace"]
}
}
}Or enable it via VS Code settings: set bpscm.mcp.enabled to true.
The extension supports two backends:
- REST API (primary) — Full feature set including code reviews, used when authenticated
- cm CLI (fallback) — Works without authentication, used when REST is unavailable
The backend is selected automatically at startup.
PlasticContext— every workspace operation runs scoped to an explicit context ({ workspaceRoot, cmPath }) instead of mutable module-level state, so the extension and the standalone MCP server can co-exist in one Node process and integration tests can drive isolatedCliBackendinstances.- Shared destructive-ops layer — both the UI's "Clean Stale Changes" button and the MCP
bpscm_clean_stale/bpscm_undo_checkouttools route throughexecuteDestructiveRevert, which writes a backup to$PLASTIC_BACKUP_DIR(or~/.plastic-scm-backups/), enforces the bulk-operation threshold, classifies Unity-critical files, and emits structured audit log entries. Backups can be restored viabpscm_restore_backupfrom the MCP side. - Webview panels extend a small
BetterPanelbase class for lifecycle (CSP, dispose, message routing); the panel-specific HTML, CSS, and client JS live in sibling files undersrc/views/panels/<name>/.
Plastic's cm CLI has a handful of footgun patterns that silently open a GUI window or stall waiting for interactive input — most notoriously cm diff <path> (working-copy form), cm gui, and cm merge when invoked without --machinereadable. When a Claude Code agent shells out to one of these without realising, the session hangs until the user manually closes the GUI.
BetterPSCM ships a two-layer guard against this:
- Layer A — in-wrapper. Inside the MCP server and the extension, every
cmcall routes throughguardCmArgs(src/core/cmGuard.ts) before spawn. The guard runs in strict mode: known footgun patterns are rejected with aCmGuardRejection, and known-required flags (e.g.--machinereadableon merges,--nototalon finds) are auto-injected. No agent action needed — the safety net is always on. - Layer B — Claude Code PreToolUse hook. A standalone Node script (
scripts/cmGuardHook.js) registers as a Bash tool hook in~/.claude/settings.json. When an agent issues a Bash command containing acm/cm.exeinvocation, the hook parses the command, runs the args through the same rules table (in permissive mode), and blocks the call with a clear suggestion when the invocation would stall. The agent sees the rewritten safe form in the rejection reason and can re-issue.
npm run build:ext # required first — produces dist/cmGuard.js that the hook loads
node scripts/install-hook.js # per-user install (~/.claude/settings.json)
node scripts/install-hook.js --project /path/to/repo # per-workspace installThe installer is idempotent — running it twice does not duplicate entries. Existing hooks unrelated to BetterPSCM are preserved.
node scripts/install-hook.js --uninstall # remove from per-user settings
node scripts/install-hook.js --uninstall --project /path # remove from per-project settingsIf you genuinely need to run cm with a raw argv (e.g. driving a tool the guard doesn't know about), pass { skipGuard: true } to execCm / execCmWithContext. The escape hatch is per-call — no global toggle.
cminvocations issued from a real terminal (not via Claude Code's Bash tool). Hook fires only on agent-driven shell-outs.- Subcommands the rules table doesn't know about — those pass through unchanged in permissive mode (and unchanged in strict mode too, since "unknown verb" is not a known footgun). Add a rule to
src/core/cmGuard.tswhen a new footgun is identified.
npm test # 465 unit tests, all mocked, fast
npm run test:integration # opt-in integration tier — drives a real cm binary
# against a throwaway Plastic workspace.
# Requires BPSCM_INTEGRATION_WORKSPACE env var.
# See test/integration/README.md for setup.The integration tier exists specifically to catch CLI semantics bugs the
unit tests can't see — flag changes, output format drifts, and path-format
assumptions across Plastic versions. The cm undocheckout -a flag bug
that motivated the v0.4.0 robustness work is locked in by a regression
test there.
MIT