@rune-kit/rune 2.22.1 → 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.1 — Convergence, Dogfooded)
86
+ ## What's New (v2.22.2 — Convergence, Dogfooded)
87
87
 
88
- > **v2.22.1 (2026-07-03):** Patch: 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.
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
 
@@ -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 { detectTiers, formatSetupResult, installTierSkills, runSetup } from '../commands/setup.js';
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
+ });
@@ -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 into <runeRoot>/skills/ so Claude Code (and any
93
- // plugin-aware platform) discovers them alongside Free skills. Without this
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.1",
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.0"
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