@rune-kit/rune 2.2.2 → 2.2.4

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 (45) hide show
  1. package/compiler/__tests__/adapters.test.js +109 -0
  2. package/compiler/__tests__/openclaw-adapter.test.js +149 -140
  3. package/compiler/__tests__/pack-split.test.js +145 -145
  4. package/compiler/__tests__/parser.test.js +3 -3
  5. package/compiler/__tests__/pipeline.test.js +110 -0
  6. package/compiler/__tests__/skill-validation.test.js +148 -0
  7. package/compiler/__tests__/transformer.test.js +70 -0
  8. package/compiler/__tests__/transforms.test.js +92 -0
  9. package/compiler/adapters/antigravity.js +5 -6
  10. package/compiler/adapters/claude.js +1 -1
  11. package/compiler/adapters/codex.js +69 -77
  12. package/compiler/adapters/cursor.js +5 -6
  13. package/compiler/adapters/generic.js +5 -6
  14. package/compiler/adapters/index.js +3 -3
  15. package/compiler/adapters/openclaw.js +146 -150
  16. package/compiler/adapters/opencode.js +78 -86
  17. package/compiler/adapters/windsurf.js +5 -6
  18. package/compiler/bin/rune.js +32 -23
  19. package/compiler/doctor.js +19 -7
  20. package/compiler/emitter.js +11 -18
  21. package/compiler/parser.js +5 -5
  22. package/compiler/transformer.js +3 -5
  23. package/compiler/transforms/branding.js +5 -4
  24. package/compiler/transforms/compliance.js +40 -40
  25. package/compiler/transforms/frontmatter.js +1 -1
  26. package/compiler/transforms/hooks.js +12 -14
  27. package/compiler/transforms/subagents.js +1 -3
  28. package/compiler/transforms/tool-names.js +1 -1
  29. package/docs/guides/index.html +48 -7
  30. package/docs/index.html +67 -2
  31. package/docs/style.css +18 -1
  32. package/extensions/security/PACK.md +4 -3
  33. package/extensions/security/skills/defense-in-depth.md +103 -0
  34. package/package.json +8 -1
  35. package/skills/completion-gate/SKILL.md +31 -1
  36. package/skills/cook/SKILL.md +20 -2
  37. package/skills/debug/SKILL.md +16 -1
  38. package/skills/hallucination-guard/SKILL.md +21 -6
  39. package/skills/plan/SKILL.md +90 -1
  40. package/skills/review/SKILL.md +1 -0
  41. package/skills/sentinel-env/SKILL.md +31 -1
  42. package/skills/skill-forge/SKILL.md +57 -1
  43. package/skills/team/SKILL.md +61 -15
  44. package/skills/test/SKILL.md +101 -10
  45. package/skills/verification/SKILL.md +29 -1
