@rune-kit/rune 2.6.0 → 2.8.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 (42) hide show
  1. package/README.md +22 -6
  2. package/compiler/__tests__/executive-dashboards.test.js +285 -0
  3. package/compiler/__tests__/inject.test.js +128 -0
  4. package/compiler/__tests__/orchestrators.test.js +151 -0
  5. package/compiler/__tests__/org-templates.test.js +447 -0
  6. package/compiler/__tests__/parser.test.js +201 -147
  7. package/compiler/__tests__/skill-index.test.js +218 -218
  8. package/compiler/__tests__/status.test.js +336 -0
  9. package/compiler/__tests__/templates.test.js +245 -0
  10. package/compiler/__tests__/visualizer.test.js +325 -0
  11. package/compiler/adapters/antigravity.js +71 -71
  12. package/compiler/bin/rune.js +444 -355
  13. package/compiler/doctor.js +272 -1
  14. package/compiler/emitter.js +939 -678
  15. package/compiler/parser.js +498 -267
  16. package/compiler/status.js +342 -0
  17. package/compiler/visualizer.js +622 -0
  18. package/package.json +1 -1
  19. package/skills/autopsy/SKILL.md +48 -1
  20. package/skills/completion-gate/SKILL.md +51 -3
  21. package/skills/context-engine/SKILL.md +141 -2
  22. package/skills/cook/SKILL.md +177 -4
  23. package/skills/debug/SKILL.md +50 -1
  24. package/skills/docs/SKILL.md +28 -3
  25. package/skills/fix/SKILL.md +26 -1
  26. package/skills/mcp-builder/SKILL.md +53 -1
  27. package/skills/onboard/SKILL.md +51 -1
  28. package/skills/perf/SKILL.md +34 -1
  29. package/skills/plan/SKILL.md +27 -1
  30. package/skills/preflight/SKILL.md +35 -1
  31. package/skills/research/SKILL.md +24 -1
  32. package/skills/retro/SKILL.md +95 -1
  33. package/skills/review/SKILL.md +45 -1
  34. package/skills/scope-guard/SKILL.md +1 -0
  35. package/skills/scout/SKILL.md +22 -1
  36. package/skills/sentinel/SKILL.md +35 -1
  37. package/skills/sentinel/references/policy-driven-constraints.md +424 -0
  38. package/skills/sentinel-env/SKILL.md +0 -2
  39. package/skills/session-bridge/SKILL.md +57 -2
  40. package/skills/session-bridge/references/evolutionary-memory-patterns.md +312 -0
  41. package/skills/team/SKILL.md +15 -1
  42. package/skills/verification/SKILL.md +0 -2
