@rune-kit/rune 2.29.0 → 2.30.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 (51) hide show
  1. package/.codex-plugin/plugin.json +1 -1
  2. package/README.md +49 -2
  3. package/agents/audit.md +1 -1
  4. package/agents/cook.md +1 -1
  5. package/agents/journal.md +1 -1
  6. package/agents/reviewer.md +23 -1
  7. package/agents/session-bridge.md +1 -1
  8. package/agents/skill-forge.md +1 -1
  9. package/agents/skill-router.md +1 -1
  10. package/agents/trend-scout.md +1 -1
  11. package/agents/worktree.md +1 -1
  12. package/compiler/__tests__/skill-attribution.test.js +109 -0
  13. package/compiler/__tests__/update.test.js +416 -0
  14. package/compiler/bin/rune.js +19 -0
  15. package/compiler/commands/update.js +354 -0
  16. package/package.json +1 -1
  17. package/skills/audit/SKILL.md +3 -3
  18. package/skills/completion-gate/SKILL.md +1 -1
  19. package/skills/constraint-check/SKILL.md +1 -1
  20. package/skills/context-engine/SKILL.md +2 -2
  21. package/skills/cook/SKILL.md +2 -2
  22. package/skills/cook/references/loop-detection.md +1 -1
  23. package/skills/cook/references/mid-run-signals.md +1 -1
  24. package/skills/debug/SKILL.md +2 -2
  25. package/skills/dependency-doctor/SKILL.md +1 -1
  26. package/skills/design/SKILL.md +1 -1
  27. package/skills/docs/SKILL.md +1 -1
  28. package/skills/docs-seeker/SKILL.md +3 -0
  29. package/skills/hallucination-guard/SKILL.md +2 -2
  30. package/skills/incident/SKILL.md +1 -1
  31. package/skills/integrity-check/SKILL.md +1 -1
  32. package/skills/launch/SKILL.md +1 -1
  33. package/skills/preflight/SKILL.md +35 -9
  34. package/skills/rescue/SKILL.md +1 -1
  35. package/skills/research/SKILL.md +1 -1
  36. package/skills/review/SKILL.md +135 -84
  37. package/skills/review/references/rules/config.md +63 -0
  38. package/skills/review/references/rules/default.md +57 -0
  39. package/skills/review/references/rules/go.md +65 -0
  40. package/skills/review/references/rules/index.md +43 -0
  41. package/skills/review/references/rules/python.md +64 -0
  42. package/skills/review/references/rules/rust.md +65 -0
  43. package/skills/review/references/rules/sql.md +65 -0
  44. package/skills/review/references/rules/ts-js.md +80 -0
  45. package/skills/sast/SKILL.md +1 -1
  46. package/skills/scout/SKILL.md +3 -0
  47. package/skills/sentinel/SKILL.md +29 -6
  48. package/skills/skill-router/SKILL.md +1 -1
  49. package/skills/team/SKILL.md +2 -2
  50. package/skills/verification/SKILL.md +3 -4
  51. package/skills/watchdog/SKILL.md +1 -1
