@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.
- package/README.md +86 -17
- package/compiler/__tests__/pack-split.test.js +141 -1
- package/compiler/__tests__/parser.test.js +147 -55
- package/compiler/__tests__/scripts-bundling.test.js +283 -0
- package/compiler/__tests__/skill-index.test.js +218 -0
- package/compiler/__tests__/tier-override.test.js +41 -0
- package/compiler/adapters/antigravity.js +71 -53
- package/compiler/adapters/codex.js +4 -0
- package/compiler/adapters/cursor.js +4 -0
- package/compiler/adapters/generic.js +4 -0
- package/compiler/adapters/openclaw.js +4 -0
- package/compiler/adapters/opencode.js +4 -0
- package/compiler/adapters/windsurf.js +4 -0
- package/compiler/bin/rune.js +355 -355
- package/compiler/doctor.js +11 -1
- package/compiler/emitter.js +678 -386
- package/compiler/parser.js +267 -247
- package/compiler/transforms/scripts-path.js +18 -0
- package/extensions/zalo/PACK.md +20 -1
- package/extensions/zalo/references/conversation-management.md +214 -0
- package/extensions/zalo/references/eval-scenarios.md +157 -0
- package/extensions/zalo/references/listen-mode.md +237 -0
- package/extensions/zalo/references/mcp-production.md +274 -0
- package/extensions/zalo/references/multi-account-proxy.md +224 -0
- package/extensions/zalo/references/vietqr-banking.md +160 -0
- package/hooks/hooks.json +12 -0
- package/hooks/intent-router/index.cjs +108 -0
- package/hooks/pre-tool-guard/index.cjs +177 -68
- package/package.json +63 -64
- package/skills/brainstorm/SKILL.md +2 -0
- package/skills/cook/SKILL.md +661 -648
- package/skills/debug/SKILL.md +394 -392
- package/skills/deploy/SKILL.md +2 -0
- package/skills/fix/SKILL.md +283 -281
- package/skills/marketing/SKILL.md +3 -0
- package/skills/onboard/SKILL.md +7 -0
- package/skills/plan/SKILL.md +344 -342
- package/skills/preflight/SKILL.md +362 -360
- package/skills/review/SKILL.md +491 -489
- package/skills/scout/SKILL.md +1 -0
- package/skills/sentinel/SKILL.md +319 -296
- package/skills/sentinel/references/auth-crypto-reference.md +192 -0
- package/skills/sentinel/references/desktop-security.md +201 -0
- package/skills/sentinel/references/supply-chain.md +160 -0
- package/skills/session-bridge/SKILL.md +1 -0
- package/skills/slides/SKILL.md +142 -0
- package/skills/slides/scripts/build-deck.js +158 -0
- package/skills/team/SKILL.md +1 -0
- package/skills/test/SKILL.md +587 -585
- package/skills/verification/SKILL.md +1 -0
- package/skills/watchdog/SKILL.md +2 -0
- package/docs/ANTIGRAVITY-GAP-ANALYSIS.md +0 -369
- package/docs/ARCHITECTURE.md +0 -332
- package/docs/COMMUNITY-PACKS.md +0 -109
- package/docs/CONTRIBUTING-L4.md +0 -215
- package/docs/CROSS-IDE-ANALYSIS.md +0 -164
- package/docs/EXTENSION-TEMPLATE.md +0 -126
- package/docs/MESH-RULES.md +0 -34
- package/docs/MULTI-PLATFORM.md +0 -804
- package/docs/SKILL-DEPTH-AUDIT.md +0 -191
- package/docs/SKILL-TEMPLATE.md +0 -118
- package/docs/TRADE-MATRIX.md +0 -327
- package/docs/VERSIONING.md +0 -91
- package/docs/VISION.md +0 -263
- package/docs/assets/demo-subtitles.srt +0 -215
- package/docs/assets/end-card.html +0 -276
- package/docs/assets/mesh-diagram.html +0 -654
- package/docs/assets/thumbnail.html +0 -295
- package/docs/guides/cli.md +0 -403
- package/docs/guides/index.html +0 -1450
- package/docs/index.html +0 -1005
- package/docs/references/claudekit-analysis.md +0 -414
- package/docs/references/voltagent-analysis.md +0 -189
- package/docs/script.js +0 -495
- package/docs/skills/index.html +0 -832
- package/docs/style.css +0 -958
- 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
|
-
|
|
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** —
|
|
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
|
-
##
|
|
35
|
+
## Benchmark: With Rune vs Without Rune
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
|
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 |
|
|
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
|
|
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 |
|
|
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 (
|
|
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:
|
|
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
|
-
|
|
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
|
+
});
|