@rune-kit/rune 2.6.0 → 2.7.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 +17 -6
- package/compiler/__tests__/executive-dashboards.test.js +285 -0
- package/compiler/__tests__/inject.test.js +128 -0
- package/compiler/__tests__/orchestrators.test.js +151 -0
- package/compiler/__tests__/org-templates.test.js +447 -0
- package/compiler/__tests__/parser.test.js +201 -147
- package/compiler/__tests__/skill-index.test.js +218 -218
- package/compiler/__tests__/status.test.js +336 -0
- package/compiler/__tests__/templates.test.js +245 -0
- package/compiler/__tests__/visualizer.test.js +325 -0
- package/compiler/adapters/antigravity.js +71 -71
- package/compiler/bin/rune.js +444 -355
- package/compiler/doctor.js +272 -1
- package/compiler/emitter.js +939 -678
- package/compiler/parser.js +498 -267
- package/compiler/status.js +342 -0
- package/compiler/visualizer.js +622 -0
- package/package.json +1 -1
- package/skills/autopsy/SKILL.md +48 -1
- package/skills/completion-gate/SKILL.md +26 -1
- package/skills/context-engine/SKILL.md +93 -2
- package/skills/cook/SKILL.md +137 -4
- package/skills/debug/SKILL.md +16 -1
- package/skills/docs/SKILL.md +28 -3
- package/skills/fix/SKILL.md +2 -1
- package/skills/mcp-builder/SKILL.md +53 -1
- package/skills/onboard/SKILL.md +51 -1
- package/skills/perf/SKILL.md +34 -1
- package/skills/plan/SKILL.md +29 -1
- package/skills/preflight/SKILL.md +35 -1
- package/skills/retro/SKILL.md +95 -1
- package/skills/review/SKILL.md +45 -1
- package/skills/scope-guard/SKILL.md +1 -0
- package/skills/sentinel/SKILL.md +35 -1
- package/skills/sentinel/references/policy-driven-constraints.md +424 -0
- package/skills/session-bridge/SKILL.md +57 -2
- package/skills/session-bridge/references/evolutionary-memory-patterns.md +312 -0
- package/skills/team/SKILL.md +15 -1
package/compiler/bin/rune.js
CHANGED
|
@@ -1,355 +1,444 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Rune CLI
|
|
5
|
-
*
|
|
6
|
-
* Commands:
|
|
7
|
-
* rune init — Interactive setup for a new project
|
|
8
|
-
* rune build — Compile skills for the configured platform
|
|
9
|
-
* rune doctor — Validate compiled output
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
import
|
|
15
|
-
import {
|
|
16
|
-
import
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if (existsSync(path.join(projectRoot, '.
|
|
53
|
-
if (existsSync(path.join(projectRoot, '.
|
|
54
|
-
if (existsSync(path.join(projectRoot, '.
|
|
55
|
-
return
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
log('');
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
log('
|
|
168
|
-
log('');
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
const
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
if (stats.
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
const
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Rune CLI
|
|
5
|
+
*
|
|
6
|
+
* Commands:
|
|
7
|
+
* rune init — Interactive setup for a new project
|
|
8
|
+
* rune build — Compile skills for the configured platform
|
|
9
|
+
* rune doctor — Validate compiled output
|
|
10
|
+
* rune status — Project dashboard (neofetch-style)
|
|
11
|
+
* rune visualize — Interactive mesh graph
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { existsSync } from 'node:fs';
|
|
15
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
16
|
+
import path from 'node:path';
|
|
17
|
+
import { createInterface } from 'node:readline';
|
|
18
|
+
import { fileURLToPath } from 'node:url';
|
|
19
|
+
import { getAdapter, listPlatforms } from '../adapters/index.js';
|
|
20
|
+
import { formatDoctorResults, runDoctor } from '../doctor.js';
|
|
21
|
+
import { buildAll } from '../emitter.js';
|
|
22
|
+
import { collectStats, renderStatus, renderStatusJson } from '../status.js';
|
|
23
|
+
import { collectGraphData, generateMeshHTML } from '../visualizer.js';
|
|
24
|
+
|
|
25
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
26
|
+
const __dirname = path.dirname(__filename);
|
|
27
|
+
const RUNE_ROOT = path.resolve(__dirname, '../..');
|
|
28
|
+
|
|
29
|
+
const CONFIG_FILE = 'rune.config.json';
|
|
30
|
+
|
|
31
|
+
// ─── Helpers ───
|
|
32
|
+
|
|
33
|
+
function log(msg) {
|
|
34
|
+
console.log(msg);
|
|
35
|
+
}
|
|
36
|
+
function logStep(icon, msg) {
|
|
37
|
+
console.log(` ${icon} ${msg}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function readConfig(projectRoot) {
|
|
41
|
+
const configPath = path.join(projectRoot, CONFIG_FILE);
|
|
42
|
+
if (!existsSync(configPath)) return null;
|
|
43
|
+
return JSON.parse(await readFile(configPath, 'utf-8'));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function writeConfig(projectRoot, config) {
|
|
47
|
+
const configPath = path.join(projectRoot, CONFIG_FILE);
|
|
48
|
+
await writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, 'utf-8');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function detectPlatform(projectRoot) {
|
|
52
|
+
if (existsSync(path.join(projectRoot, '.claude-plugin'))) return 'claude';
|
|
53
|
+
if (existsSync(path.join(projectRoot, '.cursor'))) return 'cursor';
|
|
54
|
+
if (existsSync(path.join(projectRoot, '.windsurf'))) return 'windsurf';
|
|
55
|
+
if (existsSync(path.join(projectRoot, '.agents'))) return 'antigravity';
|
|
56
|
+
if (existsSync(path.join(projectRoot, '.openclaw'))) return 'openclaw';
|
|
57
|
+
if (existsSync(path.join(projectRoot, '.codex'))) return 'codex';
|
|
58
|
+
if (existsSync(path.join(projectRoot, '.opencode'))) return 'opencode';
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function prompt(question) {
|
|
63
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
64
|
+
return new Promise((resolve) => {
|
|
65
|
+
rl.question(question, (answer) => {
|
|
66
|
+
rl.close();
|
|
67
|
+
resolve(answer.trim());
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Resolve tier source paths from config.
|
|
74
|
+
* Paths can be absolute or relative to projectRoot.
|
|
75
|
+
*
|
|
76
|
+
* Config format:
|
|
77
|
+
* "tiers": { "pro": "../Pro/extensions", "business": "../Business/extensions" }
|
|
78
|
+
*
|
|
79
|
+
* @param {Object} tiers - tier config object
|
|
80
|
+
* @param {string} projectRoot - base for relative paths
|
|
81
|
+
* @returns {Object} resolved { pro?: string, business?: string }
|
|
82
|
+
*/
|
|
83
|
+
function resolveTierSources(tiers, projectRoot) {
|
|
84
|
+
if (!tiers) return {};
|
|
85
|
+
const resolved = {};
|
|
86
|
+
for (const tier of ['pro', 'business']) {
|
|
87
|
+
if (tiers[tier]) {
|
|
88
|
+
resolved[tier] = path.resolve(projectRoot, tiers[tier]);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return resolved;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ─── Commands ───
|
|
95
|
+
|
|
96
|
+
async function cmdInit(projectRoot, args) {
|
|
97
|
+
log('');
|
|
98
|
+
log(' ╭──────────────────────────────────────────╮');
|
|
99
|
+
log(' │ Rune — Less skills. Deeper connections. │');
|
|
100
|
+
log(' ╰──────────────────────────────────────────╯');
|
|
101
|
+
log('');
|
|
102
|
+
|
|
103
|
+
// Platform detection / selection
|
|
104
|
+
let platform = args.platform || detectPlatform(projectRoot);
|
|
105
|
+
|
|
106
|
+
if (platform) {
|
|
107
|
+
logStep('→', `Detected: ${platform}`);
|
|
108
|
+
} else {
|
|
109
|
+
log(` Available platforms: ${listPlatforms().join(', ')}`);
|
|
110
|
+
const answer = await prompt(' ? Select platform: ');
|
|
111
|
+
platform = answer.toLowerCase();
|
|
112
|
+
if (!listPlatforms().includes(platform)) {
|
|
113
|
+
platform = 'generic';
|
|
114
|
+
logStep('→', `Unknown platform, using generic adapter`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (platform === 'claude') {
|
|
119
|
+
logStep('✓', 'Claude Code detected — Rune works as a native plugin. No compilation needed.');
|
|
120
|
+
log('');
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Extension pack selection
|
|
125
|
+
const extensions = args.extensions ? args.extensions.split(',') : null; // null = all
|
|
126
|
+
|
|
127
|
+
// Build config
|
|
128
|
+
const config = {
|
|
129
|
+
$schema: 'https://rune-kit.github.io/rune/config-schema.json',
|
|
130
|
+
version: 1,
|
|
131
|
+
platform,
|
|
132
|
+
source: '@rune-kit/rune',
|
|
133
|
+
skills: {
|
|
134
|
+
disabled: args.disable ? args.disable.split(',') : [],
|
|
135
|
+
},
|
|
136
|
+
extensions: {
|
|
137
|
+
enabled: extensions,
|
|
138
|
+
},
|
|
139
|
+
output: {
|
|
140
|
+
index: true,
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
await writeConfig(projectRoot, config);
|
|
145
|
+
logStep('✓', 'Created rune.config.json');
|
|
146
|
+
|
|
147
|
+
// Auto-build
|
|
148
|
+
const adapter = getAdapter(platform);
|
|
149
|
+
const tierSources = resolveTierSources(config.tiers, projectRoot);
|
|
150
|
+
const stats = await buildAll({
|
|
151
|
+
runeRoot: RUNE_ROOT,
|
|
152
|
+
outputRoot: projectRoot,
|
|
153
|
+
adapter,
|
|
154
|
+
disabledSkills: config.skills.disabled,
|
|
155
|
+
enabledPacks: config.extensions.enabled,
|
|
156
|
+
tierSources,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
logStep('✓', `Built ${stats.skillCount} skills + ${stats.packCount} extensions to ${adapter.outputDir}/`);
|
|
160
|
+
|
|
161
|
+
if (stats.errors.length > 0) {
|
|
162
|
+
for (const err of stats.errors) {
|
|
163
|
+
logStep('✗', `Error: ${err.file} — ${err.error}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
log('');
|
|
168
|
+
log(' Next steps:');
|
|
169
|
+
log(' 1. /rune onboard Generate project context (CLAUDE.md + .rune/)');
|
|
170
|
+
log(' 2. /rune cook "..." Build a feature (full TDD cycle)');
|
|
171
|
+
log(' 3. /rune help See all 59 skills');
|
|
172
|
+
log('');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async function cmdBuild(projectRoot, args) {
|
|
176
|
+
const config = await readConfig(projectRoot);
|
|
177
|
+
|
|
178
|
+
const platform = args.platform || config?.platform;
|
|
179
|
+
if (!platform) {
|
|
180
|
+
log(' ✗ No platform configured. Run `rune init` first.');
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (platform === 'claude') {
|
|
185
|
+
log(' Claude Code uses source SKILL.md files directly. No compilation needed.');
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const adapter = getAdapter(platform);
|
|
190
|
+
const runeRoot = config?.source === '@rune-kit/rune' ? RUNE_ROOT : config?.source || RUNE_ROOT;
|
|
191
|
+
const outputRoot = typeof args.output === 'string' ? args.output : projectRoot;
|
|
192
|
+
const disabledSkills = config?.skills?.disabled || [];
|
|
193
|
+
const enabledPacks = config?.extensions?.enabled || null;
|
|
194
|
+
const tierSources = resolveTierSources(config?.tiers, projectRoot);
|
|
195
|
+
|
|
196
|
+
log('');
|
|
197
|
+
log(` [parse] Discovering skills...`);
|
|
198
|
+
|
|
199
|
+
const stats = await buildAll({
|
|
200
|
+
runeRoot,
|
|
201
|
+
outputRoot,
|
|
202
|
+
adapter,
|
|
203
|
+
disabledSkills,
|
|
204
|
+
enabledPacks,
|
|
205
|
+
tierSources,
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
log(` [transform] Platform: ${stats.platform}`);
|
|
209
|
+
log(` [transform] Resolved ${stats.crossRefsResolved} cross-references`);
|
|
210
|
+
log(` [transform] Resolved ${stats.toolRefsResolved} tool-name references`);
|
|
211
|
+
log(` [emit] ${stats.skillCount} skills + ${stats.packCount} extensions`);
|
|
212
|
+
|
|
213
|
+
if (stats.tierOverrides?.length > 0) {
|
|
214
|
+
log(` [tier] ${stats.tierOverrides.length} pack(s) resolved from higher tiers:`);
|
|
215
|
+
for (const override of stats.tierOverrides) {
|
|
216
|
+
log(` → ${override.pack} (${override.tier})`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (stats.skipped.length > 0) {
|
|
221
|
+
log(` [skip] ${stats.skipped.length} disabled: ${stats.skipped.join(', ')}`);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (stats.errors.length > 0) {
|
|
225
|
+
for (const err of stats.errors) {
|
|
226
|
+
log(` [error] ${err.file}: ${err.error}`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
log('');
|
|
231
|
+
log(` ✓ Built ${stats.files.length} files to ${adapter.outputDir}/`);
|
|
232
|
+
log('');
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async function cmdDoctor(projectRoot, args) {
|
|
236
|
+
const config = await readConfig(projectRoot);
|
|
237
|
+
|
|
238
|
+
if (!config) {
|
|
239
|
+
// No config = CI or fresh clone. Run source-only checks (split packs).
|
|
240
|
+
log('');
|
|
241
|
+
log(' ℹ No rune.config.json found — running source-only checks.');
|
|
242
|
+
const results = await runDoctor({
|
|
243
|
+
outputRoot: projectRoot,
|
|
244
|
+
adapter: getAdapter('claude'),
|
|
245
|
+
config: {},
|
|
246
|
+
runeRoot: RUNE_ROOT,
|
|
247
|
+
});
|
|
248
|
+
log(formatDoctorResults(results));
|
|
249
|
+
if (!results.healthy) process.exit(1);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const platform = args.platform || config.platform;
|
|
254
|
+
const adapter = getAdapter(platform);
|
|
255
|
+
const runeRoot = config.source === '@rune-kit/rune' ? RUNE_ROOT : config.source || RUNE_ROOT;
|
|
256
|
+
|
|
257
|
+
const results = await runDoctor({
|
|
258
|
+
outputRoot: projectRoot,
|
|
259
|
+
adapter,
|
|
260
|
+
config,
|
|
261
|
+
runeRoot,
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
log(formatDoctorResults(results));
|
|
265
|
+
|
|
266
|
+
if (!results.healthy) process.exit(1);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async function cmdStatus(projectRoot, args) {
|
|
270
|
+
const config = await readConfig(projectRoot);
|
|
271
|
+
const tierSources = resolveTierSources(config?.tiers, projectRoot);
|
|
272
|
+
const runeRoot =
|
|
273
|
+
config?.source === '@rune-kit/rune'
|
|
274
|
+
? RUNE_ROOT
|
|
275
|
+
: config?.source
|
|
276
|
+
? path.resolve(projectRoot, config.source)
|
|
277
|
+
: RUNE_ROOT;
|
|
278
|
+
const platform = config?.platform || detectPlatform(projectRoot) || '';
|
|
279
|
+
|
|
280
|
+
const pkg = JSON.parse(await readFile(path.join(RUNE_ROOT, 'package.json'), 'utf-8'));
|
|
281
|
+
const projectName = path.basename(projectRoot);
|
|
282
|
+
|
|
283
|
+
const stats = await collectStats(runeRoot, tierSources);
|
|
284
|
+
|
|
285
|
+
if (args.json) {
|
|
286
|
+
log(renderStatusJson(stats, { version: pkg.version, platform, projectName }));
|
|
287
|
+
} else {
|
|
288
|
+
log('');
|
|
289
|
+
log(renderStatus(stats, { version: pkg.version, platform, projectName }));
|
|
290
|
+
log('');
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
async function cmdVisualize(projectRoot, args) {
|
|
295
|
+
const config = await readConfig(projectRoot);
|
|
296
|
+
const tierSources = resolveTierSources(config?.tiers, projectRoot);
|
|
297
|
+
const runeRoot =
|
|
298
|
+
config?.source === '@rune-kit/rune'
|
|
299
|
+
? RUNE_ROOT
|
|
300
|
+
: config?.source
|
|
301
|
+
? path.resolve(projectRoot, config.source)
|
|
302
|
+
: RUNE_ROOT;
|
|
303
|
+
|
|
304
|
+
logStep('◎', 'Collecting mesh data...');
|
|
305
|
+
const graphData = await collectGraphData(runeRoot, tierSources);
|
|
306
|
+
|
|
307
|
+
logStep(
|
|
308
|
+
'◎',
|
|
309
|
+
`Found ${graphData.stats.nodeCount} nodes, ${graphData.stats.edgeCount} edges, ${graphData.stats.signalCount} signals`,
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
const html = generateMeshHTML(graphData);
|
|
313
|
+
|
|
314
|
+
const runeDir = path.join(projectRoot, '.rune');
|
|
315
|
+
if (!existsSync(runeDir)) {
|
|
316
|
+
const { mkdir: mkdirFs } = await import('node:fs/promises');
|
|
317
|
+
await mkdirFs(runeDir, { recursive: true });
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const outputPath = args.output ? path.resolve(projectRoot, args.output) : path.join(runeDir, 'mesh.html');
|
|
321
|
+
|
|
322
|
+
const { writeFile: writeFileFs } = await import('node:fs/promises');
|
|
323
|
+
await writeFileFs(outputPath, html, 'utf-8');
|
|
324
|
+
logStep('✓', `Mesh visualization written to ${path.relative(projectRoot, outputPath)}`);
|
|
325
|
+
|
|
326
|
+
if (args.json) {
|
|
327
|
+
log(JSON.stringify(graphData, null, 2));
|
|
328
|
+
} else {
|
|
329
|
+
// Try to open in browser
|
|
330
|
+
try {
|
|
331
|
+
const { exec } = await import('node:child_process');
|
|
332
|
+
const cmd =
|
|
333
|
+
process.platform === 'win32'
|
|
334
|
+
? `start "" "${outputPath}"`
|
|
335
|
+
: process.platform === 'darwin'
|
|
336
|
+
? `open "${outputPath}"`
|
|
337
|
+
: `xdg-open "${outputPath}"`;
|
|
338
|
+
exec(cmd);
|
|
339
|
+
} catch {
|
|
340
|
+
/* ignore if browser open fails */
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// ─── Arg Parsing ───
|
|
346
|
+
|
|
347
|
+
// Flags that require a string value (not boolean)
|
|
348
|
+
const VALUE_REQUIRED_FLAGS = new Set(['platform', 'output', 'disable', 'extensions']);
|
|
349
|
+
|
|
350
|
+
function parseArgs(argv) {
|
|
351
|
+
const args = {};
|
|
352
|
+
const positional = [];
|
|
353
|
+
|
|
354
|
+
for (let i = 0; i < argv.length; i++) {
|
|
355
|
+
const arg = argv[i];
|
|
356
|
+
if (arg.startsWith('--')) {
|
|
357
|
+
const key = arg.slice(2);
|
|
358
|
+
const next = argv[i + 1];
|
|
359
|
+
if (next && !next.startsWith('--')) {
|
|
360
|
+
args[key] = next;
|
|
361
|
+
i++;
|
|
362
|
+
} else if (VALUE_REQUIRED_FLAGS.has(key)) {
|
|
363
|
+
log(` ✗ Flag --${key} requires a value. Example: --${key} <value>`);
|
|
364
|
+
process.exit(1);
|
|
365
|
+
} else {
|
|
366
|
+
args[key] = true;
|
|
367
|
+
}
|
|
368
|
+
} else {
|
|
369
|
+
positional.push(arg);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return { command: positional[0], args };
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// ─── Main ───
|
|
377
|
+
|
|
378
|
+
async function main() {
|
|
379
|
+
const { command, args } = parseArgs(process.argv.slice(2));
|
|
380
|
+
const projectRoot = process.cwd();
|
|
381
|
+
|
|
382
|
+
// Handle --version / -v as flag (not positional command)
|
|
383
|
+
if (args.version || args.v) {
|
|
384
|
+
const pkg = JSON.parse(await readFile(path.join(RUNE_ROOT, 'package.json'), 'utf-8'));
|
|
385
|
+
log(` rune v${pkg.version}`);
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
switch (command) {
|
|
390
|
+
case 'init':
|
|
391
|
+
await cmdInit(projectRoot, args);
|
|
392
|
+
break;
|
|
393
|
+
case 'build':
|
|
394
|
+
await cmdBuild(projectRoot, args);
|
|
395
|
+
break;
|
|
396
|
+
case 'doctor':
|
|
397
|
+
await cmdDoctor(projectRoot, args);
|
|
398
|
+
break;
|
|
399
|
+
case 'status':
|
|
400
|
+
await cmdStatus(projectRoot, args);
|
|
401
|
+
break;
|
|
402
|
+
case 'visualize':
|
|
403
|
+
case 'viz':
|
|
404
|
+
await cmdVisualize(projectRoot, args);
|
|
405
|
+
break;
|
|
406
|
+
case 'version':
|
|
407
|
+
case '--version':
|
|
408
|
+
case '-v': {
|
|
409
|
+
const pkg = JSON.parse(await readFile(path.join(RUNE_ROOT, 'package.json'), 'utf-8'));
|
|
410
|
+
log(` rune v${pkg.version}`);
|
|
411
|
+
break;
|
|
412
|
+
}
|
|
413
|
+
case 'help':
|
|
414
|
+
case '--help':
|
|
415
|
+
case undefined:
|
|
416
|
+
log('');
|
|
417
|
+
log(' Rune CLI — Skill mesh for AI coding assistants');
|
|
418
|
+
log('');
|
|
419
|
+
log(' Commands:');
|
|
420
|
+
log(' init Interactive setup (auto-detects platform)');
|
|
421
|
+
log(' build Compile skills for configured platform');
|
|
422
|
+
log(' doctor Validate compiled output');
|
|
423
|
+
log(' status Project dashboard (skills, signals, packs, health)');
|
|
424
|
+
log(' visualize Interactive mesh graph (opens in browser)');
|
|
425
|
+
log('');
|
|
426
|
+
log(' Options:');
|
|
427
|
+
log(
|
|
428
|
+
' --platform <name> Override platform (cursor, windsurf, antigravity, codex, openclaw, opencode, generic)',
|
|
429
|
+
);
|
|
430
|
+
log(' --output <dir> Override output directory');
|
|
431
|
+
log(' --disable <skills> Comma-separated skills to disable');
|
|
432
|
+
log(' --version, -v Show version');
|
|
433
|
+
log('');
|
|
434
|
+
break;
|
|
435
|
+
default:
|
|
436
|
+
log(` ✗ Unknown command: ${command}. Run \`rune help\` for usage.`);
|
|
437
|
+
process.exit(1);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
main().catch((err) => {
|
|
442
|
+
console.error(' ✗ Fatal:', err.message);
|
|
443
|
+
process.exit(1);
|
|
444
|
+
});
|