@rune-kit/rune 2.30.1 → 2.30.3

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rune",
3
- "version": "2.30.1",
3
+ "version": "2.30.3",
4
4
  "description": "66-skill engineering mesh with Codex-native skills, subagents, synchronous lifecycle hooks, security gates, MCP-aware workflows, and project memory.",
5
5
  "author": {
6
6
  "name": "Rune Contributors"
package/README.md CHANGED
@@ -103,11 +103,19 @@ Plus **9 domain packs** (product, sales, data-science, support, growth, media, p
103
103
 
104
104
  ---
105
105
 
106
- ## What's New (v2.30.1Hooks That Land)
106
+ ## What's New (v2.30.3Say What You Mean)
107
+
108
+ > **v2.30.3 (2026-07-29):** Claude Code 2.1.218 changed `context: fork` to run **in the background by default** (`opt out with background: false`), and six Rune skills were relying on the old default without saying so — `cook`, `team`, `launch`, `rescue` (every L1 orchestrator), plus `scout` and `docs-seeker`. A background fork reports back as a task notification instead of returning in-line, which silently breaks any caller that needs the result: `cook` waiting on `scout` is exactly that shape, and it fails looking like the sub-step did nothing. All six now declare `background: false`, and `validate-skills.js` fails on any fork skill that leaves it unstated — writing that gate is what surfaced four of the six. Measured honestly: on 2.1.220 in headless mode a 12-second fork returned in-line either way, so this removes a dependency on an unstated default the vendor has announced changing, rather than fixing a reproduced break. 1,653 tests.
109
+
110
+ ### Previous (v2.30.2 — Blocked, With a Reason)
111
+
112
+ > **v2.30.2 (2026-07-29):** Finishes the hook I/O sweep, plus the fault that sweep uncovered. Claude Code shows the model **only stderr** for an exit-2 block, and `pre-tool-guard` explained itself on stdout — so a blocked read surfaced as `hook error: […]: No stderr output`. The block worked; the model just never learned why, and could only guess whether to stop or route around it. New `emitBlock()` writes the reason to stderr (Claude Code) and the envelope to stdout (Codex), then exits 2 — verified end-to-end by the model quoting the reason back. Three hooks v2.30.1 missed are also fixed: `quarantine` hand-built its envelope and shipped it with `process.stdout.write` right before `process.exit(0)`, while `auto-format` and `typecheck` printed bare `console.log` with no envelope at all (unflushed on Claude Code, rejected outright by Codex). Two new repo-wide gate tests — no hook writes stdout directly, no hook reads stdin asynchronously — so a new hook cannot reintroduce either. 1,651 tests.
113
+
114
+ #### Earlier (v2.30.1 — Hooks That Land)
107
115
 
108
116
  > **v2.30.1 (2026-07-29):** Five hooks had been running, exiting 0, and reaching nobody — nothing logged an error, so the hook layer looked healthy while contributing nothing. Two independent faults, each fatal on its own: **`process.stdout.write` from an exit handler is not guaranteed to reach a piped stdout** (most hooks emit via `captureConsole`, which flushes on exit — the envelope was built, "written", and lost; now `fs.writeSync(1, …)`), and **a hook that collects stdin with an async listener has its stdout discarded by Claude Code** even when the write is synchronous (new `hooks/lib/hook-stdin.cjs` reads with `readFileSync(0)`; `intent-router`, `context-watch`, `metrics-collector`, `pre-tool-guard`, `quarantine` converted). Both isolated by differential test — identical hooks, one variable at a time — and confirmed by the model quoting hook-injected context back. Separately, `intent-router` never had an index to read: `skill-index.json` is emitted at build time and Claude Code doesn't compile, so all five candidate paths were missing and it exited silently on every prompt since it shipped. New `scripts/build-skill-index.js` generates it at the plugin root with a `--check` staleness gate in CI. It was also registered `"async": true`, which drops `additionalContext` outright. 1,649 tests.
109
117
 
110
- ### Previous (v2.30.0 — Tier Restored)
118
+ #### Earlier (v2.30.0 — Tier Restored)
111
119
 
112
120
  > **v2.30.0 (2026-07-29):** Rune's model tier table had quietly stopped applying on Claude Code, and had been wrong in **23 of 66 places** for longer than that. `agents/*.md` is a hand-written parallel copy of each skill's tier; nothing generated it from `skills/` and no check compared the two, so they drifted — `cook` said opus in one file and sonnet in the other, `verification` said sonnet and haiku. `skills/*/SKILL.md` is now the single source of truth, every pair agrees, and a new **`validateAgentSync`** gate fails CI on any future drift, alongside a model-split check and a warning for a top-level `model` that cannot take effect. Where the two disagreed the tier was decided by role, with one rule overriding both old values: **a gate never runs below sonnet** — `completion-gate`, `constraint-check`, `integrity-check`, `hallucination-guard`, and `verification` exist to catch unverified claims, so running them cheaper than the agent they audit defeats the point. Separately: tiering only ever applied through spawned subagents, and on Opus 5 the harness tells the model not to spawn them unless asked — so every tier assignment had become a silent no-op. `scout` and `docs-seeker` now declare `context: fork` + `model: haiku` and route through the Skill tool instead; verified end-to-end on Opus 5 by per-model billing showing `claude-haiku-4-5`. `cook` deliberately declares no model — an orchestrator inherits the session model, because choosing Opus for a session is a deliberate act. 1,647 tests.
113
121
 
@@ -5,8 +5,11 @@
5
5
  // Silent pass-through if not applicable.
6
6
 
7
7
  const { execSync } = require('child_process');
8
+ const { captureConsole } = require('../lib/hook-output.cjs');
8
9
  const path = require('path');
9
10
 
11
+ captureConsole('PostToolUse');
12
+
10
13
  const input = JSON.parse(process.env.CLAUDE_TOOL_INPUT || '{}');
11
14
  const filePath = input.file_path || input.filePath || '';
12
15
 
@@ -113,4 +113,25 @@ function captureConsole(hookEventName, options = {}) {
113
113
  return { buffer, restore };
114
114
  }
115
115
 
116
- module.exports = { outputBuffer, emit, captureConsole, CONTEXT_EVENTS };
116
+ /**
117
+ * Emit a BLOCK reason, then exit 2.
118
+ *
119
+ * A blocking hook has to speak on two channels, because the runtimes read
120
+ * different ones. Claude Code takes the reason for an exit-2 block from STDERR —
121
+ * a hook that explains itself on stdout gets `hook error: […]: No stderr output`
122
+ * shown to the model, which learns that it was blocked but never why. Codex reads
123
+ * the stdout envelope. Write both; neither runtime is confused by the other's.
124
+ *
125
+ * @param {string} hookEventName
126
+ * @param {string} text human-readable reason
127
+ */
128
+ function emitBlock(hookEventName, text) {
129
+ const reason = text.trim();
130
+ if (!reason) process.exit(2);
131
+
132
+ fs.writeSync(2, `${reason}\n`);
133
+ fs.writeSync(1, `${JSON.stringify({ systemMessage: reason, hookSpecificOutput: { hookEventName } })}\n`);
134
+ process.exit(2);
135
+ }
136
+
137
+ module.exports = { outputBuffer, emit, emitBlock, captureConsole, CONTEXT_EVENTS };
@@ -15,7 +15,7 @@
15
15
  const fs = require('fs');
16
16
  const path = require('path');
17
17
 
18
- const { captureConsole } = require('../lib/hook-output.cjs');
18
+ const { captureConsole, emitBlock } = require('../lib/hook-output.cjs');
19
19
  const { readStdinSync } = require('../lib/hook-stdin.cjs');
20
20
  // Hook stdout is a JSON contract, not free text (Codex rejects bare lines and
21
21
  // discards the output). Capture the prints below and emit one envelope on exit.
@@ -158,10 +158,16 @@ const input = readStdinSync();
158
158
  // at this layer and intentionally remain uncaptured (see GAP-1 in governance-collector.js).
159
159
  appendGateOutcome('privacy-mesh', 'blocked', `file matched BLOCK-tier pattern: ${basename}`);
160
160
 
161
- console.log(`\n🚫 [Rune privacy-mesh] BLOCKED: ${filePath}`);
162
- console.log(' This file matches a BLOCK-tier pattern (private keys, certificates).');
163
- console.log(' Override: add path to .rune/privacy.json "allow" list if intentional.\n');
164
- process.exit(2); // Exit code 2 = BLOCK
161
+ // Exit code 2 = BLOCK. The reason must go to stderr — that is the only
162
+ // channel Claude Code shows the model for a blocked call.
163
+ emitBlock(
164
+ 'PreToolUse',
165
+ [
166
+ `🚫 [Rune privacy-mesh] BLOCKED: ${filePath}`,
167
+ ' This file matches a BLOCK-tier pattern (private keys, certificates).',
168
+ ' Override: add path to .rune/privacy.json "allow" list if intentional.',
169
+ ].join('\n'),
170
+ );
165
171
  }
166
172
 
167
173
  const isWarned = warnPatterns.some((p) => p.test(basename) || p.test(normalized));
@@ -96,7 +96,9 @@ function main(raw) {
96
96
  additionalContext: notice,
97
97
  },
98
98
  };
