@rune-kit/rune 2.3.3 → 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.
Files changed (77) hide show
  1. package/README.md +86 -17
  2. package/compiler/__tests__/pack-split.test.js +141 -1
  3. package/compiler/__tests__/parser.test.js +147 -55
  4. package/compiler/__tests__/scripts-bundling.test.js +283 -0
  5. package/compiler/__tests__/skill-index.test.js +218 -0
  6. package/compiler/__tests__/tier-override.test.js +41 -0
  7. package/compiler/adapters/antigravity.js +71 -53
  8. package/compiler/adapters/codex.js +4 -0
  9. package/compiler/adapters/cursor.js +4 -0
  10. package/compiler/adapters/generic.js +4 -0
  11. package/compiler/adapters/openclaw.js +4 -0
  12. package/compiler/adapters/opencode.js +4 -0
  13. package/compiler/adapters/windsurf.js +4 -0
  14. package/compiler/bin/rune.js +355 -355
  15. package/compiler/doctor.js +11 -1
  16. package/compiler/emitter.js +678 -386
  17. package/compiler/parser.js +267 -247
  18. package/compiler/transforms/scripts-path.js +18 -0
  19. package/extensions/zalo/PACK.md +20 -1
  20. package/extensions/zalo/references/conversation-management.md +214 -0
  21. package/extensions/zalo/references/eval-scenarios.md +157 -0
  22. package/extensions/zalo/references/listen-mode.md +237 -0
  23. package/extensions/zalo/references/mcp-production.md +274 -0
  24. package/extensions/zalo/references/multi-account-proxy.md +224 -0
  25. package/extensions/zalo/references/vietqr-banking.md +160 -0
  26. package/hooks/hooks.json +12 -0
  27. package/hooks/intent-router/index.cjs +108 -0
  28. package/hooks/pre-tool-guard/index.cjs +177 -68
  29. package/package.json +63 -64
  30. package/skills/brainstorm/SKILL.md +2 -0
  31. package/skills/cook/SKILL.md +661 -648
  32. package/skills/debug/SKILL.md +394 -392
  33. package/skills/deploy/SKILL.md +2 -0
  34. package/skills/fix/SKILL.md +283 -281
  35. package/skills/marketing/SKILL.md +3 -0
  36. package/skills/onboard/SKILL.md +7 -0
  37. package/skills/plan/SKILL.md +344 -342
  38. package/skills/preflight/SKILL.md +362 -360
  39. package/skills/review/SKILL.md +491 -489
  40. package/skills/scout/SKILL.md +1 -0
  41. package/skills/sentinel/SKILL.md +319 -296
  42. package/skills/sentinel/references/auth-crypto-reference.md +192 -0
  43. package/skills/sentinel/references/desktop-security.md +201 -0
  44. package/skills/sentinel/references/supply-chain.md +160 -0
  45. package/skills/session-bridge/SKILL.md +1 -0
  46. package/skills/slides/SKILL.md +142 -0
  47. package/skills/slides/scripts/build-deck.js +158 -0
  48. package/skills/team/SKILL.md +1 -0
  49. package/skills/test/SKILL.md +587 -585
  50. package/skills/verification/SKILL.md +1 -0
  51. package/skills/watchdog/SKILL.md +2 -0
  52. package/docs/ANTIGRAVITY-GAP-ANALYSIS.md +0 -369
  53. package/docs/ARCHITECTURE.md +0 -332
  54. package/docs/COMMUNITY-PACKS.md +0 -109
  55. package/docs/CONTRIBUTING-L4.md +0 -215
  56. package/docs/CROSS-IDE-ANALYSIS.md +0 -164
  57. package/docs/EXTENSION-TEMPLATE.md +0 -126
  58. package/docs/MESH-RULES.md +0 -34
  59. package/docs/MULTI-PLATFORM.md +0 -804
  60. package/docs/SKILL-DEPTH-AUDIT.md +0 -191
  61. package/docs/SKILL-TEMPLATE.md +0 -118
  62. package/docs/TRADE-MATRIX.md +0 -327
  63. package/docs/VERSIONING.md +0 -91
  64. package/docs/VISION.md +0 -263
  65. package/docs/assets/demo-subtitles.srt +0 -215
  66. package/docs/assets/end-card.html +0 -276
  67. package/docs/assets/mesh-diagram.html +0 -654
  68. package/docs/assets/thumbnail.html +0 -295
  69. package/docs/guides/cli.md +0 -403
  70. package/docs/guides/index.html +0 -1450
  71. package/docs/index.html +0 -1005
  72. package/docs/references/claudekit-analysis.md +0 -414
  73. package/docs/references/voltagent-analysis.md +0 -189
  74. package/docs/script.js +0 -495
  75. package/docs/skills/index.html +0 -832
  76. package/docs/style.css +0 -958
  77. package/docs/video-demo-plan.md +0 -172
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)
@@ -32,19 +32,84 @@ Mesh: A ↔ B ↔ C (B fails = A reaches C via D→E)
32
32
  D ↔ E ↔ F
