@rune-kit/rune 2.2.1 → 2.2.3

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 (94) hide show
  1. package/README.md +39 -13
  2. package/compiler/__tests__/adapters.test.js +109 -0
  3. package/compiler/__tests__/openclaw-adapter.test.js +149 -140
  4. package/compiler/__tests__/pack-split.test.js +145 -145
  5. package/compiler/__tests__/parser.test.js +3 -3
  6. package/compiler/__tests__/pipeline.test.js +110 -0
  7. package/compiler/__tests__/skill-validation.test.js +148 -0
  8. package/compiler/__tests__/transformer.test.js +70 -0
  9. package/compiler/__tests__/transforms.test.js +92 -0
  10. package/compiler/adapters/antigravity.js +5 -6
  11. package/compiler/adapters/claude.js +1 -1
  12. package/compiler/adapters/codex.js +69 -77
  13. package/compiler/adapters/cursor.js +5 -6
  14. package/compiler/adapters/generic.js +5 -6
  15. package/compiler/adapters/index.js +3 -3
  16. package/compiler/adapters/openclaw.js +146 -150
  17. package/compiler/adapters/opencode.js +78 -86
  18. package/compiler/adapters/windsurf.js +5 -6
  19. package/compiler/bin/rune.js +52 -21
  20. package/compiler/doctor.js +19 -7
  21. package/compiler/emitter.js +11 -18
  22. package/compiler/parser.js +5 -5
  23. package/compiler/transformer.js +3 -5
  24. package/compiler/transforms/branding.js +5 -4
  25. package/compiler/transforms/compliance.js +40 -40
  26. package/compiler/transforms/frontmatter.js +1 -1
  27. package/compiler/transforms/hooks.js +12 -14
  28. package/compiler/transforms/subagents.js +1 -3
  29. package/compiler/transforms/tool-names.js +1 -1
  30. package/docs/ANTIGRAVITY-GAP-ANALYSIS.md +369 -0
  31. package/docs/ARCHITECTURE.md +332 -0
  32. package/docs/COMMUNITY-PACKS.md +109 -0
  33. package/docs/CONTRIBUTING-L4.md +215 -0
  34. package/docs/CROSS-IDE-ANALYSIS.md +164 -0
  35. package/docs/EXTENSION-TEMPLATE.md +108 -0
  36. package/docs/MESH-RULES.md +34 -0
  37. package/docs/MULTI-PLATFORM.md +804 -0
  38. package/docs/SKILL-DEPTH-AUDIT.md +191 -0
  39. package/docs/SKILL-TEMPLATE.md +72 -0
  40. package/docs/TRADE-MATRIX.md +327 -0
  41. package/docs/VERSIONING.md +91 -0
  42. package/docs/VISION.md +263 -0
  43. package/docs/assets/demo-subtitles.srt +215 -0
  44. package/docs/assets/end-card.html +276 -0
  45. package/docs/assets/mesh-diagram.html +654 -0
  46. package/docs/assets/thumbnail.html +295 -0
  47. package/docs/guides/cli.md +403 -0
  48. package/docs/guides/index.html +1387 -0
  49. package/docs/index.html +739 -0
  50. package/docs/references/claudekit-analysis.md +414 -0
  51. package/docs/references/voltagent-analysis.md +189 -0
  52. package/docs/script.js +277 -0
  53. package/docs/skills/index.html +832 -0
  54. package/docs/style.css +600 -0
  55. package/docs/video-demo-plan.md +172 -0
  56. package/extensions/security/PACK.md +4 -3
  57. package/extensions/security/skills/defense-in-depth.md +103 -0
  58. package/extensions/ui/PACK.md +2 -0
  59. package/extensions/ui/skills/design-decision.md +10 -0
  60. package/extensions/ui/skills/palette-picker.md +11 -0
  61. package/hooks/.gitkeep +0 -0
  62. package/hooks/auto-format/index.cjs +48 -0
  63. package/hooks/context-watch/index.cjs +68 -0
  64. package/hooks/hooks.json +99 -0
  65. package/hooks/metrics-collector/index.cjs +42 -0
  66. package/hooks/post-session-reflect/index.cjs +153 -0
  67. package/hooks/pre-compact/index.cjs +95 -0
  68. package/hooks/pre-tool-guard/index.cjs +68 -0
  69. package/hooks/run-hook +17 -0
  70. package/hooks/run-hook.cjs +16 -0
  71. package/hooks/run-hook.cmd +1 -0
  72. package/hooks/secrets-scan/index.cjs +100 -0
  73. package/hooks/session-start/index.cjs +65 -0
  74. package/hooks/typecheck/index.cjs +65 -0
  75. package/package.json +16 -4
  76. package/references/ui-pro-max-data/LICENSE-UI-PRO-MAX +21 -0
  77. package/references/ui-pro-max-data/charts.csv +26 -0
  78. package/references/ui-pro-max-data/colors.csv +162 -0
  79. package/references/ui-pro-max-data/styles.csv +85 -0
  80. package/references/ui-pro-max-data/typography.csv +74 -0
  81. package/references/ui-pro-max-data/ui-reasoning.csv +162 -0
  82. package/references/ui-pro-max-data/ux-guidelines.csv +100 -0
  83. package/skills/ba/SKILL.md +10 -0
  84. package/skills/completion-gate/SKILL.md +34 -1
  85. package/skills/context-engine/SKILL.md +13 -0
  86. package/skills/cook/SKILL.md +71 -0
  87. package/skills/debug/SKILL.md +71 -1
  88. package/skills/design/SKILL.md +11 -0
  89. package/skills/fix/SKILL.md +26 -1
  90. package/skills/plan/SKILL.md +23 -6
  91. package/skills/review/SKILL.md +2 -0
  92. package/skills/skill-forge/SKILL.md +94 -3
  93. package/skills/test/SKILL.md +44 -2
  94. package/skills/verification/SKILL.md +60 -2