99
- process.stdout.write(`${JSON.stringify(output)}\n`);
99
+ // fs.writeSync, not process.stdout.write — process.exit() follows immediately
100
+ // below, and a piped stdout is not guaranteed to flush before the process ends.
101
+ fs.writeSync(1, `${JSON.stringify(output)}\n`);
100
102
 
101
103
  writeTelemetry({ tool: toolName, decision: 'emit', source: decision.source, session_id: sessionId });
102
104
  clearTimeout(timeoutHandle);
@@ -5,9 +5,12 @@
5
5
  // Async hook — doesn't block the agent, just warns.
6
6
 
7
7
  const { execSync } = require('child_process');
8
+ const { captureConsole } = require('../lib/hook-output.cjs');
8
9
  const fs = require('fs');
9
10
  const path = require('path');
10
11
 
12
+ captureConsole('PostToolUse');
13
+
11
14
  const input = JSON.parse(process.env.CLAUDE_TOOL_INPUT || '{}');
12
15
  const filePath = input.file_path || input.filePath || '';
13
16
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rune-kit/rune",
3
- "version": "2.30.1",
3
+ "version": "2.30.3",
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": {
@@ -2,6 +2,7 @@
2
2
  name: cook
3
3
  description: "Feature implementation orchestrator. ALWAYS use this skill for ANY code change — implement, build, add feature, create, fix bug, or any task that modifies source code. This is the default route for 70% of all requests. Runs full TDD cycle: understand → plan → test → implement → quality → verify → commit."
