@rune-kit/rune 2.22.2 → 2.24.0
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 +33 -6
- package/compiler/__tests__/adapter-model-mapping.test.js +6 -6
- package/compiler/__tests__/adapters.test.js +88 -30
- package/compiler/__tests__/pack-split.test.js +5 -2
- package/compiler/__tests__/pipeline.test.js +6 -6
- package/compiler/__tests__/scripts-bundling.test.js +29 -19
- package/compiler/__tests__/transformer.test.js +4 -4
- package/compiler/adapters/antigravity.js +1 -5
- package/compiler/adapters/codex.js +39 -10
- package/compiler/adapters/copilot.js +26 -29
- package/compiler/adapters/cursor.js +23 -15
- package/compiler/adapters/gemini.js +25 -48
- package/compiler/adapters/qoder.js +14 -14
- package/compiler/adapters/qwen.js +16 -22
- package/compiler/adapters/windsurf.js +29 -7
- package/compiler/emitter.js +5 -4
- package/compiler/parser.js +11 -2
- package/extensions/ai-ml/skills/llm-architect.md +3 -3
- package/package.json +1 -1
- package/skills/adversary/references/oracle-mode.md +3 -2
- package/skills/ba/SKILL.md +6 -1
- package/skills/cook/SKILL.md +19 -1
- package/skills/marketing/SKILL.md +26 -1
- package/skills/mcp-builder/SKILL.md +2 -2
- package/skills/plan/SKILL.md +13 -2
- package/skills/review/SKILL.md +20 -2
- package/skills/session-bridge/SKILL.md +2 -2
- package/skills/video-creator/SKILL.md +74 -2
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* GitHub Copilot
|
|
2
|
+
* GitHub Copilot Adapter
|
|
3
3
|
*
|
|
4
|
-
* Emits
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Emits SKILL.md files into .github/skills/{name}/ directories — GitHub
|
|
5
|
+
* Copilot's native Agent Skills format (supported since Dec 2025 across
|
|
6
|
+
* Copilot CLI, VS Code agent mode, and github.com). Skills are loaded
|
|
7
|
+
* on-demand based on the description, keeping context lean.
|
|
7
8
|
*
|
|
8
|
-
* Copilot
|
|
9
|
-
* Copilot
|
|
9
|
+
* Copilot skills dir: .github/skills/ (also reads .claude/skills and .agents/skills)
|
|
10
|
+
* Copilot skill format: .github/skills/{name}/SKILL.md
|
|
10
11
|
* Copilot project context: AGENTS.md (Copilot Spaces / Coding Agent convention)
|
|
11
12
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* @see https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/add-custom-instructions
|
|
16
|
-
* @see https://docs.github.com/en/copilot/concepts/response-customization
|
|
13
|
+
* @see https://docs.github.com/en/copilot/concepts/agents/about-agent-skills
|
|
14
|
+
* @see https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/add-skills
|
|
17
15
|
*
|
|
18
16
|
* MODEL TIER MAPPING (v2.18+):
|
|
19
17
|
* Copilot multi-model support exposes Anthropic/OpenAI/Google providers. Tier
|
|
@@ -37,23 +35,24 @@ const TOOL_MAP = {
|
|
|
37
35
|
Grep: 'search file contents',
|
|
38
36
|
Bash: 'run a shell command',
|
|
39
37
|
TodoWrite: 'track task progress',
|
|
40
|
-
Skill: '
|
|
38
|
+
Skill: 'invoke the named skill',
|
|
41
39
|
Agent: 'execute the workflow',
|
|
42
40
|
};
|
|
43
41
|
|
|
44
42
|
export default {
|
|
45
43
|
name: 'copilot',
|
|
46
|
-
outputDir: '.github/
|
|
47
|
-
fileExtension: '.
|
|
44
|
+
outputDir: '.github/skills',
|
|
45
|
+
fileExtension: '.md',
|
|
48
46
|
skillPrefix: 'rune-',
|
|
49
47
|
skillSuffix: '',
|
|
50
48
|
|
|
51
|
-
// Copilot
|
|
52
|
-
useSkillDirectories:
|
|
49
|
+
// Copilot uses directory-per-skill: .github/skills/{name}/SKILL.md
|
|
50
|
+
useSkillDirectories: true,
|
|
51
|
+
skillFileName: 'SKILL.md',
|
|
53
52
|
|
|
54
53
|
transformReference(skillName, raw) {
|
|
55
54
|
const isBackticked = raw.startsWith('`') && raw.endsWith('`');
|
|
56
|
-
const ref = `the rune-${skillName}
|
|
55
|
+
const ref = `the rune-${skillName} skill`;
|
|
57
56
|
return isBackticked ? `\`${ref}\`` : ref;
|
|
58
57
|
},
|
|
59
58
|
|
|
@@ -62,15 +61,13 @@ export default {
|
|
|
62
61
|
},
|
|
63
62
|
|
|
64
63
|
generateHeader(skill) {
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
// survives parsing across CLI/IDE/extensions consistently.
|
|
69
|
-
const desc = (skill.description || '').replace(/\n/g, ' ');
|
|
64
|
+
// Agent Skills spec: name + description frontmatter, markdown body.
|
|
65
|
+
// Tier hint stays a body comment — Copilot has no model frontmatter key.
|
|
66
|
+
const desc = (skill.description || '').replace(/"/g, '\\"');
|
|
70
67
|
const tierHint = skill.model ? MODEL_MAP[skill.model] || skill.model : null;
|
|
71
|
-
const lines = ['---',
|
|
72
|
-
if (tierHint) lines.push(
|
|
73
|
-
lines.push(''
|
|
68
|
+
const lines = ['---', `name: rune-${skill.name}`, `description: "${desc}"`, '---', ''];
|
|
69
|
+
if (tierHint) lines.push(`<!-- tier-hint: ${tierHint} -->`, '');
|
|
70
|
+
lines.push('');
|
|
74
71
|
return lines.join('\n');
|
|
75
72
|
},
|
|
76
73
|
|
|
@@ -83,7 +80,7 @@ export default {
|
|
|
83
80
|
},
|
|
84
81
|
|
|
85
82
|
scriptsDir(skillName) {
|
|
86
|
-
return `rune-${skillName}
|
|
83
|
+
return `rune-${skillName}/scripts`;
|
|
87
84
|
},
|
|
88
85
|
|
|
89
86
|
postProcess(content) {
|
|
@@ -96,9 +93,9 @@ export default {
|
|
|
96
93
|
const copilotIndex = [
|
|
97
94
|
'# Rune — Copilot Custom Instructions',
|
|
98
95
|
'',
|
|
99
|
-
`Per-skill
|
|
96
|
+
`Per-skill Agent Skills live under \`.github/skills/rune-<name>/SKILL.md\` (${stats.skillCount} skills + ${stats.packCount} packs). Copilot discovers and loads them on demand based on each skill's description.`,
|
|
100
97
|
'',
|
|
101
|
-
'When a user request matches a skill\'s domain (e.g. "fix the failing test", "review this PR"), prefer
|
|
98
|
+
'When a user request matches a skill\'s domain (e.g. "fix the failing test", "review this PR"), prefer invoking the corresponding rune-<name> skill over freestyling.',
|
|
102
99
|
'',
|
|
103
100
|
'---',
|
|
104
101
|
'> Rune Skill Mesh — https://github.com/rune-kit/rune',
|
|
@@ -111,7 +108,7 @@ export default {
|
|
|
111
108
|
'Rune is an interconnected skill ecosystem for AI coding assistants.',
|
|
112
109
|
`${stats.skillCount} core skills + ${stats.packCount} extension packs.`,
|
|
113
110
|
'',
|
|
114
|
-
'Per-skill
|
|
111
|
+
'Per-skill Agent Skills: `.github/skills/rune-<name>/SKILL.md`',
|
|
115
112
|
'',
|
|
116
113
|
'---',
|
|
117
114
|
'> Rune Skill Mesh — https://github.com/rune-kit/rune',
|
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Cursor Adapter
|
|
3
3
|
*
|
|
4
|
-
* Emits .
|
|
5
|
-
*
|
|
4
|
+
* Emits SKILL.md files into .cursor/skills/{name}/ directories — Cursor's
|
|
5
|
+
* native Agent Skills format (Cursor 2.4+, Jan 2026). Skills are loaded
|
|
6
|
+
* dynamically when the agent decides they're relevant, unlike .cursor/rules
|
|
7
|
+
* which are always-on context.
|
|
8
|
+
*
|
|
9
|
+
* Cursor skills dir: .cursor/skills/
|
|
10
|
+
* Cursor skill format: .cursor/skills/{name}/SKILL.md
|
|
11
|
+
* @see https://cursor.com/docs/skills
|
|
12
|
+
*
|
|
13
|
+
* NOTE: .cursor/rules/ is still used by the runtime-hooks installer
|
|
14
|
+
* (adapters/hooks/cursor.js) — rules are the correct vehicle for always-on
|
|
15
|
+
* hook context. Only skill emission moved to .cursor/skills/.
|
|
6
16
|
*
|
|
7
17
|
* MODEL TIER MAPPING (v2.15+):
|
|
8
18
|
* No-op. Cursor's Anthropic API integration understands `model: opus|sonnet|haiku`
|
|
@@ -19,20 +29,24 @@ const TOOL_MAP = {
|
|
|
19
29
|
Grep: 'search file contents',
|
|
20
30
|
Bash: 'run a terminal command',
|
|
21
31
|
TodoWrite: 'track progress',
|
|
22
|
-
Skill: '
|
|
32
|
+
Skill: 'invoke the named skill',
|
|
23
33
|
Agent: 'execute the workflow',
|
|
24
34
|
};
|
|
25
35
|
|
|
26
36
|
export default {
|
|
27
37
|
name: 'cursor',
|
|
28
|
-
outputDir: '.cursor/
|
|
29
|
-
fileExtension: '.
|
|
38
|
+
outputDir: '.cursor/skills',
|
|
39
|
+
fileExtension: '.md',
|
|
30
40
|
skillPrefix: 'rune-',
|
|
31
41
|
skillSuffix: '',
|
|
32
42
|
|
|
43
|
+
// Cursor uses directory-per-skill: .cursor/skills/{name}/SKILL.md
|
|
44
|
+
useSkillDirectories: true,
|
|
45
|
+
skillFileName: 'SKILL.md',
|
|
46
|
+
|
|
33
47
|
transformReference(skillName, raw) {
|
|
34
48
|
const isBackticked = raw.startsWith('`') && raw.endsWith('`');
|
|
35
|
-
const ref =
|
|
49
|
+
const ref = `the rune-${skillName} skill`;
|
|
36
50
|
return isBackticked ? `\`${ref}\`` : ref;
|
|
37
51
|
},
|
|
38
52
|
|
|
@@ -41,14 +55,8 @@ export default {
|
|
|
41
55
|
},
|
|
42
56
|
|
|
43
57
|
generateHeader(skill) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
`description: "${skill.description}"`,
|
|
47
|
-
'globs: []',
|
|
48
|
-
`alwaysApply: ${skill.layer === 'L0'}`,
|
|
49
|
-
'---',
|
|
50
|
-
'',
|
|
51
|
-
].join('\n');
|
|
58
|
+
const desc = (skill.description || '').replace(/"/g, '\\"');
|
|
59
|
+
return ['---', `name: rune-${skill.name}`, `description: "${desc}"`, '---', '', ''].join('\n');
|
|
52
60
|
},
|
|
53
61
|
|
|
54
62
|
generateFooter() {
|
|
@@ -60,7 +68,7 @@ export default {
|
|
|
60
68
|
},
|
|
61
69
|
|
|
62
70
|
scriptsDir(skillName) {
|
|
63
|
-
return `rune-${skillName}
|
|
71
|
+
return `rune-${skillName}/scripts`;
|
|
64
72
|
},
|
|
65
73
|
|
|
66
74
|
postProcess(content) {
|
|
@@ -1,27 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Gemini CLI Adapter
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* Emits SKILL.md files into .gemini/skills/{name}/ directories — Gemini CLI's
|
|
5
|
+
* native Agent Skills format. Gemini CLI discovers skills automatically from
|
|
6
|
+
* .gemini/skills/ (and the .agents/skills/ interop alias) and lazy-loads the
|
|
7
|
+
* full SKILL.md only when a skill is invoked — no more bundling every skill
|
|
8
|
+
* into GEMINI.md (which loaded all 65 skills as always-on context).
|
|
9
9
|
*
|
|
10
|
-
* Gemini
|
|
11
|
-
* Gemini
|
|
10
|
+
* Gemini skills dir: .gemini/skills/
|
|
11
|
+
* Gemini skill format: .gemini/skills/{name}/SKILL.md
|
|
12
|
+
* Gemini project context: GEMINI.md (slim pointer, project root)
|
|
12
13
|
*
|
|
13
|
-
* @see https://
|
|
14
|
-
* @see https://
|
|
14
|
+
* @see https://geminicli.com/docs/cli/skills/
|
|
15
|
+
* @see https://github.com/google-gemini/gemini-cli/blob/main/docs/cli/skills.md
|
|
15
16
|
*
|
|
16
17
|
* MODEL TIER MAPPING (v2.18+):
|
|
17
|
-
* Gemini CLI exposes
|
|
18
|
-
* translate to Gemini families
|
|
19
|
-
*
|
|
20
|
-
* config, not from rule body.
|
|
18
|
+
* Gemini CLI exposes Pro / Flash / Flash-Lite families. Anthropic tier names
|
|
19
|
+
* translate to Gemini families as a hint comment — Gemini CLI reads model
|
|
20
|
+
* from --model flag / config, not from the skill body.
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
-
import { readFile } from 'node:fs/promises';
|
|
24
|
-
import nodePath from 'node:path';
|
|
25
23
|
import { BRANDING_FOOTER } from '../transforms/branding.js';
|
|
26
24
|
|
|
27
25
|
const MODEL_MAP = {
|
|
@@ -38,23 +36,24 @@ const TOOL_MAP = {
|
|
|
38
36
|
Grep: 'search file contents',
|
|
39
37
|
Bash: 'run a shell command',
|
|
40
38
|
TodoWrite: 'track task progress',
|
|
41
|
-
Skill: '
|
|
39
|
+
Skill: 'invoke the rune-{name} skill',
|
|
42
40
|
Agent: 'execute the workflow',
|
|
43
41
|
};
|
|
44
42
|
|
|
45
43
|
export default {
|
|
46
44
|
name: 'gemini',
|
|
47
|
-
outputDir: 'gemini/skills',
|
|
45
|
+
outputDir: '.gemini/skills',
|
|
48
46
|
fileExtension: '.md',
|
|
49
47
|
skillPrefix: 'rune-',
|
|
50
48
|
skillSuffix: '',
|
|
51
49
|
|
|
52
|
-
//
|
|
53
|
-
useSkillDirectories:
|
|
50
|
+
// Gemini CLI uses directory-per-skill: .gemini/skills/{name}/SKILL.md
|
|
51
|
+
useSkillDirectories: true,
|
|
52
|
+
skillFileName: 'SKILL.md',
|
|
54
53
|
|
|
55
54
|
transformReference(skillName, raw) {
|
|
56
55
|
const isBackticked = raw.startsWith('`') && raw.endsWith('`');
|
|
57
|
-
const ref = `the rune-${skillName}
|
|
56
|
+
const ref = `the rune-${skillName} skill`;
|
|
58
57
|
return isBackticked ? `\`${ref}\`` : ref;
|
|
59
58
|
},
|
|
60
59
|
|
|
@@ -80,46 +79,24 @@ export default {
|
|
|
80
79
|
},
|
|
81
80
|
|
|
82
81
|
scriptsDir(skillName) {
|
|
83
|
-
return `rune-${skillName}
|
|
82
|
+
return `rune-${skillName}/scripts`;
|
|
84
83
|
},
|
|
85
84
|
|
|
86
85
|
postProcess(content) {
|
|
87
86
|
return content.replace(/^context: fork\n/gm, '').replace(/^agent: general-purpose\n/gm, '');
|
|
88
87
|
},
|
|
89
88
|
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
// requires relative paths (resolved against outputRoot by the emitter).
|
|
93
|
-
async generateExtraFiles({ stats, outputDir }) {
|
|
94
|
-
const skillFiles = [...stats.files]
|
|
95
|
-
.filter((f) => f.startsWith('rune-') && f.endsWith('.md') && !f.includes('/') && !f.includes('\\'))
|
|
96
|
-
.sort();
|
|
97
|
-
|
|
98
|
-
const sections = [];
|
|
99
|
-
for (const fname of skillFiles) {
|
|
100
|
-
const sourcePath = nodePath.join(outputDir, fname);
|
|
101
|
-
try {
|
|
102
|
-
const raw = await readFile(sourcePath, 'utf-8');
|
|
103
|
-
// Strip frontmatter — Gemini reads plain markdown.
|
|
104
|
-
const stripped = raw.replace(/^---\n[\s\S]*?\n---\n?/, '').trim();
|
|
105
|
-
const skillName = fname.replace(/^rune-/, '').replace(/\.md$/, '');
|
|
106
|
-
sections.push(`\n\n## rune-${skillName}\n\n${stripped}`);
|
|
107
|
-
} catch {
|
|
108
|
-
// Skip unreadable per-skill file — sections array still produces valid bundle.
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
89
|
+
// Slim GEMINI.md pointer — skills themselves are lazy-loaded natively.
|
|
90
|
+
generateExtraFiles({ stats }) {
|
|
112
91
|
const geminiMd = [
|
|
113
92
|
'# Rune — Project Configuration',
|
|
114
93
|
'',
|
|
115
94
|
'Rune is an interconnected skill ecosystem for AI coding assistants.',
|
|
116
|
-
`${stats.skillCount} core skills + ${stats.packCount} extension packs
|
|
117
|
-
'',
|
|
118
|
-
'When a user request matches a skill\'s domain (e.g. "implement", "review", "debug"), follow the matching `## rune-<name>` section.',
|
|
95
|
+
`${stats.skillCount} core skills + ${stats.packCount} extension packs.`,
|
|
119
96
|
'',
|
|
120
|
-
'
|
|
97
|
+
'Per-skill Agent Skills live under `.gemini/skills/rune-<name>/SKILL.md`. Gemini CLI discovers and lazy-loads them automatically.',
|
|
121
98
|
'',
|
|
122
|
-
|
|
99
|
+
'When a user request matches a skill\'s domain (e.g. "implement", "review", "debug"), invoke the matching rune-<name> skill.',
|
|
123
100
|
'',
|
|
124
101
|
'---',
|
|
125
102
|
'> Rune Skill Mesh — https://github.com/rune-kit/rune',
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Qoder Adapter
|
|
3
3
|
*
|
|
4
|
-
* Emits
|
|
5
|
-
*
|
|
4
|
+
* Emits SKILL.md files into .qoder/skills/{name}/ directories — Qoder's
|
|
5
|
+
* documented project-level Agent Skills location (works identically in
|
|
6
|
+
* Qoder IDE and CLI). Qoder also reads AGENTS.md as its project-context file.
|
|
6
7
|
*
|
|
7
|
-
* Qoder
|
|
8
|
-
* Qoder
|
|
8
|
+
* Qoder skills dir: .qoder/skills/
|
|
9
|
+
* Qoder skill format: .qoder/skills/{name}/SKILL.md
|
|
9
10
|
* Qoder project context: AGENTS.md (open AGENTS.md standard)
|
|
10
11
|
*
|
|
11
|
-
* @see https://docs.qoder.com/
|
|
12
|
+
* @see https://docs.qoder.com/extensions/skills
|
|
12
13
|
* @see https://agents.md/
|
|
13
14
|
*
|
|
14
15
|
* MODEL TIER MAPPING (v2.18+):
|
|
@@ -32,23 +33,24 @@ const TOOL_MAP = {
|
|
|
32
33
|
Grep: 'search file contents',
|
|
33
34
|
Bash: 'run a shell command',
|
|
34
35
|
TodoWrite: 'track task progress',
|
|
35
|
-
Skill: '
|
|
36
|
+
Skill: 'invoke the rune-{name} skill',
|
|
36
37
|
Agent: 'execute the workflow',
|
|
37
38
|
};
|
|
38
39
|
|
|
39
40
|
export default {
|
|
40
41
|
name: 'qoder',
|
|
41
|
-
outputDir: '.qoder/
|
|
42
|
+
outputDir: '.qoder/skills',
|
|
42
43
|
fileExtension: '.md',
|
|
43
44
|
skillPrefix: 'rune-',
|
|
44
45
|
skillSuffix: '',
|
|
45
46
|
|
|
46
|
-
// Qoder
|
|
47
|
-
useSkillDirectories:
|
|
47
|
+
// Qoder uses directory-per-skill: .qoder/skills/{name}/SKILL.md
|
|
48
|
+
useSkillDirectories: true,
|
|
49
|
+
skillFileName: 'SKILL.md',
|
|
48
50
|
|
|
49
51
|
transformReference(skillName, raw) {
|
|
50
52
|
const isBackticked = raw.startsWith('`') && raw.endsWith('`');
|
|
51
|
-
const ref = `the rune-${skillName}
|
|
53
|
+
const ref = `the rune-${skillName} skill`;
|
|
52
54
|
return isBackticked ? `\`${ref}\`` : ref;
|
|
53
55
|
},
|
|
54
56
|
|
|
@@ -74,7 +76,7 @@ export default {
|
|
|
74
76
|
},
|
|
75
77
|
|
|
76
78
|
scriptsDir(skillName) {
|
|
77
|
-
return `rune-${skillName}
|
|
79
|
+
return `rune-${skillName}/scripts`;
|
|
78
80
|
},
|
|
79
81
|
|
|
80
82
|
postProcess(content) {
|
|
@@ -89,9 +91,7 @@ export default {
|
|
|
89
91
|
'Rune is an interconnected skill ecosystem for AI coding assistants.',
|
|
90
92
|
`${stats.skillCount} core skills + ${stats.packCount} extension packs.`,
|
|
91
93
|
'',
|
|
92
|
-
'Per-skill
|
|
93
|
-
'',
|
|
94
|
-
'Reference a skill by name (e.g. "follow the rune-cook rule") inside any chat — the rule file is auto-injected.',
|
|
94
|
+
'Per-skill Agent Skills live under `.qoder/skills/rune-<name>/SKILL.md`. Qoder discovers and loads them on demand.',
|
|
95
95
|
'',
|
|
96
96
|
'---',
|
|
97
97
|
'> Rune Skill Mesh — https://github.com/rune-kit/rune',
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Qwen Coder Adapter
|
|
3
3
|
*
|
|
4
|
-
* Emits
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Emits SKILL.md files into .qwen/skills/{name}/ directories — Qwen Code's
|
|
5
|
+
* native Agent Skills format. Qwen Code discovers project skills from
|
|
6
|
+
* .qwen/skills/ and lazy-loads them on demand (browse via /skills) — no more
|
|
7
|
+
* QWEN.md @import lines that loaded every skill as always-on context.
|
|
7
8
|
*
|
|
8
|
-
* Qwen
|
|
9
|
-
* Qwen
|
|
10
|
-
* Qwen project context: QWEN.md
|
|
9
|
+
* Qwen skills dir: .qwen/skills/
|
|
10
|
+
* Qwen skill format: .qwen/skills/{name}/SKILL.md
|
|
11
|
+
* Qwen project context: QWEN.md (slim pointer, loaded hierarchically)
|
|
11
12
|
*
|
|
12
|
-
* @see https://qwenlm.github.io/qwen-code-docs/en/users/
|
|
13
|
+
* @see https://qwenlm.github.io/qwen-code-docs/en/users/features/skills/
|
|
13
14
|
* @see https://github.com/QwenLM/qwen-code
|
|
14
15
|
*
|
|
15
16
|
* MODEL TIER MAPPING (v2.18+):
|
|
@@ -34,19 +35,20 @@ const TOOL_MAP = {
|
|
|
34
35
|
Grep: 'search file contents',
|
|
35
36
|
Bash: 'run a shell command',
|
|
36
37
|
TodoWrite: 'track task progress',
|
|
37
|
-
Skill: '
|
|
38
|
+
Skill: 'invoke the rune-{name} skill',
|
|
38
39
|
Agent: 'execute the workflow',
|
|
39
40
|
};
|
|
40
41
|
|
|
41
42
|
export default {
|
|
42
43
|
name: 'qwen',
|
|
43
|
-
outputDir: 'qwen/skills',
|
|
44
|
+
outputDir: '.qwen/skills',
|
|
44
45
|
fileExtension: '.md',
|
|
45
46
|
skillPrefix: 'rune-',
|
|
46
47
|
skillSuffix: '',
|
|
47
48
|
|
|
48
|
-
// Qwen
|
|
49
|
-
useSkillDirectories:
|
|
49
|
+
// Qwen Code uses directory-per-skill: .qwen/skills/{name}/SKILL.md
|
|
50
|
+
useSkillDirectories: true,
|
|
51
|
+
skillFileName: 'SKILL.md',
|
|
50
52
|
|
|
51
53
|
transformReference(skillName, raw) {
|
|
52
54
|
const isBackticked = raw.startsWith('`') && raw.endsWith('`');
|
|
@@ -76,30 +78,22 @@ export default {
|
|
|
76
78
|
},
|
|
77
79
|
|
|
78
80
|
scriptsDir(skillName) {
|
|
79
|
-
return `rune-${skillName}
|
|
81
|
+
return `rune-${skillName}/scripts`;
|
|
80
82
|
},
|
|
81
83
|
|
|
82
84
|
postProcess(content) {
|
|
83
85
|
return content.replace(/^context: fork\n/gm, '').replace(/^agent: general-purpose\n/gm, '');
|
|
84
86
|
},
|
|
85
87
|
|
|
86
|
-
//
|
|
88
|
+
// Slim QWEN.md pointer — skills themselves are lazy-loaded natively.
|
|
87
89
|
generateExtraFiles({ stats }) {
|
|
88
|
-
const skillFiles = stats.files
|
|
89
|
-
.filter((f) => f.startsWith('rune-') && f.endsWith('.md') && !f.includes('/') && !f.includes('\\'))
|
|
90
|
-
.sort();
|
|
91
|
-
|
|
92
90
|
const qwenMd = [
|
|
93
91
|
'# Rune — Project Configuration',
|
|
94
92
|
'',
|
|
95
93
|
'Rune is an interconnected skill ecosystem for AI coding assistants.',
|
|
96
94
|
`${stats.skillCount} core skills + ${stats.packCount} extension packs.`,
|
|
97
95
|
'',
|
|
98
|
-
'
|
|
99
|
-
'',
|
|
100
|
-
'Qwen Code loads each referenced file as part of this project context.',
|
|
101
|
-
'',
|
|
102
|
-
...skillFiles.map((f) => `@qwen/skills/${f}`),
|
|
96
|
+
'Per-skill Agent Skills live under `.qwen/skills/rune-<name>/SKILL.md`. Qwen Code discovers and lazy-loads them on demand (browse via /skills).',
|
|
103
97
|
'',
|
|
104
98
|
'---',
|
|
105
99
|
'> Rune Skill Mesh — https://github.com/rune-kit/rune',
|
|
@@ -1,8 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Windsurf Adapter
|
|
3
3
|
*
|
|
4
|
-
* Emits .md
|
|
5
|
-
*
|
|
4
|
+
* Emits SKILL.md files into .windsurf/skills/{name}/ directories — the native
|
|
5
|
+
* Cascade Skills format. Cascade uses progressive disclosure: only the skill's
|
|
6
|
+
* name and description are shown to the model by default; the full SKILL.md is
|
|
7
|
+
* loaded when Cascade invokes the skill (or via @skill-name).
|
|
8
|
+
*
|
|
9
|
+
* REBRAND (2026-06-02): Windsurf is now "Devin Desktop" (Cognition), rolled out
|
|
10
|
+
* over-the-air. Devin Desktop PREFERS `.devin/skills/` + `.devin/rules/` but
|
|
11
|
+
* still reads `.windsurf/skills/` + `.windsurf/rules/` as a documented fallback
|
|
12
|
+
* (docs.devin.ai). Rune keeps emitting to `.windsurf/` for maximum compat — it
|
|
13
|
+
* works on both current Devin and any un-migrated Windsurf install. The CLI flag
|
|
14
|
+
* stays `--platform windsurf`. Flip the emission dirs to `.devin/` only once
|
|
15
|
+
* Devin deprecates the `.windsurf/` fallback. @see docs.devin.ai/desktop
|
|
16
|
+
*
|
|
17
|
+
* Windsurf/Devin skills dir: .windsurf/skills/ (Devin also reads .devin/skills/)
|
|
18
|
+
* Skill format: .windsurf/skills/{name}/SKILL.md
|
|
19
|
+
*
|
|
20
|
+
* NOTE: .windsurf/rules/ is still used by the runtime-hooks installer
|
|
21
|
+
* (adapters/hooks/windsurf.js) — rules are the correct vehicle for always-on
|
|
22
|
+
* hook context. Only skill emission moved to .windsurf/skills/.
|
|
6
23
|
*
|
|
7
24
|
* MODEL TIER MAPPING (v2.15+):
|
|
8
25
|
* No-op. Windsurf's Anthropic API integration understands `model: opus|sonnet|haiku`
|
|
@@ -19,20 +36,24 @@ const TOOL_MAP = {
|
|
|
19
36
|
Grep: 'search for text in files',
|
|
20
37
|
Bash: 'run a shell command',
|
|
21
38
|
TodoWrite: 'track task progress',
|
|
22
|
-
Skill: '
|
|
39
|
+
Skill: 'invoke the named skill',
|
|
23
40
|
Agent: 'execute the workflow',
|
|
24
41
|
};
|
|
25
42
|
|
|
26
43
|
export default {
|
|
27
44
|
name: 'windsurf',
|
|
28
|
-
outputDir: '.windsurf/
|
|
45
|
+
outputDir: '.windsurf/skills',
|
|
29
46
|
fileExtension: '.md',
|
|
30
47
|
skillPrefix: 'rune-',
|
|
31
48
|
skillSuffix: '',
|
|
32
49
|
|
|
50
|
+
// Windsurf uses directory-per-skill: .windsurf/skills/{name}/SKILL.md
|
|
51
|
+
useSkillDirectories: true,
|
|
52
|
+
skillFileName: 'SKILL.md',
|
|
53
|
+
|
|
33
54
|
transformReference(skillName, raw) {
|
|
34
55
|
const isBackticked = raw.startsWith('`') && raw.endsWith('`');
|
|
35
|
-
const ref = `the rune-${skillName}
|
|
56
|
+
const ref = `the rune-${skillName} skill`;
|
|
36
57
|
return isBackticked ? `\`${ref}\`` : ref;
|
|
37
58
|
},
|
|
38
59
|
|
|
@@ -41,7 +62,8 @@ export default {
|
|
|
41
62
|
},
|
|
42
63
|
|
|
43
64
|
generateHeader(skill) {
|
|
44
|
-
|
|
65
|
+
const desc = (skill.description || '').replace(/"/g, '\\"');
|
|
66
|
+
return ['---', `name: rune-${skill.name}`, `description: "${desc}"`, '---', '', ''].join('\n');
|
|
45
67
|
},
|
|
46
68
|
|
|
47
69
|
generateFooter() {
|
|
@@ -53,7 +75,7 @@ export default {
|
|
|
53
75
|
},
|
|
54
76
|
|
|
55
77
|
scriptsDir(skillName) {
|
|
56
|
-
return `rune-${skillName}
|
|
78
|
+
return `rune-${skillName}/scripts`;
|
|
57
79
|
},
|
|
58
80
|
|
|
59
81
|
postProcess(content) {
|
package/compiler/emitter.js
CHANGED
|
@@ -65,7 +65,7 @@ async function discoverPacks(extensionsDir, enabledPacks = null) {
|
|
|
65
65
|
* Copies directories except SKILL.md (already processed) and denylisted dirs
|
|
66
66
|
*
|
|
67
67
|
* @param {string} sourceSkillDir - e.g. skills/cook/
|
|
68
|
-
* @param {string} outputSkillDir - e.g. .
|
|
68
|
+
* @param {string} outputSkillDir - e.g. .agents/skills/rune-cook/
|
|
69
69
|
* @returns {Promise<string[]>} list of copied directory names
|
|
70
70
|
*/
|
|
71
71
|
const COPY_DENYLIST = new Set(['.git', 'node_modules', '__pycache__', '.DS_Store', '.venv', '.env']);
|
|
@@ -74,7 +74,8 @@ async function copySkillExtraDirs(sourceSkillDir, outputSkillDir) {
|
|
|
74
74
|
if (!existsSync(sourceSkillDir)) return [];
|
|
75
75
|
|
|
76
76
|
const entries = await readdir(sourceSkillDir, { withFileTypes: true });
|
|
77
|
-
|
|
77
|
+
// scripts/ is excluded: copyScriptsDir handles it explicitly (with stats counting)
|
|
78
|
+
const dirs = entries.filter((e) => e.isDirectory() && !COPY_DENYLIST.has(e.name) && e.name !== 'scripts');
|
|
78
79
|
|
|
79
80
|
const copied = [];
|
|
80
81
|
for (const dir of dirs) {
|
|
@@ -91,7 +92,7 @@ async function copySkillExtraDirs(sourceSkillDir, outputSkillDir) {
|
|
|
91
92
|
* Copy scripts directory from skill source to output.
|
|
92
93
|
*
|
|
93
94
|
* @param {string} sourceScriptsDir - e.g. skills/slides/scripts/
|
|
94
|
-
* @param {string} outputScriptsDir - e.g. .cursor/
|
|
95
|
+
* @param {string} outputScriptsDir - e.g. .cursor/skills/rune-slides/scripts/
|
|
95
96
|
* @returns {Promise<string[]>} list of copied file paths
|
|
96
97
|
*/
|
|
97
98
|
async function copyScriptsDir(sourceScriptsDir, outputScriptsDir) {
|
|
@@ -524,7 +525,7 @@ export async function buildAll({
|
|
|
524
525
|
let skillDir = null;
|
|
525
526
|
|
|
526
527
|
if (adapter.useSkillDirectories) {
|
|
527
|
-
// Directory-per-skill: .
|
|
528
|
+
// Directory-per-skill: .agents/skills/rune-{name}/SKILL.md
|
|
528
529
|
const dirName = `${adapter.skillPrefix}${parsed.name}`;
|
|
529
530
|
skillDir = path.join(outputDir, dirName);
|
|
530
531
|
await mkdir(skillDir, { recursive: true });
|
package/compiler/parser.js
CHANGED
|
@@ -34,6 +34,15 @@ function parseFrontmatter(content) {
|
|
|
34
34
|
let nestedKey = null;
|
|
35
35
|
const _nestedObj = {};
|
|
36
36
|
|
|
37
|
+
// Strip outer quotes; for double-quoted scalars also unescape interior \"
|
|
38
|
+
// so consumers (adapter generateHeader) hold clean text and can re-escape
|
|
39
|
+
// exactly once. Without this, `\"` survives into skill.description and a
|
|
40
|
+
// later .replace(/"/g, '\\"') turns it into `\\"` — invalid YAML.
|
|
41
|
+
const parseScalar = (rawValue) => {
|
|
42
|
+
const stripped = rawValue.replace(/^["']|["']$/g, '');
|
|
43
|
+
return rawValue.startsWith('"') ? stripped.replace(/\\"/g, '"') : stripped;
|
|
44
|
+
};
|
|
45
|
+
|
|
37
46
|
for (const line of raw.split('\n')) {
|
|
38
47
|
const trimmed = line.trim();
|
|
39
48
|
if (!trimmed) continue;
|
|
@@ -60,7 +69,7 @@ function parseFrontmatter(content) {
|
|
|
60
69
|
|
|
61
70
|
const kvMatch = trimmed.match(/^(\w[\w-]*):\s*(.+)$/);
|
|
62
71
|
if (kvMatch) {
|
|
63
|
-
const rawValue = kvMatch[2]
|
|
72
|
+
const rawValue = parseScalar(kvMatch[2]);
|
|
64
73
|
// Comma-separated list fields → parse as array
|
|
65
74
|
if (COMMA_LIST_FIELDS.has(kvMatch[1])) {
|
|
66
75
|
frontmatter[nestedKey][kvMatch[1]] = rawValue
|
|
@@ -79,7 +88,7 @@ function parseFrontmatter(content) {
|
|
|
79
88
|
nestedKey = null;
|
|
80
89
|
const kvMatch = trimmed.match(/^(\w[\w-]*):\s*(.+)$/);
|
|
81
90
|
if (kvMatch) {
|
|
82
|
-
const value = kvMatch[2]
|
|
91
|
+
const value = parseScalar(kvMatch[2]);
|
|
83
92
|
// Comma-separated list fields at top level too (Pro/Business packs)
|
|
84
93
|
if (COMMA_LIST_FIELDS.has(kvMatch[1])) {
|
|
85
94
|
frontmatter[kvMatch[1]] = value
|
|
@@ -64,21 +64,21 @@ interface ModelConfig {
|
|
|
64
64
|
|
|
65
65
|
const MODELS: Record<string, ModelConfig> = {
|
|
66
66
|
fast: {
|
|
67
|
-
id: 'claude-haiku-4-5
|
|
67
|
+
id: 'claude-haiku-4-5',
|
|
68
68
|
provider: 'anthropic',
|
|
69
69
|
costPer1kTokens: 0.001,
|
|
70
70
|
maxTokens: 4096,
|
|
71
71
|
latencyP50Ms: 200,
|
|
72
72
|
},
|
|
73
73
|
balanced: {
|
|
74
|
-
id: 'claude-sonnet-
|
|
74
|
+
id: 'claude-sonnet-5',
|
|
75
75
|
provider: 'anthropic',
|
|
76
76
|
costPer1kTokens: 0.01,
|
|
77
77
|
maxTokens: 8192,
|
|
78
78
|
latencyP50Ms: 800,
|
|
79
79
|
},
|
|
80
80
|
deep: {
|
|
81
|
-
id: 'claude-opus-4-
|
|
81
|
+
id: 'claude-opus-4-8',
|
|
82
82
|
provider: 'anthropic',
|
|
83
83
|
costPer1kTokens: 0.05,
|
|
84
84
|
maxTokens: 16384,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rune-kit/rune",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.24.0",
|
|
4
4
|
"description": "65-skill mesh for AI coding assistants — runtime auto-discipline via native hooks (Claude/Cursor/Windsurf/Antigravity), 5-layer architecture, 204 connections + 43 signals, multi-platform compiler. converge (L3) scans spec vs code so dead-button/frontend-only implementations can't ship.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|