@wipcomputer/wip-ldm-os 0.4.80 → 0.4.82-alpha.1

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/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.80"
12
+ version: "0.4.81"
13
13
  homepage: "https://github.com/wipcomputer/wip-ldm-os"
14
14
  author: "Parker Todd Brooks"
15
15
  category: infrastructure
package/bin/ldm.js CHANGED
@@ -2334,7 +2334,11 @@ async function cmdInstallCatalog() {
2334
2334
  const currentVersion = matchEntry?.installed?.version || matchEntry?.version || '?';
2335
2335
 
2336
2336
  try {
2337
- const latest = execSync(`npm view ${comp.npm} version 2>/dev/null`, {
2337
+ const npmTag = ALPHA_FLAG ? 'alpha' : BETA_FLAG ? 'beta' : 'latest';
2338
+ const npmViewCmd = npmTag === 'latest'
2339
+ ? `npm view ${comp.npm} version 2>/dev/null`
2340
+ : `npm view ${comp.npm} dist-tags.${npmTag} 2>/dev/null`;
2341
+ const latest = execSync(npmViewCmd, {
2338
2342
  encoding: 'utf8', timeout: 10000,
2339
2343
  }).trim();
2340
2344
  if (latest && semverNewer(latest, currentVersion)) {
@@ -2608,24 +2612,18 @@ async function cmdInstallCatalog() {
2608
2612
  execSync(`ldm install ${installSource}`, { stdio: 'inherit' });
2609
2613
  updated++;
2610
2614
 
2611
- // For parent packages, update registry version for all sub-tools (#139, #262)
2615
+ // For parent packages, installFromPath already refreshes each sub-tool
2616
+ // registry entry with that sub-tool's own package version. Do not stamp
2617
+ // the parent package version onto sub-tools; their versions intentionally
2618
+ // differ from the toolbox aggregate version.
2612
2619
  if (entry.isParent && entry.registryMatches) {
2613
2620
  const registry = readJSON(REGISTRY_PATH);
2614
2621
  if (registry?.extensions) {
2615
- const now = new Date().toISOString();
2622
+ let refreshed = 0;
2616
2623
  for (const subTool of entry.registryMatches) {
2617
- if (registry.extensions[subTool]) {
2618
- registry.extensions[subTool].version = entry.latestVersion;
2619
- registry.extensions[subTool].updatedAt = now;
2620
- // Also update v2 installed block
2621
- if (registry.extensions[subTool].installed) {
2622
- registry.extensions[subTool].installed.version = entry.latestVersion;
2623
- registry.extensions[subTool].installed.updatedAt = now;
2624
- }
2625
- }
2624
+ if (registry.extensions[subTool]) refreshed++;
2626
2625
  }
2627
- writeFileSync(REGISTRY_PATH, JSON.stringify(registry, null, 2));
2628
- console.log(` + Updated registry for ${entry.registryMatches.length} sub-tools`);
2626
+ console.log(` + Refreshed registry for ${refreshed}/${entry.registryMatches.length} sub-tools`);
2629
2627
  }
2630
2628
  }
2631
2629
  } catch (e) {
package/lib/deploy.mjs CHANGED
@@ -59,6 +59,57 @@ function writeJSON(path, data) {
59
59
  writeFileSync(path, JSON.stringify(data, null, 2) + '\n');
60
60
  }
61
61
 
62
+ export function validateSkillFrontmatter(path) {
63
+ let content;
64
+ try {
65
+ content = readFileSync(path, 'utf8');
66
+ } catch (e) {
67
+ return { ok: false, line: 1, message: `cannot read SKILL.md: ${e.message}` };
68
+ }
69
+
70
+ const lines = content.split(/\r?\n/);
71
+ if (lines[0] !== '---') {
72
+ return { ok: false, line: 1, message: 'SKILL.md must start with YAML frontmatter' };
73
+ }
74
+
75
+ let end = -1;
76
+ for (let i = 1; i < lines.length; i++) {
77
+ if (lines[i] === '---') {
78
+ end = i;
79
+ break;
80
+ }
81
+ }
82
+ if (end === -1) {
83
+ return { ok: false, line: 1, message: 'SKILL.md frontmatter is missing a closing --- marker' };
84
+ }
85
+
86
+ for (let i = 1; i < end; i++) {
87
+ const line = lines[i];
88
+ const trimmed = line.trim();
89
+ if (!trimmed || trimmed.startsWith('#') || /^\s/.test(line)) continue;
90
+
91
+ const match = /^([A-Za-z0-9_-]+):(?:\s*(.*))?$/.exec(line);
92
+ if (!match) {
93
+ return { ok: false, line: i + 1, message: 'frontmatter line is not a simple key/value mapping' };
94
+ }
95
+
96
+ const value = match[2] || '';
97
+ const valueTrimmed = value.trimStart();
98
+ const first = valueTrimmed[0] || '';
99
+ const isQuoted = first === '"' || first === "'";
100
+ const isStructured = first === '[' || first === '{' || first === '|' || first === '>';
101
+ if (valueTrimmed.includes(': ') && !isQuoted && !isStructured) {
102
+ return {
103
+ ok: false,
104
+ line: i + 1,
105
+ message: 'value contains an unquoted colon; quote the scalar value',
106
+ };
107
+ }
108
+ }
109
+
110
+ return { ok: true };
111
+ }
112
+
62
113
  function ensureBinExecutable(binNames) {
63
114
  try {
64
115
  const npmPrefix = execSync('npm config get prefix', { encoding: 'utf8' }).trim();
@@ -1195,6 +1246,12 @@ function installSkill(repoPath, toolName) {
1195
1246
  if (!existsSync(skillSrc) && existsSync(permanentSkill)) skillSrc = permanentSkill;
1196
1247
  if (!existsSync(skillSrc)) return false;
1197
1248
 
1249
+ const frontmatter = validateSkillFrontmatter(skillSrc);
1250
+ if (!frontmatter.ok) {
1251
+ fail(`Skill: invalid SKILL.md frontmatter at ${skillSrc}:${frontmatter.line}: ${frontmatter.message}`);
1252
+ return false;
1253
+ }
1254
+
1198
1255
  // Find references/ source: repo path first, then permanent copy
1199
1256
  let refsSrc = join(repoPath, 'references');
1200
1257
  const permanentRefs = join(LDM_EXTENSIONS, toolName, 'references');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipcomputer/wip-ldm-os",
3
- "version": "0.4.80",
3
+ "version": "0.4.82-alpha.1",
4
4
  "type": "module",
5
5
  "description": "LDM OS: identity, memory, and sovereignty infrastructure for AI agents",
6
6
  "engines": {
@@ -19,6 +19,8 @@
19
19
  "build:bridge": "cd src/bridge && npm install && npx tsup core.ts mcp-server.ts cli.ts openclaw.ts --format esm --dts --clean --outDir ../../dist/bridge",
20
20
  "build": "npm run build:bridge",
21
21
  "prepublishOnly": "npm run build:bridge",
22
+ "test:skill-frontmatter": "node scripts/test-skill-frontmatter.mjs",
23
+ "test:installer-update-tracks": "node scripts/test-installer-update-tracks.mjs",
22
24
  "fmt": "npx prettier --write 'src/**/*.{ts,mjs}' 'lib/**/*.mjs' 'bin/**/*.js'",
23
25
  "fmt:check": "npx prettier --check 'src/**/*.{ts,mjs}' 'lib/**/*.mjs' 'bin/**/*.js'"
24
26
  },
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync } from 'node:fs';
3
+ import { dirname, join } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+
6
+ const root = dirname(dirname(fileURLToPath(import.meta.url)));
7
+ const cli = readFileSync(join(root, 'bin', 'ldm.js'), 'utf8');
8
+
9
+ const marker = '// Check parent packages for toolbox-style repos (#132)';
10
+ const idx = cli.indexOf(marker);
11
+ if (idx === -1) {
12
+ throw new Error('Could not find toolbox parent update block');
13
+ }
14
+
15
+ const parentBlock = cli.slice(idx, cli.indexOf('const totalUpdates = npmUpdates.length;', idx));
16
+ if (!parentBlock.includes("const npmTag = ALPHA_FLAG ? 'alpha' : BETA_FLAG ? 'beta' : 'latest';")) {
17
+ throw new Error('Toolbox parent update block does not select the requested release track');
18
+ }
19
+
20
+ if (!parentBlock.includes('dist-tags.${npmTag}')) {
21
+ throw new Error('Toolbox parent update block does not query alpha/beta dist-tags');
22
+ }
23
+
24
+ if (/const latest = execSync\(`npm view \$\{comp\.npm\} version 2>\/dev\/null`/.test(parentBlock)) {
25
+ throw new Error('Toolbox parent update block still hardcodes the stable npm version query');
26
+ }
27
+
28
+ const installMarker = '// For parent packages, installFromPath already refreshes each sub-tool';
29
+ const installIdx = cli.indexOf(installMarker);
30
+ if (installIdx === -1) {
31
+ throw new Error('Could not find parent registry refresh block');
32
+ }
33
+
34
+ const installBlock = cli.slice(installIdx, cli.indexOf('} catch (e) {', installIdx));
35
+ if (installBlock.includes('= entry.latestVersion')) {
36
+ throw new Error('Parent update block must not stamp parent versions onto sub-tool registry entries');
37
+ }
38
+
39
+ console.log('installer update-track regression checks passed');
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env node
2
+ import { mkdtempSync, writeFileSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { validateSkillFrontmatter } from '../lib/deploy.mjs';
6
+
7
+ const dir = mkdtempSync(join(tmpdir(), 'ldm-skill-frontmatter-'));
8
+ const bad = join(dir, 'bad-SKILL.md');
9
+ const good = join(dir, 'good-SKILL.md');
10
+
11
+ writeFileSync(bad, [
12
+ '---',
13
+ 'name: bad',
14
+ 'description: Read when: guard blocks a tool call',
15
+ '---',
16
+ '',
17
+ '# Bad',
18
+ '',
19
+ ].join('\n'));
20
+
21
+ writeFileSync(good, [
22
+ '---',
23
+ 'name: good',
24
+ 'description: "Read when: guard blocks a tool call"',
25
+ '---',
26
+ '',
27
+ '# Good',
28
+ '',
29
+ ].join('\n'));
30
+
31
+ const badResult = validateSkillFrontmatter(bad);
32
+ if (badResult.ok) {
33
+ throw new Error('expected unquoted colon frontmatter to be rejected');
34
+ }
35
+ if (badResult.line !== 3) {
36
+ throw new Error(`expected failure on line 3, got line ${badResult.line}`);
37
+ }
38
+
39
+ const goodResult = validateSkillFrontmatter(good);
40
+ if (!goodResult.ok) {
41
+ throw new Error(`expected quoted frontmatter to pass: ${goodResult.message}`);
42
+ }
43
+
44
+ console.log('skill frontmatter regression passed');
@@ -28,7 +28,9 @@ These are three distinct actions. Never combine them. Never skip the dogfooding
28
28
  | **Deploy** | Ship to public | `wip-release` (version bump, npm publish, GitHub release) + `deploy-public.sh` (sync to public repo). Package is available to the world. **Still not on our machine.** |
29
29
  | **Install** | Put it on our system | `crystal init` or equivalent. Extensions updated. Hooks configured. Only when Parker says "install." |
30
30
 
31
- **After Deploy, STOP.** Do not copy files to `~/.ldm/extensions/` or `~/.openclaw/extensions/`. Do not run `npm install -g`. Do not run `npm link`. Do not touch the installed system. Tell Parker: "v0.X.Y is published. Run the install prompt when you're ready to update."
31
+ **Prerelease exception:** agents install alpha and beta tracks for validation. Use `ldm install --alpha` after an alpha release and `ldm install --beta` after a beta release. That is test work.
32
+
33
+ **After stable Deploy, STOP.** Do not copy files to `~/.ldm/extensions/` or `~/.openclaw/extensions/`. Do not run `npm install -g`. Do not run `npm link`. Do not run `ldm install` unless Parker explicitly asks. Tell Parker: "v0.X.Y is published. Run the install prompt when you're ready to update."
32
34
 
33
35
  **We always dogfood our own software.** The install prompt exists so Parker can see what's new, review the dry run, and decide to install. If agents deploy directly to extensions, the install prompt says "already up to date" and the dogfooding loop is broken.
34
36
 
@@ -31,6 +31,8 @@ ldm install --beta # beta track (npm @beta)
31
31
 
32
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
33
 
34
+ Agents may run alpha and beta installs for prerelease validation. Stable installs are different: Parker dogfoods stable/latest releases through the install prompt unless he explicitly asks an agent to install.
35
+
34
36
  ## Source Resolution
35
37
 
36
38
  When updating, the installer finds the package in priority order:
@@ -29,11 +29,13 @@ cd /path/to/repo && git checkout main && git pull
29
29
  # 4. Alpha release
30
30
  wip-release alpha --notes="what changed"
31
31
 
32
- # 4. Install and test
32
+ # 5. Agent prerelease validation
33
33
  ldm install --alpha
34
34
  ```
35
35
 
36
- Done. No deploy-public. No public repo sync. The installer pulls from npm @alpha (or finds the local private repo if offline).
36
+ Done. No deploy-public. No public repo sync. The installer pulls from npm @alpha (or finds the local private repo if offline). Agents are expected to run alpha installs when validating prereleases.
37
+
38
+ Beta follows the same rule with `wip-release beta` and `ldm install --beta`. Agents validate alpha and beta tracks. Parker dogfoods stable/latest releases.
37
39
 
38
40
  **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.
39
41
 
@@ -53,13 +55,15 @@ wip-release patch # auto-detects release notes
53
55
  deploy-public /path/to/private-repo org/public-repo
54
56
 
55
57
  # 7. Dogfood
56
- ldm install
58
+ Read https://wip.computer/install/wip-ldm-os.txt
57
59
  ```
58
60
 
59
61
  `wip-release` handles: version bump, CHANGELOG.md, SKILL.md sync, npm publish, GitHub release.
60
62
 
61
63
  `deploy-public` syncs everything except `ai/` to the public repo. Creates a matching release.
62
64
 
65
+ For stable/latest releases, agents stop after publish and public sync. Do not run `ldm install` or `npm install -g` unless Parker explicitly asks. Parker dogfoods the release through the install prompt.
66
+
63
67
  ## Quality Gates
64
68
 
65
69
  `wip-release` enforces before publishing (stable only):
@@ -91,3 +95,5 @@ Same version = same code. Always. If code changed, the version must change. The
91
95
  | Install | `ldm install [--alpha/--beta]` | Extensions updated on your machine. |
92
96
 
93
97
  For stable releases, add a fourth step: `deploy-public` to sync the public GitHub repo.
98
+
99
+ Ownership rule: agents install alpha and beta for validation. Parker installs stable/latest releases for dogfooding unless he explicitly asks an agent to do it.
@@ -21,7 +21,9 @@ Then: repo change, PR, merge, release, `ldm install`. That's the only path.
21
21
  | **Deploy** | wip-release + deploy-public.sh | Published to npm + GitHub. Not on your machine yet. |
22
22
  | **Install** | Run the install prompt | Extensions updated on your machine. Only when Parker says "install." |
23
23
 
24
- After Deploy, STOP. Do not copy files. Do not npm install -g. Do not npm link. Dogfood the install prompt.
24
+ For alpha and beta tracks, agents install prereleases for validation: `ldm install --alpha` or `ldm install --beta`. That is test work, not owner dogfooding.
25
+
26
+ For stable/latest releases, after Deploy, STOP. Do not copy files. Do not npm install -g. Do not npm link. Do not run `ldm install` unless Parker explicitly asks. Parker dogfoods stable releases through the install prompt.
25
27
 
26
28
  ## The workflow
27
29
 
@@ -31,7 +33,7 @@ After Deploy, STOP. Do not copy files. Do not npm install -g. Do not npm link. D
31
33
  4. `git checkout main && git pull`
32
34
  5. `wip-release patch` (auto-detects release notes)
33
35
  6. `deploy-public.sh` to sync public repo
34
- 7. Dogfood: `Read https://wip.computer/install/wip-ldm-os.txt`
36
+ 7. Stop. Parker dogfoods: `Read https://wip.computer/install/wip-ldm-os.txt`
35
37
 
36
38
  ## Never run tools from repo clones
37
39