@wipcomputer/wip-ldm-os 0.4.76 → 0.4.77

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 (3) hide show
  1. package/SKILL.md +1 -1
  2. package/lib/deploy.mjs +61 -11
  3. package/package.json +1 -1
package/SKILL.md CHANGED
@@ -9,7 +9,7 @@ license: MIT
9
9
  compatibility: Requires git, npm, node. Node.js 18+.
10
10
  metadata:
11
11
  display-name: "LDM OS"
12
- version: "0.4.76"
12
+ version: "0.4.77"
13
13
  homepage: "https://github.com/wipcomputer/wip-ldm-os"
14
14
  author: "Parker Todd Brooks"
15
15
  category: infrastructure
package/lib/deploy.mjs CHANGED
@@ -14,6 +14,7 @@ import {
14
14
  lstatSync, readlinkSync, unlinkSync, chmodSync, readdirSync,
15
15
  renameSync, rmSync, statSync, symlinkSync,
16
16
  } from 'node:fs';
17
+ import { createHash } from 'node:crypto';
17
18
  import { join, basename, resolve, dirname } from 'node:path';
18
19
  import { tmpdir } from 'node:os';
19
20
  import { detectInterfaces, describeInterfaces, detectToolbox } from './detect.mjs';
@@ -750,6 +751,45 @@ function installCLI(repoPath, door) {
750
751
  }
751
752
  }
752
753
 
754
+ // Stable content hash over a directory tree. Used by deployExtension to
755
+ // decide whether to skip a redeploy. Before this, deployExtension skipped
756
+ // when source and deployed had equal versions ... but a prior partial
757
+ // install could have bumped the package.json without finishing the copy,
758
+ // leaving deployed package.json "current" while other files (guard.mjs,
759
+ // core.mjs, etc.) were stale. The 2026-04-20 wip-release 1.9.74 -> 1.9.75
760
+ // rollout hit that (source core.mjs had runNpmPublish, deployed didn't,
761
+ // both package.jsons reported 1.9.75). Skip only when versions match AND
762
+ // content hashes match; otherwise redeploy to heal the drift.
763
+ function computeTreeHash(dir) {
764
+ if (!existsSync(dir)) return null;
765
+ const skipNames = new Set([
766
+ '.git', 'node_modules', 'ai', '_trash', '.worktrees', 'logs', 'test', 'tests', '__tests__',
767
+ ]);
768
+ const hash = createHash('sha256');
769
+ function walk(d, rel) {
770
+ let entries;
771
+ try { entries = readdirSync(d, { withFileTypes: true }); }
772
+ catch { return; }
773
+ entries.sort((a, b) => a.name.localeCompare(b.name));
774
+ for (const e of entries) {
775
+ if (skipNames.has(e.name)) continue;
776
+ const p = join(d, e.name);
777
+ const r = rel ? `${rel}/${e.name}` : e.name;
778
+ if (e.isDirectory()) walk(p, r);
779
+ else if (e.isFile()) {
780
+ try {
781
+ hash.update(r);
782
+ hash.update('\0');
783
+ hash.update(readFileSync(p));
784
+ hash.update('\0');
785
+ } catch {}
786
+ }
787
+ }
788
+ }
789
+ walk(dir, '');
790
+ return hash.digest('hex');
791
+ }
792
+
753
793
  function deployExtension(repoPath, name) {
754
794
  const sourcePkg = readJSON(join(repoPath, 'package.json'));
755
795
  const ldmDest = join(LDM_EXTENSIONS, name);
@@ -759,18 +799,28 @@ function deployExtension(repoPath, name) {
759
799
 
760
800
  const cmp = compareSemver(newVersion, currentVersion);
761
801
  if (newVersion && currentVersion && cmp <= 0) {
762
- skip(`LDM: ${name} already at v${currentVersion}${cmp < 0 ? ` (source is older: v${newVersion})` : ''}`);
763
- // Ensure OC copy exists too
764
- const ocName = resolveOcPluginName(repoPath, name);
765
- const ocDest = join(OC_EXTENSIONS, ocName);
766
- if (!existsSync(ocDest) && !DRY_RUN) {
767
- mkdirSync(ocDest, { recursive: true });
768
- cpSync(ldmDest, ocDest, { recursive: true });
769
- ok(`OpenClaw: synced to ${ocDest}`);
770
- } else {
771
- skip(`OpenClaw: ${ocName} already at v${currentVersion}`);
802
+ // Versions equal or deployed is newer. Verify content hash before
803
+ // short-circuiting ... a prior partial install could have bumped
804
+ // package.json but not copied the other files, leaving deployed
805
+ // apparently "current" while code is stale.
806
+ const srcHash = computeTreeHash(repoPath);
807
+ const dstHash = computeTreeHash(ldmDest);
808
+ if (srcHash && dstHash && srcHash === dstHash) {
809
+ skip(`LDM: ${name} already at v${currentVersion}${cmp < 0 ? ` (source is older: v${newVersion})` : ''}`);
810
+ // Ensure OC copy exists too
811
+ const ocName = resolveOcPluginName(repoPath, name);
812
+ const ocDest = join(OC_EXTENSIONS, ocName);
813
+ if (!existsSync(ocDest) && !DRY_RUN) {
814
+ mkdirSync(ocDest, { recursive: true });
815
+ cpSync(ldmDest, ocDest, { recursive: true });
816
+ ok(`OpenClaw: synced to ${ocDest}`);
817
+ } else {
818
+ skip(`OpenClaw: ${ocName} already at v${currentVersion}`);
819
+ }
820
+ return true;
772
821
  }
773
- return true;
822
+ // Content differs despite matching version; fall through to redeploy.
823
+ ok(`LDM: ${name} v${currentVersion} reports same version but content differs; redeploying`);
774
824
  }
775
825
 
776
826
  if (DRY_RUN) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipcomputer/wip-ldm-os",
3
- "version": "0.4.76",
3
+ "version": "0.4.77",
4
4
  "type": "module",
5
5
  "description": "LDM OS: identity, memory, and sovereignty infrastructure for AI agents",
6
6
  "engines": {