@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.
- package/.codex-plugin/plugin.json +1 -1
- package/README.md +49 -2
- package/agents/audit.md +1 -1
- package/agents/cook.md +1 -1
- package/agents/journal.md +1 -1
- package/agents/reviewer.md +23 -1
- package/agents/session-bridge.md +1 -1
- package/agents/skill-forge.md +1 -1
- package/agents/skill-router.md +1 -1
- package/agents/trend-scout.md +1 -1
- package/agents/worktree.md +1 -1
- package/compiler/__tests__/skill-attribution.test.js +109 -0
- package/compiler/__tests__/update.test.js +416 -0
- package/compiler/bin/rune.js +19 -0
- package/compiler/commands/update.js +354 -0
- package/package.json +1 -1
- package/skills/audit/SKILL.md +3 -3
- package/skills/completion-gate/SKILL.md +1 -1
- package/skills/constraint-check/SKILL.md +1 -1
- package/skills/context-engine/SKILL.md +2 -2
- package/skills/cook/SKILL.md +2 -2
- package/skills/cook/references/loop-detection.md +1 -1
- package/skills/cook/references/mid-run-signals.md +1 -1
- package/skills/debug/SKILL.md +2 -2
- package/skills/dependency-doctor/SKILL.md +1 -1
- package/skills/design/SKILL.md +1 -1
- package/skills/docs/SKILL.md +1 -1
- package/skills/docs-seeker/SKILL.md +3 -0
- package/skills/hallucination-guard/SKILL.md +2 -2
- package/skills/incident/SKILL.md +1 -1
- package/skills/integrity-check/SKILL.md +1 -1
- package/skills/launch/SKILL.md +1 -1
- package/skills/preflight/SKILL.md +35 -9
- package/skills/rescue/SKILL.md +1 -1
- package/skills/research/SKILL.md +1 -1
- package/skills/review/SKILL.md +135 -84
- package/skills/review/references/rules/config.md +63 -0
- package/skills/review/references/rules/default.md +57 -0
- package/skills/review/references/rules/go.md +65 -0
- package/skills/review/references/rules/index.md +43 -0
- package/skills/review/references/rules/python.md +64 -0
- package/skills/review/references/rules/rust.md +65 -0
- package/skills/review/references/rules/sql.md +65 -0
- package/skills/review/references/rules/ts-js.md +80 -0
- package/skills/sast/SKILL.md +1 -1
- package/skills/scout/SKILL.md +3 -0
- package/skills/sentinel/SKILL.md +29 -6
- package/skills/skill-router/SKILL.md +1 -1
- package/skills/team/SKILL.md +2 -2
- package/skills/verification/SKILL.md +3 -4
- package/skills/watchdog/SKILL.md +1 -1
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `rune update` — one-shot updater for an already-configured project.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the manual "Updating" flow from the README in a single command:
|
|
5
|
+
* 1. `git pull --ff-only` any detected paid tier repos (Pro / Business).
|
|
6
|
+
* Detection reuses setup's logic: $RUNE_PRO_ROOT / $RUNE_BUSINESS_ROOT
|
|
7
|
+
* env vars, then sibling dirs (../Pro, ../Business), then well-known
|
|
8
|
+
* paths. Absent tiers and non-git checkouts are skipped with a note;
|
|
9
|
+
* a FAILED pull aborts the update (fail loud — never silently continue).
|
|
10
|
+
* 2. Re-run the managed setup rewrite in place, non-interactively: the
|
|
11
|
+
* installed platforms, preset, and tiers are detected from the project's
|
|
12
|
+
* existing hook config instead of prompting. Delegates to `runSetup`.
|
|
13
|
+
* 3. Verify: compiled-output doctor (when a rune.config.json build exists)
|
|
14
|
+
* + hook drift report, then print a short summary. Codex users are
|
|
15
|
+
* reminded to re-trust hooks via `/hooks` when `.codex/hooks.json`
|
|
16
|
+
* changed.
|
|
17
|
+
*
|
|
18
|
+
* Non-interactive by design — safe to run from scripts. Flags:
|
|
19
|
+
* --no-pull skip step 1 (tier repos managed some other way)
|
|
20
|
+
* --preset <p> override the detected preset
|
|
21
|
+
* --tier <t>[,<t>] override the detected tier list
|
|
22
|
+
* --dry preview: skip pulls, pass dry to setup (no writes)
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { execFile } from 'node:child_process';
|
|
26
|
+
import { existsSync } from 'node:fs';
|
|
27
|
+
import { readdir, readFile } from 'node:fs/promises';
|
|
28
|
+
import path from 'node:path';
|
|
29
|
+
import { ADAPTERS, detectPlatforms } from '../adapters/hooks/index.js';
|
|
30
|
+
import { checkHookDrift } from './hooks/drift.js';
|
|
31
|
+
import { TIER_ENV_VARS } from './hooks/tiers.js';
|
|
32
|
+
import { detectTiers, runSetup } from './setup.js';
|
|
33
|
+
|
|
34
|
+
/** Installed hook configs that may carry tier tokens (env var references). */
|
|
35
|
+
const TIER_SCAN_FILES = ['.claude/settings.json', '.codex/hooks.json'];
|
|
36
|
+
|
|
37
|
+
/** Rule/workflow dirs whose Rune-managed files carry `rune-tier:` frontmatter. */
|
|
38
|
+
const TIER_SCAN_DIRS = ['.cursor/rules', '.windsurf/rules', '.windsurf/workflows', '.antigravity/rules'];
|
|
39
|
+
|
|
40
|
+
const CODEX_HOOKS_REL_PATH = '.codex/hooks.json';
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Detect which paid tiers are wired into this project's installed hook config.
|
|
44
|
+
* Reads the config files Rune manages and looks for tier env-var tokens
|
|
45
|
+
* (`RUNE_PRO_ROOT` / `RUNE_BUSINESS_ROOT`) or `rune-tier:` frontmatter.
|
|
46
|
+
* Free preset entries never contain either marker, so a Free-only install
|
|
47
|
+
* returns [].
|
|
48
|
+
*
|
|
49
|
+
* @param {string} projectRoot
|
|
50
|
+
* @returns {Promise<string[]>} tier names, e.g. ['pro']
|
|
51
|
+
*/
|
|
52
|
+
export async function detectInstalledTiers(projectRoot) {
|
|
53
|
+
const chunks = [];
|
|
54
|
+
for (const rel of TIER_SCAN_FILES) {
|
|
55
|
+
const filePath = path.join(projectRoot, rel);
|
|
56
|
+
if (existsSync(filePath)) {
|
|
57
|
+
chunks.push(await readFile(filePath, 'utf-8').catch(() => ''));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
for (const rel of TIER_SCAN_DIRS) {
|
|
61
|
+
const dir = path.join(projectRoot, rel);
|
|
62
|
+
if (!existsSync(dir)) continue;
|
|
63
|
+
const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
64
|
+
for (const entry of entries) {
|
|
65
|
+
if (!entry.isFile() || !/\.(md|mdc)$/.test(entry.name)) continue;
|
|
66
|
+
chunks.push(await readFile(path.join(dir, entry.name), 'utf-8').catch(() => ''));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const text = chunks.join('\n');
|
|
70
|
+
const tiers = [];
|
|
71
|
+
for (const [tier, envVar] of Object.entries(TIER_ENV_VARS)) {
|
|
72
|
+
const marker = new RegExp(`${envVar}|rune-tier:\\s*${tier}\\b`);
|
|
73
|
+
if (marker.test(text)) tiers.push(tier);
|
|
74
|
+
}
|
|
75
|
+
return tiers;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Detect the preset the project was installed with, by asking each detected
|
|
80
|
+
* platform adapter for its status. A `mixed` install resolves to `gentle`
|
|
81
|
+
* (same fallback the drift reporter uses for canonical comparison).
|
|
82
|
+
*
|
|
83
|
+
* @param {string} projectRoot
|
|
84
|
+
* @returns {Promise<'gentle'|'strict'|null>} null when nothing is installed
|
|
85
|
+
*/
|
|
86
|
+
export async function detectInstalledPreset(projectRoot) {
|
|
87
|
+
for (const id of detectPlatforms(projectRoot)) {
|
|
88
|
+
const status = await ADAPTERS[id].status(projectRoot);
|
|
89
|
+
if (!status.installed || !status.preset) continue;
|
|
90
|
+
if (status.preset === 'mixed') return 'gentle';
|
|
91
|
+
if (status.preset === 'gentle' || status.preset === 'strict') return status.preset;
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Default command runner — thin execFile wrapper that never throws.
|
|
98
|
+
* @returns {Promise<{code: number, stdout: string, stderr: string}>}
|
|
99
|
+
*/
|
|
100
|
+
function defaultExec(cmd, argv) {
|
|
101
|
+
return new Promise((resolve) => {
|
|
102
|
+
execFile(cmd, argv, { windowsHide: true }, (err, stdout, stderr) => {
|
|
103
|
+
if (err) {
|
|
104
|
+
resolve({
|
|
105
|
+
code: typeof err.code === 'number' ? err.code : 1,
|
|
106
|
+
stdout: stdout ?? '',
|
|
107
|
+
stderr: stderr || err.message,
|
|
108
|
+
});
|
|
109
|
+
} else {
|
|
110
|
+
resolve({ code: 0, stdout: stdout ?? '', stderr: stderr ?? '' });
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* `git pull --ff-only` each detected tier repo.
|
|
118
|
+
*
|
|
119
|
+
* Statuses per tier:
|
|
120
|
+
* - absent — tier not detected (no env var / sibling / well-known path)
|
|
121
|
+
* - skipped — detected but not a git checkout (npx/tarball install)
|
|
122
|
+
* - pulled — git pull succeeded
|
|
123
|
+
* - failed — git pull failed (dirty tree, auth, diverged…) → result.ok=false
|
|
124
|
+
*
|
|
125
|
+
* @param {{ detected: ReturnType<typeof detectTiers>, exec?: typeof defaultExec }} opts
|
|
126
|
+
* @returns {Promise<{ ok: boolean, results: Array<{tier: string, root: string|null, status: string, detail: string}> }>}
|
|
127
|
+
*/
|
|
128
|
+
export async function pullTierRepos({ detected, exec = defaultExec }) {
|
|
129
|
+
const results = [];
|
|
130
|
+
for (const tier of Object.keys(TIER_ENV_VARS)) {
|
|
131
|
+
const info = detected[tier];
|
|
132
|
+
if (!info) {
|
|
133
|
+
results.push({ tier, root: null, status: 'absent', detail: 'not detected — skipping' });
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
// detectTiers points at <tierRoot>/hooks/manifest.json — walk back to the root.
|
|
137
|
+
const tierRoot = path.resolve(path.dirname(path.dirname(info.path)));
|
|
138
|
+
if (!existsSync(path.join(tierRoot, '.git'))) {
|
|
139
|
+
results.push({
|
|
140
|
+
tier,
|
|
141
|
+
root: tierRoot,
|
|
142
|
+
status: 'skipped',
|
|
143
|
+
detail: `${tierRoot} is not a git repo — pull skipped (update it the way it was installed)`,
|
|
144
|
+
});
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
const { code, stdout, stderr } = await exec('git', ['-C', tierRoot, 'pull', '--ff-only']);
|
|
148
|
+
if (code === 0) {
|
|
149
|
+
const summary = (stdout.trim().split('\n').pop() || 'done').trim();
|
|
150
|
+
results.push({ tier, root: tierRoot, status: 'pulled', detail: summary });
|
|
151
|
+
} else {
|
|
152
|
+
results.push({ tier, root: tierRoot, status: 'failed', detail: (stderr || stdout).trim() });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return { ok: results.every((r) => r.status !== 'failed'), results };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Compiled-output verification. Only meaningful for build-pipeline projects
|
|
160
|
+
* (rune.config.json with a non-Claude platform) — Claude Code loads source
|
|
161
|
+
* SKILL.md files natively, so there is no compiled output to check.
|
|
162
|
+
*/
|
|
163
|
+
async function defaultDoctorCheck(projectRoot, runeRoot) {
|
|
164
|
+
const configPath = path.join(projectRoot, 'rune.config.json');
|
|
165
|
+
if (!existsSync(configPath)) {
|
|
166
|
+
return { skipped: true, reason: 'no rune.config.json — compiled-output check skipped' };
|
|
167
|
+
}
|
|
168
|
+
let config;
|
|
169
|
+
try {
|
|
170
|
+
config = JSON.parse(await readFile(configPath, 'utf-8'));
|
|
171
|
+
} catch (err) {
|
|
172
|
+
return { skipped: true, reason: `rune.config.json unreadable (${err.message}) — compiled-output check skipped` };
|
|
173
|
+
}
|
|
174
|
+
if (!config.platform || config.platform === 'claude') {
|
|
175
|
+
return { skipped: true, reason: 'Claude Code native — no compiled output to check' };
|
|
176
|
+
}
|
|
177
|
+
const { runDoctor } = await import('../doctor.js');
|
|
178
|
+
const { getAdapter } = await import('../adapters/index.js');
|
|
179
|
+
const resolvedRuneRoot = config.source === '@rune-kit/rune' ? runeRoot : config.source || runeRoot;
|
|
180
|
+
const results = await runDoctor({
|
|
181
|
+
outputRoot: projectRoot,
|
|
182
|
+
adapter: getAdapter(config.platform),
|
|
183
|
+
config,
|
|
184
|
+
runeRoot: resolvedRuneRoot,
|
|
185
|
+
});
|
|
186
|
+
return { skipped: false, healthy: results.healthy, results };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* The full update flow. Pure orchestration — every side-effecting collaborator
|
|
191
|
+
* is injectable via `deps` for tests.
|
|
192
|
+
*
|
|
193
|
+
* @param {object} opts
|
|
194
|
+
* @param {string} opts.projectRoot
|
|
195
|
+
* @param {string} opts.runeRoot
|
|
196
|
+
* @param {object} [opts.args] — CLI flags: no-pull, preset, tier, dry
|
|
197
|
+
* @param {object} [opts.deps] — { exec, runSetupFn, checkHookDriftFn, doctorFn, skillTarget, wellKnownPaths }
|
|
198
|
+
* @returns {Promise<object>} structured result for formatUpdateResult
|
|
199
|
+
*/
|
|
200
|
+
export async function runUpdate({ projectRoot, runeRoot, args = {}, deps = {} }) {
|
|
201
|
+
const notes = [];
|
|
202
|
+
|
|
203
|
+
// ── 0. Something to update? ──
|
|
204
|
+
const platforms = detectPlatforms(projectRoot);
|
|
205
|
+
if (platforms.length === 0) {
|
|
206
|
+
return {
|
|
207
|
+
ok: false,
|
|
208
|
+
reason:
|
|
209
|
+
'No Rune installation detected in this project — nothing to update. Run `npx @rune-kit/rune setup` first.',
|
|
210
|
+
pull: { ok: true, results: [] },
|
|
211
|
+
notes,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ── 1. Pull tier repos (fail loud on pull errors) ──
|
|
216
|
+
const detected = detectTiers(projectRoot, deps.wellKnownPaths ? { wellKnownPaths: deps.wellKnownPaths } : {});
|
|
217
|
+
let pull;
|
|
218
|
+
if (args['no-pull'] || args.dry) {
|
|
219
|
+
const why = args['no-pull'] ? '--no-pull' : '--dry';
|
|
220
|
+
pull = {
|
|
221
|
+
ok: true,
|
|
222
|
+
results: Object.keys(TIER_ENV_VARS).map((tier) => ({
|
|
223
|
+
tier,
|
|
224
|
+
root: null,
|
|
225
|
+
status: detected[tier] ? 'skipped' : 'absent',
|
|
226
|
+
detail: detected[tier] ? `pull skipped (${why})` : 'not detected — skipping',
|
|
227
|
+
})),
|
|
228
|
+
};
|
|
229
|
+
} else {
|
|
230
|
+
pull = await pullTierRepos({ detected, exec: deps.exec });
|
|
231
|
+
}
|
|
232
|
+
if (!pull.ok) {
|
|
233
|
+
return {
|
|
234
|
+
ok: false,
|
|
235
|
+
reason: 'tier pull failed — resolve it manually (dirty tree? auth?) then re-run `rune update`',
|
|
236
|
+
pull,
|
|
237
|
+
notes,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// ── 2. Re-run managed setup, reusing what is already installed ──
|
|
242
|
+
const preset = args.preset || (await detectInstalledPreset(projectRoot)) || 'gentle';
|
|
243
|
+
let tiers;
|
|
244
|
+
if (args.tier) {
|
|
245
|
+
tiers = String(args.tier)
|
|
246
|
+
.split(',')
|
|
247
|
+
.map((t) => t.trim())
|
|
248
|
+
.filter(Boolean);
|
|
249
|
+
} else {
|
|
250
|
+
const installed = await detectInstalledTiers(projectRoot);
|
|
251
|
+
tiers = installed.filter((tier) => detected[tier]);
|
|
252
|
+
for (const tier of installed) {
|
|
253
|
+
if (!detected[tier]) {
|
|
254
|
+
const envVar = TIER_ENV_VARS[tier] || `RUNE_${tier.toUpperCase()}_ROOT`;
|
|
255
|
+
notes.push(
|
|
256
|
+
`${tier}: hooks are installed but the tier repo was not found — skipping. Set $${envVar} or clone the repo next to Free, then re-run \`rune update\`.`,
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const codexHooksPath = path.join(projectRoot, CODEX_HOOKS_REL_PATH);
|
|
263
|
+
const codexBefore = existsSync(codexHooksPath) ? await readFile(codexHooksPath, 'utf-8').catch(() => null) : null;
|
|
264
|
+
|
|
265
|
+
const setupArgs = {
|
|
266
|
+
here: true,
|
|
267
|
+
preset,
|
|
268
|
+
dry: args.dry,
|
|
269
|
+
...(tiers.length > 0 ? { tier: tiers.join(',') } : { 'no-tier': true }),
|
|
270
|
+
};
|
|
271
|
+
const setup = await (deps.runSetupFn || runSetup)({
|
|
272
|
+
projectRoot,
|
|
273
|
+
runeRoot,
|
|
274
|
+
args: setupArgs,
|
|
275
|
+
skillTarget: deps.skillTarget,
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
const codexAfter = existsSync(codexHooksPath) ? await readFile(codexHooksPath, 'utf-8').catch(() => null) : null;
|
|
279
|
+
const codexReTrust = platforms.includes('codex') && codexBefore !== codexAfter;
|
|
280
|
+
|
|
281
|
+
// ── 3. Verify: compiled output + hook drift ──
|
|
282
|
+
const doctor = await (deps.doctorFn || defaultDoctorCheck)(projectRoot, runeRoot);
|
|
283
|
+
const drift = await (deps.checkHookDriftFn || checkHookDrift)(projectRoot);
|
|
284
|
+
|
|
285
|
+
return { ok: true, pull, platforms, preset, tiers, setup, drift, doctor, codexReTrust, notes };
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const PULL_ICONS = { pulled: '✓', absent: '·', skipped: '·', failed: '✗' };
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Render a runUpdate result for console output.
|
|
292
|
+
* @param {Awaited<ReturnType<typeof runUpdate>>} result
|
|
293
|
+
* @returns {string}
|
|
294
|
+
*/
|
|
295
|
+
export function formatUpdateResult(result) {
|
|
296
|
+
const lines = [];
|
|
297
|
+
lines.push('');
|
|
298
|
+
lines.push(' Rune Update');
|
|
299
|
+
lines.push(' ────────────');
|
|
300
|
+
|
|
301
|
+
if (result.pull?.results?.length > 0) {
|
|
302
|
+
lines.push(' Tier repos:');
|
|
303
|
+
for (const r of result.pull.results) {
|
|
304
|
+
const icon = PULL_ICONS[r.status] || '·';
|
|
305
|
+
const label = r.status === 'failed' ? 'pull FAILED' : r.status;
|
|
306
|
+
lines.push(` ${icon} ${r.tier} — ${label}: ${r.detail}`);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (!result.ok) {
|
|
311
|
+
lines.push('');
|
|
312
|
+
lines.push(` ✗ Update aborted: ${result.reason}`);
|
|
313
|
+
lines.push('');
|
|
314
|
+
return lines.join('\n');
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
lines.push(' Setup rewrite:');
|
|
318
|
+
lines.push(
|
|
319
|
+
` Platforms: ${(result.setup?.platforms || result.platforms || []).join(', ') || '—'} | Preset: ${result.preset} | Tiers: Free${result.tiers?.length ? ` + ${result.tiers.join(' + ')}` : ''}`,
|
|
320
|
+
);
|
|
321
|
+
for (const sr of result.setup?.skillResults || []) {
|
|
322
|
+
if (sr.installed?.length > 0) {
|
|
323
|
+
lines.push(` Skills: ${sr.tier}: ${sr.installed.length} installed`);
|
|
324
|
+
} else if (sr.skipped?.length > 0) {
|
|
325
|
+
lines.push(` Skills: ${sr.tier}: ${sr.skipped.length} already present / skipped`);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
if (result.setup && result.setup.written === false) {
|
|
329
|
+
lines.push(' (dry-run — no files written)');
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
lines.push(' Verify:');
|
|
333
|
+
if (result.doctor?.skipped) {
|
|
334
|
+
lines.push(` Doctor: skipped — ${result.doctor.reason}`);
|
|
335
|
+
} else if (result.doctor) {
|
|
336
|
+
lines.push(` Doctor: ${result.doctor.healthy ? '✓ healthy' : '✗ issues found — run `rune doctor` for detail'}`);
|
|
337
|
+
}
|
|
338
|
+
if (result.drift?.summary) {
|
|
339
|
+
const s = result.drift.summary;
|
|
340
|
+
lines.push(` Hook drift: ${s.drifted} drifted, ${s.missing} missing, ${s.errors} error(s)`);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
for (const note of result.notes || []) {
|
|
344
|
+
lines.push(` ⚠ ${note}`);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (result.codexReTrust) {
|
|
348
|
+
lines.push('');
|
|
349
|
+
lines.push(' ⚠ Codex: .codex/hooks.json changed — open /hooks in Codex to review and re-trust the definitions.');
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
lines.push('');
|
|
353
|
+
return lines.join('\n');
|
|
354
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rune-kit/rune",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.30.0",
|
|
4
4
|
"description": "66-skill mesh for AI coding assistants — native lifecycle hooks for Claude Code and Codex, 5-layer architecture, 248 connections + 45 signals, and a 13-platform compiler.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/skills/audit/SKILL.md
CHANGED
|
@@ -5,7 +5,7 @@ metadata:
|
|
|
5
5
|
author: runedev
|
|
6
6
|
version: "0.5.0"
|
|
7
7
|
layer: L2
|
|
8
|
-
model:
|
|
8
|
+
model: opus
|
|
9
9
|
group: quality
|
|
10
10
|
tools: "Read, Bash, Glob, Grep"
|
|
11
11
|
emit: audit.complete
|
|
@@ -585,7 +585,7 @@ LOW — Nice to have. Style inconsistencies, minor refactors, doc gaps.
|
|
|
585
585
|
INFO — Observation only. Architecture notes, tech debt acknowledgment.
|
|
586
586
|
```
|
|
587
587
|
|
|
588
|
-
Apply
|
|
588
|
+
Apply the Falsification Pass (`../review/SKILL.md` → Step 6): drop a finding only when what you read contains direct counter-evidence against its key claim — never merely because you are unsure. A finding you cannot disprove is reported and typed `ASSUMED` with the unchecked premise named. Consolidate similar issues (e.g., "12 functions missing error handling in src/services/" — not 12 separate findings). Adapt judgment to project type (a `console.log` in a CLI tool is fine; in a production API handler, it's not).
|
|
589
589
|
|
|
590
590
|
## Output Format
|
|
591
591
|
|
|
@@ -644,7 +644,7 @@ Report saved to: AUDIT-REPORT.md
|
|
|
644
644
|
|
|
645
645
|
1. MUST complete all 8 phases (Phase 8 may report "no data" if .rune/metrics/ doesn't exist yet) — if any phase is skipped, state explicitly which phase and why
|
|
646
646
|
2. MUST delegate Phase 1 to dependency-doctor and Phase 2 to sentinel — no manual replacements
|
|
647
|
-
3. MUST apply
|
|
647
|
+
3. MUST apply the Falsification Pass — drop findings only on direct counter-evidence, never on uncertainty; consolidate similar issues
|
|
648
648
|
4. MUST include at least 3 positive findings — an audit with no positives is incomplete
|
|
649
649
|
5. MUST produce quantified health scores (1-10 per dimension) — not vague "needs work"
|
|
650
650
|
6. MUST NOT fabricate findings — every finding requires a specific file:line citation
|
|
@@ -6,7 +6,7 @@ metadata:
|
|
|
6
6
|
author: runedev
|
|
7
7
|
version: "1.3.0"
|
|
8
8
|
layer: L3
|
|
9
|
-
model:
|
|
9
|
+
model: sonnet
|
|
10
10
|
group: state
|
|
11
11
|
tools: "Read, Glob, Grep"
|
|
12
12
|
emit: context.preview, output.density.set
|
|
@@ -321,7 +321,7 @@ When ORANGE or RED is reached, use this table to determine whether compaction is
|
|
|
321
321
|
|
|
322
322
|
### Mid-Loop Compaction (Phase 4 Emergency)
|
|
323
323
|
|
|
324
|
-
|
|
324
|
+
Compact during the run, not just at the session boundary.
|
|
325
325
|
|
|
326
326
|
When context hits RED during Phase 4 (implementation), compaction IS possible at **clean split points**:
|
|
327
327
|
|
package/skills/cook/SKILL.md
CHANGED
|
@@ -640,7 +640,7 @@ Escalation chain: debug-fix (3x) → re-plan (1x) → brainstorm rescue (1x) →
|
|
|
640
640
|
|
|
641
641
|
### Structured Escalation Report
|
|
642
642
|
|
|
643
|
-
|
|
643
|
+
After 3 retry failures, structured escalation prevents cargo-cult retrying.
|
|
644
644
|
|
|
645
645
|
When escalation chain exhausts (all retries hit) or cook returns `BLOCKED`, produce a Structured Escalation Report instead of a vague "I can't do this":
|
|
646
646
|
|
|
@@ -678,7 +678,7 @@ When escalation chain exhausts (all retries hit) or cook returns `BLOCKED`, prod
|
|
|
678
678
|
|
|
679
679
|
### Subagent Question Gate
|
|
680
680
|
|
|
681
|
-
|
|
681
|
+
Subagents that start work without asking questions frequently produce the wrong thing.
|
|
682
682
|
|
|
683
683
|
Before dispatching a sub-skill (fix, test, review) for a non-trivial task (3+ files OR ambiguous scope):
|
|
684
684
|
|
|
@@ -4,7 +4,7 @@ The Analysis Paralysis Guard (5-read counter in SKILL.md) catches obvious paraly
|
|
|
4
4
|
This catches **same-input-same-output loops** — where the agent keeps calling the same tool
|
|
5
5
|
with the same arguments and getting the same result, making zero progress.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Hash-based detection distinguishes true stuck loops from productive retries.
|
|
8
8
|
|
|
9
9
|
## Detection Logic
|
|
10
10
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Mid-Run Signal Detection — Two-Stage Intent Classification
|
|
2
2
|
|
|
3
3
|
When user sends a message DURING cook execution (mid-phase), classify intent before acting.
|
|
4
|
-
|
|
4
|
+
Two-stage intent classification prevents expensive LLM calls for simple signals.
|
|
5
5
|
|
|
6
6
|
## Stage 1 — Keyword Fast-Path
|
|
7
7
|
|
package/skills/debug/SKILL.md
CHANGED
|
@@ -5,7 +5,7 @@ metadata:
|
|
|
5
5
|
author: runedev
|
|
6
6
|
version: "1.4.0"
|
|
7
7
|
layer: L2
|
|
8
|
-
model:
|
|
8
|
+
model: opus
|
|
9
9
|
group: development
|
|
10
10
|
tools: "Read, Bash, Glob, Grep"
|
|
11
11
|
emit: bug.diagnosed, agent.stuck
|
|
@@ -282,7 +282,7 @@ Track fix attempts in the Debug Report. If this is attempt N>1 for the same symp
|
|
|
282
282
|
|
|
283
283
|
### 3+ Fixes as Architectural Signal
|
|
284
284
|
|
|
285
|
-
|
|
285
|
+
Each fix revealing new problems elsewhere is a structural issue, not a bug hunt.
|
|
286
286
|
|
|
287
287
|
When 3+ **distinct** fixes fail (not retries of the same fix), STOP treating it as a bug:
|
|
288
288
|
|
package/skills/design/SKILL.md
CHANGED
package/skills/docs/SKILL.md
CHANGED
|
@@ -177,7 +177,7 @@ Delegate to `rune:git changelog` to produce a changelog entry from commits since
|
|
|
177
177
|
|
|
178
178
|
#### Step 4 — Cross-Doc Consistency Pass
|
|
179
179
|
|
|
180
|
-
|
|
180
|
+
Cross-document consistency prevents the second-most-common docs problem: docs that exist but contradict each other.
|
|
181
181
|
|
|
182
182
|
After updating any doc, verify consistency across all project documentation:
|
|
183
183
|
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: docs-seeker
|
|
3
3
|
description: "Find documentation for APIs, libraries, and error messages. Looks up official docs, changelog entries, and migration guides."
|
|
4
|
+
context: fork
|
|
5
|
+
agent: general-purpose
|
|
6
|
+
model: haiku
|
|
4
7
|
metadata:
|
|
5
8
|
author: runedev
|
|
6
9
|
version: "0.2.0"
|
|
@@ -5,7 +5,7 @@ metadata:
|
|
|
5
5
|
author: runedev
|
|
6
6
|
version: "0.3.0"
|
|
7
7
|
layer: L3
|
|
8
|
-
model:
|
|
8
|
+
model: sonnet
|
|
9
9
|
group: validation
|
|
10
10
|
tools: "Read, Bash, Glob, Grep"
|
|
11
11
|
---
|
|
@@ -78,7 +78,7 @@ If export not found → mark as **WARN** (symbol may not be exported).
|
|
|
78
78
|
|
|
79
79
|
### Step 3 — Verify external packages (Dependency Check Before Import)
|
|
80
80
|
|
|
81
|
-
|
|
81
|
+
Before importing ANY third-party library, check the dependency manifest.
|
|
82
82
|
|
|
83
83
|
Use `Read` on the project's dependency manifest to confirm each external package is listed:
|
|
84
84
|
|
package/skills/incident/SKILL.md
CHANGED