mover-os 4.6.6 → 4.6.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/install.js +96 -16
  2. package/package.json +1 -1
package/install.js CHANGED
@@ -1156,15 +1156,17 @@ function saveUpdateManifest(manifest) {
1156
1156
  fs.writeFileSync(p, JSON.stringify(manifest, null, 2), "utf8");
1157
1157
  }
1158
1158
 
1159
- function savePristineCopy(category, srcFile) {
1159
+ function savePristineCopy(category, srcFile, relPath) {
1160
1160
  const pristineDir = path.join(os.homedir(), ".mover", "installed", category);
1161
- fs.mkdirSync(pristineDir, { recursive: true });
1161
+ const destName = relPath || path.basename(srcFile);
1162
+ const destPath = path.join(pristineDir, destName);
1163
+ fs.mkdirSync(path.dirname(destPath), { recursive: true });
1162
1164
  const content = fs.readFileSync(srcFile, "utf8").replace(/\r\n/g, "\n");
1163
- fs.writeFileSync(path.join(pristineDir, path.basename(srcFile)), content, "utf8");
1165
+ fs.writeFileSync(destPath, content, "utf8");
1164
1166
  }
1165
1167
 
1166
- function compareForUpdate(category, srcFile, destFile, manifest) {
1167
- const fileName = `${category}/${path.basename(srcFile)}`;
1168
+ function compareForUpdate(category, srcFile, destFile, manifest, relPath) {
1169
+ const fileName = relPath ? `${category}/${relPath}` : `${category}/${path.basename(srcFile)}`;
1168
1170
  const newHash = fileContentHash(srcFile);
1169
1171
  const pristineHash = manifest?.files?.[fileName];
1170
1172
 
@@ -1312,9 +1314,9 @@ function diff3Merge(a, o, b) {
1312
1314
 
1313
1315
  // ── Auto-Merge (uses diff3 + pristine snapshots) ──────────────────────────
1314
1316
 
1315
- function tryAutoMerge(category, srcFile, destFile) {
1317
+ function tryAutoMerge(category, srcFile, destFile, relPath) {
1316
1318
  const pristineDir = path.join(os.homedir(), ".mover", "installed", category);
1317
- const pristineFile = path.join(pristineDir, path.basename(srcFile));
1319
+ const pristineFile = path.join(pristineDir, relPath || path.basename(srcFile));
1318
1320
  if (!fs.existsSync(pristineFile)) return { success: false, reason: "no-pristine" };
1319
1321
 
1320
1322
  const base = fs.readFileSync(pristineFile, "utf8").replace(/\r\n/g, "\n");
@@ -2475,18 +2477,21 @@ function writeMoverConfig(vaultPath, agentIds, licenseKey, opts = {}) {
2475
2477
 
2476
2478
  function installTemplateFiles(bundleDir, vaultPath) {
2477
2479
  const structDir = path.join(bundleDir, "src", "structure");
2478
- if (!fs.existsSync(structDir)) return 0;
2480
+ if (!fs.existsSync(structDir)) return { count: 0, hashes: {} };
2479
2481
 
2480
2482
  let count = 0;
2483
+ const hashes = {};
2481
2484
  const walk = (dir, rel) => {
2482
2485
  for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
2483
2486
  const srcPath = path.join(dir, entry.name);
2484
- const destPath = path.join(vaultPath, rel, entry.name);
2487
+ const entryRel = path.join(rel, entry.name);
2488
+ const destPath = path.join(vaultPath, entryRel);
2485
2489
 
2486
2490
  if (entry.isDirectory()) {
2487
- walk(srcPath, path.join(rel, entry.name));
2491
+ walk(srcPath, entryRel);
2488
2492
  } else {
2489
- const relNorm = rel.replace(/\\/g, "/");
2493
+ const relNorm = entryRel.replace(/\\/g, "/");
2494
+ // Engine files are SACRED — never overwrite existing
2490
2495
  if (relNorm.includes("02_Areas") && relNorm.includes("Engine") && fs.existsSync(destPath)) {
2491
2496
  continue;
2492
2497
  }
@@ -2495,11 +2500,17 @@ function installTemplateFiles(bundleDir, vaultPath) {
2495
2500
  fs.copyFileSync(srcPath, destPath);
2496
2501
  count++;
2497
2502
  }
2503
+ // Track hash and save pristine copy for ALL non-Engine template files
2504
+ if (!(relNorm.includes("02_Areas") && relNorm.includes("Engine"))) {
2505
+ const relKey = relNorm;
2506
+ hashes[`templates/${relKey}`] = fileContentHash(srcPath);
2507
+ savePristineCopy("templates", srcPath, relKey);
2508
+ }
2498
2509
  }
2499
2510
  }
2500
2511
  };
2501
2512
  walk(structDir, "");
2502
- return count;
2513
+ return { count, hashes };
2503
2514
  }
2504
2515
 
2505
2516
  function installWorkflows(bundleDir, destDir, selectedWorkflows) {
@@ -5325,9 +5336,9 @@ async function cmdUpdateComprehensive(opts, bundleDir, startTime) {
5325
5336
  statusLine("ok", "/update", "workflow synced");
5326
5337
  }
5327
5338
 
5328
- // Vault structure + templates (safe scaffolding)
5339
+ // Vault structure (folders only no file changes)
5329
5340
  createVaultStructure(vaultPath);
5330
- installTemplateFiles(bundleDir, vaultPath);
5341
+ // NOTE: templates are now handled via smart merge below (after manifest load), NOT installTemplateFiles()
5331
5342
 
5332
5343
  // Update version marker
5333
5344
  fs.writeFileSync(path.join(vaultPath, ".mover-version"), `${require("./package.json").version}\n`, "utf8");
@@ -5392,6 +5403,70 @@ async function cmdUpdateComprehensive(opts, bundleDir, startTime) {
5392
5403
  }
5393
5404
  }
5394
5405
 
5406
+ // ── Templates: vault scaffold files (same smart merge as workflows) ──
5407
+ const tmplSrcDir = path.join(bundleDir, "src", "structure");
5408
+ if (fs.existsSync(tmplSrcDir)) {
5409
+ const walkTmplUpdate = (dir, rel) => {
5410
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
5411
+ const srcPath = path.join(dir, entry.name);
5412
+ const entryRel = path.join(rel, entry.name);
5413
+ if (entry.isDirectory()) {
5414
+ walkTmplUpdate(srcPath, entryRel);
5415
+ continue;
5416
+ }
5417
+ const relNorm = entryRel.replace(/\\/g, "/");
5418
+ // Engine files are SACRED — never touch via update
5419
+ if (relNorm.includes("02_Areas") && relNorm.includes("Engine")) continue;
5420
+
5421
+ const destFile = path.join(vaultPath, entryRel);
5422
+ const result = compareForUpdate("templates", srcPath, destFile, manifest, relNorm);
5423
+
5424
+ switch (result.action) {
5425
+ case "install":
5426
+ fs.mkdirSync(path.dirname(destFile), { recursive: true });
5427
+ fs.copyFileSync(srcPath, destFile);
5428
+ savePristineCopy("templates", srcPath, relNorm);
5429
+ updated.push(relNorm);
5430
+ break;
5431
+ case "safe-overwrite":
5432
+ fs.copyFileSync(srcPath, destFile);
5433
+ savePristineCopy("templates", srcPath, relNorm);
5434
+ updated.push(relNorm);
5435
+ break;
5436
+ case "user-only-skip":
5437
+ userOnly.push(relNorm);
5438
+ break;
5439
+ case "skip":
5440
+ skippedFiles.push(relNorm);
5441
+ break;
5442
+ case "legacy-overwrite": {
5443
+ // First update with manifest — backup then overwrite
5444
+ const backupDir = path.join(os.homedir(), ".mover", "backups", `pre-update-${new Date().toISOString().slice(0, 10)}`);
5445
+ fs.mkdirSync(path.join(backupDir, "templates", path.dirname(relNorm)), { recursive: true });
5446
+ if (fs.existsSync(destFile)) fs.copyFileSync(destFile, path.join(backupDir, "templates", relNorm));
5447
+ fs.copyFileSync(srcPath, destFile);
5448
+ savePristineCopy("templates", srcPath, relNorm);
5449
+ updated.push(relNorm);
5450
+ break;
5451
+ }
5452
+ case "conflict": {
5453
+ const mergeResult = tryAutoMerge("templates", srcPath, destFile, relNorm);
5454
+ if (mergeResult.success) {
5455
+ fs.writeFileSync(destFile, mergeResult.content, "utf8");
5456
+ savePristineCopy("templates", srcPath, relNorm);
5457
+ autoMerged.push(relNorm);
5458
+ } else {
5459
+ conflicts.push({ name: relNorm, reason: mergeResult.reason, conflictCount: mergeResult.conflictCount });
5460
+ }
5461
+ break;
5462
+ }
5463
+ }
5464
+ newManifest.files[`templates/${relNorm}`] = result.newHash;
5465
+ }
5466
+ };
5467
+ walkTmplUpdate(tmplSrcDir, "");
5468
+ }
5469
+
5395
5470
  // ── Rules: keep sentinel merge + track pristine ──
5396
5471
  if (changes.rules === "changed") {
5397
5472
  const rulesSrc = path.join(bundleDir, "src", "system", "Mover_Global_Rules.md");
@@ -6151,11 +6226,12 @@ async function main() {
6151
6226
  totalSteps++;
6152
6227
 
6153
6228
  // 2. Template files (runs in both modes — only creates missing files, never overwrites)
6229
+ let tmplResult = { count: 0, hashes: {} };
6154
6230
  if (!skipTemplates) {
6155
6231
  sp = spinner("Engine templates");
6156
- const templatesInstalled = installTemplateFiles(bundleDir, vaultPath);
6232
+ tmplResult = installTemplateFiles(bundleDir, vaultPath);
6157
6233
  await sleep(200);
6158
- sp.stop(`Engine templates${templatesInstalled > 0 ? dim(` ${templatesInstalled} files`) : dim(" up to date")}`);
6234
+ sp.stop(`Engine templates${tmplResult.count > 0 ? dim(` ${tmplResult.count} files`) : dim(" up to date")}`);
6159
6235
  totalSteps++;
6160
6236
  }
6161
6237
 
@@ -6277,6 +6353,10 @@ async function main() {
6277
6353
  mfst.files[`hooks/${f}`] = fileContentHash(path.join(hooksDir, f));
6278
6354
  }
6279
6355
  }
6356
+ // Template files (vault scaffolding — installTemplateFiles now returns hashes)
6357
+ if (tmplResult && tmplResult.hashes) {
6358
+ Object.assign(mfst.files, tmplResult.hashes);
6359
+ }
6280
6360
  saveUpdateManifest(mfst);
6281
6361
  } catch {}
6282
6362
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mover-os",
3
- "version": "4.6.6",
3
+ "version": "4.6.7",
4
4
  "description": "The self-improving OS for AI agents. Turns Obsidian into an execution engine.",
5
5
  "bin": {
6
6
  "moveros": "install.js"