@wipcomputer/wip-ldm-os 0.3.1 → 0.3.2

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 +97 -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.2"
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,68 @@ 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
+ if (existsSync(scriptPath)) return true;
115
+ console.log(` + Removed stale hook: ${event} -> ${scriptPath}`);
116
+ cleaned++;
117
+ return false;
118
+ });
119
+
120
+ // Keep the group if it still has live hooks
121
+ group.hooks = liveHooks;
122
+ return liveHooks.length > 0;
123
+ });
124
+ }
125
+
126
+ if (cleaned > 0) {
127
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
128
+ }
129
+
130
+ return cleaned;
131
+ }
132
+
71
133
  // ── Catalog helpers ──
72
134
 
73
135
  function loadCatalog() {
@@ -568,6 +630,10 @@ async function cmdInstallCatalog() {
568
630
 
569
631
  console.log('');
570
632
  console.log(` Updated ${updated}/${updatable.length} extension(s).`);
633
+
634
+ // Check if CLI itself is outdated (#29)
635
+ checkCliVersion();
636
+
571
637
  console.log('');
572
638
  }
573
639
 
@@ -641,6 +707,14 @@ async function cmdDoctor() {
641
707
  }
642
708
  }
643
709
 
710
+ // --fix: clean stale hook paths in settings.json (#30)
711
+ if (FIX_FLAG) {
712
+ const hooksCleaned = cleanStaleHooks();
713
+ if (hooksCleaned > 0) {
714
+ issues = Math.max(0, issues - hooksCleaned);
715
+ }
716
+ }
717
+
644
718
  // 4. Check sacred locations
645
719
  const sacred = [
646
720
  { path: join(LDM_ROOT, 'memory'), label: 'memory/' },
@@ -665,6 +739,29 @@ async function cmdDoctor() {
665
739
  if (settings?.hooks) {
666
740
  const hookCount = Object.values(settings.hooks).reduce((sum, arr) => sum + (arr?.length || 0), 0);
667
741
  console.log(` + Claude Code hooks: ${hookCount} configured`);
742
+
743
+ // Check for stale hook paths
744
+ let staleHooks = 0;
745
+ for (const [event, hookGroups] of Object.entries(settings.hooks)) {
746
+ if (!Array.isArray(hookGroups)) continue;
747
+ for (const group of hookGroups) {
748
+ for (const h of (group.hooks || [])) {
749
+ if (!h.command) continue;
750
+ const match = h.command.match(/node\s+"?([^"]+)"?\s*$/);
751
+ if (!match) continue;
752
+ if (!existsSync(match[1])) {
753
+ staleHooks++;
754
+ if (!FIX_FLAG) {
755
+ console.log(` ! Stale hook: ${event} -> ${match[1]}`);
756
+ }
757
+ }
758
+ }
759
+ }
760
+ }
761
+ if (staleHooks > 0 && !FIX_FLAG) {
762
+ console.log(` Run: ldm doctor --fix to clean ${staleHooks} stale hook(s)`);
763
+ issues += staleHooks;
764
+ }
668
765
  } else {
669
766
  console.log(` - Claude Code hooks: none configured`);
670
767
  }
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.2",
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",