33
33
  ```
34
34
 
35
- ## What's New (v2.3.3)
35
+ ## Benchmark: With Rune vs Without Rune
36
36
 
37
- - **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
38
- - **11 Core Skill Enrichments** — deviation rules, repair operators, evidence quality gate, decision classification, debug knowledge base, CSO discipline, YAGNI pushback, formal pause/resume
39
- - **Enforcement Upgrade** — Antigravity-level IDE compliance: 5-type Request Classifier, File Ownership Matrix, Self-Verification HARD-GATE, Routing Proof, Clarification Gate, Phase Transition Protocol
40
- - **All 14 Free Packs Deep** — every L4 extension pack now exceeds 500 lines of production-ready patterns and code examples
41
- - **8 Platform Adapters** — Claude Code, Cursor, Windsurf, Google Antigravity, OpenAI Codex, OpenCode, OpenClaw, Generic
42
- - **59 Core Skills** — +4 since v2.1.0: adversary, sentinel-env, chrome-ext pack, context-pack
43
- - **Rune Pro** — 4 premium packs (product, sales, data-science, support) in private repo. $49 lifetime
37
+ We ran 10 standardized coding tasks on Claude Code once **without** Rune (vanilla), once **with** Rune and measured tokens, cost, duration, and correctness.
38
+
39
+ ### Headline Results
40
+
41
+ ```
42
+ Without Rune With Rune Delta
43
+ Avg Tokens: 541,400 454,491 ↓ 16%
44
+ Avg Cost: $0.69 $0.65 ↓ 6%
45
+ Avg Duration: 2.3 min 2.1 min ↓ 9%
46
+ Avg Tool Calls: 14 13 ↓ 7%
47
+ Correctness: 9/10 9/10 =
48
+ ```
49
+
50
+ ### Where Rune Shines: Complex Tasks
51
+
52
+ | Task | Difficulty | Tokens | Cost | Duration | Tools |
53
+ |------|-----------|--------|------|----------|-------|
54
+ | Refactor 450-line component | Medium | **-62%** | **-17%** | **-32%** | **-27%** |
55
+ | Full feature (auth + API + tests) | Complex | **-36%** | **-29%** | **-31%** | **-27%** |
56
+ | Add Zod validation | Easy | -9% | **-28%** | **-32%** | 0% |
57
+ | Dark mode across 6 components | Hard | ~0% | +10% | -7% | -6% |
58
+
59
+ Rune doesn't make Claude smarter — Claude already knows how to code. Rune makes Claude **disciplined**. The more complex the task, the more discipline matters.
60
+
61
+ > _"Without Rune, Claude writes code that works. With Rune, Claude writes code that lasts."_
62
+
63
+ <details>
64
+ <summary>Full 10-task breakdown</summary>
65
+
66
+ | # | Task | Diff | Tokens | Cost | Time | Correct |
67
+ |---|------|------|--------|------|------|---------|
68
+ | 1 | Zod Validation | Easy | -9% | -28% | -32% | ✅ → ✅ |
69
+ | 2 | Fix N+1 Query | Easy | +12% | +25% | +3% | ❌ → ❌ |
70
+ | 3 | Cursor Pagination | Med | +12% | +19% | -9% | ✅ → ✅ |
71
+ | 4 | Security Review | Med | +13% | +32% | +3% | ✅ → ✅ |
72
+ | 5 | Rate Limiting | Med | +12% | +5% | +5% | ✅ → ✅ |
73
+ | 6 | Refactor Component | Med | **-62%** | **-17%** | **-32%** | ✅ → ✅ |
74
+ | 7 | Dark Mode (6 files) | Hard | ~0% | +10% | -7% | ✅ → ✅ |
75
+ | 8 | DB Migration | Hard | +52% | +11% | +49% | ✅ → ✅ |
76
+ | 9 | Memory Leak Debug | Hard | +13% | +28% | -2% | ✅ → ✅ |
77
+ | 10 | Full Auth System | Complex | **-36%** | **-29%** | **-31%** | ✅ → ✅ |
78
+
79
+ _Methodology: Claude Code CLI headless mode (`claude -p --output-format json`), 10 tasks with fixture code, pattern-based correctness evaluation. Source: [`Benchmark/`](Benchmark/)_
80
+
81
+ </details>
82
+
83
+ ---
84
+
85
+ ## What's New (v2.6.0)
86
+
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
+ ```
44
109
 
