@wipcomputer/wip-ldm-os 0.4.73-alpha.1 → 0.4.73-alpha.10

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.
package/bin/ldm.js CHANGED
@@ -46,6 +46,53 @@ function installLog(msg) {
46
46
  } catch {}
47
47
  }
48
48
 
49
+ // ── Semver comparison (#XX) ──
50
+ // Proper semver comparison that handles prereleases correctly.
51
+ // 0.4.73-alpha.1 > 0.4.72 (higher base version, prerelease doesn't matter)
52
+ // 0.4.72-alpha.1 < 0.4.72 (same base version, prerelease is older than stable)
53
+ // 0.4.73 > 0.4.72 (straightforward)
54
+ // Returns true if version `a` is strictly newer than version `b`.
55
+ function semverNewer(a, b) {
56
+ if (!a || !b || a === b) return false;
57
+ // Split into base and prerelease: "0.4.73-alpha.1" -> ["0.4.73", "alpha.1"]
58
+ const [aBase, aPre] = a.split('-', 2);
59
+ const [bBase, bPre] = b.split('-', 2);
60
+ const aParts = aBase.split('.').map(Number);
61
+ const bParts = bBase.split('.').map(Number);
62
+ // Compare base version (major.minor.patch)
63
+ for (let i = 0; i < 3; i++) {
64
+ const av = aParts[i] || 0;
65
+ const bv = bParts[i] || 0;
66
+ if (av > bv) return true;
67
+ if (av < bv) return false;
68
+ }
69
+ // Base versions are equal. Stable > prerelease.
70
+ // If a has no prerelease and b does, a is newer (stable release of same base).
71
+ // If a has prerelease and b doesn't, a is older (prerelease of same base).
72
+ if (!aPre && bPre) return true; // a=0.4.72, b=0.4.72-alpha.1 -> a is newer
73
+ if (aPre && !bPre) return false; // a=0.4.72-alpha.1, b=0.4.72 -> a is older
74
+ // Both have prereleases with same base. Compare prerelease segments lexically.
75
+ if (aPre && bPre) {
76
+ const aSegs = aPre.split('.');
77
+ const bSegs = bPre.split('.');
78
+ for (let i = 0; i < Math.max(aSegs.length, bSegs.length); i++) {
79
+ const as = aSegs[i] || '';
80
+ const bs = bSegs[i] || '';
81
+ // Numeric segments compared numerically
82
+ const an = /^\d+$/.test(as) ? Number(as) : NaN;
83
+ const bn = /^\d+$/.test(bs) ? Number(bs) : NaN;
84
+ if (!isNaN(an) && !isNaN(bn)) {
85
+ if (an > bn) return true;
86
+ if (an < bn) return false;
87
+ } else {
88
+ if (as > bs) return true;
89
+ if (as < bs) return false;
90
+ }
91
+ }
92
+ }
93
+ return false;
94
+ }
95
+
49
96
  // Read our own version from package.json
50
97
  const pkgPath = join(__dirname, '..', 'package.json');
51
98
  let PKG_VERSION = '0.2.0';
@@ -172,7 +219,7 @@ function checkCliVersion() {
172
219
  encoding: 'utf8',
173
220
  timeout: 10000,
174
221
  }).trim();