@@ -1,218 +1,218 @@
1
- /**
2
- * Skill Index Generation Tests
3
- *
4
- * Tests that buildAll generates a valid skill-index.json with:
5
- * - Intent patterns mapped to skills
6
- * - Mesh-aware chain prediction from connections
7
- * - Complete skill graph
8
- */
9
-
10
- import assert from 'node:assert';
11
- import { existsSync } from 'node:fs';
12
- import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
13
- import { tmpdir } from 'node:os';
14
- import path from 'node:path';
15
- import { describe, test } from 'node:test';
16
- import { fileURLToPath } from 'node:url';
17
- import { getAdapter } from '../adapters/index.js';
18
- import { buildAll } from '../emitter.js';
19
-
20
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
21
- const RUNE_ROOT = path.resolve(__dirname, '../..');
22
-
23
- describe('skill-index.json generation', () => {
24
- test('buildAll emits skill-index.json with correct structure', async () => {
25
- const tmp = path.join(tmpdir(), `rune-idx-test-${Date.now()}`);
26
- try {
27
- const adapter = getAdapter('cursor');
28
- await buildAll({ runeRoot: RUNE_ROOT, outputRoot: tmp, adapter });
29
-
30
- const indexPath = path.join(tmp, adapter.outputDir, 'skill-index.json');
31
- assert.ok(existsSync(indexPath), 'skill-index.json not found in output');
32
-
33
- const index = JSON.parse(await readFile(indexPath, 'utf-8'));
34
-
35
- // Structure checks
36
- assert.strictEqual(index.version, 2);
37
- assert.ok(index.generated, 'missing generated timestamp');
38
- assert.ok(index.skillCount >= 50, `too few skills: ${index.skillCount}`);
39
- assert.ok(typeof index.skills === 'object', 'missing skills object');
40
- assert.ok(typeof index.graph === 'object', 'missing graph object');
41
- assert.ok(typeof index.signals === 'object', 'missing signals object');
42
- assert.ok(typeof index.intents === 'object', 'missing intents object');
43
- } finally {
44
- await rm(tmp, { recursive: true, force: true });
45
- }
46
- });
47
-
48
- test('skill-index contains intent patterns with chains', async () => {
49
- const tmp = path.join(tmpdir(), `rune-idx-test-${Date.now()}`);
50
- try {
51
- const adapter = getAdapter('cursor');
52
- await buildAll({ runeRoot: RUNE_ROOT, outputRoot: tmp, adapter });
53
-
54
- const index = JSON.parse(await readFile(path.join(tmp, adapter.outputDir, 'skill-index.json'), 'utf-8'));
55
-
56
- // cook intent should exist with keywords and chain
57
- assert.ok(index.intents.cook, 'missing cook intent');
58
- assert.ok(Array.isArray(index.intents.cook.keywords), 'cook keywords not array');
59
- assert.ok(index.intents.cook.keywords.includes('implement'), 'cook missing "implement" keyword');
60
- assert.ok(Array.isArray(index.intents.cook.chain), 'cook chain not array');
61
- assert.strictEqual(index.intents.cook.chain[0], 'cook', 'cook chain should start with cook');
62
- assert.ok(index.intents.cook.chain.length > 1, 'cook chain should have connected skills');
63
-
64
- // debug intent
65
- assert.ok(index.intents.debug, 'missing debug intent');
66
- assert.ok(index.intents.debug.keywords.includes('bug'), 'debug missing "bug" keyword');
67
-
68
- // sentinel intent
69
- assert.ok(index.intents.sentinel, 'missing sentinel intent');
70
- assert.ok(index.intents.sentinel.keywords.includes('security'), 'sentinel missing "security" keyword');
71
- } finally {
72
- await rm(tmp, { recursive: true, force: true });
73
- }
74
- });
75
-
76
- test('skill-index graph has connections from cross-refs', async () => {
77
- const tmp = path.join(tmpdir(), `rune-idx-test-${Date.now()}`);
78
- try {
79
- const adapter = getAdapter('cursor');
80
- await buildAll({ runeRoot: RUNE_ROOT, outputRoot: tmp, adapter });
81
-
82
- const index = JSON.parse(await readFile(path.join(tmp, adapter.outputDir, 'skill-index.json'), 'utf-8'));
83
-
84
- // cook should have outbound connections
85
- assert.ok(index.graph.cook, 'cook not in graph');
86
- assert.ok(index.graph.cook.length > 3, `cook should have many connections, got ${index.graph.cook.length}`);
87
-
88
- // Skills entry should have layer and description
89
- assert.ok(index.skills.cook, 'cook not in skills');
90
- assert.strictEqual(index.skills.cook.layer, 'L1');
91
- assert.ok(index.skills.cook.description.length > 20, 'cook description too short');
92
- } finally {
93
- await rm(tmp, { recursive: true, force: true });
94
- }
95
- });
96
-
97
- test('skill-index contains signal graph', async () => {
98
- const tmp = path.join(tmpdir(), `rune-idx-test-${Date.now()}`);
99
- try {
100
- const adapter = getAdapter('cursor');
101
- await buildAll({ runeRoot: RUNE_ROOT, outputRoot: tmp, adapter });
102
-
103
- const index = JSON.parse(await readFile(path.join(tmp, adapter.outputDir, 'skill-index.json'), 'utf-8'));
104
-
105
- // Signal graph should exist
106
- assert.ok(typeof index.signals === 'object', 'missing signals object');
107
- assert.ok(Object.keys(index.signals).length >= 10, `too few signals: ${Object.keys(index.signals).length}`);
108
-
109
- // code.changed should be a well-connected signal
110
- assert.ok(index.signals['code.changed'], 'missing code.changed signal');
111
- assert.ok(index.signals['code.changed'].emitters.includes('fix'), 'fix should emit code.changed');
112
- assert.ok(index.signals['code.changed'].listeners.length >= 3, 'code.changed should have 3+ listeners');
113
-
114
- // Per-skill signals should be included
115
- assert.ok(index.skills.test.signals, 'test skill should have signals');
116
- assert.ok(index.skills.test.signals.emit.includes('tests.passed'), 'test should emit tests.passed');
117
- assert.ok(index.skills.test.signals.listen.includes('code.changed'), 'test should listen to code.changed');
118
-
119
- // Skills without signals should not have the field
120
- const noSignalSkill = Object.values(index.skills).find((s) => !s.signals);
121
- assert.ok(noSignalSkill, 'at least one skill should have no signals');
122
- } finally {
123
- await rm(tmp, { recursive: true, force: true });
124
- }
125
- });
126
-
127
- test('skill-index signal graph with synthetic skills', async () => {
128
- const tmp = path.join(tmpdir(), `rune-idx-sig-${Date.now()}`);
129
- const skillsDir = path.join(tmp, 'skills');
130
- await mkdir(path.join(skillsDir, 'emitter'), { recursive: true });
131
- await mkdir(path.join(skillsDir, 'listener'), { recursive: true });
132
- await mkdir(path.join(tmp, 'extensions'), { recursive: true });
133
-
134
- await writeFile(
135
- path.join(skillsDir, 'emitter', 'SKILL.md'),
136
- [
137
- '---',
138
- 'name: emitter',
139
- 'description: "Emits signals"',
140
- 'metadata:',
141
- ' layer: L2',
142
- ' emit: data.ready',
143
- '---',
144
- '',
145
- '# emitter',
146
- ].join('\n'),
147
- 'utf-8',
148
- );
149
-
150
- await writeFile(
151
- path.join(skillsDir, 'listener', 'SKILL.md'),
152
- [
153
- '---',
154
- 'name: listener',
155
- 'description: "Listens to signals"',
156
- 'metadata:',
157
- ' layer: L3',
158
- ' listen: data.ready',
159
- '---',
160
- '',
161
- '# listener',
162
- ].join('\n'),
163
- 'utf-8',
164
- );
165
-
166
- try {
167
- const adapter = getAdapter('generic');
168
- await buildAll({ runeRoot: tmp, outputRoot: tmp, adapter });
169
-
170
- const index = JSON.parse(await readFile(path.join(tmp, adapter.outputDir, 'skill-index.json'), 'utf-8'));
171
-
172
- assert.ok(index.signals['data.ready'], 'data.ready signal should exist');
173
- assert.deepStrictEqual(index.signals['data.ready'].emitters, ['emitter']);
174
- assert.deepStrictEqual(index.signals['data.ready'].listeners, ['listener']);
175
- assert.ok(index.skills.emitter.signals);
176
- assert.deepStrictEqual(index.skills.emitter.signals.emit, ['data.ready']);
177
- } finally {
178
- await rm(tmp, { recursive: true, force: true });
179
- }
180
- });
181
-
182
- test('skill-index works with minimal skill tree', async () => {
183
- const tmp = path.join(tmpdir(), `rune-idx-min-${Date.now()}`);
184
- const skillsDir = path.join(tmp, 'skills', 'alpha');
185
- await mkdir(skillsDir, { recursive: true });
186
- await mkdir(path.join(tmp, 'extensions'), { recursive: true });
187
-
188
- await writeFile(
189
- path.join(skillsDir, 'SKILL.md'),
190
- [
191
- '---',
192
- 'name: alpha',
193
- 'description: "Test skill"',
194
- 'metadata:',
195
- ' layer: L3',
196
- ' group: utility',
197
- '---',
198
- '',
199
- '# alpha',
200
- '',
201
- 'Body.',
202
- ].join('\n'),
203
- 'utf-8',
204
- );
205
-
206
- try {
207
- const adapter = getAdapter('generic');
208
- await buildAll({ runeRoot: tmp, outputRoot: tmp, adapter });
209
-
210
- const index = JSON.parse(await readFile(path.join(tmp, adapter.outputDir, 'skill-index.json'), 'utf-8'));
211
- assert.strictEqual(index.skillCount, 1);
212
- assert.ok(index.skills.alpha, 'alpha not in skills');
213
- assert.deepStrictEqual(index.graph.alpha, [], 'alpha should have no connections');
214
- } finally {
215
- await rm(tmp, { recursive: true, force: true });
216
- }
217
- });
218
- });
1
+ /**
2
+ * Skill Index Generation Tests
3
+ *
4
+ * Tests that buildAll generates a valid skill-index.json with:
5
+ * - Intent patterns mapped to skills
6
+ * - Mesh-aware chain prediction from connections
7
+ * - Complete skill graph
8
+ */
9
+
10
+ import assert from 'node:assert';
11
+ import { existsSync } from 'node:fs';
12
+ import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
13
+ import { tmpdir } from 'node:os';
14
+ import path from 'node:path';
15
+ import { describe, test } from 'node:test';
16
+ import { fileURLToPath } from 'node:url';
17
+ import { getAdapter } from '../adapters/index.js';
18
+ import { buildAll } from '../emitter.js';
19
+
20
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
21
+ const RUNE_ROOT = path.resolve(__dirname, '../..');
22
+
23
+ describe('skill-index.json generation', () => {
24
+ test('buildAll emits skill-index.json with correct structure', async () => {
25
+ const tmp = path.join(tmpdir(), `rune-idx-test-${Date.now()}`);
26
+ try {
27
+ const adapter = getAdapter('cursor');
28
+ await buildAll({ runeRoot: RUNE_ROOT, outputRoot: tmp, adapter });
29
+
30
+ const indexPath = path.join(tmp, adapter.outputDir, 'skill-index.json');
31
+ assert.ok(existsSync(indexPath), 'skill-index.json not found in output');
32
+
33
+ const index = JSON.parse(await readFile(indexPath, 'utf-8'));
34
+
35
+ // Structure checks
36
+ assert.strictEqual(index.version, 2);
37
+ assert.ok(index.generated, 'missing generated timestamp');
38
+ assert.ok(index.skillCount >= 50, `too few skills: ${index.skillCount}`);
39
+ assert.ok(typeof index.skills === 'object', 'missing skills object');
40
+ assert.ok(typeof index.graph === 'object', 'missing graph object');
41
+ assert.ok(typeof index.signals === 'object', 'missing signals object');
42
+ assert.ok(typeof index.intents === 'object', 'missing intents object');
43
+ } finally {
44
+ await rm(tmp, { recursive: true, force: true });
45
+ }
46
+ });
47
+
48
+ test('skill-index contains intent patterns with chains', async () => {
49
+ const tmp = path.join(tmpdir(), `rune-idx-test-${Date.now()}`);
50
+ try {
51
+ const adapter = getAdapter('cursor');
52
+ await buildAll({ runeRoot: RUNE_ROOT, outputRoot: tmp, adapter });
53
+
54
+ const index = JSON.parse(await readFile(path.join(tmp, adapter.outputDir, 'skill-index.json'), 'utf-8'));
55
+
56
+ // cook intent should exist with keywords and chain
57
+ assert.ok(index.intents.cook, 'missing cook intent');
58
+ assert.ok(Array.isArray(index.intents.cook.keywords), 'cook keywords not array');
59
+ assert.ok(index.intents.cook.keywords.includes('implement'), 'cook missing "implement" keyword');
60
+ assert.ok(Array.isArray(index.intents.cook.chain), 'cook chain not array');
61
+ assert.strictEqual(index.intents.cook.chain[0], 'cook', 'cook chain should start with cook');
62
+ assert.ok(index.intents.cook.chain.length > 1, 'cook chain should have connected skills');
63
+
64
+ // debug intent
65
+ assert.ok(index.intents.debug, 'missing debug intent');
66
+ assert.ok(index.intents.debug.keywords.includes('bug'), 'debug missing "bug" keyword');
67
+
68
+ // sentinel intent
69
+ assert.ok(index.intents.sentinel, 'missing sentinel intent');
70
+ assert.ok(index.intents.sentinel.keywords.includes('security'), 'sentinel missing "security" keyword');
71
+ } finally {
72
+ await rm(tmp, { recursive: true, force: true });
73
+ }
74
+ });
75
+
76
+ test('skill-index graph has connections from cross-refs', async () => {
77
+ const tmp = path.join(tmpdir(), `rune-idx-test-${Date.now()}`);
78
+ try {
79
+ const adapter = getAdapter('cursor');
80
+ await buildAll({ runeRoot: RUNE_ROOT, outputRoot: tmp, adapter });
81
+
82
+ const index = JSON.parse(await readFile(path.join(tmp, adapter.outputDir, 'skill-index.json'), 'utf-8'));
83
+
84
+ // cook should have outbound connections
85
+ assert.ok(index.graph.cook, 'cook not in graph');
86
+ assert.ok(index.graph.cook.length > 3, `cook should have many connections, got ${index.graph.cook.length}`);
87
+
88
+ // Skills entry should have layer and description
89
+ assert.ok(index.skills.cook, 'cook not in skills');
90
+ assert.strictEqual(index.skills.cook.layer, 'L1');
91
+ assert.ok(index.skills.cook.description.length > 20, 'cook description too short');
92
+ } finally {
93
+ await rm(tmp, { recursive: true, force: true });
94
+ }
95
+ });
96
+
97
+ test('skill-index contains signal graph', async () => {
98
+ const tmp = path.join(tmpdir(), `rune-idx-test-${Date.now()}`);
99
+ try {
100
+ const adapter = getAdapter('cursor');
101
+ await buildAll({ runeRoot: RUNE_ROOT, outputRoot: tmp, adapter });
102
+
103
+ const index = JSON.parse(await readFile(path.join(tmp, adapter.outputDir, 'skill-index.json'), 'utf-8'));
104
+
105
+ // Signal graph should exist
106
+ assert.ok(typeof index.signals === 'object', 'missing signals object');
107
+ assert.ok(Object.keys(index.signals).length >= 10, `too few signals: ${Object.keys(index.signals).length}`);
108
+
109
+ // code.changed should be a well-connected signal
110
+ assert.ok(index.signals['code.changed'], 'missing code.changed signal');
111
+ assert.ok(index.signals['code.changed'].emitters.includes('fix'), 'fix should emit code.changed');
112
+ assert.ok(index.signals['code.changed'].listeners.length >= 3, 'code.changed should have 3+ listeners');
113
+
114
+ // Per-skill signals should be included
115
+ assert.ok(index.skills.test.signals, 'test skill should have signals');
116
+ assert.ok(index.skills.test.signals.emit.includes('tests.passed'), 'test should emit tests.passed');
117
+ assert.ok(index.skills.test.signals.listen.includes('code.changed'), 'test should listen to code.changed');
118
+
119
+ // Skills without signals should not have the field
120
+ const noSignalSkill = Object.values(index.skills).find((s) => !s.signals);
121
+ assert.ok(noSignalSkill, 'at least one skill should have no signals');
122
+ } finally {
123
+ await rm(tmp, { recursive: true, force: true });
124
+ }
125
+ });
126
+
127
+ test('skill-index signal graph with synthetic skills', async () => {
128
+ const tmp = path.join(tmpdir(), `rune-idx-sig-${Date.now()}`);
129
+ const skillsDir = path.join(tmp, 'skills');
130
+ await mkdir(path.join(skillsDir, 'emitter'), { recursive: true });
131
+ await mkdir(path.join(skillsDir, 'listener'), { recursive: true });
132
+ await mkdir(path.join(tmp, 'extensions'), { recursive: true });
133
+
134
+ await writeFile(
135
+ path.join(skillsDir, 'emitter', 'SKILL.md'),
136
+ [
137
+ '---',
138
+ 'name: emitter',
139
+ 'description: "Emits signals"',
140
+ 'metadata:',
141
+ ' layer: L2',
142
+ ' emit: data.ready',
143
+ '---',
144
+ '',
145
+ '# emitter',
146
+ ].join('\n'),
147
+ 'utf-8',
148
+ );
149
+
150
+ await writeFile(
151
+ path.join(skillsDir, 'listener', 'SKILL.md'),
152
+ [
153
+ '---',
154
+ 'name: listener',
155
+ 'description: "Listens to signals"',
156
+ 'metadata:',
157
+ ' layer: L3',
158
+ ' listen: data.ready',
159
+ '---',
160
+ '',
161
+ '# listener',
162
+ ].join('\n'),
163
+ 'utf-8',
164
+ );
165
+
166
+ try {
167
+ const adapter = getAdapter('generic');
168
+ await buildAll({ runeRoot: tmp, outputRoot: tmp, adapter });
169
+
170
+ const index = JSON.parse(await readFile(path.join(tmp, adapter.outputDir, 'skill-index.json'), 'utf-8'));
171
+
172
+ assert.ok(index.signals['data.ready'], 'data.ready signal should exist');
173
+ assert.deepStrictEqual(index.signals['data.ready'].emitters, ['emitter']);
174
+ assert.deepStrictEqual(index.signals['data.ready'].listeners, ['listener']);
175
+ assert.ok(index.skills.emitter.signals);
176
+ assert.deepStrictEqual(index.skills.emitter.signals.emit, ['data.ready']);
177
+ } finally {
178
+ await rm(tmp, { recursive: true, force: true });
179
+ }
180
+ });
181
+
182
+ test('skill-index works with minimal skill tree', async () => {
183
+ const tmp = path.join(tmpdir(), `rune-idx-min-${Date.now()}`);
184
+ const skillsDir = path.join(tmp, 'skills', 'alpha');
185
+ await mkdir(skillsDir, { recursive: true });
186
+ await mkdir(path.join(tmp, 'extensions'), { recursive: true });
187
+
188
+ await writeFile(
189
+ path.join(skillsDir, 'SKILL.md'),
190
+ [
191
+ '---',
192
+ 'name: alpha',
193
+ 'description: "Test skill"',
194
+ 'metadata:',
195
+ ' layer: L3',
196
+ ' group: utility',
197
+ '---',
198
+ '',
199
+ '# alpha',
200
+ '',
201
+ 'Body.',
202
+ ].join('\n'),
203
+ 'utf-8',
204
+ );
205
+
206
+ try {
207
+ const adapter = getAdapter('generic');
208
+ await buildAll({ runeRoot: tmp, outputRoot: tmp, adapter });
209
+
210
+ const index = JSON.parse(await readFile(path.join(tmp, adapter.outputDir, 'skill-index.json'), 'utf-8'));
211
+ assert.strictEqual(index.skillCount, 1);
212
+ assert.ok(index.skills.alpha, 'alpha not in skills');
213
+ assert.deepStrictEqual(index.graph.alpha, [], 'alpha should have no connections');
214
+ } finally {
215
+ await rm(tmp, { recursive: true, force: true });
216
+ }
217
+ });
218
+ });