@@ -0,0 +1,416 @@
1
+ import assert from 'node:assert';
2
+ import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
3
+ import { tmpdir } from 'node:os';
4
+ import path from 'node:path';
5
+ import { afterEach, beforeEach, describe, test } from 'node:test';
6
+ import { installHooks } from '../commands/hooks/install.js';
7
+ import { detectTiers } from '../commands/setup.js';
8
+ import {
9
+ detectInstalledPreset,
10
+ detectInstalledTiers,
11
+ formatUpdateResult,
12
+ pullTierRepos,
13
+ runUpdate,
14
+ } from '../commands/update.js';
15
+
16
+ let tmpRoot;
17
+
18
+ /** Seed a tier repo sibling (../Pro or ../Business) with a manifest whose
19
+ * entries reference the tier env var — matching what real tier manifests emit. */
20
+ async function seedTierRepo(projectRoot, tier, opts = {}) {
21
+ const tierRoot = path.join(projectRoot, '..', tier === 'pro' ? 'Pro' : 'Business');
22
+ await mkdir(path.join(tierRoot, 'hooks'), { recursive: true });
23
+ const envVar = tier === 'pro' ? 'RUNE_PRO_ROOT' : 'RUNE_BUSINESS_ROOT';
24
+ await writeFile(
25
+ path.join(tierRoot, 'hooks', 'manifest.json'),
26
+ JSON.stringify({
27
+ tier,
28
+ version: '1.0.0',
29
+ minFreeVersion: '2.0.0',
30
+ requires: [envVar],
31
+ entries: [
32
+ {
33
+ id: `${tier}-pulse`,
34
+ event: 'PreToolUse',
35
+ matcher: 'Edit|Write',
36
+ command: `node "\${${envVar}}/hooks/pulse.js"`,
37
+ },
38
+ ],
39
+ }),
40
+ );
41
+ if (opts.git) {
42
+ await mkdir(path.join(tierRoot, '.git'), { recursive: true });
43
+ }
44
+ return tierRoot;
45
+ }
46
+
47
+ async function seedFakeRuneRoot(root) {
48
+ const runeRoot = path.join(root, 'fake-rune');
49
+ await mkdir(path.join(runeRoot, 'skills'), { recursive: true });
50
+ return runeRoot;
51
+ }
52
+
53
+ /** Install real Free hooks (optionally + tier) into the tmp project so the
54
+ * "already installed" detection paths have something real to read. */
55
+ async function installProjectHooks(projectRoot, { preset = 'gentle', tier } = {}) {
56
+ await mkdir(path.join(projectRoot, '.claude'), { recursive: true });
57
+ await installHooks(projectRoot, { preset, tier, platform: 'claude' });
58
+ }
59
+
60
+ beforeEach(async () => {
61
+ delete process.env.RUNE_PRO_ROOT;
62
+ delete process.env.RUNE_BUSINESS_ROOT;
63
+ tmpRoot = await mkdtemp(path.join(tmpdir(), 'rune-update-'));
64
+ await mkdir(path.join(tmpRoot, 'project'), { recursive: true });
65
+ });
66
+
67
+ afterEach(async () => {
68
+ if (tmpRoot) await rm(tmpRoot, { recursive: true, force: true });
69
+ delete process.env.RUNE_PRO_ROOT;
70
+ delete process.env.RUNE_BUSINESS_ROOT;
71
+ });
72
+
73
+ describe('detectInstalledTiers', () => {
74
+ test('returns [] for a project with no Rune hook configs', async () => {
75
+ const projectRoot = path.join(tmpRoot, 'project');
76
+ assert.deepStrictEqual(await detectInstalledTiers(projectRoot), []);
77
+ });
78
+
79
+ test('returns [] when only Free preset hooks are installed (no false positive)', async () => {
80
+ const projectRoot = path.join(tmpRoot, 'project');
81
+ await installProjectHooks(projectRoot, { preset: 'gentle' });
82
+ assert.deepStrictEqual(await detectInstalledTiers(projectRoot), []);
83
+ });
84
+
85
+ test('detects pro from RUNE_PRO_ROOT tokens in .claude/settings.json', async () => {
86
+ const projectRoot = path.join(tmpRoot, 'project');
87
+ await seedTierRepo(projectRoot, 'pro');
88
+ await installProjectHooks(projectRoot, { preset: 'gentle', tier: ['pro'] });
89
+ assert.deepStrictEqual(await detectInstalledTiers(projectRoot), ['pro']);
90
+ });
91
+
92
+ test('detects business from tier token in .codex/hooks.json', async () => {
93
+ const projectRoot = path.join(tmpRoot, 'project');
94
+ await mkdir(path.join(projectRoot, '.codex'), { recursive: true });
95
+ await writeFile(
96
+ path.join(projectRoot, '.codex', 'hooks.json'),
97
+ JSON.stringify({
98
+ hooks: {
99
+ PreToolUse: [
100
+ {
101
+ matcher: '.*',
102
+ // biome-ignore lint/suspicious/noTemplateCurlyInString: literal env token by design
103
+ hooks: [{ type: 'command', command: 'node "${RUNE_BUSINESS_ROOT}/hooks/gate.js"' }],
104
+ },
105
+ ],
106
+ },
107
+ }),
108
+ );
109
+ assert.deepStrictEqual(await detectInstalledTiers(projectRoot), ['business']);
110
+ });
111
+
112
+ test('detects pro from rune-tier frontmatter in .cursor/rules', async () => {
113
+ const projectRoot = path.join(tmpRoot, 'project');
114
+ const rulesDir = path.join(projectRoot, '.cursor', 'rules');
115
+ await mkdir(rulesDir, { recursive: true });
116
+ await writeFile(path.join(rulesDir, 'rune-pro-pulse.mdc'), '---\nrune-managed: true\nrune-tier: pro\n---\n# x\n');
117
+ assert.deepStrictEqual(await detectInstalledTiers(projectRoot), ['pro']);
118
+ });
119
+ });
120
+
121
+ describe('detectInstalledPreset', () => {
122
+ test('returns null when nothing is installed', async () => {
123
+ const projectRoot = path.join(tmpRoot, 'project');
124
+ assert.strictEqual(await detectInstalledPreset(projectRoot), null);
125
+ });
126
+
127
+ test('returns the installed preset (strict)', async () => {
128
+ const projectRoot = path.join(tmpRoot, 'project');
129
+ await installProjectHooks(projectRoot, { preset: 'strict' });
130
+ assert.strictEqual(await detectInstalledPreset(projectRoot), 'strict');
131
+ });
132
+
133
+ test('returns gentle for a gentle install', async () => {
134
+ const projectRoot = path.join(tmpRoot, 'project');
135
+ await installProjectHooks(projectRoot, { preset: 'gentle' });
136
+ assert.strictEqual(await detectInstalledPreset(projectRoot), 'gentle');
137
+ });
138
+ });
139
+
140
+ describe('pullTierRepos', () => {
141
+ test('reports absent tiers as skipped without calling git', async () => {
142
+ const projectRoot = path.join(tmpRoot, 'project');
143
+ const calls = [];
144
+ const exec = async (...args) => {
145
+ calls.push(args);
146
+ return { code: 0, stdout: '', stderr: '' };
147
+ };
148
+ const detected = detectTiers(projectRoot, { wellKnownPaths: { pro: [], business: [] } });
149
+ const result = await pullTierRepos({ detected, exec });
150
+ assert.strictEqual(result.ok, true);
151
+ assert.strictEqual(calls.length, 0);
152
+ for (const r of result.results) {
153
+ assert.strictEqual(r.status, 'absent');
154
+ }
155
+ });
156
+
157
+ test('skips (with note) a detected tier that is not a git repo', async () => {
158
+ const projectRoot = path.join(tmpRoot, 'project');
159
+ await seedTierRepo(projectRoot, 'pro'); // no .git
160
+ const exec = async () => {
161
+ throw new Error('git must not be called');
162
+ };
163
+ const detected = detectTiers(projectRoot, { wellKnownPaths: { pro: [], business: [] } });
164
+ const result = await pullTierRepos({ detected, exec });
165
+ assert.strictEqual(result.ok, true);
166
+ const pro = result.results.find((r) => r.tier === 'pro');
167
+ assert.strictEqual(pro.status, 'skipped');
168
+ assert.match(pro.detail, /not a git repo/i);
169
+ });
170
+
171
+ test('pulls a detected git tier with git -C <root> pull --ff-only', async () => {
172
+ const projectRoot = path.join(tmpRoot, 'project');
173
+ const tierRoot = await seedTierRepo(projectRoot, 'pro', { git: true });
174
+ const calls = [];
175
+ const exec = async (cmd, argv) => {
176
+ calls.push([cmd, argv]);
177
+ return { code: 0, stdout: 'Already up to date.\n', stderr: '' };
178
+ };
179
+ const detected = detectTiers(projectRoot, { wellKnownPaths: { pro: [], business: [] } });
180
+ const result = await pullTierRepos({ detected, exec });
181
+ assert.strictEqual(result.ok, true);
182
+ const pro = result.results.find((r) => r.tier === 'pro');
183
+ assert.strictEqual(pro.status, 'pulled');
184
+ assert.strictEqual(calls.length, 1);
185
+ assert.strictEqual(calls[0][0], 'git');
186
+ assert.deepStrictEqual(calls[0][1], ['-C', path.resolve(tierRoot), 'pull', '--ff-only']);
187
+ });
188
+
189
+ test('fails loud when git pull fails (dirty tree, auth, ...)', async () => {
190
+ const projectRoot = path.join(tmpRoot, 'project');
191
+ await seedTierRepo(projectRoot, 'pro', { git: true });
192
+ const exec = async () => ({ code: 1, stdout: '', stderr: 'error: Your local changes would be overwritten' });
193
+ const detected = detectTiers(projectRoot, { wellKnownPaths: { pro: [], business: [] } });
194
+ const result = await pullTierRepos({ detected, exec });
195
+ assert.strictEqual(result.ok, false);
196
+ const pro = result.results.find((r) => r.tier === 'pro');
197
+ assert.strictEqual(pro.status, 'failed');
198
+ assert.match(pro.detail, /local changes/);
199
+ });
200
+ });
201
+
202
+ describe('runUpdate', () => {
203
+ test('fails with guidance when no Rune installation is detected', async () => {
204
+ const projectRoot = path.join(tmpRoot, 'project');
205
+ const runeRoot = await seedFakeRuneRoot(tmpRoot);
206
+ const result = await runUpdate({
207
+ projectRoot,
208
+ runeRoot,
209
+ args: {},
210
+ deps: { wellKnownPaths: { pro: [], business: [] } },
211
+ });
212
+ assert.strictEqual(result.ok, false);
213
+ assert.match(result.reason, /setup/i);
214
+ });
215
+
216
+ test('re-runs setup reusing installed preset + tier, pulls tier repo', async () => {
217
+ const projectRoot = path.join(tmpRoot, 'project');
218
+ const runeRoot = await seedFakeRuneRoot(tmpRoot);
219
+ await seedTierRepo(projectRoot, 'pro', { git: true });
220
+ await installProjectHooks(projectRoot, { preset: 'strict', tier: ['pro'] });
221
+ const execCalls = [];
222
+ const exec = async (cmd, argv) => {
223
+ execCalls.push([cmd, argv]);
224
+ return { code: 0, stdout: 'Already up to date.\n', stderr: '' };
225
+ };
226
+
227
+ const result = await runUpdate({
228
+ projectRoot,
229
+ runeRoot,
230
+ args: {},
231
+ deps: { exec, skillTarget: runeRoot, wellKnownPaths: { pro: [], business: [] } },
232
+ });
233
+
234
+ assert.strictEqual(result.ok, true);
235
+ assert.strictEqual(result.preset, 'strict');
236
+ assert.deepStrictEqual(result.tiers, ['pro']);
237
+ assert.strictEqual(execCalls.length, 1);
238
+ assert.ok(result.setup, 'setup result expected');
239
+ assert.deepStrictEqual(result.setup.tiers, ['pro']);
240
+ assert.strictEqual(result.setup.preset, 'strict');
241
+ assert.ok(result.drift, 'drift result expected');
242
+ const pro = result.pull.results.find((r) => r.tier === 'pro');
243
+ assert.strictEqual(pro.status, 'pulled');
244
+ });
245
+
246
+ test('does NOT run setup when a tier pull fails', async () => {
247
+ const projectRoot = path.join(tmpRoot, 'project');
248
+ const runeRoot = await seedFakeRuneRoot(tmpRoot);
249
+ await seedTierRepo(projectRoot, 'pro', { git: true });
250
+ await installProjectHooks(projectRoot, { preset: 'gentle', tier: ['pro'] });
251
+ let setupRan = false;
252
+ const result = await runUpdate({
253
+ projectRoot,
254
+ runeRoot,
255
+ args: {},
256
+ deps: {
257
+ exec: async () => ({ code: 128, stdout: '', stderr: 'fatal: could not read Username' }),
258
+ runSetupFn: async () => {
259
+ setupRan = true;
260
+ return {};
261
+ },
262
+ wellKnownPaths: { pro: [], business: [] },
263
+ },
264
+ });
265
+ assert.strictEqual(result.ok, false);
266
+ assert.strictEqual(setupRan, false);
267
+ assert.match(result.reason, /pull failed/i);
268
+ });
269
+
270
+ test('--no-pull skips git pulls entirely', async () => {
271
+ const projectRoot = path.join(tmpRoot, 'project');
272
+ const runeRoot = await seedFakeRuneRoot(tmpRoot);
273
+ await seedTierRepo(projectRoot, 'pro', { git: true });
274
+ await installProjectHooks(projectRoot, { preset: 'gentle', tier: ['pro'] });
275
+ const exec = async () => {
276
+ throw new Error('git must not be called with --no-pull');
277
+ };
278
+ const result = await runUpdate({
279
+ projectRoot,
280
+ runeRoot,
281
+ args: { 'no-pull': true },
282
+ deps: { exec, skillTarget: runeRoot, wellKnownPaths: { pro: [], business: [] } },
283
+ });
284
+ assert.strictEqual(result.ok, true);
285
+ for (const r of result.pull.results) {
286
+ assert.notStrictEqual(r.status, 'pulled');
287
+ }
288
+ });
289
+
290
+ test('skips (with note) an installed tier whose repo is no longer found', async () => {
291
+ const projectRoot = path.join(tmpRoot, 'project');
292
+ const runeRoot = await seedFakeRuneRoot(tmpRoot);
293
+ // Install hooks WITH the pro tier while the repo exists...
294
+ await seedTierRepo(projectRoot, 'pro');
295
+ await installProjectHooks(projectRoot, { preset: 'gentle', tier: ['pro'] });
296
+ // ...then the repo disappears (user deleted the clone).
297
+ await rm(path.join(projectRoot, '..', 'Pro'), { recursive: true, force: true });
298
+
299
+ const result = await runUpdate({
300
+ projectRoot,
301
+ runeRoot,
302
+ args: {},
303
+ deps: { skillTarget: runeRoot, wellKnownPaths: { pro: [], business: [] } },
304
+ });
305
+
306
+ assert.strictEqual(result.ok, true);
307
+ assert.deepStrictEqual(result.tiers, []);
308
+ assert.ok(
309
+ result.notes.some((n) => /pro/.test(n) && /not found/i.test(n)),
310
+ `expected a "repo not found" note, got: ${JSON.stringify(result.notes)}`,
311
+ );
312
+ });
313
+
314
+ test('--dry passes dry through to setup (no writes)', async () => {
315
+ const projectRoot = path.join(tmpRoot, 'project');
316
+ const runeRoot = await seedFakeRuneRoot(tmpRoot);
317
+ await installProjectHooks(projectRoot, { preset: 'gentle' });
318
+ const result = await runUpdate({
319
+ projectRoot,
320
+ runeRoot,
321
+ args: { dry: true, 'no-pull': true },
322
+ deps: { skillTarget: runeRoot, wellKnownPaths: { pro: [], business: [] } },
323
+ });
324
+ assert.strictEqual(result.ok, true);
325
+ assert.strictEqual(result.setup.written, false);
326
+ });
327
+
328
+ test('flags Codex re-trust when .codex/hooks.json content changed', async () => {
329
+ const projectRoot = path.join(tmpRoot, 'project');
330
+ const runeRoot = await seedFakeRuneRoot(tmpRoot);
331
+ await mkdir(path.join(projectRoot, '.codex'), { recursive: true });
332
+ // Stale/no hooks file → setup rewrite will change it.
333
+ const result = await runUpdate({
334
+ projectRoot,
335
+ runeRoot,
336
+ args: { 'no-pull': true },
337
+ deps: { skillTarget: runeRoot, wellKnownPaths: { pro: [], business: [] } },
338
+ });
339
+ assert.strictEqual(result.ok, true);
340
+ assert.strictEqual(result.codexReTrust, true);
341
+ });
342
+ });
343
+
344
+ describe('formatUpdateResult', () => {
345
+ test('renders pull, setup, and verification summary', () => {
346
+ const out = formatUpdateResult({
347
+ ok: true,
348
+ pull: {
349
+ ok: true,
350
+ results: [
351
+ { tier: 'pro', status: 'pulled', detail: 'Already up to date.' },
352
+ { tier: 'business', status: 'absent', detail: 'not detected' },
353
+ ],
354
+ },
355
+ platforms: ['claude'],
356
+ preset: 'gentle',
357
+ tiers: ['pro'],
358
+ setup: {
359
+ scope: 'current',
360
+ targetRoot: '/p',
361
+ tiers: ['pro'],
362
+ preset: 'gentle',
363
+ platforms: ['claude'],
364
+ written: true,
365
+ skillResults: [],
366
+ },
367
+ drift: {
368
+ findings: [],
369
+ summary: { drifted: 0, missing: 0, errors: 0 },
370
+ platforms: [{ platform: 'claude', preset: 'gentle' }],
371
+ },
372
+ doctor: { skipped: true, reason: 'no rune.config.json' },
373
+ codexReTrust: false,
374
+ notes: [],
375
+ });
376
+ assert.match(out, /Rune Update/);
377
+ assert.match(out, /pro.*pulled/i);
378
+ assert.match(out, /business.*not detected/i);
379
+ assert.match(out, /preset.*gentle/i);
380
+ assert.match(out, /0 drifted, 0 missing/);
381
+ assert.doesNotMatch(out, /\/hooks/);
382
+ });
383
+
384
+ test('renders the Codex re-trust reminder when hooks.json changed', () => {
385
+ const out = formatUpdateResult({
386
+ ok: true,
387
+ pull: { ok: true, results: [] },
388
+ platforms: ['codex'],
389
+ preset: 'gentle',
390
+ tiers: [],
391
+ setup: { platforms: ['codex'], written: true, skillResults: [] },
392
+ drift: { findings: [], summary: { drifted: 0, missing: 0, errors: 0 }, platforms: [] },
393
+ doctor: { skipped: true, reason: 'no rune.config.json' },
394
+ codexReTrust: true,
395
+ notes: [],
396
+ });
397
+ assert.match(out, /\/hooks/);
398
+ assert.match(out, /re-trust/i);
399
+ });
400
+
401
+ test('renders loud failure for a failed pull', () => {
402
+ const out = formatUpdateResult({
403
+ ok: false,
404
+ reason: 'tier pull failed — resolve manually and re-run `rune update`',
405
+ pull: {
406
+ ok: false,
407
+ results: [{ tier: 'pro', status: 'failed', detail: 'error: Your local changes would be overwritten' }],
408
+ },
409
+ notes: [],
410
+ });
411
+ assert.match(out, /✗/);
412
+ assert.match(out, /pro/);
413
+ assert.match(out, /local changes/);
414
+ assert.match(out, /re-run/i);
415
+ });
416
+ });
@@ -25,6 +25,7 @@ import { installHooks } from '../commands/hooks/install.js';
25
25
  import { hookStatus } from '../commands/hooks/status.js';
