@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
package/README.md CHANGED
@@ -1,10 +1,16 @@
1
- # Rune
1
+ <p align="center">
2
+ <img src="assets/banner.svg" alt="Rune — Skill Mesh for AI Coding Assistants" width="100%">
3
+ </p>
2
4
 
3
- **Less skills. Deeper connections.**
5
+ <p align="center">
6
+ <strong>Less skills. Deeper connections.</strong><br>
7
+ A lean, interconnected skill ecosystem for AI coding assistants.<br>
8
+ 58 skills · 200+ mesh connections · 8 platforms · MIT
9
+ </p>
4
10
 
5
- A lean, interconnected skill ecosystem for AI coding assistants. 58 skills, 200+ mesh connections, full project lifecycle — from idea to production.
6
-
7
- Works on **Claude Code** (native plugin) · **Cursor** · **Windsurf** · **Google Antigravity** · **OpenAI Codex** · **OpenCode** · any AI IDE
11
+ <p align="center">
12
+ <strong>Claude Code</strong> (native plugin) · <strong>Cursor</strong> · <strong>Windsurf</strong> · <strong>Google Antigravity</strong> · <strong>OpenAI Codex</strong> · <strong>OpenCode</strong> · any AI IDE
13
+ </p>
8
14
 
9
15
  ## Why Rune?
10
16
 
@@ -20,9 +26,11 @@ Mesh: A ↔ B ↔ C (B fails = A reaches C via D→E)
20
26
  D ↔ E ↔ F
21
27
  ```
22
28
 
23
- ## What's New (v2.2.1)
29
+ ## What's New (v2.2.2)
24
30
 
25
- - **Enforcement Upgrade** — Antigravity-level IDE compliance: 5-type Request Classifier, File Ownership Matrix, Self-Verification HARD-GATE, Routing Proof, Clarification Gate, Phase Transition Protocol. Distributed via compiler — every skill file gets compliance preamble on non-Claude platforms
31
+ - **UI/UX Pro Max Integration** — 161 palettes, 84 styles, 73 font pairings, 161 reasoning rules from [UI/UX Pro Max](https://github.com/nextlevelbuilder/ui-ux-pro-max-skill) (MIT, 42.8k★) meshed into `design` skill + `@rune/ui` pack
32
+ - **11 Core Skill Enrichments** — deviation rules, repair operators, evidence quality gate, decision classification, debug knowledge base, CSO discipline, YAGNI pushback, formal pause/resume
33
+ - **Enforcement Upgrade** — Antigravity-level IDE compliance: 5-type Request Classifier, File Ownership Matrix, Self-Verification HARD-GATE, Routing Proof, Clarification Gate, Phase Transition Protocol
26
34
  - **All 14 Free Packs Deep** — every L4 extension pack now exceeds 500 lines of production-ready patterns and code examples
27
35
  - **8 Platform Adapters** — Claude Code, Cursor, Windsurf, Google Antigravity, OpenAI Codex, OpenCode, OpenClaw, Generic
28
36
  - **58 Core Skills** — +3 since v2.1.0: adversary (red team), sentinel-env (env validation), chrome-ext pack
@@ -62,11 +70,12 @@ Rune started as a **Claude Code plugin** and now compiles to **every major AI ID
62
70
  ### Claude Code (Native Plugin)
63
71
 
64
72
  ```bash
