How `update-system.mjs` Manages Version Checking, Apply, Dismiss, and Rollback in santifer/career-ops
The update-system.mjs script in santifer/career-ops provides four CLI commands—check, apply, dismiss, and rollback—that safely update the system layer without touching user data, using semantic version comparison, backup branches, and a hidden marker file for suppression.
The update-system.mjs script serves as the safe auto-updater for the career-ops repository. It operates exclusively on the system layer—mode files, scripts, and templates—while strictly protecting any user-layer data. This article breaks down exactly how the script handles version checking, applying updates, dismissing notifications, and rolling back changes, based on the source implementation in santifer/career-ops.
Version Checking in update-system.mjs
The check command determines whether a newer version exists upstream through a three-step process.
Reading Local and Remote Versions
localVersion() reads the VERSION file at the repository root and normalizes its contents:
function localVersion() {
const vPath = join(ROOT, 'VERSION');
return existsSync(vPath) ? parseVersionFile(readFileSync(vPath, 'utf-8')) : '0.0.0';
}
(source: lines 53-63 of update-system.mjs)
For the remote version, the script fetches the latest commit via git fetch and reads the upstream VERSION from FETCH_HEAD using the RAW_VERSION_URL constant.
Semantic Version Comparison
compareVersions(a, b) implements standard semver logic:
function compareVersions(a, b) {
const pa = a.split('.').map(Number);
const pb = b.split('.').map(Number);
for (let i = 0; i < 3; i++) {
if ((pa[i] || 0) < (pb[i] || 0)) return -1;
if ((pa[i] || 0) > (pb[i] || 0)) return 1;
}
return 0;
}
(source: lines 64-71 of update-system.mjs)
If the remote version is greater, the script outputs {"status": "update-available", ...}; otherwise, it returns {"status": "up-to-date"}.
Applying Updates Safely
The apply command implements a multi-layer safety protocol before modifying any files.
Pre-Update Safety Checks
- Build user-layer path list –
effectiveUserPaths()combines the built-inUSER_PATHSarray with any additional entries fromconfig/local-paths.txt - Detect conflicts –
userLayerViolations()aborts if anySYSTEM_PATHSentry overlaps with user-layer paths - Flag local modifications –
locallyModifiedSystemFiles()identifies system files the user has edited; these receive.bakpreservation copies
Executing the Update
The script performs a raw checkout (no merge) for each system path:
git checkout FETCH_HEAD -- <path>
After checkout, it stages changes, commits with a message including the backup branch name, and creates a timestamped backup branch via updateBackupBranchName(). The .update-dismissed marker is removed if present.
Dismissing Update Checks
The dismiss command suppresses automatic update notifications until explicitly re-enabled:
function dismiss() {
writeFileSync(join(ROOT, '.update-dismissed'), new Date().toISOString());
console.log('Update check dismissed. Run "node update-system.mjs check" or say "check for updates" to re-enable.');
}
(source: lines 2308-2310 of update-system.mjs)
When .update-dismissed exists, subsequent check commands return {"status": "dismissed"} without network activity.
Rolling Back Updates
Rollback restores the pre-update state using backup branches created during apply.
Backup Branch Naming Convention
updateBackupBranchName() generates recoverable branch names:
function updateBackupBranchName(version, date = new Date()) {
const stamp = date.toISOString()
.replace(/[-:]/g, '')
.replace(/\.\d{3}Z$/, 'Z');
return `backup-pre-update-${version}-${stamp}`;
}
Rollback Execution Process
newestBackupBranch()parsesgit branchoutput to find the most recent backup- The identified branch is checked out to restore original system files
- The backup branch is deleted after successful restoration
- Files introduced by the rolled-back update are removed from the working tree
Practical Usage Examples
| Command | Purpose | Example Output |
|---|---|---|
node update-system.mjs check |
Query for available updates | {"status": "update-available", "local": "1.8.0", "remote": "1.9.0"} |
node update-system.mjs apply |
Apply the latest update safely | Success message with backup branch name |
node update-system.mjs apply --force |
Overwrite local system file edits | Same as above, skipping modification checks |
node update-system.mjs dismiss |
Suppress future update prompts | Update check dismissed... |
node update-system.mjs rollback |
Revert to pre-update state | Restoration confirmation |
Key Files and Constants in update-system.mjs
| File/Constant | Role |
|---|---|
update-system.mjs |
Core auto-updater implementation with all four commands |
VERSION |
Local semantic version identifier |
.update-dismissed |
Hidden marker file disabling automatic checks |
SYSTEM_PATHS |
Whitelist of system-layer paths safe to overwrite |
USER_PATHS |
Protected user-layer paths that block updates on conflict |
config/local-paths.txt |
Optional extension for fork-specific protected paths |
Summary
- Version checking in
update-system.mjscompares localVERSIONagainst remote using strict semver logic - Applying updates validates against user-layer conflicts, preserves modified files, creates recoverable backups, and performs raw checkouts
- Dismissing checks writes a
.update-dismissedmarker that short-circuits futurecheckcommands - Rolling back locates the newest
backup-pre-update-*branch and restores that state cleanly
Frequently Asked Questions
How does update-system.mjs prevent overwriting my data?
The script builds a protection list via effectiveUserPaths() combining USER_PATHS and config/local-paths.txt. userLayerViolations() aborts the update if any SYSTEM_PATHS entry conflicts with these protected paths. Additionally, locallyModifiedSystemFiles() creates .bak copies of system files you've edited before checkout.
Can I force an update if I've modified system files?
Yes. Run node update-system.mjs apply --force to bypass the local modification check. Without --force, the script preserves your edits as .bak files and aborts.
How long does a dismissed update stay suppressed?
Indefinitely. The .update-dismissed marker persists until you explicitly run node update-system.mjs check or remove the file manually. There is no automatic expiration.
Where are rollback backups stored?
Backup branches remain in your local git repository following the pattern backup-pre-update-{version}-{timestamp}. These branches are local-only and deleted after successful rollback.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →