@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.
- package/README.md +39 -13
- 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 +52 -21
- 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/ANTIGRAVITY-GAP-ANALYSIS.md +369 -0
- package/docs/ARCHITECTURE.md +332 -0
- package/docs/COMMUNITY-PACKS.md +109 -0
- package/docs/CONTRIBUTING-L4.md +215 -0
- package/docs/CROSS-IDE-ANALYSIS.md +164 -0
- package/docs/EXTENSION-TEMPLATE.md +108 -0
- package/docs/MESH-RULES.md +34 -0
- package/docs/MULTI-PLATFORM.md +804 -0
- package/docs/SKILL-DEPTH-AUDIT.md +191 -0
- package/docs/SKILL-TEMPLATE.md +72 -0
- package/docs/TRADE-MATRIX.md +327 -0
- package/docs/VERSIONING.md +91 -0
- package/docs/VISION.md +263 -0
- package/docs/assets/demo-subtitles.srt +215 -0
- package/docs/assets/end-card.html +276 -0
- package/docs/assets/mesh-diagram.html +654 -0
- package/docs/assets/thumbnail.html +295 -0
- package/docs/guides/cli.md +403 -0
- package/docs/guides/index.html +1387 -0
- package/docs/index.html +739 -0
- package/docs/references/claudekit-analysis.md +414 -0
- package/docs/references/voltagent-analysis.md +189 -0
- package/docs/script.js +277 -0
- package/docs/skills/index.html +832 -0
- package/docs/style.css +600 -0
- package/docs/video-demo-plan.md +172 -0
- package/extensions/security/PACK.md +4 -3
- package/extensions/security/skills/defense-in-depth.md +103 -0
- package/extensions/ui/PACK.md +2 -0
- package/extensions/ui/skills/design-decision.md +10 -0
- package/extensions/ui/skills/palette-picker.md +11 -0
- package/hooks/.gitkeep +0 -0
- package/hooks/auto-format/index.cjs +48 -0
- package/hooks/context-watch/index.cjs +68 -0
- package/hooks/hooks.json +99 -0
- package/hooks/metrics-collector/index.cjs +42 -0
- package/hooks/post-session-reflect/index.cjs +153 -0
- package/hooks/pre-compact/index.cjs +95 -0
- package/hooks/pre-tool-guard/index.cjs +68 -0
- package/hooks/run-hook +17 -0
- package/hooks/run-hook.cjs +16 -0
- package/hooks/run-hook.cmd +1 -0
- package/hooks/secrets-scan/index.cjs +100 -0
- package/hooks/session-start/index.cjs +65 -0
- package/hooks/typecheck/index.cjs +65 -0
- package/package.json +16 -4
- package/references/ui-pro-max-data/LICENSE-UI-PRO-MAX +21 -0
- package/references/ui-pro-max-data/charts.csv +26 -0
- package/references/ui-pro-max-data/colors.csv +162 -0
- package/references/ui-pro-max-data/styles.csv +85 -0
- package/references/ui-pro-max-data/typography.csv +74 -0
- package/references/ui-pro-max-data/ui-reasoning.csv +162 -0
- package/references/ui-pro-max-data/ux-guidelines.csv +100 -0
- package/skills/ba/SKILL.md +10 -0
- package/skills/completion-gate/SKILL.md +34 -1
- package/skills/context-engine/SKILL.md +13 -0
- package/skills/cook/SKILL.md +71 -0
- package/skills/debug/SKILL.md +71 -1
- package/skills/design/SKILL.md +11 -0
- package/skills/fix/SKILL.md +26 -1
- package/skills/plan/SKILL.md +23 -6
- package/skills/review/SKILL.md +2 -0
- package/skills/skill-forge/SKILL.md +94 -3
- package/skills/test/SKILL.md +44 -2
- 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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
'',
|
|
62
|
-
'',
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
'>
|
|
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
|
};
|
package/compiler/bin/rune.js
CHANGED
|
@@ -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) {
|
|
30
|
-
|
|
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)
|
|
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(
|
|
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:
|
|
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:
|
|
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
|
-
|
|
199
|
-
|
|
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(
|
|
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
|
});
|
package/compiler/doctor.js
CHANGED
|
@@ -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({
|
|
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({
|
|
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
|
}
|
package/compiler/emitter.js
CHANGED
|
@@ -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 {
|
|
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
|
|
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 {
|
|
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
|
}
|
package/compiler/parser.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
package/compiler/transformer.js
CHANGED
|
@@ -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 {
|
|
14
|
-
import {
|
|
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
|
-
'>
|
|
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
|
|
27
|
+
return `${body}\n${footer}`;
|
|
27
28
|
}
|