65
- # Via Claude Code plugin marketplace
66
- /plugin marketplace add rune-kit/rune
67
- /plugin install rune@rune-kit
73
+ # Install via Claude Code CLI
74
+ claude plugin add rune-kit/rune
68
75
  ```
69
76
 
77
+ Or add manually in `~/.claude/settings.json` under `installed_plugins`.
78
+
70
79
  Full mesh: subagents, hooks, adaptive routing, mesh analytics.
71
80
 
72
81
  ### Cursor / Windsurf / Antigravity / Any IDE
@@ -315,7 +324,20 @@ Business department packs for product, sales, and data teams. Same PACK.md forma
315
324
  | @rune-pro/data-science | data-exploration, sql-advanced, visualization, statistical-testing, dashboards | Data Science |
316
325
  | @rune-pro/support | ticket-triage, response-drafting, knowledge-base, escalation, faq, metrics | Customer Support |
317
326
 
318
- **$49 lifetime** (Pro) · **$149 lifetime** (Business). See [docs/plans/RUNE-PRO-PLAN.md](docs/plans/RUNE-PRO-PLAN.md).
327
+ **$49 lifetime** [rune-kit/rune-pro](https://github.com/rune-kit/rune-pro)
328
+
329
+ ### Rune Business (Enterprise)
330
+
331
+ Department packs for finance, legal, HR, and cross-system search. Requires Rune Free core.
332
+
333
+ | Pack | Skills | For |
334
+ |------|--------|-----|
335
+ | @rune-pro/finance | budget-planning, expense-analysis, revenue-forecasting, financial-reporting, compliance, invoicing, cash-flow | Finance Ops |
336
+ | @rune-pro/legal | contract-review, compliance-check, policy-generator, nda-triage, ip-protection, incident-legal | Legal & Compliance |
337
+ | @rune-pro/hr | jd-writer, resume-screener, interview-planner, scorecard, onboarding, comp-benchmarker, policy-writer | HR & Recruiting |
338
+ | @rune-pro/enterprise-search | query-planner, source-connector, result-merger, permission-guard, knowledge-graph, search-analytics | Knowledge Retrieval |
339
+
340
+ **$149 lifetime** — [rune-kit/rune-business](https://github.com/rune-kit/rune-business)
319
341
 
320
342
  ## Multi-Platform Compiler
321
343
 
@@ -349,15 +371,19 @@ See [docs/MULTI-PLATFORM.md](docs/MULTI-PLATFORM.md) for the full architecture.
349
371
 
350
372
  ```
351
373
  Core Skills: 58 (L0: 1 │ L1: 5 │ L2: 27 │ L3: 25)
352
- Extension Packs: 14 free + 4 pro + 2 business
374
+ Extension Packs: 14 free + 4 pro + 4 business
353
375
  Mesh Connections: 200+ cross-references
354
376
  Connections/Skill: 3.4 avg
355
377
  Platforms: 8 (Claude Code, Cursor, Windsurf, Antigravity, Codex, OpenCode, OpenClaw, Generic)
356
378
  Compiler: ~1400 LOC (parser + 8 transforms + 8 adapters + CLI)
357
379
  Quality: 58/58 skills with Output Format, Sharp Edges, Done When, Cost Profile
