@wipcomputer/wip-ldm-os 0.3.1 → 0.3.3

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/bin/ldm.js +103 -0
  3. package/package.json +1 -1
package/SKILL.md CHANGED
@@ -5,7 +5,7 @@ license: MIT
5
5
  interface: [cli, skill]
6
6
  metadata:
7
7
  display-name: "LDM OS"
8
- version: "0.3.1"
8
+ version: "0.3.3"
9
9
  homepage: "https://github.com/wipcomputer/wip-ldm-os"
10
10
  author: "Parker Todd Brooks"
11
11
  category: infrastructure
package/bin/ldm.js CHANGED
@@ -68,6 +68,71 @@ function writeJSON(path, data) {
68
68
  writeFileSync(path, JSON.stringify(data, null, 2) + '\n');
69
69
  }
70
70
 
71
+ // ── CLI version check (#29) ──
72
+
73
+ function checkCliVersion() {
74
+ try {
75
+ const result = execSync('npm view @wipcomputer/wip-ldm-os version 2>/dev/null', {
76
+ encoding: 'utf8',
77
+ timeout: 10000,
78
+ }).trim();
79
+ if (result && result !== PKG_VERSION) {
80
+ console.log('');
81
+ console.log(` CLI is outdated: v${PKG_VERSION} installed, v${result} available.`);
82
+ console.log(` Run: npm install -g @wipcomputer/wip-ldm-os@${result}`);
83
+ }
84
+ } catch {
85
+ // npm check failed, skip silently
86
+ }
87
+ }
88
+
89
+ // ── Stale hook cleanup (#30) ──
90
+
91
+ function cleanStaleHooks() {
92
+ const settingsPath = join(HOME, '.claude', 'settings.json');
93
+ const settings = readJSON(settingsPath);
94
+ if (!settings?.hooks) return 0;
95
+
96
+ let cleaned = 0;
97
+
98
+ for (const [event, hookGroups] of Object.entries(settings.hooks)) {
99
+ if (!Array.isArray(hookGroups)) continue;
100
+
101
+ // Filter out hook groups where ALL hooks point to non-existent paths
102
+ const original = hookGroups.length;
103
+ settings.hooks[event] = hookGroups.filter(group => {
104
+ const hooks = group.hooks || [];
105
+ if (hooks.length === 0) return true; // keep empty groups (matcher-only)
106
+
107
+ // Check each hook command for stale paths
108
+ const liveHooks = hooks.filter(h => {
109
+ if (!h.command) return true;
110
+ // Extract the path from "node /path/to/file.mjs" or "node \"/path/to/file.mjs\""
111
+ const match = h.command.match(/node\s+"?([^"]+)"?\s*$/);
112
+ if (!match) return true; // keep non-node commands
113
+ const scriptPath = match[1];
114
+ // /tmp/ paths will vanish on reboot, always treat as stale
115
+ const isTmp = scriptPath.startsWith('/tmp/') || scriptPath.startsWith('/private/tmp/');
116
+ if (existsSync(scriptPath) && !isTmp) return true;
117
+ const reason = isTmp ? '(temp path, will break on reboot)' : '(missing)';
118
+ console.log(` + Removed stale hook: ${event} -> ${scriptPath} ${reason}`);
119
+ cleaned++;
120
+ return false;
121
+ });
122
+
123
+ // Keep the group if it still has live hooks
124
+ group.hooks = liveHooks;
125
+ return liveHooks.length > 0;
126
+ });
127
+ }
128
+
129
+ if (cleaned > 0) {
130
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
131
+ }
132
+
133
+ return cleaned;
134
+ }
135
+
71
136
  // ── Catalog helpers ──
72
137
 
73
138
  function loadCatalog() {
@@ -568,6 +633,10 @@ async function cmdInstallCatalog() {
568
633
 
569
634
  console.log('');
570
635
  console.log(` Updated ${updated}/${updatable.length} extension(s).`);
636
+
637
+ // Check if CLI itself is outdated (#29)
638
+ checkCliVersion();
639
+
571
640
  console.log('');
572
641
  }
573
642
 
@@ -641,6 +710,14 @@ async function cmdDoctor() {
641
710
  }
642
711
  }
643
712
 
713
+ // --fix: clean stale hook paths in settings.json (#30)
714
+ if (FIX_FLAG) {
715
+ const hooksCleaned = cleanStaleHooks();
716
+ if (hooksCleaned > 0) {
717
+ issues = Math.max(0, issues - hooksCleaned);
718
+ }
719
+ }
720
+
644
721
  // 4. Check sacred locations
645
722
  const sacred = [
646
723
  { path: join(LDM_ROOT, 'memory'), label: 'memory/' },
@@ -665,6 +742,32 @@ async function cmdDoctor() {
665
742
  if (settings?.hooks) {
666
743
  const hookCount = Object.values(settings.hooks).reduce((sum, arr) => sum + (arr?.length || 0), 0);
667
744
  console.log(` + Claude Code hooks: ${hookCount} configured`);
745
+
746
+ // Check for stale hook paths
747
+ let staleHooks = 0;
748
+ for (const [event, hookGroups] of Object.entries(settings.hooks)) {
749
+ if (!Array.isArray(hookGroups)) continue;
750
+ for (const group of hookGroups) {
751
+ for (const h of (group.hooks || [])) {
752
+ if (!h.command) continue;
753
+ const match = h.command.match(/node\s+"?([^"]+)"?\s*$/);
754
+ if (!match) continue;
755
+ const hookPath = match[1];
756
+ const isTmpPath = hookPath.startsWith('/tmp/') || hookPath.startsWith('/private/tmp/');
757
+ if (!existsSync(hookPath) || isTmpPath) {
758
+ staleHooks++;
759
+ if (!FIX_FLAG) {
760
+ const reason = isTmpPath ? '(temp path)' : '(missing)';
761
+ console.log(` ! Stale hook: ${event} -> ${hookPath} ${reason}`);
762
+ }
763
+ }
764
+ }
765
+ }
766
+ }
767
+ if (staleHooks > 0 && !FIX_FLAG) {
768
+ console.log(` Run: ldm doctor --fix to clean ${staleHooks} stale hook(s)`);
769
+ issues += staleHooks;
770
+ }
668
771
  } else {
669
772
  console.log(` - Claude Code hooks: none configured`);
670
773
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipcomputer/wip-ldm-os",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "type": "module",
5
5
  "description": "LDM OS: identity, memory, and sovereignty infrastructure for AI agents",
6
6
  "main": "src/boot/boot-hook.mjs",