@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
@@ -0,0 +1,342 @@
1
+ /**
2
+ * Status — Project Neofetch
3
+ *
4
+ * Shows a rich boxed dashboard of the current Rune project.
5
+ * Output varies by detected tier: Free, Pro, Business.
6
+ */
7
+
8
+ import { existsSync } from 'node:fs';
9
+ import { readdir, readFile } from 'node:fs/promises';
10
+ import path from 'node:path';
11
+ import { parseSkill } from './parser.js';
12
+
13
+ // ─── Constants ───
14
+
15
+ // ─── Box Drawing ───
16
+
17
+ function box(lines, { title = '', width = 52 } = {}) {
18
+ const inner = width - 2;
19
+ const output = [];
20
+
21
+ const titleStr = title ? ` ${title} ` : '';
22
+ const topFill = inner - titleStr.length - 1;
23
+ output.push(`╭─${titleStr}${'─'.repeat(Math.max(topFill, 0))}╮`);
24
+
25
+ for (const line of lines) {
26
+ const dw = displayWidth(line);
27
+ const pad = inner - dw;
28
+ output.push(`│ ${line}${' '.repeat(Math.max(pad, 0))}│`);
29
+ }
30
+
31
+ output.push(`╰${'─'.repeat(inner + 1)}╯`);
32
+ return output.join('\n');
33
+ }
34
+
35
+ function displayWidth(str) {
36
+ let w = 0;
37
+ for (const ch of str) {
38
+ const code = ch.codePointAt(0);
39
+ // Emoji and wide chars take 2 columns
40
+ if (
41
+ code > 0x1f600 ||
42
+ (code >= 0x2600 && code <= 0x27bf) ||
43
+ (code >= 0x2700 && code <= 0x27bf) ||
44
+ code === 0x2713 ||
45
+ code === 0x2717 ||
46
+ code === 0x2192 ||
47
+ code === 0x2593 ||
48
+ code === 0x2591
49
+ ) {
50
+ w += 1; // These specific Unicode symbols are single-width in most terminals
51
+ } else if (code > 0xffff) {
52
+ w += 2; // Emoji (surrogate pairs) are double-width
53
+ } else {
54
+ w += 1;
55
+ }
56
+ }
57
+ return w;
58
+ }
59
+
60
+ function progressBar(pct, width = 20) {
61
+ const filled = Math.round((pct / 100) * width);
62
+ const empty = width - filled;
63
+ return '▓'.repeat(filled) + '░'.repeat(empty);
64
+ }
65
+
66
+ // ─── Data Collection ───
67
+
68
+ export async function collectStats(runeRoot, tierSources = {}) {
69
+ const skillsDir = path.join(runeRoot, 'skills');
70
+ const extDir = path.join(runeRoot, 'extensions');
71
+
72
+ // Count core skills by layer
73
+ const layers = { L0: 0, L1: 0, L2: 0, L3: 0 };
74
+ const skillNames = [];
75
+ let signalCount = 0;
76
+ const signalMap = { emitters: {}, listeners: {} };
77
+ const parsedSkills = [];
78
+
79
+ if (existsSync(skillsDir)) {
80
+ const entries = await readdir(skillsDir, { withFileTypes: true });
81
+ for (const entry of entries) {
82
+ if (!entry.isDirectory()) continue;
83
+ const skillFile = path.join(skillsDir, entry.name, 'SKILL.md');
84
+ if (!existsSync(skillFile)) continue;
85
+
86
+ const content = await readFile(skillFile, 'utf-8');
87
+ const parsed = parseSkill(content, skillFile);
88
+ parsedSkills.push(parsed);
89
+ skillNames.push(parsed.name);
90
+
91
+ const layer = parsed.layer || 'L3';
92
+ if (layers[layer] !== undefined) layers[layer]++;
93
+
94
+ if (parsed.signals?.emit && parsed.signals?.listen) {
95
+ for (const sig of parsed.signals.emit) {
96
+ if (!signalMap.emitters[sig]) signalMap.emitters[sig] = [];
97
+ signalMap.emitters[sig].push(parsed.name);
98
+ }
99
+ for (const sig of parsed.signals.listen) {
100
+ if (!signalMap.listeners[sig]) signalMap.listeners[sig] = [];
101
+ signalMap.listeners[sig].push(parsed.name);
102
+ }
103
+ }
104
+ }
105
+ const allSignals = new Set([...Object.keys(signalMap.emitters), ...Object.keys(signalMap.listeners)]);
106
+ signalCount = allSignals.size;
107
+ }
108
+
109
+ // Count connections
110
+ let totalConnections = 0;
111
+ for (const skill of parsedSkills) {
112
+ totalConnections += new Set((skill.crossRefs ?? []).map((r) => r.skillName)).size;
113
+ }
114
+ const avgConnections = parsedSkills.length > 0 ? (totalConnections / parsedSkills.length).toFixed(1) : '0';
115
+
116
+ // Count free packs
117
+ const freePacks = [];
118
+ if (existsSync(extDir)) {
119
+ const entries = await readdir(extDir, { withFileTypes: true });
120
+ for (const entry of entries) {
121
+ if (!entry.isDirectory()) continue;
122
+ const packFile = path.join(extDir, entry.name, 'PACK.md');
123
+ if (!existsSync(packFile)) continue;
124
+ freePacks.push(entry.name);
125
+ }
126
+ }
127
+
128
+ // Count Pro packs
129
+ const proPacks = await scanTierPacks(tierSources.pro);
130
+ const bizPacks = await scanTierPacks(tierSources.business);
131
+
132
+ // Detect tier
133
+ const tier = bizPacks.length > 0 ? 'business' : proPacks.length > 0 ? 'pro' : 'free';
134
+
135
+ return {
136
+ tier,
137
+ skillCount: parsedSkills.length,
138
+ layers,
139
+ signalCount,
140
+ signalMap,
141
+ totalConnections,
142
+ avgConnections,
143
+ freePacks,
144
+ proPacks,
145
+ bizPacks,
146
+ parsedSkills,
147
+ };
148
+ }
149
+
150
+ async function scanTierPacks(tierDir) {
151
+ const packs = [];
152
+ if (!tierDir || !existsSync(tierDir)) return packs;
153
+
154
+ const entries = await readdir(tierDir, { withFileTypes: true });
155
+ for (const entry of entries) {
156
+ if (!entry.isDirectory()) continue;
157
+ const packDir = path.join(tierDir, entry.name);
158
+ const packFile = path.join(packDir, 'PACK.md');
159
+ if (!existsSync(packFile)) continue;
160
+
161
+ // Count total lines in pack
162
+ let lines = 0;
163
+ const subEntries = await readdir(packDir, { withFileTypes: true });
164
+ for (const sub of subEntries) {
165
+ if (sub.isFile() && sub.name.endsWith('.md')) {
166
+ const content = await readFile(path.join(packDir, sub.name), 'utf-8');
167
+ lines += content.split('\n').length;
168
+ }
169
+ }
170
+
171
+ packs.push({ name: entry.name, lines });
172
+ }
173
+ return packs;
174
+ }
175
+
176
+ // ─── Rendering ───
177
+
178
+ function tierIcon(tier) {
179
+ if (tier === 'business') return '🏢';
180
+ if (tier === 'pro') return '⚡';
181
+ return '🔮';
182
+ }
183
+
184
+ function tierLabel(tier) {
185
+ if (tier === 'business') return 'Rune Business';
186
+ if (tier === 'pro') return 'Rune Pro';
187
+ return 'Rune';
188
+ }
189
+
190
+ function fmtNum(n) {
191
+ return n.toLocaleString('en-US');
192
+ }
193
+
194
+ export function renderStatus(stats, { version = '', platform = '', projectName = '' } = {}) {
195
+ const lines = [];
196
+ const icon = tierIcon(stats.tier);
197
+ const label = tierLabel(stats.tier);
198
+
199
+ // Header
200
+ lines.push('');
201
+
202
+ // Project info
203
+ if (projectName) lines.push(`Project ${projectName}`);
204
+ if (platform) lines.push(`Platform ${platform}`);
205
+ if (version) lines.push(`Version ${version}`);
206
+ if (projectName || platform || version) lines.push('');
207
+
208
+ // Core stats
209
+ const layerStr = Object.entries(stats.layers)
210
+ .filter(([, v]) => v > 0)
211
+ .map(([k, v]) => `${k}:${v}`)
212
+ .join(' ');
213
+ lines.push(`Skills ${stats.skillCount} core (${layerStr})`);
214
+
215
+ const packParts = [`${stats.freePacks.length} free`];
216
+ if (stats.proPacks.length > 0) packParts.push(`${stats.proPacks.length} pro`);
217
+ if (stats.bizPacks.length > 0) packParts.push(`${stats.bizPacks.length} business`);
218
+ lines.push(`Packs ${packParts.join(' + ')}`);
219
+
220
+ lines.push(`Signals ${stats.signalCount} defined`);
221
+ lines.push(`Mesh ${stats.totalConnections}+ connections (${stats.avgConnections} avg/skill)`);
222
+ lines.push('');
223
+
224
+ // Health bar (based on signals + connections + pack count as simple heuristic)
225
+ const healthScore = computeHealth(stats);
226
+ lines.push(`${progressBar(healthScore)} ${healthScore}% mesh health`);
227
+ lines.push('');
228
+
229
+ // Pro section
230
+ if (stats.proPacks.length > 0) {
231
+ lines.push('Pro Packs');
232
+ for (const pack of stats.proPacks) {
233
+ lines.push(` ✓ ${formatPackName(pack.name, 'pro')} ${fmtNum(pack.lines)} lines`);
234
+ }
235
+ lines.push('');
236
+ }
237
+
238
+ // Business section
239
+ if (stats.bizPacks.length > 0) {
240
+ lines.push('Business Packs');
241
+ for (const pack of stats.bizPacks) {
242
+ lines.push(` ✓ ${formatPackName(pack.name, 'business')} ${fmtNum(pack.lines)} lines`);
243
+ }
244
+ lines.push('');
245
+ }
246
+
247
+ // Top signals (show signal flow)
248
+ const topSignals = getTopSignals(stats.signalMap, 3);
249
+ if (topSignals.length > 0) {
250
+ lines.push('Active Signals');
251
+ for (const sig of topSignals) {
252
+ const emitters = sig.emitters.slice(0, 2).join(', ');
253
+ const listeners = sig.listeners.slice(0, 3).join(', ');
254
+ let sigLine = ` → ${sig.name} (${emitters} → ${listeners})`;
255
+ if (sigLine.length > 64) sigLine = `${sigLine.slice(0, 61)}...`;
256
+ lines.push(sigLine);
257
+ }
258
+ lines.push('');
259
+ }
260
+
261
+ const title = `${icon} ${label}`;
262
+ const maxLineLen = lines.reduce((max, l) => Math.max(max, displayWidth(l)), 48);
263
+ const boxWidth = Math.min(Math.max(maxLineLen + 4, 52), 72);
264
+
265
+ return box(lines, { title, width: boxWidth });
266
+ }
267
+
268
+ export function renderStatusJson(stats, { version = '', platform = '', projectName = '' } = {}) {
269
+ return JSON.stringify(
270
+ {
271
+ project: projectName || undefined,
272
+ platform: platform || undefined,
273
+ version: version || undefined,
274
+ tier: stats.tier,
275
+ skills: {
276
+ total: stats.skillCount,
277
+ layers: stats.layers,
278
+ },
279
+ packs: {
280
+ free: stats.freePacks.length,
281
+ pro: stats.proPacks.map((p) => ({ name: p.name, lines: p.lines })),
282
+ business: stats.bizPacks.map((p) => ({ name: p.name, lines: p.lines })),
283
+ },
284
+ signals: {
285
+ count: stats.signalCount,
286
+ top: getTopSignals(stats.signalMap, 5).map((s) => ({
287
+ name: s.name,
288
+ emitters: s.emitters,
289
+ listeners: s.listeners,
290
+ })),
291
+ },
292
+ mesh: {
293
+ connections: stats.totalConnections,
294
+ avgPerSkill: parseFloat(stats.avgConnections),
295
+ },
296
+ health: computeHealth(stats),
297
+ },
298
+ null,
299
+ 2,
300
+ );
301
+ }
302
+
303
+ function formatPackName(dirName, tier = 'free') {
304
+ const baseName = dirName.replace(/^(pro|business)-/, '');
305
+ if (tier === 'business') return `@rune-biz/${baseName}`.padEnd(26);
306
+ if (tier === 'pro') return `@rune-pro/${baseName}`.padEnd(26);
307
+ return `@rune/${baseName}`.padEnd(26);
308
+ }
309
+
310
+ function getTopSignals(signalMap, limit) {
311
+ const signals = new Set([...Object.keys(signalMap.emitters), ...Object.keys(signalMap.listeners)]);
312
+
313
+ const scored = [...signals].map((name) => ({
314
+ name,
315
+ emitters: signalMap.emitters[name] || [],
316
+ listeners: signalMap.listeners[name] || [],
317
+ score: (signalMap.emitters[name]?.length || 0) + (signalMap.listeners[name]?.length || 0),
318
+ }));
319
+
320
+ scored.sort((a, b) => b.score - a.score);
321
+ return scored.slice(0, limit);
322
+ }
323
+
324
+ function computeHealth(stats) {
325
+ let score = 0;
326
+
327
+ // Skills (max 25)
328
+ score += Math.min(stats.skillCount / 60, 1) * 25;
329
+
330
+ // Signals (max 25)
331
+ score += Math.min(stats.signalCount / 15, 1) * 25;
332
+
333
+ // Connections density (max 25)
334
+ const avgConn = parseFloat(stats.avgConnections);
335
+ score += Math.min(avgConn / 3.5, 1) * 25;
336
+
337
+ // Pack coverage (max 25)
338
+ const totalPacks = stats.freePacks.length + stats.proPacks.length + stats.bizPacks.length;
339
+ score += Math.min(totalPacks / 14, 1) * 25;
340
+
341
+ return Math.round(score);
342
+ }