@@ -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,16 +96,14 @@ 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 = {
102
103
  $schema: 'https://rune-kit.github.io/rune/config-schema.json',
103
104
  version: 1,
104
105
  platform,
105
- source: RUNE_ROOT,
106
+ source: '@rune-kit/rune',
106
107
  skills: {
107
108
  disabled: args.disable ? args.disable.split(',') : [],
108
109
  },
@@ -136,7 +137,10 @@ async function cmdInit(projectRoot, args) {
136
137
  }
137
138
 
138
139
  log('');
139
- log(' Next: Start coding. Rune skills are active in your AI assistant.');
140
+ log(' Next steps:');
141
+ log(' 1. /rune onboard Generate project context (CLAUDE.md + .rune/)');
142
+ log(' 2. /rune cook "..." Build a feature (full TDD cycle)');
143
+ log(' 3. /rune help See all 58 skills');
140
144
  log('');
141
145
  }
142
146
 
@@ -155,7 +159,7 @@ async function cmdBuild(projectRoot, args) {
155
159
  }
156
160
 
157
161
  const adapter = getAdapter(platform);
158
- const runeRoot = config?.source || RUNE_ROOT;
162
+ const runeRoot = config?.source === '@rune-kit/rune' ? RUNE_ROOT : config?.source || RUNE_ROOT;
159
163
  const outputRoot = typeof args.output === 'string' ? args.output : projectRoot;
160
164
  const disabledSkills = config?.skills?.disabled || [];
161
165
  const enabledPacks = config?.extensions?.enabled || null;
@@ -195,13 +199,23 @@ async function cmdDoctor(projectRoot, args) {
195
199
  const config = await readConfig(projectRoot);
196
200
 
197
201
  if (!config) {
198
- log(' ✗ No rune.config.json found. Run `rune init` first.');
199
- 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;
200
214
  }
201
215
 
202
216
  const platform = args.platform || config.platform;
203
217
  const adapter = getAdapter(platform);
204
- const runeRoot = config.source || RUNE_ROOT;
218
+ const runeRoot = config.source === '@rune-kit/rune' ? RUNE_ROOT : config.source || RUNE_ROOT;
205
219
 
206
220
  const results = await runDoctor({
207
221
  outputRoot: projectRoot,
@@ -252,6 +266,13 @@ async function main() {
252
266
  const { command, args } = parseArgs(process.argv.slice(2));
253
267
  const projectRoot = process.cwd();
254
268
 
269
+ // Handle --version / -v as flag (not positional command)
270
+ if (args.version || args.v) {
271
+ const pkg = JSON.parse(await readFile(path.join(RUNE_ROOT, 'package.json'), 'utf-8'));
272
+ log(` rune v${pkg.version}`);
273
+ return;
274
+ }
275
+
255
276
  switch (command) {
256
277
  case 'init':
257
278
  await cmdInit(projectRoot, args);
@@ -262,6 +283,13 @@ async function main() {
262
283
  case 'doctor':
263
284
  await cmdDoctor(projectRoot, args);
264
285
  break;
286
+ case 'version':
287
+ case '--version':
288
+ case '-v': {
289
+ const pkg = JSON.parse(await readFile(path.join(RUNE_ROOT, 'package.json'), 'utf-8'));
290
+ log(` rune v${pkg.version}`);
291
+ break;
292
+ }
265
293
  case 'help':
266
294
  case '--help':
267
295
  case undefined:
@@ -274,9 +302,12 @@ async function main() {
274
302
  log(' doctor Validate compiled output');
275
303
  log('');
276
304
  log(' Options:');
277
- 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
+ );
278
308
  log(' --output <dir> Override output directory');
279
309
  log(' --disable <skills> Comma-separated skills to disable');
310
+ log(' --version, -v Show version');
280
311
  log('');
281
312
  break;
282
313
  default:
@@ -285,7 +316,7 @@ async function main() {
285
316
  }
286
317
  }
287
318
 
288
- main().catch(err => {
319
+ main().catch((err) => {
289
320
  console.error(' ✗ Fatal:', err.message);
290
321
  process.exit(1);
291
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
  }