ChangeSnap (v1.4)
Tracks org metadata changes during a feature/bugfix session, auto-marks
changed lines in code, flags possible concurrent edits, and generates a
DevOps-tool-agnostic markdown handoff file
(.changesnap/<story>-handoff.md). No dependency on git, Copado,
Gearset, or any specific pipeline — it only reads org state via the
Salesforce CLI (plus local file diffing for markers).
Prerequisites
- Salesforce CLI (
sf) installed
- An authenticated org set as your default:
sf org login web --set-default
- Node.js 18+, VS Code 1.85+
- (Optional)
git installed, purely so your name can be auto-detected for markers — not used for anything else
Setup
npm install
npm run compile
Then press F5 to launch an Extension Development Host, or package with vsce package to install as a .vsix.
Commands
| Command |
What it does |
ChangeSnap: Start Session |
Prompts for story number (optional), snapshots the org's metadata state as baseline, and turns on automatic change markers |
ChangeSnap: End Session (Generate Handoff) |
Re-snapshots, diffs, runs dependency scan, runs the concurrent-change check, prompts for manual steps, writes the markdown handoff |
ChangeSnap: Show Active Session Status |
Shows org + start time of the current session |
ChangeSnap: Cancel Active Session |
Discards the baseline and turns off markers without generating a handoff |
Everything else is automatic — no extra commands or clicks. Once a
session is started, saving a .cls/.trigger/.js file wraps changed
lines in markers by itself, and the overlap check runs by itself when
you end the session.
Feature: Automatic change markers
On every save of a supported file (default: .cls, .trigger, .js
— configurable via changeSnap.markerExtensions), ChangeSnap:
- Diffs the file's current content against a baseline captured at
session start (via a simple LCS line diff — no git dependency)
- Wraps each contiguous changed block with:
// [ChangeSnap:US-4521] Pranshu Moghe - START
...changed lines...
// [ChangeSnap:US-4521] Pranshu Moghe - END
- Re-derives markers fresh on every save (strips old ones, re-diffs,
re-inserts), so it stays accurate as you keep editing — no manual
cleanup needed.
Your name is resolved automatically from git config user.name on the
first session in a workspace (zero-click). If git isn't available,
you're asked once and it's cached in changeSnap.developerName for
that workspace from then on.
Why this helps at merge/conflict time: during a Copado merge or
manual conflict resolution, DevOps (or another dev) can see exactly
which lines changed and who touched them, without needing to ask you
or reconstruct it from memory.
Known limitations
- Baseline is in-memory only — if VS Code restarts mid-session,
file baselines are lost (the org metadata baseline survives, since
that's saved to disk; only line-level tracking resets). Markers
already inserted stay in the file; new edits just won't get
additional automatic markers until the next
Start Session.
- Scoped to
.cls/.trigger/.js by default. Flow and other
metadata-XML files are deliberately excluded — inserting comments
into Flow XML risks corrupting Flow Builder's rendering, so this
wasn't worth the risk for v1. Extend changeSnap.markerExtensions
at your own judgment for other plain-text formats.
- Nearby edits merge into one block if separated by ≤1 unchanged
line, to avoid marker spam on small, closely-spaced changes.
Genuinely separate edits further apart stay as distinct blocks
(verified in testing — this was tuned down from an earlier looser
setting that incorrectly merged unrelated method-level changes).
- Files over ~4000 lines are skipped for marker insertion, as a
performance guard on the diff algorithm — you'll still get full
metadata tracking and handoff notes for those files, just no inline
markers.
Feature: Concurrent-change detection
At End Session, for every changed component, ChangeSnap checks the
Metadata API's lastModifiedByName (already fetched as part of the
normal snapshot — no extra API calls) against your own display name.
If a component you changed was actually last modified by someone
else, it's flagged in a "Possible Concurrent Changes" section of the
handoff, and you get an immediate warning notification.
This deliberately reuses data ChangeSnap already fetches rather than
building a shared session registry — no new org objects, no write
access needed, no dependency on other developers also using
ChangeSnap.
Known limitations
- Only catches changes visible by End Session. If someone else
edits and then re-edits a component back to a state that happens to
match what you'd expect, or if their edit lands and gets overwritten
again before your session ends, this won't catch it. It's a
same-session snapshot comparison, not a live/continuous watch.
A periodic background check (opt-in) is a reasonable next step if
this proves too coarse in practice.
- Requires resolving your own Salesforce display name, which needs
one extra API call to the
User object at End Session. If that
lookup fails (e.g. transient API issue), the check is skipped
entirely rather than risking false positives against an unknown name.
How the rest works (unchanged from v1.3)
- Baseline — Metadata API
listMetadata(), one call per
configured type, run with bounded concurrency (5 at a time).
- Diff —
lastModifiedDate per component vs baseline.
- Dependency scan —
MetadataComponentDependency, batched (15
components per query) and run with bounded concurrency (3 at a
time) at End Session only.
- Handoff file — self-contained markdown.
Configuration
| Setting |
Default |
Purpose |
changeSnap.metadataTypes |
~20 common types |
Metadata types checked at snapshot time |
changeSnap.developerName |
(blank, auto-filled) |
Name used in markers |
changeSnap.markerExtensions |
.cls, .trigger, .js |
Which files get automatic markers |
Next steps
- Static-scan dependency fallback (for types
MetadataComponentDependency misses)
package.xml / JSON export alongside the markdown
- CustomField tracking via per-object describe calls
- Optional periodic (not just end-of-session) concurrent-change polling
- Optional: detect if source tracking is available on the target org
and use the more precise
SourceMember approach when it is