26
26
  import { uninstallHooks } from '../commands/hooks/uninstall.js';
27
27
  import { formatSetupResult, runSetup } from '../commands/setup.js';
28
+ import { formatUpdateResult, runUpdate } from '../commands/update.js';
28
29
  import { generateComprehensionHTML } from '../comprehension.js';
29
30
  import { generateDashboardHTML } from '../dashboard.js';
30
31
  import { checkMeshIntegrity, formatDoctorResults, formatMeshResults, runDoctor } from '../doctor.js';
@@ -338,6 +339,18 @@ async function cmdSetup(projectRoot, args) {
338
339
  }
339
340
  }
340
341
 
342
+ async function cmdUpdate(projectRoot, args) {
343
+ try {
344
+ const result = await runUpdate({ projectRoot, runeRoot: RUNE_ROOT, args });
345
+ log(formatUpdateResult(result));
346
+ if (!result.ok) process.exit(1);
347
+ } catch (err) {
348
+ log('');
349
+ log(` ✗ Update failed: ${err.message}`);
350
+ process.exit(1);
351
+ }
352
+ }
353
+
341
354
  async function readVersion() {
342
355
  try {
343
356
  const pkg = JSON.parse(await readFile(path.join(RUNE_ROOT, 'package.json'), 'utf-8'));
@@ -852,6 +865,9 @@ async function main() {
852
865
  case 'setup':
853
866
  await cmdSetup(projectRoot, args);
854
867
  break;
868
+ case 'update':
869
+ await cmdUpdate(projectRoot, args);
870
+ break;
855
871
  case 'status':
856
872
  await cmdStatus(projectRoot, args);
857
873
  break;
@@ -887,6 +903,9 @@ async function main() {
887
903
  ' setup Interactive wizard — auto-detect tiers, pick scope, install hooks (recommended for first-time)',
888
904
  );
889
905
  log(' [--here|--global] [--tier pro,business] [--preset gentle|strict] [--dry]');
906
+ log(' update Update an existing install — git-pull detected tier repos, re-run the managed');
907
+ log(' setup rewrite in place (reuses installed platforms/preset/tiers), verify with doctor');
908
+ log(' [--no-pull] [--preset gentle|strict] [--tier pro,business] [--dry]');
890
909
  log(' init Interactive setup for build pipeline (auto-detects platform)');
891
910
  log(' build Compile skills for configured platform');
892
911
  log(' doctor Validate compiled output + mesh integrity');