@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.
@@ -1,19 +1,17 @@
1
1
  /**
2
- * GitHub Copilot CLI Adapter
2
+ * GitHub Copilot Adapter
3
3
  *
4
- * Emits per-skill instruction files into .github/instructions/ — the documented
5
- * convention for GitHub Copilot custom instructions (read by both the CLI and
6
- * the GitHub Copilot IDE plugin family).
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 instructions dir: .github/instructions/
9
- * Copilot instruction file: .github/instructions/rune-{name}.instructions.md
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
- * Each .instructions.md uses YAML frontmatter with `applyTo` for path scoping.
13
- * Default applyTo: "**" means "apply to all files."
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: 'follow the referenced instructions',
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/instructions',
47
- fileExtension: '.instructions.md',
44
+ outputDir: '.github/skills',
45
+ fileExtension: '.md',
48
46
  skillPrefix: 'rune-',
49
47
  skillSuffix: '',
50
48
 
51
- // Copilot instructions are flat per-skill files, not directories.
52
- useSkillDirectories: false,
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} instructions`;
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
- // Per docs.github.com Copilot CLI custom-instructions spec, the only
66
- // documented frontmatter key for `.instructions.md` is `applyTo`. Other
67
- // metadata (description, tier hint) belongs in the markdown body so it
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 = ['---', 'applyTo: "**"', '---', '', `# rune-${skill.name}`, '', `> ${desc}`];
72
- if (tierHint) lines.push('', `<!-- tier-hint: ${tierHint} -->`);
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}-scripts`;
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 instructions live under \`.github/instructions/rune-<name>.instructions.md\` (${stats.skillCount} skills + ${stats.packCount} packs). Copilot loads them based on each file's \`applyTo\` glob.`,
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 following the corresponding rune-<name>.instructions.md over freestyling.',
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 custom instructions: `.github/instructions/rune-<name>.instructions.md`',
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 .mdc rule files for .cursor/rules/ directory.
5
- * Uses @file references for cross-skill mesh.
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: 'follow the referenced skill rules',
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/rules',
29
- fileExtension: '.mdc',
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 = `@rune-${skillName}.mdc`;
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
- return [
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}-scripts`;
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
- * Gemini CLI loads a single GEMINI.md at project root for context. It does NOT
5
- * support per-skill imports as of writing so the canonical output is a
6
- * bundled GEMINI.md with every skill concatenated under `## rune-<name>` H2
7
- * sections. Per-skill files are still emitted under gemini/skills/ for human
8
- * inspection and forward-compatibility if Gemini adds @import support later.
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 context file: GEMINI.md (project root)
11
- * Gemini per-skill files: gemini/skills/rune-{name}.md (forward-compat staging)
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://github.com/google-gemini/gemini-cli
14
- * @see https://geminicli.com/docs/reference/configuration/
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 1.5 Pro / 1.5 Flash / 2.0 Flash etc. Anthropic tier names
18
- * translate to Gemini families: opus→1.5-pro, sonnet→1.5-flash, haiku→
19
- * 2.0-flash-lite. Hint only Gemini CLI reads model from --model flag /
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: 'follow the referenced rune-{name} section in GEMINI.md',
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
- // Per-skill files staged for forward compat; canonical entry is bundled GEMINI.md.
53
- useSkillDirectories: false,
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} section in GEMINI.md`;
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}-scripts`;
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
- // Bundle every per-skill file into a single GEMINI.md with H2 section per skill.
91
- // Gemini CLI loads GEMINI.md from project root as its context file. Hook contract
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, bundled below.`,
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
- '> Per-skill files are staged in `gemini/skills/` for forward-compat if Gemini CLI adds @import support.',
97
+ 'Per-skill Agent Skills live under `.gemini/skills/rune-<name>/SKILL.md`. Gemini CLI discovers and lazy-loads them automatically.',
121
98
  '',
122
- ...sections,
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 per-skill rule files into .qoder/rules/ — Qoder's documented project-level
5
- * rule directory. Qoder also reads AGENTS.md as its project-context file.
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 rules dir: .qoder/rules/
8
- * Qoder rule file: .qoder/rules/rune-{name}.md (one file per skill)
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/user-guide/rules
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: 'follow the referenced rune-{name} rule',
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/rules',
42
+ outputDir: '.qoder/skills',
42
43
  fileExtension: '.md',
43
44
  skillPrefix: 'rune-',
44
45
  skillSuffix: '',
45
46
 
46
- // Qoder rules are flat .md files, not directory-per-skill
47
- useSkillDirectories: false,
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} rule`;
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}-scripts`;
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 rules live under `.qoder/rules/rune-<name>.md`. Qoder loads them automatically.',
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 per-skill rule files into qwen/skills/ and a top-level QWEN.md that
5
- * uses Qwen Code's @import syntax to load each rule file. Qwen Code loads
6
- * QWEN.md hierarchically (cwd parent ~/.qwen/QWEN.md).
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 rules dir: qwen/skills/
9
- * Qwen rule file: qwen/skills/rune-{name}.md
10
- * Qwen project context: QWEN.md at project root (with @path imports)
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/configuration/settings/
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: 'follow the imported rune-{name} 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 skills are flat per-skill markdown files imported from QWEN.md.
49
- useSkillDirectories: false,
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}-scripts`;
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
- // Emit QWEN.md at project root with @import lines for every per-skill file.
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
- '## Loaded Skills',
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 rule files for .windsurf/rules/ directory.
5
- * Uses prose references for cross-skill mesh (no @file support).
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: 'follow the referenced skill workflow',
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/rules',
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} rule file`;
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
- return `# rune-${skill.name}\n\n> Layer: ${skill.layer} | Group: ${skill.group}\n\n`;
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}-scripts`;
78
+ return `rune-${skillName}/scripts`;
57
79
  },
58
80
 
59
81
  postProcess(content) {
@@ -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. .codex/skills/rune-cook/
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
- const dirs = entries.filter((e) => e.isDirectory() && !COPY_DENYLIST.has(e.name));
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/rules/rune-slides-scripts/
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: .codex/skills/rune-{name}/SKILL.md
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 });
@@ -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].replace(/^["']|["']$/g, '');
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].replace(/^["']|["']$/g, '');
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-20251001',
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-4-6',
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-6',
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.22.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": {