@@ -1,86 +1,78 @@
1
- /**
2
- * OpenCode Adapter
3
- *
4
- * Emits SKILL.md files into .opencode/skills/{name}/ directories.
5
- * OpenCode uses the same SKILL.md frontmatter format (name, description)
6
- * with markdown body — identical to Codex pattern.
7
- *
8
- * OpenCode project context: AGENTS.md (+ CLAUDE.md fallback)
9
- * OpenCode skills dir: .opencode/skills/
10
- * OpenCode skill format: .opencode/skills/{name}/SKILL.md
11
- * OpenCode agents dir: .opencode/agents/
12
- *
13
- * OpenCode also searches:
14
- * .claude/skills/{name}/SKILL.md (Claude-compatible)
15
- * .agents/skills/{name}/SKILL.md (agent-compatible)
16
- *
17
- * @see https://opencode.ai/docs/skills/
18
- * @see https://opencode.ai/docs/agents/
19
- */
20
-
21
- const TOOL_MAP = {
22
- Read: 'read the file',
23
- Write: 'write/create the file',
24
- Edit: 'edit the file',
25
- Glob: 'find files by pattern',
26
- Grep: 'search file contents',
27
- Bash: 'run a shell command',
28
- TodoWrite: 'track task progress',
29
- Skill: 'invoke the named skill',
30
- Agent: 'delegate to a subagent',
31
- };
32
-
33
- export default {
34
- name: 'opencode',
35
- outputDir: '.opencode/skills',
36
- fileExtension: '.md',
37
- skillPrefix: 'rune-',
38
- skillSuffix: '',
39
-
40
- // OpenCode uses directory-per-skill: .opencode/skills/{name}/SKILL.md
41
- useSkillDirectories: true,
42
- skillFileName: 'SKILL.md',
43
-
44
- transformReference(skillName, raw) {
45
- const isBackticked = raw.startsWith('`') && raw.endsWith('`');
46
- const ref = `the rune-${skillName} skill`;
47
- return isBackticked ? `\`${ref}\`` : ref;
48
- },
49
-
50
- transformToolName(toolName) {
51
- return TOOL_MAP[toolName] || toolName;
52
- },
53
-
54
- generateHeader(skill) {
55
- const desc = (skill.description || '').replace(/"/g, '\\"');
56
- return [
57
- '---',
58
- `name: rune-${skill.name}`,
59
- `description: "${desc}"`,
60
- '---',
61
- '',
62
- '',
63
- ].join('\n');
64
- },
65
-
66
- generateFooter() {
67
- return [
68
- '',
69
- '---',
70
- '> **Rune Skill Mesh** — 58 skills, 200+ connections',
71
- '> Source: https://github.com/rune-kit/rune',
72
- '> Full experience with subagents, hooks, adaptive routing → use Rune on Claude Code.',
73
- ].join('\n');
74
- },
75
-
76
- transformSubagentInstruction(text) {
77
- // OpenCode has native subagent support — preserve parallel agent instructions
78
- return text;
79
- },
80
-
81
- postProcess(content) {
82
- return content
83
- .replace(/^context: fork\n/gm, '')
84
- .replace(/^agent: general-purpose\n/gm, '');
85
- },
86
- };
1
+ /**
2
+ * OpenCode Adapter
3
+ *
4
+ * Emits SKILL.md files into .opencode/skills/{name}/ directories.
5
+ * OpenCode uses the same SKILL.md frontmatter format (name, description)
6
+ * with markdown body — identical to Codex pattern.
7
+ *
8
+ * OpenCode project context: AGENTS.md (+ CLAUDE.md fallback)
9
+ * OpenCode skills dir: .opencode/skills/
10
+ * OpenCode skill format: .opencode/skills/{name}/SKILL.md
11
+ * OpenCode agents dir: .opencode/agents/
12
+ *
13
+ * OpenCode also searches:
14
+ * .claude/skills/{name}/SKILL.md (Claude-compatible)
15
+ * .agents/skills/{name}/SKILL.md (agent-compatible)
16
+ *
17
+ * @see https://opencode.ai/docs/skills/
18
+ * @see https://opencode.ai/docs/agents/
19
+ */
20
+
21
+ const TOOL_MAP = {
22
+ Read: 'read the file',
23
+ Write: 'write/create the file',
24
+ Edit: 'edit the file',
25
+ Glob: 'find files by pattern',
26
+ Grep: 'search file contents',
27
+ Bash: 'run a shell command',
28
+ TodoWrite: 'track task progress',
29
+ Skill: 'invoke the named skill',
30
+ Agent: 'delegate to a subagent',
31
+ };
32
+
33
+ export default {
34
+ name: 'opencode',
35
+ outputDir: '.opencode/skills',
36
+ fileExtension: '.md',
37
+ skillPrefix: 'rune-',
38
+ skillSuffix: '',
39
+
40
+ // OpenCode uses directory-per-skill: .opencode/skills/{name}/SKILL.md
41
+ useSkillDirectories: true,
42
+ skillFileName: 'SKILL.md',
43
+
44
+ transformReference(skillName, raw) {
45
+ const isBackticked = raw.startsWith('`') && raw.endsWith('`');
46
+ const ref = `the rune-${skillName} skill`;
47
+ return isBackticked ? `\`${ref}\`` : ref;
48
+ },
49
+
50
+ transformToolName(toolName) {
51
+ return TOOL_MAP[toolName] || toolName;
52
+ },
53
+
54
+ generateHeader(skill) {
55
+ const desc = (skill.description || '').replace(/"/g, '\\"');
56
+ return ['---', `name: rune-${skill.name}`, `description: "${desc}"`, '---', '', ''].join('\n');
57
+ },
58
+
59
+ generateFooter() {
60
+ return [
61
+ '',
62
+ '---',
63
+ '> **Rune Skill Mesh** — 58 skills, 200+ connections, 14 extension packs',
64
+ '> Source: https://github.com/rune-kit/rune (MIT)',
65
+ '> **Rune Pro** ($49 lifetime) — product, sales, data-science, support packs → [rune-kit/rune-pro](https://github.com/rune-kit/rune-pro)',
66
+ '> **Rune Business** ($149 lifetime) — finance, legal, HR, enterprise-search packs → [rune-kit/rune-business](https://github.com/rune-kit/rune-business)',
67
+ ].join('\n');
68
+ },
69
+
70
+ transformSubagentInstruction(text) {
71
+ // OpenCode has native subagent support — preserve parallel agent instructions
72
+ return text;
73
+ },
74
+
75
+ postProcess(content) {
76
+ return content.replace(/^context: fork\n/gm, '').replace(/^agent: general-purpose\n/gm, '');
77
+ },
78
+ };
@@ -42,9 +42,10 @@ export default {
42
42
  return [
43
43
  '',
44
44
  '---',
45
- '> **Rune Skill Mesh** — 58 skills, 200+ connections',
46
- '> Source: https://github.com/rune-kit/rune',
47
- '> Full experience with subagents, hooks, adaptive routinguse Rune on Claude Code.',
45
+ '> **Rune Skill Mesh** — 58 skills, 200+ connections, 14 extension packs',
46
+ '> Source: https://github.com/rune-kit/rune (MIT)',
47
+ '> **Rune Pro** ($49 lifetime) — product, sales, data-science, support packs [rune-kit/rune-pro](https://github.com/rune-kit/rune-pro)',
48
+ '> **Rune Business** ($149 lifetime) — finance, legal, HR, enterprise-search packs → [rune-kit/rune-business](https://github.com/rune-kit/rune-business)',
48
49
  ].join('\n');
49
50
  },
50
51
 
@@ -53,8 +54,6 @@ export default {
53
54
  },
54
55
 
55
56
  postProcess(content) {
56
- return content
57
- .replace(/^context: fork\n/gm, '')
58
- .replace(/^agent: general-purpose\n/gm, '');
57
+ return content.replace(/^context: fork\n/gm, '').replace(/^agent: general-purpose\n/gm, '');
59
58
  },
60
59
  };
@@ -9,14 +9,14 @@
9
9
  * rune doctor — Validate compiled output
10
10
  */
11
11
 
12
- import { readFile, writeFile } from 'node:fs/promises';
13
12
  import { existsSync } from 'node:fs';
13
+ import { readFile, writeFile } from 'node:fs/promises';
14
14
  import path from 'node:path';
15
- import { fileURLToPath } from 'node:url';
16
15
  import { createInterface } from 'node:readline';
16
+ import { fileURLToPath } from 'node:url';
17
17
  import { getAdapter, listPlatforms } from '../adapters/index.js';
18
+ import { formatDoctorResults, runDoctor } from '../doctor.js';
18
19
  import { buildAll } from '../emitter.js';
19
- import { runDoctor, formatDoctorResults } from '../doctor.js';
20
20
 
21
21
  const __filename = fileURLToPath(import.meta.url);
22
22
  const __dirname = path.dirname(__filename);
@@ -26,8 +26,12 @@ const CONFIG_FILE = 'rune.config.json';
26
26
 
27
27
  // ─── Helpers ───
28
28
 
29
- function log(msg) { console.log(msg); }
30
- function logStep(icon, msg) { console.log(` ${icon} ${msg}`); }
29
+ function log(msg) {
30
+ console.log(msg);
31
+ }
32
+ function logStep(icon, msg) {
33
+ console.log(` ${icon} ${msg}`);
34
+ }
31
35
 
32
36
  async function readConfig(projectRoot) {
33
37
  const configPath = path.join(projectRoot, CONFIG_FILE);
@@ -37,7 +41,7 @@ async function readConfig(projectRoot) {
37
41
 
38
42
  async function writeConfig(projectRoot, config) {
39
43
  const configPath = path.join(projectRoot, CONFIG_FILE);
40
- await writeFile(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
44
+ await writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, 'utf-8');
41
45
  }
42
46
 
43
47
  function detectPlatform(projectRoot) {
@@ -51,11 +55,10 @@ function detectPlatform(projectRoot) {
51
55
  return null;
52
56
  }
53
57
 
54
-
55
58
  async function prompt(question) {
56
59
  const rl = createInterface({ input: process.stdin, output: process.stdout });
57
- return new Promise(resolve => {
58
- rl.question(question, answer => {
60
+ return new Promise((resolve) => {
61
+ rl.question(question, (answer) => {
59
62
  rl.close();
60
63
  resolve(answer.trim());
61
64
  });
@@ -77,7 +80,7 @@ async function cmdInit(projectRoot, args) {
77
80
  if (platform) {
78
81
  logStep('→', `Detected: ${platform}`);
79
82
  } else {
80
- log(' Available platforms: ' + listPlatforms().join(', '));
83
+ log(` Available platforms: ${listPlatforms().join(', ')}`);
81
84
  const answer = await prompt(' ? Select platform: ');
82
85
  platform = answer.toLowerCase();
83
86
  if (!listPlatforms().includes(platform)) {
@@ -93,9 +96,7 @@ async function cmdInit(projectRoot, args) {
93
96
  }
94
97
 
95
98
  // Extension pack selection
96
- const extensions = args.extensions
97
- ? args.extensions.split(',')
98
- : null; // null = all
99
+ const extensions = args.extensions ? args.extensions.split(',') : null; // null = all
99
100
 
100
101
  // Build config
101
102
  const config = {
@@ -158,9 +159,7 @@ async function cmdBuild(projectRoot, args) {
158
159
  }
159
160
 
160
161
  const adapter = getAdapter(platform);
161
- const runeRoot = (config?.source === '@rune-kit/rune')
162
- ? RUNE_ROOT
163
- : (config?.source || RUNE_ROOT);
162
+ const runeRoot = config?.source === '@rune-kit/rune' ? RUNE_ROOT : config?.source || RUNE_ROOT;
164
163
  const outputRoot = typeof args.output === 'string' ? args.output : projectRoot;
165
164
  const disabledSkills = config?.skills?.disabled || [];
166
165
  const enabledPacks = config?.extensions?.enabled || null;
@@ -200,15 +199,23 @@ async function cmdDoctor(projectRoot, args) {
200
199
  const config = await readConfig(projectRoot);
201
200
 
202
201
  if (!config) {
203
- log(' ✗ No rune.config.json found. Run `rune init` first.');
204
- process.exit(1);
202
+ // No config = CI or fresh clone. Run source-only checks (split packs).
203
+ log('');
204
+ log(' ℹ No rune.config.json found — running source-only checks.');
205
+ const results = await runDoctor({
206
+ outputRoot: projectRoot,
207
+ adapter: getAdapter('claude'),
208
+ config: {},
209
+ runeRoot: RUNE_ROOT,
210
+ });
211
+ log(formatDoctorResults(results));
212
+ if (!results.healthy) process.exit(1);
213
+ return;
205
214
  }
206
215
 
207
216
  const platform = args.platform || config.platform;
208
217
  const adapter = getAdapter(platform);
209
- const runeRoot = (config.source === '@rune-kit/rune')
210
- ? RUNE_ROOT
211
- : (config.source || RUNE_ROOT);
218
+ const runeRoot = config.source === '@rune-kit/rune' ? RUNE_ROOT : config.source || RUNE_ROOT;
212
219
 
213
220
  const results = await runDoctor({
214
221
  outputRoot: projectRoot,
@@ -295,7 +302,9 @@ async function main() {
295
302
  log(' doctor Validate compiled output');
296
303
  log('');
297
304
  log(' Options:');
298
- log(' --platform <name> Override platform (cursor, windsurf, antigravity, codex, openclaw, opencode, generic)');
305
+ log(
306
+ ' --platform <name> Override platform (cursor, windsurf, antigravity, codex, openclaw, opencode, generic)',
307
+ );
299
308
  log(' --output <dir> Override output directory');
300
309
  log(' --disable <skills> Comma-separated skills to disable');
301
310
  log(' --version, -v Show version');
@@ -307,7 +316,7 @@ async function main() {
307
316
  }
308
317
  }
309
318
 
310
- main().catch(err => {
319
+ main().catch((err) => {
311
320
  console.error(' ✗ Fatal:', err.message);
312
321
  process.exit(1);
313
322
  });
@@ -4,8 +4,8 @@
4
4
  * Checks: files exist, cross-references resolve, layer discipline, source freshness.
5
5
  */
6
6
 
7
- import { readdir, readFile, stat } from 'node:fs/promises';
8
7
  import { existsSync } from 'node:fs';
8
+ import { readdir, readFile } from 'node:fs/promises';
9
9
  import path from 'node:path';
10
10
  import { parsePack } from './parser.js';
11
11
 
@@ -28,14 +28,18 @@ export async function runDoctor({ outputRoot, adapter, config, runeRoot }) {
28
28
  healthy: true,
29
29
  };
30
30
 
31
- // Check 1: Config exists
31
+ // Check 1: Config exists (skip in CI / source-only mode)
32
32
  const configPath = path.join(outputRoot, 'rune.config.json');
33
33
  if (existsSync(configPath)) {
34
34
  results.checks.push({ name: 'Config file', status: 'pass' });
35
- } else {
35
+ } else if (config && Object.keys(config).length > 0) {
36
+ // Config was passed but file doesn't exist on disk — real problem
36
37
  results.checks.push({ name: 'Config file', status: 'fail', detail: 'rune.config.json not found' });
37
38
  results.errors.push('rune.config.json not found. Run `rune init` first.');
38
39
  results.healthy = false;
40
+ } else {
41
+ // No config at all (CI / fresh clone) — skip gracefully
42
+ results.checks.push({ name: 'Config file', status: 'skip', detail: 'No config — source-only mode' });
39
43
  }
40
44
 
41
45
  // Check 2: Output directory exists
@@ -56,13 +60,17 @@ export async function runDoctor({ outputRoot, adapter, config, runeRoot }) {
56
60
 
57
61
  // Check 3: Count skill files
58
62
  const files = await readdir(outputDir);
59
- const skillFiles = files.filter(f => f.startsWith('rune-') && f !== `rune-index${adapter.fileExtension}`);
63
+ const skillFiles = files.filter((f) => f.startsWith('rune-') && f !== `rune-index${adapter.fileExtension}`);
60
64
  const expectedSkillCount = 55 - (config.skills?.disabled?.length || 0);
61
65
 
62
66
  if (skillFiles.length >= expectedSkillCount) {
63
67
  results.checks.push({ name: 'Skill files', status: 'pass', detail: `${skillFiles.length}/${expectedSkillCount}` });
64
68
  } else {
65
- results.checks.push({ name: 'Skill files', status: 'warn', detail: `${skillFiles.length}/${expectedSkillCount} present` });
69
+ results.checks.push({
70
+ name: 'Skill files',
71
+ status: 'warn',
72
+ detail: `${skillFiles.length}/${expectedSkillCount} present`,
73
+ });
66
74
  results.warnings.push(`Expected ${expectedSkillCount} skill files, found ${skillFiles.length}`);
67
75
  }
68
76
 
@@ -97,7 +105,11 @@ export async function runDoctor({ outputRoot, adapter, config, runeRoot }) {
97
105
  if (splitPackErrors.length === 0) {
98
106
  results.checks.push({ name: 'Split packs', status: 'pass' });
99
107
  } else {
100
- results.checks.push({ name: 'Split packs', status: 'fail', detail: `${splitPackErrors.length} missing skill files` });
108
+ results.checks.push({
109
+ name: 'Split packs',
110
+ status: 'fail',
111
+ detail: `${splitPackErrors.length} missing skill files`,
112
+ });
101
113
  results.errors.push(...splitPackErrors);
102
114
  }
103
115
  }
@@ -169,7 +181,7 @@ export function formatDoctorResults(results) {
169
181
  lines.push(`\n Platform: ${results.platform}`);
170
182
 
171
183
  for (const check of results.checks) {
172
- const icon = check.status === 'pass' ? '✓' : check.status === 'warn' ? '!' : '✗';
184
+ const icon = check.status === 'pass' ? '✓' : check.status === 'warn' ? '!' : check.status === 'skip' ? '–' : '✗';
173
185
  const detail = check.detail ? ` (${check.detail})` : '';
174
186
  lines.push(` [${icon}] ${check.name}${detail}`);
175
187
  }
@@ -5,10 +5,10 @@
5
5
  * Handles file naming, directory creation, and index generation.
6
6
  */
7
7
 
8
- import { readdir, readFile, mkdir, writeFile } from 'node:fs/promises';
9
8
  import { existsSync } from 'node:fs';
9
+ import { mkdir, readdir, readFile, writeFile } from 'node:fs/promises';
10
10
  import path from 'node:path';
11
- import { parseSkill, parsePack, extractCrossRefs, extractToolRefs } from './parser.js';
11
+ import { extractCrossRefs, extractToolRefs, parsePack, parseSkill } from './parser.js';
12
12
  import { transformSkill } from './transformer.js';
13
13
 
14
14
  /**
@@ -176,7 +176,7 @@ export async function buildAll({ runeRoot, outputRoot, adapter, disabledSkills =
176
176
  }
177
177
  }
178
178
  // Concatenate: index body + all skill bodies
179
- parsed.body = parsed.body + '\n\n' + skillBodies.join('\n\n---\n\n');
179
+ parsed.body = `${parsed.body}\n\n${skillBodies.join('\n\n---\n\n')}`;
180
180
  // Re-extract refs from the full concatenated body
181
181
  parsed.crossRefs = extractCrossRefs(parsed.body);
182
182
  parsed.toolRefs = extractToolRefs(parsed.body);
@@ -232,7 +232,9 @@ export async function buildAll({ runeRoot, outputRoot, adapter, disabledSkills =
232
232
  try {
233
233
  const c = await readFile(sp, 'utf-8');
234
234
  parsedSkills.push(parseSkill(c, sp));
235
- } catch { /* skip on error */ }
235
+ } catch {
236
+ /* skip on error */
237
+ }
236
238
  }
237
239
 
238
240
  // Read skill-router content for system prompt injection
@@ -245,11 +247,7 @@ export async function buildAll({ runeRoot, outputRoot, adapter, disabledSkills =
245
247
  // Write openclaw.plugin.json to parent of skills dir (.openclaw/rune/)
246
248
  const openclawRoot = path.resolve(outputDir, '..');
247
249
  const manifest = adapter.generateManifest(parsedSkills, pluginJson);
248
- await writeFile(
249
- path.join(openclawRoot, 'openclaw.plugin.json'),
250
- JSON.stringify(manifest, null, 2) + '\n',
251
- 'utf-8',
252
- );
250
+ await writeFile(path.join(openclawRoot, 'openclaw.plugin.json'), `${JSON.stringify(manifest, null, 2)}\n`, 'utf-8');
253
251
  stats.files.push('openclaw.plugin.json');
254
252
 
255
253
  // Write src/index.ts entry point
@@ -274,21 +272,16 @@ function generateIndex(stats, adapter) {
274
272
  '',
275
273
  '## Core Skills',
276
274
  '',
277
- ...stats.files
278
- .filter(f => !f.match(/[-/]ext-/) && !f.includes('index'))
279
- .map(f => `- ${f}`),
275
+ ...stats.files.filter((f) => !f.match(/[-/]ext-/) && !f.includes('index')).map((f) => `- ${f}`),
280
276
  '',
281
277
  ];
282
278
 
283
- const extFiles = stats.files.filter(f => f.match(/[-/]ext-/));
279
+ const extFiles = stats.files.filter((f) => f.match(/[-/]ext-/));
284
280
  if (extFiles.length > 0) {
285
- lines.push('## Extension Packs', '', ...extFiles.map(f => `- ${f}`), '');
281
+ lines.push('## Extension Packs', '', ...extFiles.map((f) => `- ${f}`), '');
286
282
  }
287
283
 
288
- lines.push(
289
- '---',
290
- '> Rune Skill Mesh — https://github.com/rune-kit/rune',
291
- );
284
+ lines.push('---', '> Rune Skill Mesh — https://github.com/rune-kit/rune');
292
285
 
293
286
  return lines.join('\n');
294
287
  }
@@ -8,7 +8,7 @@
8
8
  const CROSS_REF_PATTERN = /`?rune:([a-z][\w-]*)`?/g;
9
9
  const TOOL_REF_PATTERN = /`(Read|Write|Edit|Glob|Grep|Bash|TodoWrite|Skill|Agent)`/g;
10
10
  const HARD_GATE_PATTERN = /<HARD-GATE>([\s\S]*?)<\/HARD-GATE>/g;
11
- const SECTION_PATTERN = /^## (.+)$/gm;
11
+ const _SECTION_PATTERN = /^## (.+)$/gm;
12
12
 
13
13
  /**
14
14
  * Parse YAML-like frontmatter from SKILL.md
@@ -26,7 +26,7 @@ function parseFrontmatter(content) {
26
26
 
27
27
  let currentIndent = null;
28
28
  let nestedKey = null;
29
- const nestedObj = {};
29
+ const _nestedObj = {};
30
30
 
31
31
  for (const line of raw.split('\n')) {
32
32
  const trimmed = line.trim();
@@ -43,7 +43,7 @@ function parseFrontmatter(content) {
43
43
  if (currentIndent === 'nested' && line.startsWith(' ')) {
44
44
  const kvMatch = trimmed.match(/^(\w+):\s*(.+)$/);
45
45
  if (kvMatch) {
46
- let value = kvMatch[2].replace(/^["']|["']$/g, '');
46
+ const value = kvMatch[2].replace(/^["']|["']$/g, '');
47
47
  frontmatter[nestedKey][kvMatch[1]] = value;
48
48
  }
49
49
  continue;
@@ -54,7 +54,7 @@ function parseFrontmatter(content) {
54
54
  nestedKey = null;
55
55
  const kvMatch = trimmed.match(/^(\w[\w-]*):\s*(.+)$/);
56
56
  if (kvMatch) {
57
- let value = kvMatch[2].replace(/^["']|["']$/g, '');
57
+ const value = kvMatch[2].replace(/^["']|["']$/g, '');
58
58
  frontmatter[kvMatch[1]] = value;
59
59
  }
60
60
  }
@@ -226,7 +226,7 @@ export function parsePack(content, filePath = '') {
226
226
  function parseSkillManifest(skills) {
227
227
  if (!Array.isArray(skills)) return [];
228
228
 
229
- return skills.map(skill => {
229
+ return skills.map((skill) => {
230
230
  // Handle both string format ("skill-name") and object format ({name, file, model, description})
231
231
  if (typeof skill === 'string') {
232
232
  return {
@@ -5,13 +5,11 @@
5
5
  * Pipeline: frontmatter → cross-refs → tool-names → subagents → compliance → hooks → branding
6
6
  */
7
7
 
8
+ import { transformCompliance } from './transforms/compliance.js';
8
9
  import { transformCrossReferences } from './transforms/cross-references.js';
9
- import { transformToolNames } from './transforms/tool-names.js';
10
- import { transformFrontmatter } from './transforms/frontmatter.js';
11
- import { transformSubagents } from './transforms/subagents.js';
12
10
  import { generateHookConstraints } from './transforms/hooks.js';
13
- import { transformCompliance } from './transforms/compliance.js';
14
- import { addBranding } from './transforms/branding.js';
11
+ import { transformSubagents } from './transforms/subagents.js';
12
+ import { transformToolNames } from './transforms/tool-names.js';
15
13
 
16
14
  /**
17
15
  * Run the full transform pipeline on a parsed skill
@@ -7,9 +7,10 @@
7
7
  const DEFAULT_FOOTER = [
8
8
  '',
9
9
  '---',
10
- '> **Rune Skill Mesh** — 58 skills, 200+ connections',
11
- '> Source: https://github.com/rune-kit/rune',
12
- '> For the full experience with subagents, hooks, adaptive routing, and mesh analytics — use Rune as a Claude Code plugin.',
10
+ '> **Rune Skill Mesh** — 58 skills, 200+ connections, 14 extension packs',
11
+ '> Source: https://github.com/rune-kit/rune (MIT)',
12
+ '> **Rune Pro** ($49 lifetime) product, sales, data-science, support packs [rune-kit/rune-pro](https://github.com/rune-kit/rune-pro)',
13
+ '> **Rune Business** ($149 lifetime) — finance, legal, HR, enterprise-search packs → [rune-kit/rune-business](https://github.com/rune-kit/rune-business)',
13
14
  ].join('\n');
14
15
 
15
16
  /**
@@ -23,5 +24,5 @@ export function addBranding(body, adapter) {
23
24
  if (adapter.name === 'claude') return body;
24
25
 
25
26
  const footer = adapter.generateFooter ? adapter.generateFooter() : DEFAULT_FOOTER;
26
- return body + '\n' + footer;
27
+ return `${body}\n${footer}`;
27
28
  }
@@ -1,40 +1,40 @@
1
- /**
2
- * Compliance Transform
3
- *
4
- * Injects a compliance preamble into every compiled skill file.
5
- * This distributes enforcement to ALL platforms (Cursor, Windsurf, etc.)
6
- * so the AI is reminded of routing discipline even if skill-router
7
- * itself is not loaded in context.
8
- *
9
- * Only applies to non-Claude platforms (Claude has native plugin enforcement).
10
- */
11
-
12
- const COMPLIANCE_PREAMBLE = [
13
- '',
14
- '> **RUNE COMPLIANCE**: Before ANY code response, you MUST:',
15
- '> 1. Classify this request (CODE_CHANGE | QUESTION | DEBUG | REVIEW | EXPLORE)',
16
- '> 2. Route through the correct Rune skill (see skill-router routing table)',
17
- "> 3. Follow the skill's workflow — do NOT freelance or skip steps",
18
- '> Violation: writing code without skill routing = incorrect behavior.',
19
- '',
20
- ].join('\n');
21
-
22
- /**
23
- * Inject compliance preamble after the first heading in a skill body.
24
- *
25
- * @param {string} body - skill body text
26
- * @param {object} adapter - platform adapter
27
- * @returns {string} body with compliance preamble injected
28
- */
29
- export function transformCompliance(body, adapter) {
30
- if (adapter.name === 'claude') return body;
31
-
32
- // Insert after first heading (# skill-name), before first ## section
33
- const firstH2 = body.indexOf('\n## ');
34
- if (firstH2 !== -1) {
35
- return body.slice(0, firstH2) + COMPLIANCE_PREAMBLE + body.slice(firstH2);
36
- }
37
-
38
- // Fallback: append at end if no ## sections found
39
- return body + COMPLIANCE_PREAMBLE;
40
- }
1
+ /**
2
+ * Compliance Transform
3
+ *
4
+ * Injects a compliance preamble into every compiled skill file.
5
+ * This distributes enforcement to ALL platforms (Cursor, Windsurf, etc.)
6
+ * so the AI is reminded of routing discipline even if skill-router
7
+ * itself is not loaded in context.
8
+ *
9
+ * Only applies to non-Claude platforms (Claude has native plugin enforcement).
10
+ */
11
+
12
+ const COMPLIANCE_PREAMBLE = [
13
+ '',
14
+ '> **RUNE COMPLIANCE**: Before ANY code response, you MUST:',
15
+ '> 1. Classify this request (CODE_CHANGE | QUESTION | DEBUG | REVIEW | EXPLORE)',
16
+ '> 2. Route through the correct Rune skill (see skill-router routing table)',
17
+ "> 3. Follow the skill's workflow — do NOT freelance or skip steps",
18
+ '> Violation: writing code without skill routing = incorrect behavior.',
19
+ '',
20
+ ].join('\n');
21
+
22
+ /**
23
+ * Inject compliance preamble after the first heading in a skill body.
24
+ *
25
+ * @param {string} body - skill body text
26
+ * @param {object} adapter - platform adapter
27
+ * @returns {string} body with compliance preamble injected
28
+ */
29
+ export function transformCompliance(body, adapter) {
30
+ if (adapter.name === 'claude') return body;
31
+
32
+ // Insert after first heading (# skill-name), before first ## section
33
+ const firstH2 = body.indexOf('\n## ');
34
+ if (firstH2 !== -1) {
35
+ return body.slice(0, firstH2) + COMPLIANCE_PREAMBLE + body.slice(firstH2);
36
+ }
37
+
38
+ // Fallback: append at end if no ## sections found
39
+ return body + COMPLIANCE_PREAMBLE;
40
+ }
@@ -26,7 +26,7 @@ export function transformFrontmatter(content, adapter) {
26
26
  // Remove Claude Code-specific directives
27
27
  const cleaned = frontmatterBlock
28
28
  .split('\n')
29
- .filter(line => {
29
+ .filter((line) => {
30
30
  const trimmed = line.trim();
31
31
  if (trimmed.startsWith('context:')) return false;
32
32
  if (trimmed.startsWith('agent:')) return false;