Skip to content

Repository files navigation

BetterPSCM

Feature-complete Plastic SCM integration for VS Code — staging, diffs, branches, code reviews, and AI agent support via MCP.

Features

Source Control

  • 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 Management

  • 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

History

  • 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

Code Reviews

  • 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)

Merges & Labels

  • 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 surfacebpscm_list_pending_merges, bpscm_get_merge_contributors, and bpscm_abort_merge audit and revert the most recent merge from a persisted .plastic/bpscm/last-merge.json record. Abort uses a non-destructive subtractive-merge changeset.
  • protectedPaths guard — pass glob patterns to bpscm_merge and the wrapper will warn (or abort, per policy) when the merge would delete a matching path.
  • Labels — Create and manage labels on changesets

Lock Management

  • Lock rules — Create file-pattern lock rules for binary assets
  • Release locks — Manage and release file locks

Workspace

  • 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

AI Agent Support (MCP)

  • 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 /path without VS Code.
  • Headless safety guard — Every cm invocation from the MCP server routes through a centralised guard that injects machine-readable flags, rewrites bare cm 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 to cm / cm.exe can't stall the session either. See Headless safety hook below for the install command.

Requirements

  • Plastic SCM client tools installed (cm CLI available in PATH), or
  • Unity Version Control cloud workspace with REST API access

Extension Settings

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.

MCP Server Configuration

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.

Dual Backend

The extension supports two backends:

  1. REST API (primary) — Full feature set including code reviews, used when authenticated
  2. cm CLI (fallback) — Works without authentication, used when REST is unavailable

The backend is selected automatically at startup.

Architecture

  • 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 isolated CliBackend instances.
  • Shared destructive-ops layer — both the UI's "Clean Stale Changes" button and the MCP bpscm_clean_stale / bpscm_undo_checkout tools route through executeDestructiveRevert, 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 via bpscm_restore_backup from the MCP side.
  • Webview panels extend a small BetterPanel base class for lifecycle (CSP, dispose, message routing); the panel-specific HTML, CSS, and client JS live in sibling files under src/views/panels/<name>/.

Headless safety hook

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 cm call routes through guardCmArgs (src/core/cmGuard.ts) before spawn. The guard runs in strict mode: known footgun patterns are rejected with a CmGuardRejection, and known-required flags (e.g. --machinereadable on merges, --nototal on 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 a cm / cm.exe invocation, 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.

Install

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 install

The installer is idempotent — running it twice does not duplicate entries. Existing hooks unrelated to BetterPSCM are preserved.

Uninstall

node scripts/install-hook.js --uninstall                       # remove from per-user settings
node scripts/install-hook.js --uninstall --project /path       # remove from per-project settings

Opt out of the in-wrapper guard for a specific call

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

What the guard does NOT catch

  • cm invocations 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.ts when a new footgun is identified.

Testing

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.

License

MIT

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages