@rune-kit/rune 2.22.0 → 2.22.2
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
CHANGED
|
@@ -83,9 +83,9 @@ _Methodology: Claude Code CLI headless mode (`claude -p --output-format json`),
|
|
|
83
83
|
|
|
84
84
|
---
|
|
85
85
|
|
|
86
|
-
## What's New (v2.22.
|
|
86
|
+
## What's New (v2.22.2 — Convergence, Dogfooded)
|
|
87
87
|
|
|
88
|
-
> **v2.22.
|
|
88
|
+
> **v2.22.2 (2026-07-04):** Patch: `rune setup` now installs tier skills into the Claude Code PLUGIN CACHE (newest version dir) instead of the executing package's own root — npx runs were copying Pro skills into npx's ephemeral cache (invisible to the plugin runtime, 'Unknown skill: rune:autopilot' returned), and source-checkout runs polluted the git tree. **v2.22.1:** tier-hook loader accepts PreCompact + SessionStart lifecycle events — required by Pro hooks v1.2.0 (context-reset). **v2.22.0:** The v2.21.0 gates went through a live-fire dogfood: fresh executor agents (zero author context) ran `converge` and `verification` Level 3.5 against a fixture with a dead Submit button, a handler-less Export button, a navigation-anchor decoy, and a declared placeholder. **Both gates caught the dead button with file:line evidence and zero false positives** — and the 16 ambiguities the executors reported became spec fixes: converge v0.2.0 adds a `deferred-debt` class (declared design debt can no longer force a false escalation), a `Plan Claims vs Reality` section (tasks marked `[x]` whose code doesn't exist — surfaced first-class), and derived story verdicts; verification v0.7.0 gets FAIL-dominates precedence, per-route reverse checks, and server/static entry-point exemptions. The dogfood fixture itself shipped as the seed of **`npm run eval`** — a behavioral eval harness that runs a fresh headless agent against fixture repos and asserts outcomes, because structural validation can't prove a skill makes an agent behave. Pro `autopilot` v1.6.0 now explicitly runs Phase 6.5 CONVERGE in autonomous mode.
|
|
89
89
|
|
|
90
90
|
### Previous (v2.21.0 — Convergence)
|
|
91
91
|
|
|
@@ -161,6 +161,21 @@ describe('validateManifest', () => {
|
|
|
161
161
|
});
|
|
162
162
|
assert.strictEqual(m.entries[0].matcher, null);
|
|
163
163
|
});
|
|
164
|
+
|
|
165
|
+
test('accepts PreCompact and SessionStart lifecycle events without matcher', () => {
|
|
166
|
+
// Pro hooks manifest v1.2.0 wires context-reset on both — Free must load it (was rejected pre-v2.22.1)
|
|
167
|
+
const m = validateManifest({
|
|
168
|
+
tier: 'pro',
|
|
169
|
+
entries: [
|
|
170
|
+
{ id: 'context-reset-precompact', event: 'PreCompact', command: 'node context-reset.cjs' },
|
|
171
|
+
{ id: 'context-reset-sessionstart', event: 'SessionStart', command: 'node context-reset.cjs' },
|
|
172
|
+
],
|
|
173
|
+
});
|
|
174
|
+
assert.strictEqual(m.entries.length, 2);
|
|
175
|
+
assert.strictEqual(m.entries[0].event, 'PreCompact');
|
|
176
|
+
assert.strictEqual(m.entries[0].matcher, null);
|
|
177
|
+
assert.strictEqual(m.entries[1].event, 'SessionStart');
|
|
178
|
+
});
|
|
164
179
|
});
|
|
165
180
|
|
|
166
181
|
describe('loadTierManifest + locateTierManifest', () => {
|
|
@@ -5,7 +5,13 @@ import { platform as osPlatform, tmpdir } from 'node:os';
|
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { afterEach, beforeEach, describe, test } from 'node:test';
|
|
7
7
|
import { resolveTier } from '../commands/hooks/tiers.js';
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
detectTiers,
|
|
10
|
+
formatSetupResult,
|
|
11
|
+
installTierSkills,
|
|
12
|
+
resolveSkillInstallRoot,
|
|
13
|
+
runSetup,
|
|
14
|
+
} from '../commands/setup.js';
|
|
9
15
|
|
|
10
16
|
let tmpRoot;
|
|
11
17
|
|
|
@@ -366,6 +372,9 @@ describe('runSetup — Pro skill installation (regression: rune:autopilot Unknow
|
|
|
366
372
|
projectRoot,
|
|
367
373
|
runeRoot,
|
|
368
374
|
args: { here: true, preset: 'gentle', tier: 'pro' },
|
|
375
|
+
// Pin the install target — without this, resolveSkillInstallRoot would
|
|
376
|
+
// target the REAL plugin cache on dev machines (test side-effect).
|
|
377
|
+
skillTarget: runeRoot,
|
|
369
378
|
});
|
|
370
379
|
|
|
371
380
|
assert.deepStrictEqual(result.tiers, ['pro']);
|
|
@@ -481,3 +490,43 @@ describe('formatSetupResult', () => {
|
|
|
481
490
|
assert.match(out, /⚠ \.\.\/escape: rejected: unsafe directory name/);
|
|
482
491
|
});
|
|
483
492
|
});
|
|
493
|
+
|
|
494
|
+
describe('resolveSkillInstallRoot', () => {
|
|
495
|
+
let home;
|
|
496
|
+
|
|
497
|
+
beforeEach(async () => {
|
|
498
|
+
home = await mkdtemp(path.join(tmpdir(), 'rune-home-'));
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
afterEach(async () => {
|
|
502
|
+
await rm(home, { recursive: true, force: true });
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
test('targets the NEWEST plugin-cache version dir when the cache exists', async () => {
|
|
506
|
+
// npx/source-checkout executions must NOT install into their own runeRoot
|
|
507
|
+
// when a plugin cache exists — the plugin runtime only reads the cache.
|
|
508
|
+
const cache = path.join(home, '.claude', 'plugins', 'cache', 'rune-kit', 'rune');
|
|
509
|
+
await mkdir(path.join(cache, '2.17.1', 'skills'), { recursive: true });
|
|
510
|
+
await mkdir(path.join(cache, '2.22.1', 'skills'), { recursive: true });
|
|
511
|
+
await mkdir(path.join(cache, '2.9.0', 'skills'), { recursive: true });
|
|
512
|
+
|
|
513
|
+
const target = resolveSkillInstallRoot('/some/npx/ephemeral/root', { homedir: home });
|
|
514
|
+
assert.strictEqual(target.source, 'plugin-cache');
|
|
515
|
+
assert.strictEqual(target.root, path.join(cache, '2.22.1'));
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
test('ignores cache version dirs without a skills/ folder', async () => {
|
|
519
|
+
const cache = path.join(home, '.claude', 'plugins', 'cache', 'rune-kit', 'rune');
|
|
520
|
+
await mkdir(path.join(cache, '2.22.1', 'skills'), { recursive: true });
|
|
521
|
+
await mkdir(path.join(cache, '2.23.0'), { recursive: true }); // half-installed, no skills/
|
|
522
|
+
|
|
523
|
+
const target = resolveSkillInstallRoot('/fallback', { homedir: home });
|
|
524
|
+
assert.strictEqual(target.root, path.join(cache, '2.22.1'));
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
test('falls back to runeRoot when no plugin cache exists', async () => {
|
|
528
|
+
const target = resolveSkillInstallRoot('/plain/rune/root', { homedir: home });
|
|
529
|
+
assert.strictEqual(target.source, 'rune-root');
|
|
530
|
+
assert.strictEqual(target.root, '/plain/rune/root');
|
|
531
|
+
});
|
|
532
|
+
});
|
|
@@ -78,7 +78,18 @@ export function compareSemver(a, b) {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
/** Valid event names a manifest entry may declare. */
|
|
81
|
-
const VALID_EVENTS = new Set([
|
|
81
|
+
const VALID_EVENTS = new Set([
|
|
82
|
+
'UserPromptSubmit',
|
|
83
|
+
'PreToolUse',
|
|
84
|
+
'PostToolUse',
|
|
85
|
+
'Stop',
|
|
86
|
+
'statusLine',
|
|
87
|
+
'PreCompact',
|
|
88
|
+
'SessionStart',
|
|
89
|
+
]);
|
|
90
|
+
|
|
91
|
+
/** Lifecycle events where `matcher` is optional (PreCompact matches manual|auto, SessionStart matches startup|resume — both default to all when omitted). */
|
|
92
|
+
const MATCHER_OPTIONAL_EVENTS = new Set(['statusLine', 'Stop', 'PreCompact', 'SessionStart']);
|
|
82
93
|
|
|
83
94
|
/** Tier names must be simple lowercase identifiers. Blocks `../../etc` and similar traversal. */
|
|
84
95
|
const TIER_NAME_RE = /^[a-z][a-z0-9-]{0,31}$/;
|
|
@@ -178,7 +189,7 @@ export function validateManifest(manifest, source = '<memory>') {
|
|
|
178
189
|
if (raw.matcher !== undefined && typeof raw.matcher !== 'string') {
|
|
179
190
|
throw new Error(`${where}: 'matcher' must be a string if present`);
|
|
180
191
|
}
|
|
181
|
-
if (
|
|
192
|
+
if (!MATCHER_OPTIONAL_EVENTS.has(raw.event) && !raw.matcher) {
|
|
182
193
|
throw new Error(`${where}: '${raw.event}' requires a 'matcher' string (e.g. 'Edit|Write' or '.*')`);
|
|
183
194
|
}
|
|
184
195
|
if (raw.globs !== undefined && !Array.isArray(raw.globs)) {
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* 3. Well-known paths: `D:/Project/Rune/Pro`, `~/rune-pro`, etc.
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
23
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
24
24
|
import { cp, readdir, readFile, rm } from 'node:fs/promises';
|
|
25
25
|
import os from 'node:os';
|
|
26
26
|
import path from 'node:path';
|
|
@@ -28,6 +28,43 @@ import { createInterface } from 'node:readline';
|
|
|
28
28
|
import { installHooks } from './hooks/install.js';
|
|
29
29
|
import { resolveTier, TIER_ENV_VARS } from './hooks/tiers.js';
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Resolve where tier skills should actually be installed so the Claude Code
|
|
33
|
+
* plugin runtime SEES them.
|
|
34
|
+
*
|
|
35
|
+
* `runeRoot` is the root of whichever rune.js is executing — which is the
|
|
36
|
+
* WRONG target in two common cases:
|
|
37
|
+
* - `npx @rune-kit/rune setup` → runeRoot is npx's ephemeral cache; skills
|
|
38
|
+
* copied there are invisible to the plugin runtime (and soon deleted)
|
|
39
|
+
* - running from a source checkout → skills are copied into the git repo,
|
|
40
|
+
* polluting the MIT source tree with paid-tier content
|
|
41
|
+
*
|
|
42
|
+
* The plugin runtime reads `~/.claude/plugins/cache/rune-kit/rune/<ver>/skills/`.
|
|
43
|
+
* If that cache exists, target its NEWEST version dir; otherwise fall back to
|
|
44
|
+
* runeRoot (plugin-cache executions land here anyway — cache is runeRoot).
|
|
45
|
+
*
|
|
46
|
+
* @param {string} runeRoot
|
|
47
|
+
* @param {{ homedir?: string }} [opts] — injectable for tests
|
|
48
|
+
* @returns {{ root: string, source: 'plugin-cache' | 'rune-root' }}
|
|
49
|
+
*/
|
|
50
|
+
export function resolveSkillInstallRoot(runeRoot, opts = {}) {
|
|
51
|
+
const home = opts.homedir || os.homedir();
|
|
52
|
+
const cacheRoot = path.join(home, '.claude', 'plugins', 'cache', 'rune-kit', 'rune');
|
|
53
|
+
if (existsSync(cacheRoot)) {
|
|
54
|
+
const versions = readdirSync(cacheRoot)
|
|
55
|
+
.filter((d) => /^\d+\.\d+\.\d+$/.test(d) && existsSync(path.join(cacheRoot, d, 'skills')))
|
|
56
|
+
.sort((a, b) => {
|
|
57
|
+
const [a1, a2, a3] = a.split('.').map(Number);
|
|
58
|
+
const [b1, b2, b3] = b.split('.').map(Number);
|
|
59
|
+
return b1 - a1 || b2 - a2 || b3 - a3;
|
|
60
|
+
});
|
|
61
|
+
if (versions.length > 0) {
|
|
62
|
+
return { root: path.join(cacheRoot, versions[0]), source: 'plugin-cache' };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return { root: runeRoot, source: 'rune-root' };
|
|
66
|
+
}
|
|
67
|
+
|
|
31
68
|
export const WELL_KNOWN_TIER_PATHS = {
|
|
32
69
|
pro: ['D:/Project/Rune/Pro', path.join(os.homedir(), 'rune-pro'), path.join(os.homedir(), 'Project', 'Rune', 'Pro')],
|
|
33
70
|
business: [
|
|
@@ -41,7 +78,7 @@ export const WELL_KNOWN_TIER_PATHS = {
|
|
|
41
78
|
* @param {{ projectRoot: string, runeRoot: string, args: object }} opts
|
|
42
79
|
* @returns {Promise<{ scope: string, tiers: string[], preset: string, written: boolean, files: string[], notes: string[], skillResults: Array<{tier: string, installed: string[], skipped: Array<{skill: string, reason: string}>, reason: string|null}> }>}
|
|
43
80
|
*/
|
|
44
|
-
export async function runSetup({ projectRoot, runeRoot, args = {} }) {
|
|
81
|
+
export async function runSetup({ projectRoot, runeRoot, args = {}, skillTarget: skillTargetOverride }) {
|
|
45
82
|
const detected = detectTiers(projectRoot);
|
|
46
83
|
|
|
47
84
|
// Scope resolution
|
|
@@ -89,10 +126,14 @@ export async function runSetup({ projectRoot, runeRoot, args = {} }) {
|
|
|
89
126
|
dry: args.dry,
|
|
90
127
|
});
|
|
91
128
|
|
|
92
|
-
// Install tier skill files
|
|
93
|
-
// plugin
|
|
129
|
+
// Install tier skill files where the plugin runtime actually LOOKS — the
|
|
130
|
+
// Claude Code plugin cache when present, runeRoot otherwise. Without this
|
|
94
131
|
// step, paid tiers ship hooks only — `rune:autopilot` returns "Unknown skill"
|
|
95
132
|
// because Pro/skills/autopilot/ is invisible to the plugin runtime.
|
|
133
|
+
const skillTarget =
|
|
134
|
+
typeof skillTargetOverride === 'string'
|
|
135
|
+
? { root: skillTargetOverride, source: 'override' }
|
|
136
|
+
: (skillTargetOverride ?? resolveSkillInstallRoot(runeRoot));
|
|
96
137
|
const skillResults = [];
|
|
97
138
|
for (const tier of tiers) {
|
|
98
139
|
try {
|
|
@@ -100,7 +141,7 @@ export async function runSetup({ projectRoot, runeRoot, args = {} }) {
|
|
|
100
141
|
const skillResult = await installTierSkills({
|
|
101
142
|
tier,
|
|
102
143
|
tierManifest,
|
|
103
|
-
runeRoot,
|
|
144
|
+
runeRoot: skillTarget.root,
|
|
104
145
|
dry: args.dry,
|
|
105
146
|
});
|
|
106
147
|
skillResults.push(skillResult);
|
|
@@ -116,6 +157,7 @@ export async function runSetup({ projectRoot, runeRoot, args = {} }) {
|
|
|
116
157
|
preset,
|
|
117
158
|
detected,
|
|
118
159
|
skillResults,
|
|
160
|
+
skillTarget,
|
|
119
161
|
...result,
|
|
120
162
|
};
|
|
121
163
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rune-kit/rune",
|
|
3
|
-
"version": "2.22.
|
|
3
|
+
"version": "2.22.2",
|
|
4
4
|
"description": "65-skill mesh for AI coding assistants — runtime auto-discipline via native hooks (Claude/Cursor/Windsurf/Antigravity), 5-layer architecture, 204 connections + 43 signals, multi-platform compiler. converge (L3) scans spec vs code so dead-button/frontend-only implementations can't ship.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -3,7 +3,7 @@ name: preflight
|
|
|
3
3
|
description: "Pre-commit quality gate that catches 'almost right' code. Use when about to commit — auto-fires before commit to validate logic correctness, error handling, regressions, and completeness. Goes beyond linting."
|
|
4
4
|
metadata:
|
|
5
5
|
author: runedev
|
|
6
|
-
version: "1.2.
|
|
6
|
+
version: "1.2.1"
|
|
7
7
|
layer: L2
|
|
8
8
|
model: sonnet
|
|
9
9
|
group: quality
|
|
@@ -209,7 +209,7 @@ If a domain hook flags BLOCK, the overall preflight verdict is BLOCK regardless
|
|
|
209
209
|
| `openapi.*`, `*.graphql`, `*.proto` | API Contract | Breaking changes flagged, version bumped, deprecated fields documented |
|
|
210
210
|
| `docs/policies/*`, `PRIVACY*`, `TERMS*` | Legal/Compliance | No placeholder text, review date current, practice matches policy |
|
|
211
211
|
| `**/billing*`, `**/payment*`, `**/invoice*` | Financial | Decimal precision correct, currency locale-aware, no hardcoded rates |
|
|
212
|
-
| `*.tsx`, `*.jsx`, `*.svelte`, `*.vue`, `components/*` | UI/Frontend | Design token compliance, animation a11y, touch targets, visual hierarchy |
|
|
212
|
+
| `*.tsx`, `*.jsx`, `*.svelte`, `*.vue`, `*.html` (with script/template content), `components/*` | UI/Frontend | Design token compliance, animation a11y, touch targets, visual hierarchy |
|
|
213
213
|
| `skills/*/SKILL.md`, `extensions/*/PACK.md` | Rune Skill | Frontmatter valid, all required sections present, word count within layer budget |
|
|
214
214
|
| `*.test.*`, `*.spec.*`, `__tests__/*` | Test Quality | No `.skip`/`.only` left in, assertions present (not empty tests), no hardcoded timeouts |
|
|
215
215
|
|
|
@@ -225,7 +225,7 @@ For each detected domain, run its checks on the relevant files in the diff:
|
|
|
225
225
|
|
|
226
226
|
#### UI/Frontend Domain Checks
|
|
227
227
|
|
|
228
|
-
When UI/Frontend hook is triggered, run these checks on all `.tsx`/`.jsx`/`.svelte`/`.vue` files in the diff.
|
|
228
|
+
When UI/Frontend hook is triggered, run these checks on all `.tsx`/`.jsx`/`.svelte`/`.vue`/`.html` files in the diff (plain `.html` counts when it carries interactive markup or inline scripts — a vanilla-JS page is still a UI).
|
|
229
229
|
|
|
230
230
|
**Preamble — load design contract**: If `.rune/design-system.md` exists, read it once. Apply the project's **Scale Minimums** block over the defaults below (e.g., a project declaring `body ≥18px` should flag 16px body text). If the file is absent, use defaults and emit a LOW advisory: "No `.rune/design-system.md` — run `rune design` to lock visual decisions."
|
|
231
231
|
|