4
4
  context: fork
5
+ background: false
5
6
  agent: general-purpose
6
7
  metadata:
7
8
  author: runedev
@@ -3,6 +3,7 @@ name: docs-seeker
3
3
  description: "Find documentation for APIs, libraries, and error messages. Looks up official docs, changelog entries, and migration guides."
4
4
  context: fork
5
5
  agent: general-purpose
6
+ background: false
6
7
  model: haiku
7
8
  metadata:
8
9
  author: runedev
@@ -2,6 +2,7 @@
2
2
  name: launch
3
3
  description: "Deploy + marketing orchestrator. Use when user says 'launch', 'ship to production', 'deploy and announce', or 'go live'. Runs the full pipeline — pre-flight tests, deployment, live verification, marketing asset creation, and announcement."
4
4
  context: fork
5
+ background: false
5
6
  agent: general-purpose
6
7
  disable-model-invocation: true
7
8
  metadata:
@@ -2,6 +2,7 @@
2
2
  name: rescue
3
3
  description: "Legacy refactoring orchestrator. Use when user says 'refactor', 'modernize', 'clean up this mess', 'rescue', or when dealing with old/messy/legacy code. Multi-session workflow — autopsy, safety net, incremental surgery, progress tracking."
4
4
  context: fork
5
+ background: false
5
6
  agent: general-purpose
6
7
  metadata:
7
8
  author: runedev
@@ -3,6 +3,7 @@ name: scout
3
3
  description: "Fast codebase scanner. Use when any skill needs codebase context. Finds files, patterns, dependencies, project structure. Pure read-only — never modifies files."
4
4
  context: fork
5
5
  agent: general-purpose
6
+ background: false
6
7
  model: haiku
7
8
  metadata:
8
9
  author: runedev
@@ -2,6 +2,7 @@
2
2
  name: team
3
3
  description: "Multi-agent meta-orchestrator. Use when task spans 5+ files or 3+ modules, or when user says 'parallel', 'split this up', 'do all of these'. Decomposes large tasks into parallel workstreams, assigns to isolated cook instances, coordinates merging."
4
4
  context: fork
5
+ background: false
5
6
  agent: general-purpose
6
7
  metadata:
7
8
  author: runedev