@rune-kit/rune 2.4.0 → 2.6.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 CHANGED
@@ -5,7 +5,7 @@
5
5
  <p align="center">
6
6
  <strong>Less skills. Deeper connections.</strong><br>
7
7
  A lean, interconnected skill ecosystem for AI coding assistants.<br>
8
- 59 skills · 200+ mesh connections · 8 platforms · MIT
8
+ 61 skills · 200+ mesh connections · 8 platforms · MIT
9
9
  </p>
10
10
 
11
11
  <p align="center">
@@ -22,7 +22,7 @@
22
22
 
23
23
  Most skill ecosystems are either **too many isolated skills** (540+ that don't talk to each other) or **rigid pipelines** (A → B → C, if B fails everything stops).
24
24
 
25
- Rune is a **mesh** — 59 skills with 200+ connections across a 5-layer architecture. Skills call each other bidirectionally, forming resilient workflows that adapt when things go wrong.
25
+ Rune is a **mesh** — 61 skills with 200+ connections across a 5-layer architecture. Skills call each other bidirectionally, forming resilient workflows that adapt when things go wrong.
26
26
 
27
27
  ```
28
28
  Pipeline: A → B → C → D (B fails = stuck)
@@ -82,19 +82,34 @@ _Methodology: Claude Code CLI headless mode (`claude -p --output-format json`),
82
82
 
83
83
  ---
84
84
 
85
- ## What's New (v2.4.0)
85
+ ## What's New (v2.6.0)
86
86
 
87
- - **UI/UX Pro Max Integration** — 161 palettes, 84 styles, 73 font pairings, 161 reasoning rules from [UI/UX Pro Max](https://github.com/nextlevelbuilder/ui-ux-pro-max-skill) (MIT, 42.8k★) meshed into `design` skill + `@rune/ui` pack
88
- - **11 Core Skill Enrichments** — deviation rules, repair operators, evidence quality gate, decision classification, debug knowledge base, CSO discipline, YAGNI pushback, formal pause/resume
89
- - **Enforcement Upgrade** — Antigravity-level IDE compliance: 5-type Request Classifier, File Ownership Matrix, Self-Verification HARD-GATE, Routing Proof, Clarification Gate, Phase Transition Protocol
90
- - **All 14 Free Packs Deep** every L4 extension pack now exceeds 500 lines of production-ready patterns and code examples
91
- - **8 Platform Adapters** — Claude Code, Cursor, Windsurf, Google Antigravity, OpenAI Codex, OpenCode, OpenClaw, Generic
92
- - **59 Core Skills** — +4 since v2.1.0: adversary, sentinel-env, chrome-ext pack, context-pack
93
- - **Rune Pro** — 4 premium packs (product, sales, data-science, support) in private repo. $49 lifetime
87
+ - **Mesh Signals** — event-driven skill communication via frontmatter. Skills declare `emit` and `listen` signals; compiler builds a signal graph in `skill-index.json`. 17 signals across 15 core skills. Completes the spec-kit trilogy: Tier Override → Mesh Contract → Mesh Signals
88
+ - **Signal Validation** — `scripts/validate-signals.js` checks orphan listeners (hard error), unlistened emitters (warning), signal naming conventions
89
+ - **Mesh Contract** (v2.5.0) `.rune/contract.md` project-level invariants enforced by cook + sentinel as hard gates
90
+ - **Compiled Intent Mesh** (v2.5.0)compile-time `skill-index.json` with intent keywords, mesh graph, chain predictions
91
+ - **Tier Override** — Pro/Business packs override Free packs with skill-level merging
92
+ - **Scripts Bundling** — compiler copies `scripts/` directories, resolves `{scripts_dir}` placeholders
93
+ - **566 Tests** — compiler + signals + hooks + scripts validation
94
+
95
+ ### Signal Graph
96
+
97
+ Skills communicate through declarative signals — no runtime event bus, just metadata for discovery, validation, and routing:
98
+
99
+ ```
100
+ scout ──emit:codebase.scanned──→ plan, brainstorm
101
+ fix ────emit:code.changed──────→ test, sentinel, review, preflight, verification
102
+ test ───emit:tests.passed──────→ deploy
103
+ test ───emit:tests.failed──────→ debug
104
+ sentinel─emit:security.passed──→ deploy
105
+ debug ──emit:bug.diagnosed─────→ fix
106
+ deploy ─emit:deploy.complete───→ watchdog
107
+ cook ───emit:phase.complete────→ session-bridge
108
+ ```
94
109
 
95
110
  ## What Rune Is (and Isn't)
96
111
 
97
- Rune started as a **Claude Code plugin** and now compiles to **every major AI IDE**. Same 59 skills, same mesh connections, same workflows — zero knowledge loss across platforms.
112
+ Rune started as a **Claude Code plugin** and now compiles to **every major AI IDE**. Same 61 skills, same mesh connections, same workflows — zero knowledge loss across platforms.
98
113
 
99
114
  | | Rune Provides | Claude Code Provides |
100
115
  |---|---|---|
@@ -118,7 +133,7 @@ Rune started as a **Claude Code plugin** and now compiles to **every major AI ID
118
133
  | CI quality gates | `verification` skill: lint + typecheck + tests + build (actual commands, not LLM review) |
119
134
  | Memory / state | `session-bridge` + `journal`: cross-session decisions, conventions, ADRs, module health |
120
135
  | Multi-model strategy | Every skill has assigned model: haiku (scan), sonnet (code), opus (architecture) |
121
- | Agent specialization | 58 specialized skills with dedicated roles (architect, coder, reviewer, scanner, researcher, BA, scaffolder) — each runs as a Task agent via Claude Code |
136
+ | Agent specialization | 61 specialized skills with dedicated roles (architect, coder, reviewer, scanner, researcher, BA, scaffolder) — each runs as a Task agent via Claude Code |
122
137
  | Security scanning | `sentinel`: OWASP patterns, secret scanning, dependency audit. `sast`: static analysis |
123
138
 
124
139
  ## Install
@@ -146,13 +161,13 @@ npx @rune-kit/rune init --platform windsurf
146
161
  npx @rune-kit/rune init --platform antigravity
147
162
  ```
148
163
 
149
- This compiles all 59 skills into your IDE's rules format. Same knowledge, same workflows.
164
+ This compiles all 61 skills into your IDE's rules format. Same knowledge, same workflows.
150
165
 
151
166
  ### Platform Comparison
152
167
 
153
168
  | Feature | Claude Code | Cursor / Windsurf / Others |
154
169
  |---------|-------------|---------------------------|
155
- | Skills available | 59/59 | 59/59 |
170
+ | Skills available | 61/61 | 61/61 |
156
171
  | Mesh connections | 200+ (programmatic) | 200+ (rule references) |
157
172
  | Workflows & HARD-GATEs | Full | Full |
158
173
  | Extension packs | 14 | 14 |
@@ -229,8 +244,9 @@ This compiles all 59 skills into your IDE's rules format. Same knowledge, same w
229
244
  ║ Delivery: deploy │ marketing │ incident │ docs ║
230
245
  ║ Rescue: autopsy │ safeguard │ surgeon ║
231
246
  ║ Security: adversary ║
247
+ ║ Velocity: retro ║
232
248
  ╠══════════════════════════════════════════════════════╣
233
- ║ L3: UTILITIES (26) ║
249
+ ║ L3: UTILITIES (27) ║
234
250
  ║ Stateless, pure capabilities ║
235
251
  ║ ║
236
252
  ║ Knowledge: research │ docs-seeker │ trend-scout ║
@@ -249,6 +265,8 @@ This compiles all 59 skills into your IDE's rules format. Same knowledge, same w
249
265
  ║ Documents: doc-processor ║
250
266
  ║ Security: sentinel-env ║
251
267
  ║ Memory: neural-memory ║
268
+ ║ Packs: context-pack ║
269
+ ║ Slides: slides ║
252
270
  ╠══════════════════════════════════════════════════════╣
253
271
  ║ L4: EXTENSION PACKS (14) ║
254
272
  ║ Domain-specific, install what you need ║
@@ -426,13 +444,14 @@ See [docs/MULTI-PLATFORM.md](docs/MULTI-PLATFORM.md) for the full architecture.
426
444
  ## Numbers
427
445
 
428
446
  ```
429
- Core Skills: 59 (L0: 1 │ L1: 5 │ L2: 28 │ L3: 26)
447
+ Core Skills: 61 (L0: 1 │ L1: 5 │ L2: 28 │ L3: 27)
430
448
  Extension Packs: 14 free + 4 pro + 4 business
431
449
  Mesh Connections: 200+ cross-references
450
+ Mesh Signals: 17 signals across 15 skills (emit/listen graph)
432
451
  Connections/Skill: 3.4 avg
433
452
  Platforms: 8 (Claude Code, Cursor, Windsurf, Antigravity, Codex, OpenCode, OpenClaw, Generic)
434
453
  Compiler: ~1400 LOC (parser + 8 transforms + 8 adapters + CLI)
435
- Quality: 59/59 skills with Output Format, Sharp Edges, Done When, Cost Profile
454
+ Tests: 566 (compiler + signals + hooks + scripts)
436
455
  Pack Depth: 22 packs total (14 free + 4 pro + 4 business, all free packs rated Deep)
437
456
  ```
438
457
 
@@ -1,8 +1,12 @@
1
1
  import assert from 'node:assert';
2
2
  import { existsSync, readFileSync } from 'node:fs';
3
+ import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
4
+ import { tmpdir } from 'node:os';
3
5
  import path from 'node:path';
4
- import { test } from 'node:test';
6
+ import { describe, test } from 'node:test';
5
7
  import { fileURLToPath } from 'node:url';
8
+ import { getAdapter } from '../adapters/index.js';
9
+ import { buildAll } from '../emitter.js';
6
10
  import { parsePack } from '../parser.js';
7
11
 
8
12
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -143,3 +147,139 @@ test('parsePack: real backend PACK.md parses as split (post-split)', () => {
143
147
  assert.strictEqual(parsed.layer, 'L4');
144
148
  assert.ok(parsed.sections.size > 0);
145
149
  });
150
+
151
+ // --- Integration: buildAll auto-discovers split pack skill files ---
152
+
153
+ describe('buildAll split pack auto-discovery', () => {
154
+ async function createTempWithSplitPack() {
155
+ const tmp = path.join(tmpdir(), `rune-split-test-${Date.now()}`);
156
+ const skillsDir = path.join(tmp, 'skills', 'test-skill');
157
+ const extDir = path.join(tmp, 'extensions', 'test-pack');
158
+ const extSkillsDir = path.join(extDir, 'skills');
159
+
160
+ await mkdir(skillsDir, { recursive: true });
161
+ await mkdir(extSkillsDir, { recursive: true });
162
+
163
+ // Minimal core skill (required for build)
164
+ await writeFile(
165
+ path.join(skillsDir, 'SKILL.md'),
166
+ [
167
+ '---',
168
+ 'name: test-skill',
169
+ 'description: "A test skill"',
170
+ 'layer: L3',
171
+ 'group: utility',
172
+ 'connections: []',
173
+ 'tags: [test]',
174
+ '---',
175
+ '',
176
+ '# test-skill',
177
+ '',
178
+ 'Hello.',
179
+ ].join('\n'),
180
+ 'utf-8',
181
+ );
182
+
183
+ // Split pack WITHOUT skills: array in frontmatter
184
+ await writeFile(
185
+ path.join(extDir, 'PACK.md'),
186
+ [
187
+ '---',
188
+ 'name: "@rune/test-pack"',
189
+ 'description: "Test pack"',
190
+ 'metadata:',
191
+ ' version: "0.1.0"',
192
+ ' format: split',
193
+ '---',
194
+ '',
195
+ '# @rune/test-pack',
196
+ '',
197
+ 'Pack index body.',
198
+ ].join('\n'),
199
+ 'utf-8',
200
+ );
201
+
202
+ // Two skill files in skills/ subdir
203
+ await writeFile(
204
+ path.join(extSkillsDir, 'alpha.md'),
205
+ [
206
+ '---',
207
+ 'name: "alpha"',
208
+ 'description: "Alpha skill"',
209
+ 'model: sonnet',
210
+ '---',
211
+ '',
212
+ '# alpha',
213
+ '',
214
+ 'Alpha body content here.',
215
+ ].join('\n'),
216
+ 'utf-8',
217
+ );
218
+ await writeFile(
219
+ path.join(extSkillsDir, 'beta.md'),
220
+ [
221
+ '---',
222
+ 'name: "beta"',
223
+ 'description: "Beta skill"',
224
+ 'model: sonnet',
225
+ '---',
226
+ '',
227
+ '# beta',
228
+ '',
229
+ 'Beta body content here.',
230
+ ].join('\n'),
231
+ 'utf-8',
232
+ );
233
+
234
+ return tmp;
235
+ }
236
+
237
+ test('auto-discovers skill files from skills/ subdir when manifest is empty', async () => {
238
+ const tmp = await createTempWithSplitPack();
239
+ try {
240
+ const outputRoot = path.join(tmp, 'out');
241
+ const adapter = getAdapter('cursor');
242
+ const stats = await buildAll({ runeRoot: tmp, outputRoot, adapter });
243
+
244
+ assert.strictEqual(stats.packCount, 1, 'should build 1 pack');
245
+
246
+ // Read the compiled pack output
247
+ const packOutput = await readFile(path.join(outputRoot, adapter.outputDir, 'rune-ext-test-pack.mdc'), 'utf-8');
248
+
249
+ // Should contain the pack index body
250
+ assert.ok(packOutput.includes('Pack index body'), 'missing pack index body');
251
+
252
+ // Should contain BOTH auto-discovered skill bodies
253
+ assert.ok(packOutput.includes('Alpha body content here'), 'missing alpha skill body');
254
+ assert.ok(packOutput.includes('Beta body content here'), 'missing beta skill body');
255
+ } finally {
256
+ await rm(tmp, { recursive: true, force: true });
257
+ }
258
+ });
259
+
260
+ test('real ai-ml pack includes skill content after build', async () => {
261
+ const aimlPath = path.join(EXTENSIONS_DIR, 'ai-ml', 'PACK.md');
262
+ if (!existsSync(aimlPath)) {
263
+ console.log(' skip: ai-ml PACK.md not found');
264
+ return;
265
+ }
266
+
267
+ const tmp = path.join(tmpdir(), `rune-aiml-test-${Date.now()}`);
268
+ const runeRoot = path.resolve(__dirname, '../..');
269
+ const adapter = getAdapter('cursor');
270
+ await buildAll({ runeRoot, outputRoot: tmp, adapter });
271
+
272
+ try {
273
+ const packOutput = await readFile(path.join(tmp, adapter.outputDir, 'rune-ext-ai-ml.mdc'), 'utf-8');
274
+
275
+ // Should be significantly longer than just the index body (~99 lines without skills)
276
+ const lineCount = packOutput.split('\n').length;
277
+ assert.ok(lineCount > 200, `ai-ml pack output too short (${lineCount} lines) — skills likely not included`);
278
+
279
+ // Should contain content from individual skill files
280
+ assert.ok(packOutput.includes('ai-agents') || packOutput.includes('AI agent'), 'missing ai-agents skill content');
281
+ } finally {
282
+ await rm(tmp, { recursive: true, force: true });
283
+ }
284
+ });
285
+ });
@@ -1,55 +1,147 @@
1
- import assert from 'node:assert';
2
- import { readFileSync } from 'node:fs';
3
- import path from 'node:path';
4
- import { test } from 'node:test';
5
- import { fileURLToPath } from 'node:url';
6
- import { parseSkill } from '../parser.js';
7
-
8
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
- const SKILLS_DIR = path.resolve(__dirname, '../../skills');
10
-
11
- test('parse cook SKILL.md', () => {
12
- const content = readFileSync(path.join(SKILLS_DIR, 'cook/SKILL.md'), 'utf-8');
13
- const parsed = parseSkill(content, 'cook/SKILL.md');
14
-
15
- console.log('name:', JSON.stringify(parsed.name));
16
- console.log('description:', JSON.stringify(parsed.description?.substring(0, 50)));
17
- console.log('layer:', parsed.layer);
18
- console.log('model:', parsed.model);
19
- console.log('group:', parsed.group);
20
- console.log('contextFork:', parsed.contextFork);
21
- console.log('agentType:', parsed.agentType);
22
- console.log('crossRefs:', parsed.crossRefs.length);
23
- console.log('toolRefs:', parsed.toolRefs.length);
24
- console.log('hardGates:', parsed.hardGates.length);
25
- console.log('sections:', [...parsed.sections.keys()]);
26
- console.log('frontmatter keys:', Object.keys(parsed.frontmatter));
27
-
28
- assert.strictEqual(parsed.name, 'cook');
29
- assert.strictEqual(parsed.layer, 'L1');
30
- assert.ok(parsed.crossRefs.length > 0);
31
- assert.ok(parsed.hardGates.length > 0);
32
- });
33
-
34
- test('parse fix SKILL.md', () => {
35
- const content = readFileSync(path.join(SKILLS_DIR, 'fix/SKILL.md'), 'utf-8');
36
- const parsed = parseSkill(content, 'fix/SKILL.md');
37
-
38
- console.log('fix name:', JSON.stringify(parsed.name));
39
- console.log('fix layer:', parsed.layer);
40
-
41
- assert.strictEqual(parsed.name, 'fix');
42
- assert.strictEqual(parsed.layer, 'L2');
43
- });
44
-
45
- test('parse verification SKILL.md', () => {
46
- const content = readFileSync(path.join(SKILLS_DIR, 'verification/SKILL.md'), 'utf-8');
47
- const parsed = parseSkill(content, 'verification/SKILL.md');
48
-
49
- console.log('verification name:', JSON.stringify(parsed.name));
50
- console.log('verification layer:', parsed.layer);
51
- console.log('verification toolRefs:', parsed.toolRefs.length);
52
-
53
- assert.strictEqual(parsed.name, 'verification');
54
- assert.strictEqual(parsed.layer, 'L3');
55
- });
1
+ import assert from 'node:assert';
2
+ import { readFileSync } from 'node:fs';
3
+ import path from 'node:path';
4
+ import { describe, test } from 'node:test';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { parseSkill } from '../parser.js';
7
+
8
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
+ const SKILLS_DIR = path.resolve(__dirname, '../../skills');
10
+
11
+ test('parse cook SKILL.md', () => {
12
+ const content = readFileSync(path.join(SKILLS_DIR, 'cook/SKILL.md'), 'utf-8');
13
+ const parsed = parseSkill(content, 'cook/SKILL.md');
14
+
15
+ console.log('name:', JSON.stringify(parsed.name));
16
+ console.log('description:', JSON.stringify(parsed.description?.substring(0, 50)));
17
+ console.log('layer:', parsed.layer);
18
+ console.log('model:', parsed.model);
19
+ console.log('group:', parsed.group);
20
+ console.log('contextFork:', parsed.contextFork);
21
+ console.log('agentType:', parsed.agentType);
22
+ console.log('crossRefs:', parsed.crossRefs.length);
23
+ console.log('toolRefs:', parsed.toolRefs.length);
24
+ console.log('hardGates:', parsed.hardGates.length);
25
+ console.log('sections:', [...parsed.sections.keys()]);
26
+ console.log('frontmatter keys:', Object.keys(parsed.frontmatter));
27
+
28
+ assert.strictEqual(parsed.name, 'cook');
29
+ assert.strictEqual(parsed.layer, 'L1');
30
+ assert.ok(parsed.crossRefs.length > 0);
31
+ assert.ok(parsed.hardGates.length > 0);
32
+ });
33
+
34
+ test('parse fix SKILL.md', () => {
35
+ const content = readFileSync(path.join(SKILLS_DIR, 'fix/SKILL.md'), 'utf-8');
36
+ const parsed = parseSkill(content, 'fix/SKILL.md');
37
+
38
+ console.log('fix name:', JSON.stringify(parsed.name));
39
+ console.log('fix layer:', parsed.layer);
40
+
41
+ assert.strictEqual(parsed.name, 'fix');
42
+ assert.strictEqual(parsed.layer, 'L2');
43
+ });
44
+
45
+ test('parse verification SKILL.md', () => {
46
+ const content = readFileSync(path.join(SKILLS_DIR, 'verification/SKILL.md'), 'utf-8');
47
+ const parsed = parseSkill(content, 'verification/SKILL.md');
48
+
49
+ console.log('verification name:', JSON.stringify(parsed.name));
50
+ console.log('verification layer:', parsed.layer);
51
+ console.log('verification toolRefs:', parsed.toolRefs.length);
52
+
53
+ assert.strictEqual(parsed.name, 'verification');
54
+ assert.strictEqual(parsed.layer, 'L3');
55
+ });
56
+
57
+ describe('signals parsing', () => {
58
+ test('parses emit and listen from metadata', () => {
59
+ const content = [
60
+ '---',
61
+ 'name: alpha',
62
+ 'description: "Test skill"',
63
+ 'metadata:',
64
+ ' layer: L2',
65
+ ' emit: code.changed, tests.passed',
66
+ ' listen: plan.ready',
67
+ '---',
68
+ '',
69
+ '# alpha',
70
+ ].join('\n');
71
+
72
+ const parsed = parseSkill(content);
73
+ assert.ok(parsed.signals, 'signals should not be null');
74
+ assert.deepStrictEqual(parsed.signals.emit, ['code.changed', 'tests.passed']);
75
+ assert.deepStrictEqual(parsed.signals.listen, ['plan.ready']);
76
+ });
77
+
78
+ test('emit only — no listen', () => {
79
+ const content = [
80
+ '---',
81
+ 'name: beta',
82
+ 'description: "Emitter only"',
83
+ 'metadata:',
84
+ ' layer: L3',
85
+ ' emit: deploy.complete',
86
+ '---',
87
+ '',
88
+ '# beta',
89
+ ].join('\n');
90
+
91
+ const parsed = parseSkill(content);
92
+ assert.ok(parsed.signals);
93
+ assert.deepStrictEqual(parsed.signals.emit, ['deploy.complete']);
94
+ assert.deepStrictEqual(parsed.signals.listen, []);
95
+ });
96
+
97
+ test('listen only — no emit', () => {
98
+ const content = [
99
+ '---',
100
+ 'name: gamma',
101
+ 'description: "Listener only"',
102
+ 'metadata:',
103
+ ' layer: L3',
104
+ ' listen: code.changed',
105
+ '---',
106
+ '',
107
+ '# gamma',
108
+ ].join('\n');
109
+
110
+ const parsed = parseSkill(content);
111
+ assert.ok(parsed.signals);
112
+ assert.deepStrictEqual(parsed.signals.emit, []);
113
+ assert.deepStrictEqual(parsed.signals.listen, ['code.changed']);
114
+ });
115
+
116
+ test('no signals — returns null', () => {
117
+ const content = [
118
+ '---',
119
+ 'name: delta',
120
+ 'description: "No signals"',
121
+ 'metadata:',
122
+ ' layer: L3',
123
+ '---',
124
+ '',
125
+ '# delta',
126
+ ].join('\n');
127
+
128
+ const parsed = parseSkill(content);
129
+ assert.strictEqual(parsed.signals, null);
130
+ });
131
+
132
+ test('real cook skill has signals', () => {
133
+ const content = readFileSync(path.join(SKILLS_DIR, 'cook/SKILL.md'), 'utf-8');
134
+ const parsed = parseSkill(content, 'cook/SKILL.md');
135
+ assert.ok(parsed.signals, 'cook should have signals');
136
+ assert.ok(parsed.signals.emit.includes('phase.complete'));
137
+ });
138
+
139
+ test('real test skill has emit and listen', () => {
140
+ const content = readFileSync(path.join(SKILLS_DIR, 'test/SKILL.md'), 'utf-8');
141
+ const parsed = parseSkill(content, 'test/SKILL.md');
142
+ assert.ok(parsed.signals, 'test should have signals');
143
+ assert.ok(parsed.signals.emit.includes('tests.passed'));
144
+ assert.ok(parsed.signals.emit.includes('tests.failed'));
145
+ assert.ok(parsed.signals.listen.includes('code.changed'));
146
+ });
147
+ });
@@ -138,11 +138,7 @@ describe('buildAll with scripts', () => {
138
138
 
139
139
  // Minimal .claude-plugin for openclaw
140
140
  await mkdir(path.join(tmp, '.claude-plugin'), { recursive: true });
141
- await writeFile(
142
- path.join(tmp, '.claude-plugin', 'plugin.json'),
143
- JSON.stringify({ version: '0.0.1' }),
144
- 'utf-8',
145
- );
141
+ await writeFile(path.join(tmp, '.claude-plugin', 'plugin.json'), JSON.stringify({ version: '0.0.1' }), 'utf-8');
146
142
 
147
143
  return tmp;
148
144
  }
@@ -168,7 +164,10 @@ describe('buildAll with scripts', () => {
168
164
  assert.ok(mdc.includes('.cursor/rules/rune-test-slide-scripts/build-deck.js'), 'resolved path missing');
169
165
 
170
166
  // Plain skill: no scripts dir created
171
- assert.ok(!existsSync(path.join(outputRoot, adapter.outputDir, 'rune-test-plain-scripts')), 'plain skill should not have scripts dir');
167
+ assert.ok(
168
+ !existsSync(path.join(outputRoot, adapter.outputDir, 'rune-test-plain-scripts')),
169
+ 'plain skill should not have scripts dir',
170
+ );
172
171
  } finally {
173
172
  await rm(tmp, { recursive: true, force: true });
174
173
  }
@@ -189,10 +188,7 @@ describe('buildAll with scripts', () => {
189
188
  assert.ok(existsSync(path.join(scriptsOut, 'helper.py')), 'helper.py missing');
190
189
 
191
190
  // Placeholder resolved
192
- const md = await readFile(
193
- path.join(outputRoot, adapter.outputDir, 'rune-test-slide', 'SKILL.md'),
194
- 'utf-8',
195
- );
191
+ const md = await readFile(path.join(outputRoot, adapter.outputDir, 'rune-test-slide', 'SKILL.md'), 'utf-8');
196
192
  assert.ok(!md.includes('{scripts_dir}'), 'placeholder should be resolved');
197
193
  assert.ok(md.includes('.codex/skills/rune-test-slide/scripts/build-deck.js'), 'resolved path missing');
198
194
  } finally {
@@ -254,7 +250,10 @@ describe('buildAll with scripts', () => {
254
250
  await buildAll({ runeRoot: tmp, outputRoot, adapter });
255
251
 
256
252
  const original = await readFile(path.join(tmp, 'skills', 'test-slide', 'scripts', 'build-deck.js'), 'utf-8');
257
- const copied = await readFile(path.join(outputRoot, adapter.outputDir, 'rune-test-slide-scripts', 'build-deck.js'), 'utf-8');
253
+ const copied = await readFile(
254
+ path.join(outputRoot, adapter.outputDir, 'rune-test-slide-scripts', 'build-deck.js'),
255
+ 'utf-8',
256
+ );
258
257
  assert.strictEqual(copied, original, 'script content should be identical');
259
258
  } finally {
260
259
  await rm(tmp, { recursive: true, force: true });