@rune-kit/rune 2.26.1 → 2.26.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
|
@@ -103,11 +103,15 @@ Plus **9 domain packs** (product, sales, data-science, support, growth, media, p
|
|
|
103
103
|
|
|
104
104
|
---
|
|
105
105
|
|
|
106
|
-
## What's New (v2.26.
|
|
106
|
+
## What's New (v2.26.2 — Hook Output Contract)
|
|
107
|
+
|
|
108
|
+
> **v2.26.2 (2026-07-22):** The other half of the Codex wiring fix — v2.26.1 made the hook matchers fire, this makes the hooks **succeed**. Codex parses hook stdout as JSON and reports anything else as `hook: <Event> Failed`, discarding the output; Rune's hooks printed bare `[Rune: ...]` lines, so every hook that loaded on Codex ran, exited 0, and had its output thrown away. Hooks now emit the envelope both runtimes accept — `hookSpecificOutput.additionalContext` for context events, `systemMessage` otherwise — which is Claude Code's documented contract too, not a Codex branch. Verified live against codex-cli 0.145: the same hook goes `SessionStart Failed` → `SessionStart Completed`.
|
|
109
|
+
|
|
110
|
+
### Previous (v2.26.1 — Codex Wiring)
|
|
107
111
|
|
|
108
112
|
> **v2.26.1 (2026-07-22):** Rune's runtime hooks were silently inert on **Codex CLI**. `hooks/hooks.json` is loaded by both Claude Code and Codex — Codex reads `<plugin>/hooks/hooks.json`, the same path, and maps the event names — but every tool matcher named only Claude's tools. Codex has no `Read`, `Write`, `Edit` or `Bash` tool; it issues `shell_command`, `exec`, `apply_patch`, `view_image`, `spawn_agent`. So the privacy gate and the secret scanner matched nothing and never fired. Matchers now name both platforms' tools (plain alternation — Claude behaviour is byte-for-byte unchanged), and `pre-tool-guard` reads the target path out of a Codex `apply_patch` payload (`*** Update File: <path>`), which is what makes it an actual gate there instead of a no-op. Note that Codex does not support `async` hooks yet and skips them, so Rune's six background hooks stay inactive on Codex — `async` is kept because Claude Code honours it.
|
|
109
113
|
|
|
110
|
-
|
|
114
|
+
#### Earlier (v2.26.0 — Motion Craft)
|
|
111
115
|
|
|
112
116
|
> **v2.26.0 (2026-07-18):** Rune's UI mesh gains a deep **motion authority**. New reference `skills/design/MOTION-CRAFT.md` is the canonical source for animation decisions: the *should-it-animate* frequency gate (never animate keyboard/100+-per-day actions), easing decision tree with strong custom curves, per-element duration budgets (UI under 300ms, modals/drawers exempt to 500ms), physicality rules (never `scale(0)`, origin-aware popovers, press feedback), spring physics (damping/response, velocity handoff, momentum projection, rubber-banding), interruptibility (transitions vs keyframes, `@starting-style`), motion performance, reduced-motion, and a reverse-lookup vocabulary glossary. `design` (v0.8.0) loads it whenever a domain involves motion and gains an advisory motion-audit mode. `review` (v1.5.0) adds **Motion Craft Checks** — an advisory lens that fires only on motion diffs, flagging `ease-in` on UI, `scale(0)` entrances, animation on high-frequency actions, layout-property animation, and more, citing MOTION-CRAFT for exact fixes. `perf` (v0.6.0) adds **Step 5.5 Motion Performance** — GPU-property, Framer-Motion-shorthand, and recalc-storm detection ranked in the Cost Impact Hierarchy. Advisory-first throughout (no new HARD-GATEs) — enrichment only, mesh unchanged at 66 skills.
|
|
113
117
|
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Hook stdout envelope — the one format both runtimes accept.
|
|
4
|
+
//
|
|
5
|
+
// Claude Code tolerates bare text on a hook's stdout. Codex CLI does not: it
|
|
6
|
+
// parses stdout as its `HookUniversalOutputWire` JSON and reports anything else
|
|
7
|
+
// as `hook: <Event> Failed`, discarding the output. Every Rune hook that printed
|
|
8
|
+
// a plain `[Rune: ...]` line was therefore failing on Codex — the hooks loaded,
|
|
9
|
+
// ran, exited 0, and had their output thrown away.
|
|
10
|
+
//
|
|
11
|
+
// Both runtimes accept the same envelope, so emitting it is not a Codex-specific
|
|
12
|
+
// branch — it is simply the correct output contract:
|
|
13
|
+
//
|
|
14
|
+
// {"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":"…"}}
|
|
15
|
+
// {"systemMessage":"…"}
|
|
16
|
+
//
|
|
17
|
+
// Verified against codex-cli 0.145 (schema read from the binary: decision,
|
|
18
|
+
// reason, continue, stopReason, suppressOutput, systemMessage, and
|
|
19
|
+
// hookSpecificOutput.{hookEventName,additionalContext,permissionDecision,…};
|
|
20
|
+
// unknown keys are rejected) and Claude Code's documented hook output.
|
|
21
|
+
//
|
|
22
|
+
// Buffer-then-emit, because the envelope is ONE JSON object: a hook that prints
|
|
23
|
+
// three lines must accumulate them and emit once at exit, not three times.
|
|
24
|
+
|
|
25
|
+
/** Events where the model-facing payload is `additionalContext`. */
|
|
26
|
+
const CONTEXT_EVENTS = new Set(['SessionStart', 'UserPromptSubmit']);
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Collects a hook's output and emits a single valid envelope.
|
|
30
|
+
*
|
|
31
|
+
* @param {string} hookEventName Claude/Codex event name, e.g. 'SessionStart'
|
|
32
|
+
* @returns {{line: (text: string) => void, emit: () => void, isEmpty: () => boolean}}
|
|
33
|
+
*/
|
|
34
|
+
function outputBuffer(hookEventName) {
|
|
35
|
+
const parts = [];
|
|
36
|
+
return {
|
|
37
|
+
/** Queue one line of output. Empty/blank input is ignored. */
|
|
38
|
+
line(text) {
|
|
39
|
+
if (typeof text !== 'string') return;
|
|
40
|
+
const trimmed = text.replace(/\s+$/, '');
|
|
41
|
+
if (trimmed.trim()) parts.push(trimmed);
|
|
42
|
+
},
|
|
43
|
+
isEmpty() {
|
|
44
|
+
return parts.length === 0;
|
|
45
|
+
},
|
|
46
|
+
/** Write the envelope. Emits nothing at all when there is nothing to say. */
|
|
47
|
+
emit() {
|
|
48
|
+
if (parts.length === 0) return;
|
|
49
|
+
const text = parts.join('\n');
|
|
50
|
+
const payload = CONTEXT_EVENTS.has(hookEventName)
|
|
51
|
+
? { hookSpecificOutput: { hookEventName, additionalContext: text } }
|
|
52
|
+
: { systemMessage: text };
|
|
53
|
+
process.stdout.write(`${JSON.stringify(payload)}\n`);
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* One-shot form for hooks with a single message.
|
|
60
|
+
*
|
|
61
|
+
* @param {string} hookEventName
|
|
62
|
+
* @param {string} text
|
|
63
|
+
*/
|
|
64
|
+
function emit(hookEventName, text) {
|
|
65
|
+
const buf = outputBuffer(hookEventName);
|
|
66
|
+
buf.line(text);
|
|
67
|
+
buf.emit();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Route a hook's existing `console.log` calls into one envelope.
|
|
72
|
+
*
|
|
73
|
+
* Hooks build their message across many prints and several early `process.exit`
|
|
74
|
+
* paths. Rewriting each print site into an explicit buffer would mean touching
|
|
75
|
+
* every branch — and missing one silently reintroduces the bare-text bug. So
|
|
76
|
+
* capture at the boundary instead: the message logic stays exactly as written,
|
|
77
|
+
* and the envelope is emitted once on exit no matter which branch got there.
|
|
78
|
+
*
|
|
79
|
+
* `console.error` is untouched — stderr is not parsed as the output contract.
|
|
80
|
+
*
|
|
81
|
+
* @param {string} hookEventName
|
|
82
|
+
* @returns {{buffer: ReturnType<typeof outputBuffer>, restore: () => void}}
|
|
83
|
+
*/
|
|
84
|
+
function captureConsole(hookEventName) {
|
|
85
|
+
const buffer = outputBuffer(hookEventName);
|
|
86
|
+
const original = console.log;
|
|
87
|
+
console.log = (...args) => {
|
|
88
|
+
buffer.line(args.map((a) => (typeof a === 'string' ? a : String(a))).join(' '));
|
|
89
|
+
};
|
|
90
|
+
const restore = () => {
|
|
91
|
+
console.log = original;
|
|
92
|
+
};
|
|
93
|
+
// Fires for a natural end AND for process.exit(), which is how the BLOCK
|
|
94
|
+
// paths leave — those messages must survive too.
|
|
95
|
+
process.on('exit', () => {
|
|
96
|
+
restore();
|
|
97
|
+
buffer.emit();
|
|
98
|
+
});
|
|
99
|
+
return { buffer, restore };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
module.exports = { outputBuffer, emit, captureConsole, CONTEXT_EVENTS };
|
|
@@ -5,6 +5,11 @@
|
|
|
5
5
|
const fs = require('fs');
|
|
6
6
|
const path = require('path');
|
|
7
7
|
const os = require('os');
|
|
8
|
+
const { captureConsole } = require('../lib/hook-output.cjs');
|
|
9
|
+
// Hook stdout is a JSON contract, not free text (Codex rejects bare lines and
|
|
10
|
+
// discards the output). Capture the prints below and emit one envelope on exit.
|
|
11
|
+
captureConsole('Stop');
|
|
12
|
+
|
|
8
13
|
|
|
9
14
|
const cwd = process.cwd();
|
|
10
15
|
const hash = Buffer.from(cwd).toString('base64url').slice(0, 16);
|
|
@@ -9,6 +9,12 @@ const fs = require('fs');
|
|
|
9
9
|
const path = require('path');
|
|
10
10
|
const os = require('os');
|
|
11
11
|
|
|
12
|
+
const { captureConsole } = require('../lib/hook-output.cjs');
|
|
13
|
+
// Hook stdout is a JSON contract, not free text (Codex rejects bare lines and
|
|
14
|
+
// discards the output). Capture the prints below and emit one envelope on exit.
|
|
15
|
+
captureConsole('PreCompact');
|
|
16
|
+
|
|
17
|
+
|
|
12
18
|
const cwd = process.cwd();
|
|
13
19
|
const runeDir = path.join(cwd, '.rune');
|
|
14
20
|
|
|
@@ -15,6 +15,12 @@
|
|
|
15
15
|
const fs = require('fs');
|
|
16
16
|
const path = require('path');
|
|
17
17
|
|
|
18
|
+
const { captureConsole } = require('../lib/hook-output.cjs');
|
|
19
|
+
// Hook stdout is a JSON contract, not free text (Codex rejects bare lines and
|
|
20
|
+
// discards the output). Capture the prints below and emit one envelope on exit.
|
|
21
|
+
captureConsole('PreToolUse');
|
|
22
|
+
|
|
23
|
+
|
|
18
24
|
/**
|
|
19
25
|
* Append a structured gate-outcome record to .rune/metrics/gate-outcomes.jsonl
|
|
20
26
|
* relative to process.cwd(). Best-effort: any error is silently swallowed so
|
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const path = require('path');
|
|
6
6
|
const os = require('os');
|
|
7
|
+
const { captureConsole } = require('../lib/hook-output.cjs');
|
|
8
|
+
// Hook stdout is a JSON contract, not free text (Codex rejects bare lines and
|
|
9
|
+
// discards the output). Capture the prints below and emit one envelope on exit.
|
|
10
|
+
captureConsole('SessionStart');
|
|
11
|
+
|
|
7
12
|
|
|
8
13
|
const cwd = process.cwd();
|
|
9
14
|
const runeDir = path.join(cwd, '.rune');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rune-kit/rune",
|
|
3
|
-
"version": "2.26.
|
|
3
|
+
"version": "2.26.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": {
|