175
- if (result && result !== PKG_VERSION) {
222
+ if (result && semverNewer(result, PKG_VERSION)) {
176
223
  console.log('');
177
224
  console.log(` CLI is outdated: v${PKG_VERSION} installed, v${result} available.`);
178
225
  console.log(` Run: npm install -g @wipcomputer/wip-ldm-os@${result}`);
@@ -1274,23 +1321,31 @@ async function cmdInstall() {
1274
1321
 
1275
1322
  // Check if target looks like an npm package (starts with @ or is a plain name without /)
1276
1323
  if (resolvedTarget.startsWith('@') || (!resolvedTarget.includes('/') && !existsSync(resolve(resolvedTarget)))) {
1277
- // Try npm install to temp dir
1324
+ // Try npm pack + tar extract to temp dir
1325
+ // npm install --prefix silently fails for scoped packages in temp directories...
1326
+ // it creates the lock file but doesn't extract files. npm pack is reliable.
1278
1327
  const npmName = resolvedTarget;
1279
1328
  const tempDir = join(LDM_TMP, `npm-${Date.now()}`);
1280
1329
  console.log('');
1281
1330
  console.log(` Installing ${npmName} from npm...`);
1282
1331
  try {
1283
1332
  mkdirSync(tempDir, { recursive: true });
1284
- execSync(`npm install ${npmName} --prefix "${tempDir}"`, { stdio: 'pipe' });
1285
- // Find the installed package in node_modules
1286
- const pkgName = npmName.startsWith('@') ? npmName : npmName;
1287
- const installed = join(tempDir, 'node_modules', pkgName);
1288
- if (existsSync(installed)) {
1289
- console.log(` + Installed from npm`);
1290
- repoPath = installed;
1333
+ // Use npm pack + tar instead of npm install --prefix
1334
+ const tarball = execSync(`npm pack ${npmName} --pack-destination "${tempDir}" 2>/dev/null`, {
1335
+ encoding: 'utf8', timeout: 60000, cwd: tempDir,
1336
+ }).trim();
1337
+ const tarPath = join(tempDir, tarball);
1338
+ if (existsSync(tarPath)) {
1339
+ execSync(`tar xzf "${tarPath}" -C "${tempDir}"`, { stdio: 'pipe' });
1340
+ const extracted = join(tempDir, 'package');
1341
+ if (existsSync(extracted)) {
1342
+ console.log(` + Installed from npm`);
1343
+ repoPath = extracted;
1344
+ } else {
1345
+ console.error(` x npm pack succeeded but extraction failed`);
1346
+ }
1291
1347
  } else {
1292
- console.error(` x Package installed but not found at expected path`);
1293
- process.exit(1);
1348
+ console.error(` x npm pack failed: tarball not found`);
1294
1349
  }
1295
1350
  } catch (e) {
1296
1351
  // npm failed, fall through to git clone or path resolution
@@ -1513,7 +1568,7 @@ async function cmdInstallCatalog() {
1513
1568
  const latest = execSync(npmViewCmd, {
1514
1569
  encoding: 'utf8', timeout: 15000,
1515
1570
  }).trim();
1516
- if (latest && latest !== PKG_VERSION) {
1571
+ if (latest && semverNewer(latest, PKG_VERSION)) {
1517
1572
  console.log(` LDM OS CLI v${PKG_VERSION} -> v${latest}${trackLabel}. Updating first...`);
1518
1573
  try {
1519
1574
  execSync(`npm install -g @wipcomputer/wip-ldm-os@${latest}`, { stdio: 'inherit', timeout: 60000 });
@@ -1771,7 +1826,7 @@ async function cmdInstallCatalog() {
1771
1826
  const cliLatest = execSync('npm view @wipcomputer/wip-ldm-os version 2>/dev/null', {
1772
1827
  encoding: 'utf8', timeout: 10000,
1773
1828
  }).trim();
1774
- if (cliLatest && cliLatest !== PKG_VERSION) {
1829
+ if (cliLatest && semverNewer(cliLatest, PKG_VERSION)) {
1775
1830
  npmUpdates.push({
1776
1831
  name: 'LDM OS CLI',
1777
1832
  catalogNpm: '@wipcomputer/wip-ldm-os',
@@ -1840,7 +1895,7 @@ async function cmdInstallCatalog() {
1840
1895
  encoding: 'utf8', timeout: 10000,
1841
1896
  }).trim();
1842
1897
 
1843
- if (latestVersion && latestVersion !== currentVersion) {
1898
+ if (latestVersion && semverNewer(latestVersion, currentVersion)) {
1844
1899
  npmUpdates.push({
1845
1900
  name,
1846
1901
  catalogRepo: repoUrl,
@@ -1867,7 +1922,7 @@ async function cmdInstallCatalog() {
1867
1922
  const tagMatch = tags.match(/refs\/tags\/v?(\d+\.\d+\.\d+)/);
1868
1923
  if (tagMatch) {
1869
1924
  const latestVersion = tagMatch[1];
1870
- if (latestVersion !== currentVersion) {
1925
+ if (semverNewer(latestVersion, currentVersion)) {
1871
1926
  npmUpdates.push({
1872
1927
  name,
1873
1928
  catalogRepo: repoUrl,
@@ -1907,7 +1962,7 @@ async function cmdInstallCatalog() {
1907
1962
  const latestVersion = execSync(npmViewCmd, {
1908
1963
  encoding: 'utf8', timeout: 10000,
1909
1964
  }).trim();
1910
- if (latestVersion && latestVersion !== currentVersion) {
1965
+ if (latestVersion && semverNewer(latestVersion, currentVersion)) {
1911
1966
  npmUpdates.push({
1912
1967
  name: binName,
1913
1968
  catalogRepo: catalogComp.repo,
@@ -1940,7 +1995,7 @@ async function cmdInstallCatalog() {
1940
1995
  const latest = execSync(`npm view ${comp.npm} version 2>/dev/null`, {
1941
1996
  encoding: 'utf8', timeout: 10000,
1942
1997
  }).trim();
1943
- if (latest && latest !== currentVersion) {
1998
+ if (latest && semverNewer(latest, currentVersion)) {
1944
1999
  // Remove any sub-tool entries that belong to this parent.
1945
2000
  const parentMatches = new Set(comp.registryMatches || []);
1946
2001
  for (let i = npmUpdates.length - 1; i >= 0; i--) {
@@ -2159,13 +2214,56 @@ async function cmdInstallCatalog() {
2159
2214
  continue;
2160
2215
  }
2161
2216
 
2162
- if (!entry.catalogRepo) {
2217
+ if (!entry.catalogRepo && !entry.catalogNpm) {
2163
2218
  console.log(` Skipping ${entry.name}: no catalog repo (install manually with ldm install <org/repo>)`);
2164
2219
  continue;
2165
2220
  }
2166
- console.log(` Updating ${entry.name} v${entry.currentVersion} -> v${entry.latestVersion} (from ${entry.catalogRepo})...`);
2221
+
2222
+ // Source resolution chain (#264):
2223
+ // 1. npm (when --alpha/--beta or npm package available) - works online, any machine
2224
+ // 2. Local private repo (offline, developer machine) - works without internet
2225
+ // 3. GitHub clone (fallback) - works online, any machine
2226
+ let installSource = null;
2227
+ const npmTag = ALPHA_FLAG ? 'alpha' : BETA_FLAG ? 'beta' : null;
2228
+
2229
+ // Try npm first when using alpha/beta tracks or when npm is available
2230
+ if (entry.catalogNpm && (npmTag || !entry.catalogRepo)) {
2231
+ const ver = npmTag ? `${entry.catalogNpm}@${npmTag}` : `${entry.catalogNpm}@${entry.latestVersion}`;
2232
+ installSource = ver;
2233
+ console.log(` Updating ${entry.name} v${entry.currentVersion} -> v${entry.latestVersion} (from npm ${npmTag || 'latest'})...`);
2234
+ }
2235
+
2236
+ // Try local private repo (for offline/developer installs)
2237
+ if (!installSource && entry.catalogRepo) {
2238
+ const repoName = basename(entry.catalogRepo);
2239
+ const privateRepoName = repoName + '-private';
2240
+ const WORKSPACE = join(HOME, 'wipcomputerinc');
2241
+ // Search known repo locations
2242
+ const searchDirs = ['repos/ldm-os/devops', 'repos/ldm-os/components', 'repos/ldm-os/utilities', 'repos/ldm-os/apps', 'repos/ldm-os/apis', 'repos/ldm-os/identity'];
2243
+ for (const dir of searchDirs) {
2244
+ const localPrivate = join(WORKSPACE, dir, privateRepoName);
2245
+ const localPublic = join(WORKSPACE, dir, repoName);
2246
+ if (existsSync(localPrivate)) {
2247
+ installSource = localPrivate;
2248
+ console.log(` Updating ${entry.name} v${entry.currentVersion} -> v${entry.latestVersion} (from local ${privateRepoName})...`);
2249
+ break;
2250
+ }
2251
+ if (existsSync(localPublic)) {
2252
+ installSource = localPublic;
2253
+ console.log(` Updating ${entry.name} v${entry.currentVersion} -> v${entry.latestVersion} (from local ${repoName})...`);
2254
+ break;
2255
+ }
2256
+ }
2257
+ }
2258
+
2259
+ // Fallback: GitHub clone
2260
+ if (!installSource) {
2261
+ installSource = entry.catalogRepo;
2262
+ console.log(` Updating ${entry.name} v${entry.currentVersion} -> v${entry.latestVersion} (from ${entry.catalogRepo})...`);
2263
+ }
2264
+
2167
2265
  try {
2168
- execSync(`ldm install ${entry.catalogRepo}`, { stdio: 'inherit' });
2266
+ execSync(`ldm install ${installSource}`, { stdio: 'inherit' });
2169
2267
  updated++;
2170
2268
 
2171
2269
  // For parent packages, update registry version for all sub-tools (#139, #262)
@@ -2582,7 +2680,7 @@ function cmdStatus() {
2582
2680
  const latest = execSync('npm view @wipcomputer/wip-ldm-os version 2>/dev/null', {
2583
2681
  encoding: 'utf8', timeout: 10000,
2584
2682
  }).trim();
2585
- if (latest && latest !== PKG_VERSION) cliUpdate = latest;
2683
+ if (latest && semverNewer(latest, PKG_VERSION)) cliUpdate = latest;
2586
2684
  } catch {}
2587
2685
 
2588
2686
  // Check extensions against npm using registry source info (#262)
@@ -2602,7 +2700,7 @@ function cmdStatus() {
2602
2700
  const latest = execSync(`npm view ${npmPkg} version 2>/dev/null`, {
2603
2701
  encoding: 'utf8', timeout: 10000,
2604
2702
  }).trim();
2605
- if (latest && latest !== currentVersion) {
2703
+ if (latest && semverNewer(latest, currentVersion)) {
2606
2704
  updates.push({ name, current: currentVersion, latest, npm: npmPkg });
2607
2705
  }
2608
2706
  } catch {}
package/lib/deploy.mjs CHANGED
@@ -394,14 +394,38 @@ function runBuildIfNeeded(repoPath) {
394
394
 
395
395
  function compareSemver(a, b) {
396
396
  if (!a || !b) return 0;
397
- const pa = a.split('.').map(Number);
398
- const pb = b.split('.').map(Number);
397
+ if (a === b) return 0;
398
+ const [aBase, aPre] = a.split('-', 2);
399
+ const [bBase, bPre] = b.split('-', 2);
400
+ const pa = aBase.split('.').map(Number);
401
+ const pb = bBase.split('.').map(Number);
399
402
  for (let i = 0; i < 3; i++) {
400
403
  const na = pa[i] || 0;
401
404
  const nb = pb[i] || 0;
402
405
  if (na > nb) return 1;
403
406
  if (na < nb) return -1;
404
407
  }
408
+ // Base versions equal. Stable > prerelease.
409
+ if (!aPre && bPre) return 1; // a is stable, b is prerelease -> a is newer
410
+ if (aPre && !bPre) return -1; // a is prerelease, b is stable -> b is newer
411
+ // Both have prereleases. Compare segments.
412
+ if (aPre && bPre) {
413
+ const aSegs = aPre.split('.');
414
+ const bSegs = bPre.split('.');
415
+ for (let i = 0; i < Math.max(aSegs.length, bSegs.length); i++) {
416
+ const as = aSegs[i] || '';
417
+ const bs = bSegs[i] || '';
418
+ const an = Number(as);
419
+ const bn = Number(bs);
420
+ if (!isNaN(an) && !isNaN(bn)) {
421
+ if (an > bn) return 1;
422
+ if (an < bn) return -1;
423
+ } else {
424
+ if (as > bs) return 1;
425
+ if (as < bs) return -1;
426
+ }
427
+ }
428
+ }
405
429
  return 0;
406
430
  }
407
431
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipcomputer/wip-ldm-os",
3
- "version": "0.4.73-alpha.1",
3
+ "version": "0.4.73-alpha.10",
4
4
  "type": "module",
5
5
  "description": "LDM OS: identity, memory, and sovereignty infrastructure for AI agents",
6
6
  "engines": {
@@ -16,11 +16,31 @@ Your AI reads the spec, explains what LDM OS is, checks if it's already installe
16
16
  1. **Self-update.** Checks npm for a newer version of itself. Updates first, then re-runs with new code.
17
17
  2. **System scan.** Reads `~/.ldm/extensions/`, `~/.openclaw/extensions/`, Claude Code MCP config, CLI binaries on PATH.
18
18
  3. **Catalog check.** Matches installed extensions against the catalog. Shows what's available, what's behind.
19
- 4. **npm version check.** Checks every installed extension against npm for newer versions.
20
- 5. **Update.** Clones from GitHub, builds if needed, deploys to extensions dirs, registers MCP/hooks/skills.
19
+ 4. **npm version check.** Checks every installed extension against npm for newer versions (uses dist-tags for alpha/beta).
20
+ 5. **Update.** Resolves the install source (see Source Resolution below), deploys to extensions dirs, registers MCP/hooks/skills.
21
21
  6. **Health check.** Verifies CLIs exist, no broken symlinks, no stale configs.
22
22
  7. **Cleanup.** Removes staging dirs, prunes ghost entries from registry.
23
23
 
24
+ ## Release Tracks
25
+
26
+ ```bash
27
+ ldm install # stable (npm @latest)
28
+ ldm install --alpha # alpha track (npm @alpha)
29
+ ldm install --beta # beta track (npm @beta)
30
+ ```
31
+
32
+ Each track overwrites whatever is installed. Alpha, beta, and stable all use the same install path. The flag only changes which npm dist-tag is checked for updates.
33
+
34
+ ## Source Resolution
35
+
36
+ When updating, the installer finds the package in priority order:
37
+
38
+ 1. **npm** (when `--alpha` or `--beta`): downloads from npm dist-tag. Works on any machine.
39
+ 2. **Local private repo** (developer machine): finds the repo at `~/wipcomputerinc/repos/ldm-os/`. Works offline, no npm or internet needed.
40
+ 3. **GitHub clone** (fallback): clones the public repo. Existing behavior.
41
+
42
+ Alpha/beta installs never require `deploy-public`. Merge to private main, `wip-release alpha`, `ldm install --alpha`, done.
43
+
24
44
  ## Dry Run
25
45
 
26
46
  Always preview first:
@@ -1,77 +1,91 @@
1
1
  # How Releases Work
2
2
 
3
- ## The Pipeline
3
+ ## Four Release Tracks
4
4
 
5
- Every release follows these steps. No shortcuts.
5
+ | Track | npm tag | Who | Purpose |
6
+ |-------|---------|-----|---------|
7
+ | **Alpha** | `@alpha` | Developer | Moving fast. Fix, test, iterate. No deploy-public needed. |
8
+ | **Beta** | `@beta` | Testers | Testing the distribution pipeline. npm + installer verified. |
9
+ | **Hotfix** | `@latest` | Everyone | Emergency fix. Goes straight to stable. No deploy-public. |
10
+ | **Stable** | `@latest` | Everyone | Full release. deploy-public syncs the public repo. |
6
11
 
7
- ### 1. Branch and Code
12
+ ## The Alpha Flow (Development)
8
13
 
9
- Create a worktree, make your changes, commit:
10
- ```bash
11
- ldm worktree add my-prefix/feature-name
12
- cd _worktrees/repo--my-prefix--feature-name/
13
- # edit files
14
- git add <files>
15
- git commit -m "description"
16
- ```
17
-
18
- ### 2. Write Release Notes
19
-
20
- Create `RELEASE-NOTES-v{version}.md` (dashes, not dots) in the repo root. Commit it on the branch with the code. It gets reviewed in the PR.
21
-
22
- ### 3. Push and PR
14
+ This is the fast path. Most work happens here.
23
15
 
24
16
  ```bash
25
- git push -u origin my-prefix/feature-name
26
- gh pr create --title "..." --body "..."
27
- gh pr merge --merge --delete-branch
28
- ```
17
+ # 1. Branch and code
18
+ git worktree add .worktrees/repo--my-prefix--feature -b my-prefix/feature
19
+ cd .worktrees/repo--my-prefix--feature/
20
+ # edit, commit
29
21
 
30
- Never squash merge. Always `--merge`.
22
+ # 2. Push, PR, merge
23
+ git push -u origin my-prefix/feature
24
+ gh pr create && gh pr merge --merge
31
25
 
32
- ### 4. Release
26
+ # 3. Alpha release
27
+ cd /path/to/repo && git checkout main && git pull
28
+ wip-release alpha --notes="what changed"
33
29
 
34
- ```bash
35
- cd /path/to/repo # main working tree, not worktree
36
- git checkout main && git pull
37
- wip-release patch # auto-detects the release notes file
30
+ # 4. Install and test
31
+ ldm install --alpha
38
32
  ```
39
33
 
40
- `wip-release` handles: version bump, CHANGELOG.md, SKILL.md version sync, npm publish, GitHub release, website skill publish.
34
+ Done. No deploy-public. No public repo sync. The installer pulls from npm @alpha (or finds the local private repo if offline).
41
35
 
42
- ### 5. Deploy to Public
36
+ **Sub-tool versions:** If you changed a sub-tool's code, bump its `package.json` version in the PR. Same version = same code. `wip-release` warns if files changed without a version bump.
37
+
38
+ ## The Stable Flow (Public Release)
43
39
 
44
40
  ```bash
45
- bash deploy-public.sh /path/to/private-repo org/public-repo
46
- ```
41
+ # 1-3. Same as alpha: branch, PR, merge
47
42
 
48
- Syncs everything except `ai/` to the public repo. Creates matching release with notes.
43
+ # 4. Write release notes on the branch
44
+ # RELEASE-NOTES-v{version}.md in repo root, committed with the code
49
45
 
50
- ### 6. Install (Dogfood)
46
+ # 5. Stable release
47
+ git checkout main && git pull
48
+ wip-release patch # auto-detects release notes
51
49
 
52
- Open a new AI session and paste:
53
- ```
54
- Read https://wip.computer/install/wip-ldm-os.txt
50
+ # 6. Deploy to public
51
+ deploy-public /path/to/private-repo org/public-repo
52
+
53
+ # 7. Dogfood
54
+ ldm install
55
55
  ```
56
56
 
57
- The AI walks through: explain, dry run, install. Never `npm install -g` directly.
57
+ `wip-release` handles: version bump, CHANGELOG.md, SKILL.md sync, npm publish, GitHub release.
58
+
59
+ `deploy-public` syncs everything except `ai/` to the public repo. Creates a matching release.
58
60
 
59
61
  ## Quality Gates
60
62
 
61
- `wip-release` enforces before publishing:
63
+ `wip-release` enforces before publishing (stable only):
62
64
  - Release notes must be a file (not a flag)
63
- - Must reference a GitHub issue
64
65
  - Product docs must be updated
65
- - Technical docs must be updated if source changed
66
66
  - No stale merged branches
67
67
  - Must run from main working tree (not worktree)
68
68
 
69
+ Alpha/beta skip most gates for speed.
70
+
71
+ ## Source Resolution
72
+
73
+ The installer resolves sources in priority order:
74
+
75
+ 1. **npm** (with dist-tag for alpha/beta): works on any machine with internet
76
+ 2. **Local private repo**: works offline on developer machines
77
+ 3. **GitHub clone**: fallback for stable installs and third-party tools
78
+
79
+ ## Version Immutability
80
+
81
+ Same version = same code. Always. If code changed, the version must change. The installer uses version comparison to decide whether to deploy. `wip-release` validates that sub-tools with changed files have bumped versions.
82
+
69
83
  ## Three Separate Steps
70
84
 
71
85
  | Step | What happens | What it means |
72
86
  |------|-------------|---------------|
73
87
  | Merge | PR merged to main | Code lands. Nothing else changes. |
74
- | Deploy | wip-release + deploy-public | Published to npm + GitHub. Not on your machine yet. |
75
- | Install | Run the install prompt | Extensions updated on your machine. |
88
+ | Release | `wip-release alpha/beta/patch` | Published to npm. Available for install. |
89
+ | Install | `ldm install [--alpha/--beta]` | Extensions updated on your machine. |
76
90
 
77
- After Deploy, stop. Don't copy files. Don't npm install. Dogfood the install prompt.
91
+ For stable releases, add a fourth step: `deploy-public` to sync the public GitHub repo.