mover-os 4.6.5 → 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 +99 -17
  2. package/package.json +1 -1
package/install.js CHANGED
@@ -30,6 +30,9 @@ function jsonOut(command, data, ok = true) {
30
30
  process.stdout.write(JSON.stringify(envelope, null, 2) + "\n");
31
31
  }
32
32
 
33
+ // ─── TTY detection (must be before output budget check) ──────────────────────
34
+ const IS_TTY = process.stdout.isTTY && process.stdin.isTTY;
35
+
33
36
  // ─── Output budget (30K chars = Anthropic bash tool truncation limit) ────────
34
37
  const MAX_OUTPUT_CHARS = 28000; // Leave 2K buffer below 30K limit
35
38
  let _outputCharCount = 0;
@@ -51,7 +54,6 @@ if (!IS_TTY) {
51
54
  const EXIT = { OK: 0, ERROR: 1, USAGE: 2, NOT_FOUND: 3, PERMISSION: 4, CONFLICT: 5, NETWORK: 6 };
52
55
 
53
56
  // ─── ANSI ────────────────────────────────────────────────────────────────────
54
- const IS_TTY = process.stdout.isTTY && process.stdin.isTTY;
55
57
  const S = IS_TTY
56
58
  ? {
57
59
  reset: "\x1b[0m",
@@ -1154,15 +1156,17 @@ function saveUpdateManifest(manifest) {
1154
1156
  fs.writeFileSync(p, JSON.stringify(manifest, null, 2), "utf8");
1155
1157
  }
1156
1158
 
1157
- function savePristineCopy(category, srcFile) {
1159
+ function savePristineCopy(category, srcFile, relPath) {
1158
1160
  const pristineDir = path.join(os.homedir(), ".mover", "installed", category);
1159
- 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 });
1160
1164
  const content = fs.readFileSync(srcFile, "utf8").replace(/\r\n/g, "\n");
1161
- fs.writeFileSync(path.join(pristineDir, path.basename(srcFile)), content, "utf8");
1165
+ fs.writeFileSync(destPath, content, "utf8");
1162
1166
  }
1163
1167
 
1164
- function compareForUpdate(category, srcFile, destFile, manifest) {
1165
- const fileName = `${category}/${path.basename(srcFile)}`;
1168
+ function compareForUpdate(category, srcFile, destFile, manifest, relPath) {
1169
+ const fileName = relPath ? `${category}/${relPath}` : `${category}/${path.basename(srcFile)}`;
1166
1170
  const newHash = fileContentHash(srcFile);
1167
1171
  const pristineHash = manifest?.files?.[fileName];
1168
1172
 
@@ -1310,9 +1314,9 @@ function diff3Merge(a, o, b) {
1310
1314
 
1311
1315
  // ── Auto-Merge (uses diff3 + pristine snapshots) ──────────────────────────
1312
1316
 
1313
- function tryAutoMerge(category, srcFile, destFile) {
1317
+ function tryAutoMerge(category, srcFile, destFile, relPath) {
1314
1318
  const pristineDir = path.join(os.homedir(), ".mover", "installed", category);
1315
- const pristineFile = path.join(pristineDir, path.basename(srcFile));
1319
+ const pristineFile = path.join(pristineDir, relPath || path.basename(srcFile));
1316
1320
  if (!fs.existsSync(pristineFile)) return { success: false, reason: "no-pristine" };
1317
1321
 
1318
1322
  const base = fs.readFileSync(pristineFile, "utf8").replace(/\r\n/g, "\n");
@@ -2473,18 +2477,21 @@ function writeMoverConfig(vaultPath, agentIds, licenseKey, opts = {}) {
2473
2477
 
2474
2478
  function installTemplateFiles(bundleDir, vaultPath) {
2475
2479
  const structDir = path.join(bundleDir, "src", "structure");
2476
- if (!fs.existsSync(structDir)) return 0;
2480
+ if (!fs.existsSync(structDir)) return { count: 0, hashes: {} };
2477
2481
 
2478
2482
  let count = 0;
2483
+ const hashes = {};
2479
2484
  const walk = (dir, rel) => {
2480
2485
  for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
2481
2486
  const srcPath = path.join(dir, entry.name);
2482
- const destPath = path.join(vaultPath, rel, entry.name);
2487
+ const entryRel = path.join(rel, entry.name);
2488
+ const destPath = path.join(vaultPath, entryRel);
2483
2489
 
2484
2490
  if (entry.isDirectory()) {
2485
- walk(srcPath, path.join(rel, entry.name));
2491
+ walk(srcPath, entryRel);
2486
2492
  } else {
2487
- const relNorm = rel.replace(/\\/g, "/");
2493
+ const relNorm = entryRel.replace(/\\/g, "/");
2494
+ // Engine files are SACRED — never overwrite existing
2488
2495
  if (relNorm.includes("02_Areas") && relNorm.includes("Engine") && fs.existsSync(destPath)) {
2489
2496
  continue;
2490
2497
  }
@@ -2493,11 +2500,17 @@ function installTemplateFiles(bundleDir, vaultPath) {
2493
2500
  fs.copyFileSync(srcPath, destPath);
2494
2501
  count++;
2495
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
+ }
2496
2509
  }
2497
2510
  }
2498
2511
  };
2499
2512
  walk(structDir, "");
2500
- return count;
2513
+ return { count, hashes };
2501
2514
  }
2502
2515
 
2503
2516
  function installWorkflows(bundleDir, destDir, selectedWorkflows) {
@@ -5323,9 +5336,9 @@ async function cmdUpdateComprehensive(opts, bundleDir, startTime) {
5323
5336
  statusLine("ok", "/update", "workflow synced");
5324
5337
  }
5325
5338
 
5326
- // Vault structure + templates (safe scaffolding)
5339
+ // Vault structure (folders only no file changes)
5327
5340
  createVaultStructure(vaultPath);
5328
- installTemplateFiles(bundleDir, vaultPath);
5341
+ // NOTE: templates are now handled via smart merge below (after manifest load), NOT installTemplateFiles()
5329
5342
 
5330
5343
  // Update version marker
5331
5344
  fs.writeFileSync(path.join(vaultPath, ".mover-version"), `${require("./package.json").version}\n`, "utf8");
@@ -5390,6 +5403,70 @@ async function cmdUpdateComprehensive(opts, bundleDir, startTime) {
5390
5403
  }
5391
5404
  }
5392
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
+
5393
5470
  // ── Rules: keep sentinel merge + track pristine ──
5394
5471
  if (changes.rules === "changed") {
5395
5472
  const rulesSrc = path.join(bundleDir, "src", "system", "Mover_Global_Rules.md");
@@ -6149,11 +6226,12 @@ async function main() {
6149
6226
  totalSteps++;
6150
6227
 
6151
6228
  // 2. Template files (runs in both modes — only creates missing files, never overwrites)
6229
+ let tmplResult = { count: 0, hashes: {} };
6152
6230
  if (!skipTemplates) {
6153
6231
  sp = spinner("Engine templates");
6154
- const templatesInstalled = installTemplateFiles(bundleDir, vaultPath);
6232
+ tmplResult = installTemplateFiles(bundleDir, vaultPath);
6155
6233
  await sleep(200);
6156
- 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")}`);
6157
6235
  totalSteps++;
6158
6236
  }
6159
6237
 
@@ -6275,6 +6353,10 @@ async function main() {
6275
6353
  mfst.files[`hooks/${f}`] = fileContentHash(path.join(hooksDir, f));
6276
6354
  }
6277
6355
  }
6356
+ // Template files (vault scaffolding — installTemplateFiles now returns hashes)
6357
+ if (tmplResult && tmplResult.hashes) {
6358
+ Object.assign(mfst.files, tmplResult.hashes);
6359
+ }
6278
6360
  saveUpdateManifest(mfst);
6279
6361
  } catch {}
6280
6362
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mover-os",
3
- "version": "4.6.5",
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"