358
- Pack Depth: 20 packs total (14 free + 4 pro + 2 business, all free packs rated Deep)
380
+ Pack Depth: 22 packs total (14 free + 4 pro + 4 business, all free packs rated Deep)
359
381
  ```
360
382
 
383
+ ## Acknowledgments
384
+
385
+ - **[UI/UX Pro Max](https://github.com/nextlevelbuilder/ui-ux-pro-max-skill)** (MIT, 42.8k★) — Design intelligence databases powering Rune's `design` skill and `@rune/ui` pack: 161 color palettes, 84 UI styles, 73 font pairings, 99 UX guidelines, 161 industry reasoning rules.
386
+
361
387
  ## License
362
388
 
363
389
  MIT
@@ -0,0 +1,109 @@
1
+ import assert from 'node:assert';
2
+ import { describe, test } from 'node:test';
3
+ import { getAdapter, listPlatforms } from '../adapters/index.js';
4
+
5
+ // --- Adapter registry ---
6
+
7
+ test('listPlatforms returns all 8 platform adapters', () => {
8
+ const adapters = listPlatforms();
9
+ const expected = ['claude', 'cursor', 'windsurf', 'antigravity', 'codex', 'opencode', 'openclaw', 'generic'];
10
+ for (const name of expected) {
11
+ assert.ok(adapters.includes(name), `missing adapter: ${name}`);
12
+ }
13
+ });
14
+
15
+ test('getAdapter returns adapter by name', () => {
16
+ const cursor = getAdapter('cursor');
17
+ assert.strictEqual(cursor.name, 'cursor');
18
+ assert.strictEqual(cursor.fileExtension, '.mdc');
19
+ });
20
+
21
+ test('getAdapter throws for unknown adapter', () => {
22
+ assert.throws(() => getAdapter('nonexistent'), /unknown/i);
23
+ });
24
+
25
+ // --- Shared adapter contract ---
26
+
27
+ const REQUIRED_METHODS = [
28
+ 'transformReference',
29
+ 'transformToolName',
30
+ 'generateHeader',
31
+ 'generateFooter',
32
+ 'transformSubagentInstruction',
33
+ 'postProcess',
34
+ ];
35
+
36
+ const REQUIRED_PROPS = ['name', 'outputDir', 'fileExtension', 'skillPrefix', 'skillSuffix'];
37
+
38
+ const ADAPTER_NAMES = ['cursor', 'windsurf', 'antigravity', 'codex', 'opencode', 'generic'];
39
+
40
+ for (const adapterName of ADAPTER_NAMES) {
41
+ describe(`${adapterName} adapter contract`, () => {
42
+ const adapter = getAdapter(adapterName);
43
+
44
+ test('has all required properties', () => {
45
+ for (const prop of REQUIRED_PROPS) {
46
+ assert.ok(prop in adapter, `${adapterName} missing prop: ${prop}`);
47
+ }
48
+ });
49
+
50
+ test('has all required methods', () => {
51
+ for (const method of REQUIRED_METHODS) {
52
+ assert.strictEqual(typeof adapter[method], 'function', `${adapterName} missing method: ${method}`);
53
+ }
54
+ });
55
+
56
+ test('transformReference handles plain and backticked refs', () => {
57
+ const plain = adapter.transformReference('cook', 'cook');
58
+ assert.ok(typeof plain === 'string' && plain.length > 0);
59
+
60
+ const backticked = adapter.transformReference('cook', '`cook`');
61
+ assert.ok(typeof backticked === 'string' && backticked.length > 0);
62
+ });
63
+
64
+ test('transformToolName maps known tools', () => {
65
+ const result = adapter.transformToolName('Read');
66
+ assert.ok(typeof result === 'string' && result.length > 0);
67
+ });
68
+
69
+ test('generateHeader returns string', () => {
70
+ const skill = { name: 'test', layer: 'L2', group: 'workflow', description: 'Test skill' };
71
+ const header = adapter.generateHeader(skill);
72
+ assert.strictEqual(typeof header, 'string');
73
+ });
74
+
75
+ test('generateFooter includes Rune branding', () => {
76
+ const footer = adapter.generateFooter();
77
+ assert.ok(footer.includes('Rune'), `${adapterName} footer missing Rune branding`);
78
+ });
79
+
80
+ test('postProcess strips Claude-specific directives', () => {
81
+ const input = 'context: fork\ncontent\nagent: general-purpose\nmore';
82
+ const result = adapter.postProcess(input);
83
+ assert.ok(!result.includes('context: fork'));
84
+ assert.ok(!result.includes('agent: general-purpose'));
85
+ });
86
+ });
87
+ }
88
+
89
+ // --- Cursor-specific ---
90
+
91
+ test('cursor adapter generates .mdc frontmatter with alwaysApply', () => {
92
+ const cursor = getAdapter('cursor');
93
+ const l0Skill = { name: 'router', layer: 'L0', group: 'meta', description: 'Routes tasks' };
94
+ const l2Skill = { name: 'fix', layer: 'L2', group: 'workflow', description: 'Fix code' };
95
+
96
+ const l0Header = cursor.generateHeader(l0Skill);
97
+ assert.ok(l0Header.includes('alwaysApply: true'));
98
+
99
+ const l2Header = cursor.generateHeader(l2Skill);
100
+ assert.ok(l2Header.includes('alwaysApply: false'));
101
+ });
102
+
103
+ // --- Codex-specific ---
104
+
105
+ test('codex adapter uses skill directories', () => {
106
+ const codex = getAdapter('codex');
107
+ assert.strictEqual(codex.useSkillDirectories, true);
108
+ assert.strictEqual(codex.skillFileName, 'SKILL.md');
109
+ });
@@ -1,140 +1,149 @@
1
- import openclaw from '../adapters/openclaw.js';
2
- import { test } from 'node:test';
3
- import assert from 'node:assert';
4
-
5
- // --- Adapter shape ---
6
-
7
- test('openclaw adapter has all required properties', () => {
8
- const required = [
9
- 'name', 'outputDir', 'fileExtension', 'skillPrefix', 'skillSuffix',
10
- 'transformReference', 'transformToolName', 'generateHeader', 'generateFooter',
11
- 'transformSubagentInstruction', 'postProcess',
12
- 'generateManifest', 'generateEntryPoint',
13
- ];
14
- for (const prop of required) {
15
- assert.ok(prop in openclaw, `missing property: ${prop}`);
16
- }
17
- });
18
-
19
- test('openclaw adapter has correct name and outputDir', () => {
20
- assert.strictEqual(openclaw.name, 'openclaw');
21
- assert.strictEqual(openclaw.outputDir, '.openclaw/rune/skills');
22
- assert.strictEqual(openclaw.fileExtension, '.md');
23
- assert.strictEqual(openclaw.skillPrefix, 'rune-');
24
- assert.strictEqual(openclaw.skillSuffix, '');
25
- });
26
-
27
- // --- transformReference ---
28
-
29
- test('transformReference returns correct skill file reference', () => {
30
- const result = openclaw.transformReference('cook', 'cook');
31
- assert.strictEqual(result, 'rune-cook.md');
32
- });
33
-
34
- test('transformReference preserves backticks', () => {
35
- const result = openclaw.transformReference('plan', '`plan`');
36
- assert.strictEqual(result, '`rune-plan.md`');
37
- });
38
-
39
- // --- transformToolName ---
40
-
41
- test('transformToolName maps Claude Code tools to OpenClaw equivalents', () => {
42
- assert.strictEqual(openclaw.transformToolName('Read'), 'read_file');
43
- assert.strictEqual(openclaw.transformToolName('Write'), 'write_file');
44
- assert.strictEqual(openclaw.transformToolName('Edit'), 'edit_file');
45
- assert.strictEqual(openclaw.transformToolName('Bash'), 'run_command');
46
- assert.strictEqual(openclaw.transformToolName('Glob'), 'glob');
47
- assert.strictEqual(openclaw.transformToolName('Grep'), 'grep');
48
- });
49
-
50
- test('transformToolName passes through unknown tools', () => {
51
- assert.strictEqual(openclaw.transformToolName('CustomTool'), 'CustomTool');
52
- });
53
-
54
- // --- generateHeader / generateFooter ---
55
-
56
- test('generateHeader produces valid markdown', () => {
57
- const skill = { name: 'cook', layer: 'L1', group: 'orchestrator' };
58
- const header = openclaw.generateHeader(skill);
59
- assert.ok(header.startsWith('# rune-cook'));
60
- assert.ok(header.includes('L1'));
61
- assert.ok(header.includes('orchestrator'));
62
- });
63
-
64
- test('generateFooter includes Rune branding', () => {
65
- const footer = openclaw.generateFooter();
66
- assert.ok(footer.includes('Rune Skill Mesh'));
67
- assert.ok(footer.includes('github.com/rune-kit/rune'));
68
- });
69
-
70
- // --- postProcess ---
71
-
72
- test('postProcess strips Claude-specific directives', () => {
73
- const input = 'context: fork\nsome content\nagent: general-purpose\nmore content';
74
- const result = openclaw.postProcess(input);
75
- assert.ok(!result.includes('context: fork'));
76
- assert.ok(!result.includes('agent: general-purpose'));
77
- assert.ok(result.includes('some content'));
78
- assert.ok(result.includes('more content'));
79
- });
80
-
81
- // --- generateManifest ---
82
-
83
- test('generateManifest returns valid openclaw.plugin.json structure', () => {
84
- const skills = [
85
- { name: 'cook', layer: 'L1', group: 'orchestrator' },
86
- { name: 'plan', layer: 'L2', group: 'workflow' },
87
- ];
88
- const pluginJson = { version: '2.1.1', name: 'rune' };
89
-
90
- const manifest = openclaw.generateManifest(skills, pluginJson);
91
-
92
- assert.strictEqual(manifest.id, 'rune');
93
- assert.strictEqual(manifest.name, 'Rune');
94
- assert.strictEqual(manifest.kind, 'skills');
95
- assert.strictEqual(manifest.version, '2.1.1');
96
- assert.ok(Array.isArray(manifest.skills));
97
- assert.deepStrictEqual(manifest.skills, ['./skills']);
98
- assert.ok(manifest.configSchema);
99
- assert.ok(manifest.configSchema.jsonSchema);
100
- assert.strictEqual(manifest.configSchema.jsonSchema.type, 'object');
101
- });
102
-
103
- test('generateManifest defaults version when missing', () => {
104
- const manifest = openclaw.generateManifest([], {});
105
- assert.strictEqual(manifest.version, '0.0.0');
106
- });
107
-
108
- // --- generateEntryPoint ---
109
-
110
- test('generateEntryPoint returns valid TypeScript with register(api)', () => {
111
- const skills = [
112
- { name: 'cook', layer: 'L1', group: 'orchestrator', description: 'Feature orchestrator' },
113
- { name: 'fix', layer: 'L2', group: 'workflow', description: 'Apply fixes' },
114
- ];
115
- const routerContent = '# skill-router\n\nRoute all tasks.';
116
-
117
- const ts = openclaw.generateEntryPoint(skills, routerContent);
118
-
119
- assert.ok(ts.includes('register(api'));
120
- assert.ok(ts.includes("before_agent_start"));
121
- assert.ok(ts.includes('prependSystemContext'));
122
- assert.ok(ts.includes('export default plugin'));
123
- assert.ok(ts.includes('cook'));
124
- assert.ok(ts.includes('fix'));
125
- assert.ok(ts.includes('skill-router'));
126
- });
127
-
128
- test('generateEntryPoint handles empty router content', () => {
129
- const ts = openclaw.generateEntryPoint([], '');
130
- assert.ok(ts.includes('register(api'));
131
- assert.ok(ts.includes('export default plugin'));
132
- });
133
-
134
- test('generateEntryPoint escapes backticks in router content', () => {
135
- const routerContent = 'Use `cook` skill for code tasks';
136
- const ts = openclaw.generateEntryPoint([], routerContent);
137
- // Should not have unescaped backticks inside template literal
138
- assert.ok(!ts.includes('Use `cook`'));
139
- assert.ok(ts.includes('\\`cook\\`'));
140
- });
1
+ import assert from 'node:assert';
2
+ import { test } from 'node:test';
3
+ import openclaw from '../adapters/openclaw.js';
4
+
5
+ // --- Adapter shape ---
6
+
7
+ test('openclaw adapter has all required properties', () => {
8
+ const required = [
9
+ 'name',
10
+ 'outputDir',
11
+ 'fileExtension',
12
+ 'skillPrefix',
13
+ 'skillSuffix',
14
+ 'transformReference',
15
+ 'transformToolName',
16
+ 'generateHeader',
17
+ 'generateFooter',
18
+ 'transformSubagentInstruction',
19
+ 'postProcess',
20
+ 'generateManifest',
21
+ 'generateEntryPoint',
22
+ ];
23
+ for (const prop of required) {
24
+ assert.ok(prop in openclaw, `missing property: ${prop}`);
25
+ }
26
+ });
27
+
28
+ test('openclaw adapter has correct name and outputDir', () => {
29
+ assert.strictEqual(openclaw.name, 'openclaw');
30
+ assert.strictEqual(openclaw.outputDir, '.openclaw/rune/skills');
31
+ assert.strictEqual(openclaw.fileExtension, '.md');
32
+ assert.strictEqual(openclaw.skillPrefix, 'rune-');
33
+ assert.strictEqual(openclaw.skillSuffix, '');
34
+ });
35
+
36
+ // --- transformReference ---
37
+
38
+ test('transformReference returns correct skill file reference', () => {
39
+ const result = openclaw.transformReference('cook', 'cook');
40
+ assert.strictEqual(result, 'rune-cook.md');
41
+ });
42
+
43
+ test('transformReference preserves backticks', () => {
44
+ const result = openclaw.transformReference('plan', '`plan`');
45
+ assert.strictEqual(result, '`rune-plan.md`');
46
+ });
47
+
48
+ // --- transformToolName ---
49
+
50
+ test('transformToolName maps Claude Code tools to OpenClaw equivalents', () => {
51
+ assert.strictEqual(openclaw.transformToolName('Read'), 'read_file');
52
+ assert.strictEqual(openclaw.transformToolName('Write'), 'write_file');
53
+ assert.strictEqual(openclaw.transformToolName('Edit'), 'edit_file');
54
+ assert.strictEqual(openclaw.transformToolName('Bash'), 'run_command');
55
+ assert.strictEqual(openclaw.transformToolName('Glob'), 'glob');
56
+ assert.strictEqual(openclaw.transformToolName('Grep'), 'grep');
57
+ });
58
+
59
+ test('transformToolName passes through unknown tools', () => {
60
+ assert.strictEqual(openclaw.transformToolName('CustomTool'), 'CustomTool');
61
+ });
62
+
63
+ // --- generateHeader / generateFooter ---
64
+
65
+ test('generateHeader produces valid markdown', () => {
66
+ const skill = { name: 'cook', layer: 'L1', group: 'orchestrator' };
67
+ const header = openclaw.generateHeader(skill);
68
+ assert.ok(header.startsWith('# rune-cook'));
69
+ assert.ok(header.includes('L1'));
70
+ assert.ok(header.includes('orchestrator'));
71
+ });
72
+
73
+ test('generateFooter includes Rune branding', () => {
74
+ const footer = openclaw.generateFooter();
75
+ assert.ok(footer.includes('Rune Skill Mesh'));
76
+ assert.ok(footer.includes('github.com/rune-kit/rune'));
77
+ });
78
+
79
+ // --- postProcess ---
80
+
81
+ test('postProcess strips Claude-specific directives', () => {
82
+ const input = 'context: fork\nsome content\nagent: general-purpose\nmore content';
83
+ const result = openclaw.postProcess(input);
84
+ assert.ok(!result.includes('context: fork'));
85
+ assert.ok(!result.includes('agent: general-purpose'));
86
+ assert.ok(result.includes('some content'));
87
+ assert.ok(result.includes('more content'));
88
+ });
89
+
90
+ // --- generateManifest ---
91
+
92
+ test('generateManifest returns valid openclaw.plugin.json structure', () => {
93
+ const skills = [
94
+ { name: 'cook', layer: 'L1', group: 'orchestrator' },
95
+ { name: 'plan', layer: 'L2', group: 'workflow' },
96
+ ];
97
+ const pluginJson = { version: '2.1.1', name: 'rune' };
98
+
99
+ const manifest = openclaw.generateManifest(skills, pluginJson);
100
+
101
+ assert.strictEqual(manifest.id, 'rune');
102
+ assert.strictEqual(manifest.name, 'Rune');
103
+ assert.strictEqual(manifest.kind, 'skills');
104
+ assert.strictEqual(manifest.version, '2.1.1');
105
+ assert.ok(Array.isArray(manifest.skills));
106
+ assert.deepStrictEqual(manifest.skills, ['./skills']);
107
+ assert.ok(manifest.configSchema);
108
+ assert.ok(manifest.configSchema.jsonSchema);
109
+ assert.strictEqual(manifest.configSchema.jsonSchema.type, 'object');
110
+ });
111
+
112
+ test('generateManifest defaults version when missing', () => {
113
+ const manifest = openclaw.generateManifest([], {});
114
+ assert.strictEqual(manifest.version, '0.0.0');
115
+ });
116
+
117
+ // --- generateEntryPoint ---
118
+
119
+ test('generateEntryPoint returns valid TypeScript with register(api)', () => {
120
+ const skills = [
121
+ { name: 'cook', layer: 'L1', group: 'orchestrator', description: 'Feature orchestrator' },
122
+ { name: 'fix', layer: 'L2', group: 'workflow', description: 'Apply fixes' },
123
+ ];
124
+ const routerContent = '# skill-router\n\nRoute all tasks.';
125
+
126
+ const ts = openclaw.generateEntryPoint(skills, routerContent);
127
+
128
+ assert.ok(ts.includes('register(api'));
129
+ assert.ok(ts.includes('before_agent_start'));
130
+ assert.ok(ts.includes('prependSystemContext'));
131
+ assert.ok(ts.includes('export default plugin'));
132
+ assert.ok(ts.includes('cook'));
133
+ assert.ok(ts.includes('fix'));
134
+ assert.ok(ts.includes('skill-router'));
135
+ });
136
+
137
+ test('generateEntryPoint handles empty router content', () => {
138
+ const ts = openclaw.generateEntryPoint([], '');
139
+ assert.ok(ts.includes('register(api'));
140
+ assert.ok(ts.includes('export default plugin'));
141
+ });
142
+
143
+ test('generateEntryPoint escapes backticks in router content', () => {
144
+ const routerContent = 'Use `cook` skill for code tasks';
145
+ const ts = openclaw.generateEntryPoint([], routerContent);
146
+ // Should not have unescaped backticks inside template literal
147
+ assert.ok(!ts.includes('Use `cook`'));
148
+ assert.ok(ts.includes('\\`cook\\`'));
149
+ });