@wipcomputer/wip-ldm-os 0.4.80 → 0.4.81

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/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.81",
4
4
  "type": "module",
5
5
  "description": "LDM OS: identity, memory, and sovereignty infrastructure for AI agents",
6
6
  "engines": {
@@ -19,6 +19,7 @@
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",
22
23
  "fmt": "npx prettier --write 'src/**/*.{ts,mjs}' 'lib/**/*.mjs' 'bin/**/*.js'",
23
24
  "fmt:check": "npx prettier --check 'src/**/*.{ts,mjs}' 'lib/**/*.mjs' 'bin/**/*.js'"
24
25
  },
@@ -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');