@rune-kit/rune 2.3.0 → 2.3.1
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 +395 -389
- package/compiler/__tests__/tier-override.test.js +158 -0
- package/compiler/adapters/antigravity.js +3 -8
- package/compiler/adapters/codex.js +3 -8
- package/compiler/adapters/cursor.js +3 -8
- package/compiler/adapters/generic.js +3 -8
- package/compiler/adapters/openclaw.js +4 -9
- package/compiler/adapters/opencode.js +3 -8
- package/compiler/adapters/windsurf.js +3 -8
- package/compiler/bin/rune.js +34 -1
- package/compiler/emitter.js +94 -5
- package/compiler/transforms/branding.js +10 -3
- package/docs/ARCHITECTURE.md +3 -3
- package/docs/VISION.md +3 -3
- package/docs/guides/index.html +14 -14
- package/docs/index.html +7 -7
- package/docs/skills/index.html +832 -832
- package/extensions/ai-ml/PACK.md +7 -0
- package/extensions/content/PACK.md +7 -0
- package/extensions/mobile/PACK.md +9 -9
- package/extensions/zalo/PACK.md +9 -0
- package/package.json +2 -2
- package/skills/audit/SKILL.md +526 -529
- package/skills/ba/SKILL.md +349 -351
- package/skills/completion-gate/SKILL.md +260 -263
- package/skills/context-engine/SKILL.md +0 -6
- package/skills/cook/SKILL.md +2 -11
- package/skills/debug/SKILL.md +392 -394
- package/skills/deploy/references/post-deploy-integration.md +192 -0
- package/skills/fix/SKILL.md +281 -282
- package/skills/onboard/SKILL.md +0 -4
- package/skills/plan/references/completeness-scoring.md +0 -1
- package/skills/plan/references/outcome-block.md +0 -1
- package/skills/plan/references/workflow-registry.md +0 -1
- package/skills/preflight/SKILL.md +360 -365
- package/skills/rescue/SKILL.md +1 -0
- package/skills/research/SKILL.md +149 -150
- package/skills/review/SKILL.md +489 -495
- package/skills/sentinel/SKILL.md +0 -11
- package/skills/sentinel/references/destructive-commands.md +0 -1
- package/skills/sentinel/references/skill-content-guard.md +0 -1
- package/skills/session-bridge/SKILL.md +0 -4
- package/skills/skill-router/{SKILL.md → skill.md} +446 -397
- package/skills/team/SKILL.md +1 -2
- package/skills/test/SKILL.md +585 -593
- package/skills/watchdog/references/webhook-health-checks.md +243 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tier Override Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests the tier resolution logic: Business > Pro > Free.
|
|
5
|
+
* When the same normalized pack name exists in multiple tiers,
|
|
6
|
+
* the highest-priority tier wins.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import assert from 'node:assert';
|
|
10
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
11
|
+
import { tmpdir } from 'node:os';
|
|
12
|
+
import path from 'node:path';
|
|
13
|
+
import { describe, test } from 'node:test';
|
|
14
|
+
import { discoverTieredPacks } from '../emitter.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Create a temp directory structure with PACK.md files for testing
|
|
18
|
+
*/
|
|
19
|
+
function createTierFixture() {
|
|
20
|
+
const root = mkdtempSync(path.join(tmpdir(), 'rune-tier-'));
|
|
21
|
+
|
|
22
|
+
const freePacks = path.join(root, 'free');
|
|
23
|
+
const proPacks = path.join(root, 'pro');
|
|
24
|
+
const bizPacks = path.join(root, 'business');
|
|
25
|
+
|
|
26
|
+
// Free: saas, trading, ui
|
|
27
|
+
for (const pack of ['saas', 'trading', 'ui']) {
|
|
28
|
+
const dir = path.join(freePacks, pack);
|
|
29
|
+
mkdirSync(dir, { recursive: true });
|
|
30
|
+
writeFileSync(path.join(dir, 'PACK.md'), `---\nname: "@rune/${pack}"\n---\n\nFree ${pack} pack.\n`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Pro: pro-product, pro-saas (overrides free saas)
|
|
34
|
+
for (const pack of ['pro-product', 'pro-saas']) {
|
|
35
|
+
const dir = path.join(proPacks, pack);
|
|
36
|
+
mkdirSync(dir, { recursive: true });
|
|
37
|
+
writeFileSync(path.join(dir, 'PACK.md'), `---\nname: "@rune-pro/${pack}"\n---\n\nPro ${pack} pack.\n`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Business: business-saas (overrides pro-saas AND free saas), business-finance
|
|
41
|
+
for (const pack of ['business-saas', 'business-finance']) {
|
|
42
|
+
const dir = path.join(bizPacks, pack);
|
|
43
|
+
mkdirSync(dir, { recursive: true });
|
|
44
|
+
writeFileSync(path.join(dir, 'PACK.md'), `---\nname: "@rune-biz/${pack}"\n---\n\nBusiness ${pack} pack.\n`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return { root, freePacks, proPacks, bizPacks };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// --- discoverTieredPacks ---
|
|
51
|
+
|
|
52
|
+
describe('discoverTieredPacks', () => {
|
|
53
|
+
test('returns free packs when no tier sources provided', async () => {
|
|
54
|
+
const { freePacks, root } = createTierFixture();
|
|
55
|
+
try {
|
|
56
|
+
const packs = await discoverTieredPacks(freePacks, {});
|
|
57
|
+
assert.strictEqual(packs.length, 3);
|
|
58
|
+
assert.ok(packs.every((p) => p.tier === 'free'));
|
|
59
|
+
const names = packs.map((p) => p.dirName).sort();
|
|
60
|
+
assert.deepStrictEqual(names, ['saas', 'trading', 'ui']);
|
|
61
|
+
} finally {
|
|
62
|
+
rmSync(root, { recursive: true, force: true });
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('pro pack overrides free pack with same normalized name', async () => {
|
|
67
|
+
const { freePacks, proPacks, root } = createTierFixture();
|
|
68
|
+
try {
|
|
69
|
+
const packs = await discoverTieredPacks(freePacks, { pro: proPacks });
|
|
70
|
+
|
|
71
|
+
// "saas" normalized name should come from pro (pro-saas), not free (saas)
|
|
72
|
+
const saasPack = packs.find((p) => p.dirName === 'pro-saas' || p.dirName === 'saas');
|
|
73
|
+
assert.ok(saasPack, 'saas pack should exist');
|
|
74
|
+
assert.strictEqual(saasPack.tier, 'pro');
|
|
75
|
+
assert.strictEqual(saasPack.dirName, 'pro-saas');
|
|
76
|
+
|
|
77
|
+
// pro-product should be added (no free equivalent)
|
|
78
|
+
const productPack = packs.find((p) => p.dirName === 'pro-product');
|
|
79
|
+
assert.ok(productPack, 'pro-product should exist');
|
|
80
|
+
assert.strictEqual(productPack.tier, 'pro');
|
|
81
|
+
|
|
82
|
+
// trading and ui should remain free
|
|
83
|
+
const tradingPack = packs.find((p) => p.dirName === 'trading');
|
|
84
|
+
assert.ok(tradingPack, 'trading should exist');
|
|
85
|
+
assert.strictEqual(tradingPack.tier, 'free');
|
|
86
|
+
} finally {
|
|
87
|
+
rmSync(root, { recursive: true, force: true });
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test('business pack overrides both pro and free', async () => {
|
|
92
|
+
const { freePacks, proPacks, bizPacks, root } = createTierFixture();
|
|
93
|
+
try {
|
|
94
|
+
const packs = await discoverTieredPacks(freePacks, { pro: proPacks, business: bizPacks });
|
|
95
|
+
|
|
96
|
+
// "saas" should come from business (highest tier)
|
|
97
|
+
const saasPack = packs.find((p) => p.dirName.includes('saas'));
|
|
98
|
+
assert.ok(saasPack, 'saas pack should exist');
|
|
99
|
+
assert.strictEqual(saasPack.tier, 'business');
|
|
100
|
+
assert.strictEqual(saasPack.dirName, 'business-saas');
|
|
101
|
+
|
|
102
|
+
// business-finance should be added
|
|
103
|
+
const financePack = packs.find((p) => p.dirName === 'business-finance');
|
|
104
|
+
assert.ok(financePack, 'business-finance should exist');
|
|
105
|
+
assert.strictEqual(financePack.tier, 'business');
|
|
106
|
+
|
|
107
|
+
// pro-product should still exist (no business override)
|
|
108
|
+
const productPack = packs.find((p) => p.dirName === 'pro-product');
|
|
109
|
+
assert.ok(productPack, 'pro-product should exist');
|
|
110
|
+
assert.strictEqual(productPack.tier, 'pro');
|
|
111
|
+
|
|
112
|
+
// trading and ui should remain free
|
|
113
|
+
const tradingPack = packs.find((p) => p.dirName === 'trading');
|
|
114
|
+
assert.strictEqual(tradingPack.tier, 'free');
|
|
115
|
+
} finally {
|
|
116
|
+
rmSync(root, { recursive: true, force: true });
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test('handles missing tier directories gracefully', async () => {
|
|
121
|
+
const { freePacks, root } = createTierFixture();
|
|
122
|
+
try {
|
|
123
|
+
const packs = await discoverTieredPacks(freePacks, {
|
|
124
|
+
pro: '/nonexistent/pro/extensions',
|
|
125
|
+
business: '/nonexistent/business/extensions',
|
|
126
|
+
});
|
|
127
|
+
assert.strictEqual(packs.length, 3);
|
|
128
|
+
assert.ok(packs.every((p) => p.tier === 'free'));
|
|
129
|
+
} finally {
|
|
130
|
+
rmSync(root, { recursive: true, force: true });
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test('respects enabledPacks filter across tiers', async () => {
|
|
135
|
+
const { freePacks, proPacks, root } = createTierFixture();
|
|
136
|
+
try {
|
|
137
|
+
const packs = await discoverTieredPacks(freePacks, { pro: proPacks }, ['trading', 'pro-product']);
|
|
138
|
+
|
|
139
|
+
assert.strictEqual(packs.length, 2);
|
|
140
|
+
const names = packs.map((p) => p.dirName).sort();
|
|
141
|
+
assert.deepStrictEqual(names, ['pro-product', 'trading']);
|
|
142
|
+
} finally {
|
|
143
|
+
rmSync(root, { recursive: true, force: true });
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test('output is sorted by dirName for deterministic builds', async () => {
|
|
148
|
+
const { freePacks, proPacks, bizPacks, root } = createTierFixture();
|
|
149
|
+
try {
|
|
150
|
+
const packs = await discoverTieredPacks(freePacks, { pro: proPacks, business: bizPacks });
|
|
151
|
+
const names = packs.map((p) => p.dirName);
|
|
152
|
+
const sorted = [...names].sort();
|
|
153
|
+
assert.deepStrictEqual(names, sorted);
|
|
154
|
+
} finally {
|
|
155
|
+
rmSync(root, { recursive: true, force: true });
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
});
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
* Emits .md rule files for .agent/rules/ directory.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { BRANDING_FOOTER } from '../transforms/branding.js';
|
|
8
|
+
|
|
7
9
|
const TOOL_MAP = {
|
|
8
10
|
Read: 'read the file',
|
|
9
11
|
Write: 'write/create the file',
|
|
@@ -38,14 +40,7 @@ export default {
|
|
|
38
40
|
},
|
|
39
41
|
|
|
40
42
|
generateFooter() {
|
|
41
|
-
return
|
|
42
|
-
'',
|
|
43
|
-
'---',
|
|
44
|
-
'> **Rune Skill Mesh** — 58 skills, 200+ connections, 14 extension packs',
|
|
45
|
-
'> Source: https://github.com/rune-kit/rune (MIT)',
|
|
46
|
-
'> **Rune Pro** ($49 lifetime) — product, sales, data-science, support packs → [rune-kit/rune-pro](https://github.com/rune-kit/rune-pro)',
|
|
47
|
-
'> **Rune Business** ($149 lifetime) — finance, legal, HR, enterprise-search packs → [rune-kit/rune-business](https://github.com/rune-kit/rune-business)',
|
|
48
|
-
].join('\n');
|
|
43
|
+
return BRANDING_FOOTER;
|
|
49
44
|
},
|
|
50
45
|
|
|
51
46
|
transformSubagentInstruction(text) {
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
* Codex skill format: .codex/skills/{name}/SKILL.md
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
+
import { BRANDING_FOOTER } from '../transforms/branding.js';
|
|
14
|
+
|
|
13
15
|
const TOOL_MAP = {
|
|
14
16
|
Read: 'read the file',
|
|
15
17
|
Write: 'write/create the file',
|
|
@@ -49,14 +51,7 @@ export default {
|
|
|
49
51
|
},
|
|
50
52
|
|
|
51
53
|
generateFooter() {
|
|
52
|
-
return
|
|
53
|
-
'',
|
|
54
|
-
'---',
|
|
55
|
-
'> **Rune Skill Mesh** — 58 skills, 200+ connections, 14 extension packs',
|
|
56
|
-
'> Source: https://github.com/rune-kit/rune (MIT)',
|
|
57
|
-
'> **Rune Pro** ($49 lifetime) — product, sales, data-science, support packs → [rune-kit/rune-pro](https://github.com/rune-kit/rune-pro)',
|
|
58
|
-
'> **Rune Business** ($149 lifetime) — finance, legal, HR, enterprise-search packs → [rune-kit/rune-business](https://github.com/rune-kit/rune-business)',
|
|
59
|
-
].join('\n');
|
|
54
|
+
return BRANDING_FOOTER;
|
|
60
55
|
},
|
|
61
56
|
|
|
62
57
|
transformSubagentInstruction(text) {
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* Uses @file references for cross-skill mesh.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { BRANDING_FOOTER } from '../transforms/branding.js';
|
|
9
|
+
|
|
8
10
|
const TOOL_MAP = {
|
|
9
11
|
Read: 'read the file',
|
|
10
12
|
Write: 'write/create the file',
|
|
@@ -46,14 +48,7 @@ export default {
|
|
|
46
48
|
},
|
|
47
49
|
|
|
48
50
|
generateFooter() {
|
|
49
|
-
return
|
|
50
|
-
'',
|
|
51
|
-
'---',
|
|
52
|
-
'> **Rune Skill Mesh** — 58 skills, 200+ connections, 14 extension packs',
|
|
53
|
-
'> Source: https://github.com/rune-kit/rune (MIT)',
|
|
54
|
-
'> **Rune Pro** ($49 lifetime) — product, sales, data-science, support packs → [rune-kit/rune-pro](https://github.com/rune-kit/rune-pro)',
|
|
55
|
-
'> **Rune Business** ($149 lifetime) — finance, legal, HR, enterprise-search packs → [rune-kit/rune-business](https://github.com/rune-kit/rune-business)',
|
|
56
|
-
].join('\n');
|
|
51
|
+
return BRANDING_FOOTER;
|
|
57
52
|
},
|
|
58
53
|
|
|
59
54
|
transformSubagentInstruction(text) {
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* Uses the most portable format possible.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { BRANDING_FOOTER } from '../transforms/branding.js';
|
|
9
|
+
|
|
8
10
|
const TOOL_MAP = {
|
|
9
11
|
Read: 'read the file',
|
|
10
12
|
Write: 'write/create the file',
|
|
@@ -39,14 +41,7 @@ export default {
|
|
|
39
41
|
},
|
|
40
42
|
|
|
41
43
|
generateFooter() {
|
|
42
|
-
return
|
|
43
|
-
'',
|
|
44
|
-
'---',
|
|
45
|
-
'> **Rune Skill Mesh** — 58 skills, 200+ connections, 14 extension packs',
|
|
46
|
-
'> Source: https://github.com/rune-kit/rune (MIT)',
|
|
47
|
-
'> **Rune Pro** ($49 lifetime) — product, sales, data-science, support packs → [rune-kit/rune-pro](https://github.com/rune-kit/rune-pro)',
|
|
48
|
-
'> **Rune Business** ($149 lifetime) — finance, legal, HR, enterprise-search packs → [rune-kit/rune-business](https://github.com/rune-kit/rune-business)',
|
|
49
|
-
].join('\n');
|
|
44
|
+
return BRANDING_FOOTER;
|
|
50
45
|
},
|
|
51
46
|
|
|
52
47
|
transformSubagentInstruction(text) {
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
* Follows the NeuralMemory OpenClaw plugin pattern.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
import { BRANDING_FOOTER } from '../transforms/branding.js';
|
|
13
|
+
|
|
12
14
|
const TOOL_MAP = {
|
|
13
15
|
Read: 'read_file',
|
|
14
16
|
Write: 'write_file',
|
|
@@ -43,14 +45,7 @@ export default {
|
|
|
43
45
|
},
|
|
44
46
|
|
|
45
47
|
generateFooter() {
|
|
46
|
-
return
|
|
47
|
-
'',
|
|
48
|
-
'---',
|
|
49
|
-
'> **Rune Skill Mesh** — 58 skills, 200+ connections, 14 extension packs',
|
|
50
|
-
'> Source: https://github.com/rune-kit/rune (MIT)',
|
|
51
|
-
'> **Rune Pro** ($49 lifetime) — product, sales, data-science, support packs → [rune-kit/rune-pro](https://github.com/rune-kit/rune-pro)',
|
|
52
|
-
'> **Rune Business** ($149 lifetime) — finance, legal, HR, enterprise-search packs → [rune-kit/rune-business](https://github.com/rune-kit/rune-business)',
|
|
53
|
-
].join('\n');
|
|
48
|
+
return BRANDING_FOOTER;
|
|
54
49
|
},
|
|
55
50
|
|
|
56
51
|
transformSubagentInstruction(text) {
|
|
@@ -74,7 +69,7 @@ export default {
|
|
|
74
69
|
name: 'Rune',
|
|
75
70
|
kind: 'skills',
|
|
76
71
|
description:
|
|
77
|
-
'
|
|
72
|
+
'59-skill mesh for AI coding assistants. Routes all code tasks through specialized skills. 200+ connections, 14 extension packs.',
|
|
78
73
|
version: pluginJson.version || '0.0.0',
|
|
79
74
|
skills: ['./skills'],
|
|
80
75
|
configSchema: {
|
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
* @see https://opencode.ai/docs/agents/
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
+
import { BRANDING_FOOTER } from '../transforms/branding.js';
|
|
22
|
+
|
|
21
23
|
const TOOL_MAP = {
|
|
22
24
|
Read: 'read the file',
|
|
23
25
|
Write: 'write/create the file',
|
|
@@ -57,14 +59,7 @@ export default {
|
|
|
57
59
|
},
|
|
58
60
|
|
|
59
61
|
generateFooter() {
|
|
60
|
-
return
|
|
61
|
-
'',
|
|
62
|
-
'---',
|
|
63
|
-
'> **Rune Skill Mesh** — 58 skills, 200+ connections, 14 extension packs',
|
|
64
|
-
'> Source: https://github.com/rune-kit/rune (MIT)',
|
|
65
|
-
'> **Rune Pro** ($49 lifetime) — product, sales, data-science, support packs → [rune-kit/rune-pro](https://github.com/rune-kit/rune-pro)',
|
|
66
|
-
'> **Rune Business** ($149 lifetime) — finance, legal, HR, enterprise-search packs → [rune-kit/rune-business](https://github.com/rune-kit/rune-business)',
|
|
67
|
-
].join('\n');
|
|
62
|
+
return BRANDING_FOOTER;
|
|
68
63
|
},
|
|
69
64
|
|
|
70
65
|
transformSubagentInstruction(text) {
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* Uses prose references for cross-skill mesh (no @file support).
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { BRANDING_FOOTER } from '../transforms/branding.js';
|
|
9
|
+
|
|
8
10
|
const TOOL_MAP = {
|
|
9
11
|
Read: 'read the file',
|
|
10
12
|
Write: 'write/create the file',
|
|
@@ -39,14 +41,7 @@ export default {
|
|
|
39
41
|
},
|
|
40
42
|
|
|
41
43
|
generateFooter() {
|
|
42
|
-
return
|
|
43
|
-
'',
|
|
44
|
-
'---',
|
|
45
|
-
'> **Rune Skill Mesh** — 58 skills, 200+ connections, 14 extension packs',
|
|
46
|
-
'> Source: https://github.com/rune-kit/rune (MIT)',
|
|
47
|
-
'> **Rune Pro** ($49 lifetime) — product, sales, data-science, support packs → [rune-kit/rune-pro](https://github.com/rune-kit/rune-pro)',
|
|
48
|
-
'> **Rune Business** ($149 lifetime) — finance, legal, HR, enterprise-search packs → [rune-kit/rune-business](https://github.com/rune-kit/rune-business)',
|
|
49
|
-
].join('\n');
|
|
44
|
+
return BRANDING_FOOTER;
|
|
50
45
|
},
|
|
51
46
|
|
|
52
47
|
transformSubagentInstruction(text) {
|
package/compiler/bin/rune.js
CHANGED
|
@@ -65,6 +65,28 @@ async function prompt(question) {
|
|
|
65
65
|
});
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Resolve tier source paths from config.
|
|
70
|
+
* Paths can be absolute or relative to projectRoot.
|
|
71
|
+
*
|
|
72
|
+
* Config format:
|
|
73
|
+
* "tiers": { "pro": "../Pro/extensions", "business": "../Business/extensions" }
|
|
74
|
+
*
|
|
75
|
+
* @param {Object} tiers - tier config object
|
|
76
|
+
* @param {string} projectRoot - base for relative paths
|
|
77
|
+
* @returns {Object} resolved { pro?: string, business?: string }
|
|
78
|
+
*/
|
|
79
|
+
function resolveTierSources(tiers, projectRoot) {
|
|
80
|
+
if (!tiers) return {};
|
|
81
|
+
const resolved = {};
|
|
82
|
+
for (const tier of ['pro', 'business']) {
|
|
83
|
+
if (tiers[tier]) {
|
|
84
|
+
resolved[tier] = path.resolve(projectRoot, tiers[tier]);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return resolved;
|
|
88
|
+
}
|
|
89
|
+
|
|
68
90
|
// ─── Commands ───
|
|
69
91
|
|
|
70
92
|
async function cmdInit(projectRoot, args) {
|
|
@@ -120,12 +142,14 @@ async function cmdInit(projectRoot, args) {
|
|
|
120
142
|
|
|
121
143
|
// Auto-build
|
|
122
144
|
const adapter = getAdapter(platform);
|
|
145
|
+
const tierSources = resolveTierSources(config.tiers, projectRoot);
|
|
123
146
|
const stats = await buildAll({
|
|
124
147
|
runeRoot: RUNE_ROOT,
|
|
125
148
|
outputRoot: projectRoot,
|
|
126
149
|
adapter,
|
|
127
150
|
disabledSkills: config.skills.disabled,
|
|
128
151
|
enabledPacks: config.extensions.enabled,
|
|
152
|
+
tierSources,
|
|
129
153
|
});
|
|
130
154
|
|
|
131
155
|
logStep('✓', `Built ${stats.skillCount} skills + ${stats.packCount} extensions to ${adapter.outputDir}/`);
|
|
@@ -140,7 +164,7 @@ async function cmdInit(projectRoot, args) {
|
|
|
140
164
|
log(' Next steps:');
|
|
141
165
|
log(' 1. /rune onboard Generate project context (CLAUDE.md + .rune/)');
|
|
142
166
|
log(' 2. /rune cook "..." Build a feature (full TDD cycle)');
|
|
143
|
-
log(' 3. /rune help See all
|
|
167
|
+
log(' 3. /rune help See all 59 skills');
|
|
144
168
|
log('');
|
|
145
169
|
}
|
|
146
170
|
|
|
@@ -163,6 +187,7 @@ async function cmdBuild(projectRoot, args) {
|
|
|
163
187
|
const outputRoot = typeof args.output === 'string' ? args.output : projectRoot;
|
|
164
188
|
const disabledSkills = config?.skills?.disabled || [];
|
|
165
189
|
const enabledPacks = config?.extensions?.enabled || null;
|
|
190
|
+
const tierSources = resolveTierSources(config?.tiers, projectRoot);
|
|
166
191
|
|
|
167
192
|
log('');
|
|
168
193
|
log(` [parse] Discovering skills...`);
|
|
@@ -173,6 +198,7 @@ async function cmdBuild(projectRoot, args) {
|
|
|
173
198
|
adapter,
|
|
174
199
|
disabledSkills,
|
|
175
200
|
enabledPacks,
|
|
201
|
+
tierSources,
|
|
176
202
|
});
|
|
177
203
|
|
|
178
204
|
log(` [transform] Platform: ${stats.platform}`);
|
|
@@ -180,6 +206,13 @@ async function cmdBuild(projectRoot, args) {
|
|
|
180
206
|
log(` [transform] Resolved ${stats.toolRefsResolved} tool-name references`);
|
|
181
207
|
log(` [emit] ${stats.skillCount} skills + ${stats.packCount} extensions`);
|
|
182
208
|
|
|
209
|
+
if (stats.tierOverrides?.length > 0) {
|
|
210
|
+
log(` [tier] ${stats.tierOverrides.length} pack(s) resolved from higher tiers:`);
|
|
211
|
+
for (const override of stats.tierOverrides) {
|
|
212
|
+
log(` → ${override.pack} (${override.tier})`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
183
216
|
if (stats.skipped.length > 0) {
|
|
184
217
|
log(` [skip] ${stats.skipped.length} disabled: ${stats.skipped.join(', ')}`);
|
|
185
218
|
}
|
package/compiler/emitter.js
CHANGED
|
@@ -59,6 +59,71 @@ async function discoverPacks(extensionsDir, enabledPacks = null) {
|
|
|
59
59
|
return paths.sort();
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Tier priority: higher number = higher priority (wins override)
|
|
64
|
+
*/
|
|
65
|
+
const TIER_PRIORITY = { free: 0, pro: 1, business: 2 };
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Normalize pack name for tier comparison.
|
|
69
|
+
* Strips tier prefixes (pro-, business-) so packs can be compared across tiers.
|
|
70
|
+
* e.g. "pro-product" → "product", "saas" → "saas"
|
|
71
|
+
*/
|
|
72
|
+
function normalizePackName(dirName) {
|
|
73
|
+
return dirName.replace(/^(pro|business)-/, '');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Discover packs across multiple tier sources and resolve overrides.
|
|
78
|
+
* Business > Pro > Free: if the same normalized pack name exists in multiple tiers,
|
|
79
|
+
* the highest-priority tier wins.
|
|
80
|
+
*
|
|
81
|
+
* @param {string} freeExtDir - path to free extensions/ directory
|
|
82
|
+
* @param {Object<string, string>} [tierSources] - { pro: "/path/to/pro/extensions", business: "/path/to/business/extensions" }
|
|
83
|
+
* @param {string[]} [enabledPacks] - list of enabled pack names (null = all)
|
|
84
|
+
* @returns {Promise<Array<{path: string, tier: string, dirName: string}>>} resolved pack entries
|
|
85
|
+
*/
|
|
86
|
+
export async function discoverTieredPacks(freeExtDir, tierSources = {}, enabledPacks = null) {
|
|
87
|
+
// Collect all packs with their tier info: Map<normalizedName, {path, tier, priority, dirName}>
|
|
88
|
+
const packMap = new Map();
|
|
89
|
+
|
|
90
|
+
// Helper: scan one extensions directory for packs
|
|
91
|
+
async function scanDir(extDir, tier) {
|
|
92
|
+
if (!existsSync(extDir)) return;
|
|
93
|
+
const entries = await readdir(extDir, { withFileTypes: true });
|
|
94
|
+
|
|
95
|
+
for (const entry of entries) {
|
|
96
|
+
if (!entry.isDirectory()) continue;
|
|
97
|
+
if (enabledPacks && !enabledPacks.includes(entry.name) && !enabledPacks.includes(`@rune/${entry.name}`)) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const packFile = path.join(extDir, entry.name, 'PACK.md');
|
|
101
|
+
if (!existsSync(packFile)) continue;
|
|
102
|
+
|
|
103
|
+
const normalized = normalizePackName(entry.name);
|
|
104
|
+
const priority = TIER_PRIORITY[tier] ?? 0;
|
|
105
|
+
const existing = packMap.get(normalized);
|
|
106
|
+
|
|
107
|
+
// Higher priority tier wins
|
|
108
|
+
if (!existing || priority > existing.priority) {
|
|
109
|
+
packMap.set(normalized, { path: packFile, tier, priority, dirName: entry.name });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Scan free first (lowest priority), then pro, then business
|
|
115
|
+
await scanDir(freeExtDir, 'free');
|
|
116
|
+
if (tierSources.pro) {
|
|
117
|
+
await scanDir(tierSources.pro, 'pro');
|
|
118
|
+
}
|
|
119
|
+
if (tierSources.business) {
|
|
120
|
+
await scanDir(tierSources.business, 'business');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Return sorted by dirName for deterministic output
|
|
124
|
+
return [...packMap.values()].sort((a, b) => a.dirName.localeCompare(b.dirName));
|
|
125
|
+
}
|
|
126
|
+
|
|
62
127
|
/**
|
|
63
128
|
* Generate output filename for a skill
|
|
64
129
|
*/
|
|
@@ -75,9 +140,17 @@ function outputFileName(skillName, adapter) {
|
|
|
75
140
|
* @param {object} options.adapter - platform adapter
|
|
76
141
|
* @param {string[]} [options.disabledSkills] - skills to skip
|
|
77
142
|
* @param {string[]} [options.enabledPacks] - extension packs to include (null = all)
|
|
143
|
+
* @param {Object<string, string>} [options.tierSources] - tier extension dirs { pro: "path", business: "path" }
|
|
78
144
|
* @returns {Promise<object>} build result stats
|
|
79
145
|
*/
|
|
80
|
-
export async function buildAll({
|
|
146
|
+
export async function buildAll({
|
|
147
|
+
runeRoot,
|
|
148
|
+
outputRoot,
|
|
149
|
+
adapter,
|
|
150
|
+
disabledSkills = [],
|
|
151
|
+
enabledPacks = null,
|
|
152
|
+
tierSources = {},
|
|
153
|
+
}) {
|
|
81
154
|
// Claude Code = passthrough, no build needed
|
|
82
155
|
if (adapter.name === 'claude') {
|
|
83
156
|
return {
|
|
@@ -97,7 +170,16 @@ export async function buildAll({ runeRoot, outputRoot, adapter, disabledSkills =
|
|
|
97
170
|
await mkdir(outputDir, { recursive: true });
|
|
98
171
|
|
|
99
172
|
const skillPaths = await discoverSkills(skillsDir);
|
|
100
|
-
|
|
173
|
+
|
|
174
|
+
// Tier-aware pack discovery: if tierSources provided, resolve overrides
|
|
175
|
+
const hasTiers = tierSources && (tierSources.pro || tierSources.business);
|
|
176
|
+
const packEntries = hasTiers
|
|
177
|
+
? await discoverTieredPacks(extensionsDir, tierSources, enabledPacks)
|
|
178
|
+
: (await discoverPacks(extensionsDir, enabledPacks)).map((p) => ({
|
|
179
|
+
path: p,
|
|
180
|
+
tier: 'free',
|
|
181
|
+
dirName: path.basename(path.dirname(p)),
|
|
182
|
+
}));
|
|
101
183
|
|
|
102
184
|
const stats = {
|
|
103
185
|
platform: adapter.name,
|
|
@@ -108,6 +190,7 @@ export async function buildAll({ runeRoot, outputRoot, adapter, disabledSkills =
|
|
|
108
190
|
files: [],
|
|
109
191
|
skipped: [],
|
|
110
192
|
errors: [],
|
|
193
|
+
tierOverrides: [],
|
|
111
194
|
};
|
|
112
195
|
|
|
113
196
|
// Build skills
|
|
@@ -152,14 +235,20 @@ export async function buildAll({ runeRoot, outputRoot, adapter, disabledSkills =
|
|
|
152
235
|
}
|
|
153
236
|
}
|
|
154
237
|
|
|
155
|
-
// Build extension packs
|
|
156
|
-
for (const
|
|
238
|
+
// Build extension packs (tier-aware)
|
|
239
|
+
for (const packEntry of packEntries) {
|
|
157
240
|
try {
|
|
241
|
+
const packPath = packEntry.path;
|
|
158
242
|
const content = await readFile(packPath, 'utf-8');
|
|
159
243
|
const parsed = parsePack(content, packPath);
|
|
160
|
-
const packName =
|
|
244
|
+
const packName = packEntry.dirName;
|
|
161
245
|
const packDir = path.dirname(packPath);
|
|
162
246
|
|
|
247
|
+
// Track tier overrides for reporting
|
|
248
|
+
if (packEntry.tier !== 'free') {
|
|
249
|
+
stats.tierOverrides.push({ pack: packName, tier: packEntry.tier });
|
|
250
|
+
}
|
|
251
|
+
|
|
163
252
|
// For split packs, load individual skill files and concatenate into body
|
|
164
253
|
if (parsed.isSplit && parsed.skillManifest.length > 0) {
|
|
165
254
|
const skillBodies = [];
|
|
@@ -2,17 +2,24 @@
|
|
|
2
2
|
* Branding Transform
|
|
3
3
|
*
|
|
4
4
|
* Adds Rune attribution footer to compiled skill files.
|
|
5
|
+
* All adapters MUST import BRANDING_FOOTER instead of hardcoding stats.
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Single source of truth for branding footer.
|
|
10
|
+
* Update these numbers here — all adapters inherit automatically.
|
|
11
|
+
*/
|
|
12
|
+
export const BRANDING_FOOTER = [
|
|
8
13
|
'',
|
|
9
14
|
'---',
|
|
10
|
-
'> **Rune Skill Mesh** —
|
|
11
|
-
'>
|
|
15
|
+
'> **Rune Skill Mesh** — 59 skills, 200+ connections, 14 extension packs',
|
|
16
|
+
'> [Landing Page](https://rune-kit.github.io/rune) · [Source](https://github.com/rune-kit/rune) (MIT)',
|
|
12
17
|
'> **Rune Pro** ($49 lifetime) — product, sales, data-science, support packs → [rune-kit/rune-pro](https://github.com/rune-kit/rune-pro)',
|
|
13
18
|
'> **Rune Business** ($149 lifetime) — finance, legal, HR, enterprise-search packs → [rune-kit/rune-business](https://github.com/rune-kit/rune-business)',
|
|
14
19
|
].join('\n');
|
|
15
20
|
|
|
21
|
+
const DEFAULT_FOOTER = BRANDING_FOOTER;
|
|
22
|
+
|
|
16
23
|
/**
|
|
17
24
|
* Add branding footer to skill output
|
|
18
25
|
*
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
|-------|------|-------|----------|----------|-------|
|
|
7
7
|
| **L0** | **Router** | **1** | **L1-L3 (routing)** | **Every message** | **Stateless (rule-based)** |
|
|
8
8
|
| L1 | Orchestrators | 5 | L2, L3 | L0, User | Stateful (workflow) |
|
|
9
|
-
| L2 | Workflow Hubs |
|
|
10
|
-
| L3 | Utilities |
|
|
11
|
-
| L4 | Extension Packs | 14 free + 4 pro +
|
|
9
|
+
| L2 | Workflow Hubs | 28 | L2 (cross-hub), L3 | L1, L2 | Stateful (task) |
|
|
10
|
+
| L3 | Utilities | 26 | Nothing (pure)* | L1, L2 | Stateless |
|
|
11
|
+
| L4 | Extension Packs | 14 free + 4 pro + 4 business | L3 | L2 (domain match) | Config-based |
|
|
12
12
|
|
|
13
13
|
### L0 — The Enforcement Layer
|
|
14
14
|
|
package/docs/VISION.md
CHANGED
|
@@ -30,7 +30,7 @@ Rune is a **skill mesh** — not a skill collection, not a pipeline, not an AI a
|
|
|
30
30
|
|
|
31
31
|
**Technical definition:**
|
|
32
32
|
|
|
33
|
-
> **Rune =
|
|
33
|
+
> **Rune = 59 skills × 200+ bidirectional connections × cross-session memory × multi-platform compiler**
|
|
34
34
|
|
|
35
35
|
All three components are equally essential:
|
|
36
36
|
- Remove connections → becomes a collection (The Bloat wins)
|
|
@@ -211,7 +211,7 @@ Rune operates on three time horizons. The roadmap is intentionally non-prescript
|
|
|
211
211
|
|
|
212
212
|
**Implementation**: Zero new L1-L3 skills added. 1 new hook (`metrics-collector`), 3 modified hooks, 4 extended skills (`audit`, `cook`, `skill-router`, `onboard`), 2 new commands (`/rune metrics`, `/rune pack`).
|
|
213
213
|
|
|
214
|
-
**Constraint:** Core mesh expanded to
|
|
214
|
+
**Constraint:** Core mesh expanded to 59 skills (v2.1.0+). Further growth happens in L4 and community packs.
|
|
215
215
|
|
|
216
216
|
---
|
|
217
217
|
|
|
@@ -220,7 +220,7 @@ Rune operates on three time horizons. The roadmap is intentionally non-prescript
|
|
|
220
220
|
*Rune is successful when these are true. Not when the feature list is long.*
|
|
221
221
|
|
|
222
222
|
### Mesh Health
|
|
223
|
-
- **Connection density** ≥ 3.0 connections/skill (currently: 3.4 at 200+ connections /
|
|
223
|
+
- **Connection density** ≥ 3.0 connections/skill (currently: 3.4 at 200+ connections / 59 skills) — do not let this drop below 2.5
|
|
224
224
|
- **Dead nodes** = 0 — every skill has ≥1 inbound and ≥1 outbound connection
|
|
225
225
|
- **Max chain depth used** < 6 in practice (ceiling is 8) — if chains regularly hit 8, the mesh needs restructuring
|
|
226
226
|
- **Bloat Index** = 0.00 — dead nodes / total skills
|