@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.
- package/compiler/__tests__/adapters.test.js +109 -0
- package/compiler/__tests__/openclaw-adapter.test.js +149 -140
- package/compiler/__tests__/pack-split.test.js +145 -145
- package/compiler/__tests__/parser.test.js +3 -3
- package/compiler/__tests__/pipeline.test.js +110 -0
- package/compiler/__tests__/skill-validation.test.js +148 -0
- package/compiler/__tests__/transformer.test.js +70 -0
- package/compiler/__tests__/transforms.test.js +92 -0
- package/compiler/adapters/antigravity.js +5 -6
- package/compiler/adapters/claude.js +1 -1
- package/compiler/adapters/codex.js +69 -77
- package/compiler/adapters/cursor.js +5 -6
- package/compiler/adapters/generic.js +5 -6
- package/compiler/adapters/index.js +3 -3
- package/compiler/adapters/openclaw.js +146 -150
- package/compiler/adapters/opencode.js +78 -86
- package/compiler/adapters/windsurf.js +5 -6
- package/compiler/bin/rune.js +32 -23
- package/compiler/doctor.js +19 -7
- package/compiler/emitter.js +11 -18
- package/compiler/parser.js +5 -5
- package/compiler/transformer.js +3 -5
- package/compiler/transforms/branding.js +5 -4
- package/compiler/transforms/compliance.js +40 -40
- package/compiler/transforms/frontmatter.js +1 -1
- package/compiler/transforms/hooks.js +12 -14
- package/compiler/transforms/subagents.js +1 -3
- package/compiler/transforms/tool-names.js +1 -1
- package/docs/guides/index.html +48 -7
- package/docs/index.html +67 -2
- package/docs/style.css +18 -1
- package/extensions/security/PACK.md +4 -3
- package/extensions/security/skills/defense-in-depth.md +103 -0
- package/package.json +8 -1
- package/skills/completion-gate/SKILL.md +31 -1
- package/skills/cook/SKILL.md +20 -2
- package/skills/debug/SKILL.md +16 -1
- package/skills/hallucination-guard/SKILL.md +21 -6
- package/skills/plan/SKILL.md +90 -1
- package/skills/review/SKILL.md +1 -0
- package/skills/sentinel-env/SKILL.md +31 -1
- package/skills/skill-forge/SKILL.md +57 -1
- package/skills/team/SKILL.md +61 -15
- package/skills/test/SKILL.md +101 -10
- package/skills/verification/SKILL.md +29 -1
|
@@ -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
|
|
2
|
-
import { test } from 'node:test';
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
// --- Adapter shape ---
|
|
6
|
-
|
|
7
|
-
test('openclaw adapter has all required properties', () => {
|
|
8
|
-
const required = [
|
|
9
|
-
'name',
|
|
10
|
-
'
|
|
11
|
-
'
|
|
12
|
-
'
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
assert.strictEqual(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
assert.strictEqual(
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
test('transformToolName
|
|
51
|
-
assert.strictEqual(openclaw.transformToolName('
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
assert.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const
|
|
75
|
-
assert.ok(
|
|
76
|
-
assert.ok(
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
assert.
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
assert.ok(ts.includes('
|
|
131
|
-
assert.ok(ts.includes('export default plugin'));
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
assert.ok(ts.includes('
|
|
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
|
+
});
|