45
110
  ## What Rune Is (and Isn't)
46
111
 
47
- 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.
48
113
 
49
114
  | | Rune Provides | Claude Code Provides |
50
115
  |---|---|---|
@@ -68,7 +133,7 @@ Rune started as a **Claude Code plugin** and now compiles to **every major AI ID
68
133
  | CI quality gates | `verification` skill: lint + typecheck + tests + build (actual commands, not LLM review) |
69
134
  | Memory / state | `session-bridge` + `journal`: cross-session decisions, conventions, ADRs, module health |
70
135
  | Multi-model strategy | Every skill has assigned model: haiku (scan), sonnet (code), opus (architecture) |
71
- | 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 |
72
137
  | Security scanning | `sentinel`: OWASP patterns, secret scanning, dependency audit. `sast`: static analysis |
73
138
 
74
139
  ## Install
@@ -96,13 +161,13 @@ npx @rune-kit/rune init --platform windsurf
96
161
  npx @rune-kit/rune init --platform antigravity
97
162
  ```
98
163
 
99
- 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.
100
165
 
101
166
  ### Platform Comparison
102
167
 
103
168
  | Feature | Claude Code | Cursor / Windsurf / Others |
104
169
  |---------|-------------|---------------------------|
105
- | Skills available | 59/59 | 59/59 |
170
+ | Skills available | 61/61 | 61/61 |
106
171
  | Mesh connections | 200+ (programmatic) | 200+ (rule references) |
107
172
  | Workflows & HARD-GATEs | Full | Full |
108
173
  | Extension packs | 14 | 14 |
@@ -179,8 +244,9 @@ This compiles all 59 skills into your IDE's rules format. Same knowledge, same w
179
244
  ║ Delivery: deploy │ marketing │ incident │ docs ║
180
245
  ║ Rescue: autopsy │ safeguard │ surgeon ║
181
246
  ║ Security: adversary ║
247
+ ║ Velocity: retro ║
182
248
  ╠══════════════════════════════════════════════════════╣
183
- ║ L3: UTILITIES (26) ║
249
+ ║ L3: UTILITIES (27) ║
184
250
  ║ Stateless, pure capabilities ║
185
251
  ║ ║
186
252
  ║ Knowledge: research │ docs-seeker │ trend-scout ║
@@ -199,6 +265,8 @@ This compiles all 59 skills into your IDE's rules format. Same knowledge, same w
199
265
  ║ Documents: doc-processor ║
200
266
  ║ Security: sentinel-env ║
201
267
  ║ Memory: neural-memory ║
268
+ ║ Packs: context-pack ║
269
+ ║ Slides: slides ║
202
270
  ╠══════════════════════════════════════════════════════╣
203
271
  ║ L4: EXTENSION PACKS (14) ║
204
272
  ║ Domain-specific, install what you need ║
@@ -376,13 +444,14 @@ See [docs/MULTI-PLATFORM.md](docs/MULTI-PLATFORM.md) for the full architecture.
376
444
  ## Numbers
377
445
 
378
446
  ```
379
- Core Skills: 59 (L0: 1 │ L1: 5 │ L2: 28 │ L3: 26)
447
+ Core Skills: 61 (L0: 1 │ L1: 5 │ L2: 28 │ L3: 27)
380
448
  Extension Packs: 14 free + 4 pro + 4 business
381
449
  Mesh Connections: 200+ cross-references
450
+ Mesh Signals: 17 signals across 15 skills (emit/listen graph)
382
451
  Connections/Skill: 3.4 avg
383
452
  Platforms: 8 (Claude Code, Cursor, Windsurf, Antigravity, Codex, OpenCode, OpenClaw, Generic)
384
453
  Compiler: ~1400 LOC (parser + 8 transforms + 8 adapters + CLI)
385
- Quality: 59/59 skills with Output Format, Sharp Edges, Done When, Cost Profile
454
+ Tests: 566 (compiler + signals + hooks + scripts)
386
455
  Pack Depth: 22 packs total (14 free + 4 pro + 4 business, all free packs rated Deep)
387
456
  ```
388
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
+ });