@rune-kit/rune 2.22.2 → 2.24.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/README.md +33 -6
- package/compiler/__tests__/adapter-model-mapping.test.js +6 -6
- package/compiler/__tests__/adapters.test.js +88 -30
- package/compiler/__tests__/pack-split.test.js +5 -2
- package/compiler/__tests__/pipeline.test.js +6 -6
- package/compiler/__tests__/scripts-bundling.test.js +29 -19
- package/compiler/__tests__/transformer.test.js +4 -4
- package/compiler/adapters/antigravity.js +1 -5
- package/compiler/adapters/codex.js +39 -10
- package/compiler/adapters/copilot.js +26 -29
- package/compiler/adapters/cursor.js +23 -15
- package/compiler/adapters/gemini.js +25 -48
- package/compiler/adapters/qoder.js +14 -14
- package/compiler/adapters/qwen.js +16 -22
- package/compiler/adapters/windsurf.js +29 -7
- package/compiler/emitter.js +5 -4
- package/compiler/parser.js +11 -2
- package/extensions/ai-ml/skills/llm-architect.md +3 -3
- package/package.json +1 -1
- package/skills/adversary/references/oracle-mode.md +3 -2
- package/skills/ba/SKILL.md +6 -1
- package/skills/cook/SKILL.md +19 -1
- package/skills/marketing/SKILL.md +26 -1
- package/skills/mcp-builder/SKILL.md +2 -2
- package/skills/plan/SKILL.md +13 -2
- package/skills/review/SKILL.md +20 -2
- package/skills/session-bridge/SKILL.md +2 -2
- package/skills/video-creator/SKILL.md +74 -2
package/README.md
CHANGED
|
@@ -83,7 +83,33 @@ _Methodology: Claude Code CLI headless mode (`claude -p --output-format json`),
|
|
|
83
83
|
|
|
84
84
|
---
|
|
85
85
|
|
|
86
|
-
##
|
|
86
|
+
## Level Up: Rune Pro
|
|
87
|
+
|
|
88
|
+
<p align="center">
|
|
89
|
+
<a href="https://github.com/rune-kit/rune-pro"><img src="assets/pro-upgrade.svg" alt="Rune Pro — Autopilot + Context Intelligence · $49 lifetime" width="100%"></a>
|
|
90
|
+
</p>
|
|
91
|
+
|
|
92
|
+
Free gives Claude discipline. **Pro** gives it two things the free tier can't:
|
|
93
|
+
|
|
94
|
+
- **Autopilot** — approve a plan once and walk away. It executes each phase autonomously (self-reviews with zero-HIGH gates, regression-checks against baselines, commits per phase) and **resumes across sessions**. `cook` gets the job done; `autopilot` gets it done while you sleep.
|
|
95
|
+
- **Context Intelligence** — a live **context cockpit** in your statusline (real %, the active skill's mesh layer, cost, predictive "~4m until auto-compaction") that also makes Claude *itself* aware of the pressure, so it saves decisions + progress to `.rune/` **before** compact wipes the session. Zero data loss.
|
|
96
|
+
|
|
97
|
+
Plus **9 domain packs** (product, sales, data-science, support, growth, media, personal-brand, ecommerce, vietnam). **$49 lifetime, one-time — no subscription.**
|
|
98
|
+
|
|
99
|
+
<p align="center">
|
|
100
|
+
<a href="https://github.com/rune-kit/rune-pro"><img src="https://img.shields.io/badge/Unlock_Pro_%2449-lifetime-a855f7?style=for-the-badge" alt="Unlock Rune Pro"></a>
|
|
101
|
+
<a href="https://rune-kit.github.io/rune#pricing"><img src="https://img.shields.io/badge/See_Pricing-rune--kit.github.io-blue?style=for-the-badge" alt="Pricing"></a>
|
|
102
|
+
</p>
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## What's New (v2.24.0 — Market Refresh)
|
|
107
|
+
|
|
108
|
+
> **v2.24.0 (2026-07-11):** Model lineup + platform adapters refreshed to the mid-2026 landscape. Claude tiers across the mesh → **Opus 4.8 / Sonnet 5 / Haiku 4.5**; the **Codex** adapter → **GPT-5.6** `sol` / `terra` / `luna` (verified against codex-cli 0.144.1), with a suggested tier→`model_reasoning_effort` mapping in the generated `AGENTS.md`. **Fable 5** is positioned as an API-only frontier **oracle** target rather than a routing tier (opus stays the ceiling). **Windsurf → Devin Desktop** rebrand documented (emission kept on `.windsurf/`, which Devin still reads). New **goal-first advisory** in `plan`/`ba` to leverage native `/goal` + Managed Agents Outcomes. CI 1572/1572.
|
|
109
|
+
|
|
110
|
+
> **v2.23.0 (2026-07-04):** Seven platform adapters move to the **Agent Skills open standard** (dir-per-skill `SKILL.md`, discovered and lazy-loaded by each platform's native loader). The headline fix: **Codex** dropped `.codex/skills/` from its scan list, so compiled skills were only findable via the AGENTS.md pointer — agents kept "re-finding" the path mid-session. Codex now emits to **`.agents/skills/`** (scanned CWD → repo root). Same treatment across the fleet: **cursor** `.cursor/rules/*.mdc` → `.cursor/skills/` (Cursor 2.4+ Skills, on-demand instead of always-on), **windsurf** → `.windsurf/skills/` (Cascade Skills), **copilot** → `.github/skills/`, **qoder** → `.qoder/skills/`, and **gemini/qwen** drop their all-skills-always-on context bombs (GEMINI.md bundle, QWEN.md `@import` wall) for native `.gemini/skills/` / `.qwen/skills/` + slim pointer files — a big context-window win on those platforms. Runtime hooks intentionally stay on `.cursor/rules` / `.windsurf/rules` (always-on is correct for hook context). Also fixes a YAML double-escaping bug that corrupted compiled frontmatter for skills with quoted descriptions on 6 platforms, and a duplicate `scripts/` copy in dir-per-skill builds. If you previously built for Codex/Cursor/Windsurf/Copilot/Gemini/Qwen/Qoder: re-run `npx @rune-kit/rune build` and delete the old output dirs. CI 1571/1571.
|
|
111
|
+
|
|
112
|
+
### Previous (v2.22.2 — Convergence, Dogfooded)
|
|
87
113
|
|
|
88
114
|
> **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
115
|
|
|
@@ -134,7 +160,7 @@ _Methodology: Claude Code CLI headless mode (`claude -p --output-format json`),
|
|
|
134
160
|
- **`adversary` v0.2.0 — Mode: oracle** — when `agent.stuck` fires from `debug` (3 disproved hypotheses) or `fix` (2+ failed attempts), oracle-mode dispatches a stateless second-model pass with explicit "no prior context" framing. Bundle format is regex-validated (`[SYSTEM]` invariant role-priming + `[USER]` template + `### File N`), token-capped (100k bundle, 4k per file, 12 files max), citation-required reply contract. Secrets auto-redacted. Breaks the confirmation-bias loop that scout's zoom-out (structural pivot) cannot.
|
|
135
161
|
- **`session-bridge` v0.8.0 — Detach Mode** — async escalation primitive. Heavy-model second-opinion calls (1-10 min wall time) no longer block the primary agent. `.rune/oracle-pending/<sessionId>.json` is the rendezvous file; idempotent dispatch (bundleHash-keyed); 10min default timeout; 24h orphan cleanup on session start. `cook` Phase 4 and `team` Phase 3 reattach via filesystem poll between adjacent tasks.
|
|
136
162
|
- **`context-engine` v1.1.0 — Mode: preview** — pre-flight cost gate. Caller emits `context.preview` BEFORE bundle build with file list + estimated tokens (chars × 0.25). Per-caller thresholds: adversary 50k/100k, team 30k/80k (per worker), review 40k/100k, audit 60k/120k. Action enum `proceed | warn | block`. Override via `RUNE_CONTEXT_THRESHOLDS_<CALLER>`. Stops `team` parallel workstreams from silently blowing $20 of Opus tokens.
|
|
137
|
-
- **Cross-provider model mapping** — 5 non-Anthropic adapters now translate `model: opus|sonnet|haiku` to provider-correct names. **codex** → gpt-5-
|
|
163
|
+
- **Cross-provider model mapping** — 5 non-Anthropic adapters now translate `model: opus|sonnet|haiku` to provider-correct names. **codex** → gpt-5.6-sol / gpt-5.6-terra / gpt-5.6-luna. **antigravity** → gemini-3-pro / gemini-3-flash / gemini-3-flash-lite. **opencode / openclaw / generic** → tier:heavy / tier:mid / tier:light (provider-agnostic). claude / cursor / windsurf remain no-op (Anthropic backend understands native names).
|
|
138
164
|
- **Routing clarity sweep** — all 63 SKILL.md descriptions now double-quoted (YAML safety). 13 ambiguous-name skills got explicit "Use when…" routing hints so skill-router doesn't have to guess: ba, completion-gate, constraint-check, doc-processor, integrity-check, logic-guardian, onboard, preflight, sentinel-env, watchdog, worktree, hallucination-guard, mcp-builder.
|
|
139
165
|
- **4 new mesh signals** — `oracle.dispatched`, `oracle.response`, `oracle.failed`, `context.preview`. All registered in Signal Catalog with full emit/listen mapping. `agent.stuck` listeners updated to include adversary in addition to scout.
|
|
140
166
|
- **1,331 tests** — +71 from v2.14.0 across 5 new test files: adapter-model-mapping (18), oracle-bundle-format (19), oracle-pending-schema (16), context-preview-signal (13), skill-description-quality (5).
|
|
@@ -591,14 +617,15 @@ Domain-specific skills that plug into the core mesh:
|
|
|
591
617
|
|
|
592
618
|
> *Free Rune makes Claude disciplined. Pro makes Claude self-aware.*
|
|
593
619
|
|
|
594
|
-
**Context Intelligence** — the headline Pro feature. Claude Code auto-compacts
|
|
620
|
+
**Context Intelligence** — the headline Pro feature. Claude Code auto-compacts without warning, wiping your session. Pro turns the statusline into a live **context cockpit** and makes Claude *know* when the window is filling — so it saves decisions, progress, and discoveries before compact hits.
|
|
595
621
|
|
|
596
622
|
```
|
|
597
|
-
|
|
598
|
-
Pro: Claude sees real-time context % → saves state → compact → reloads → zero loss
|
|
623
|
+
◈ Opus 4.8 │ ████████░░░ 73% WARM │ ⬡ cook·L1 │ $0.91 │ 5h ██░░ 22% 7d ███░ 61%
|
|
599
624
|
```
|
|
600
625
|
|
|
601
|
-
|
|
626
|
+
Three things a blind meter can't do: a **glitch-proof %** (no phantom 0%, no false "100% URGENT" during light work), a **burn-rate ETA** (*"~4m until auto-compaction"*), and **what's eating your context** (*"files 61% · mcp 22%"* → trim the biggest, don't nuke blind). At 70/80/90% real, `context-inject` writes the pressure into Claude's own context → it triggers `session-bridge` + `neural-memory`, you `/compact`, and `session-start` reloads `.rune/`. Zero loss.
|
|
627
|
+
|
|
628
|
+
Free ships the blind tool-call counter; the real-% cockpit + predictive intelligence is Pro.
|
|
602
629
|
|
|
603
630
|
**Autopilot** — approve a plan, walk away. Autonomous multi-session execution with zero-HIGH-tolerance quality gates, baseline regression checks, cross-phase coherence review, and structured completion reports. `cook` gets the job done. `autopilot` gets it done while you sleep.
|
|
604
631
|
|
|
@@ -25,19 +25,19 @@ const baseSkill = {
|
|
|
25
25
|
describe('codex adapter model mapping', () => {
|
|
26
26
|
const codex = getAdapter('codex');
|
|
27
27
|
|
|
28
|
-
test('opus translates to gpt-5-
|
|
28
|
+
test('opus translates to gpt-5.6-sol', () => {
|
|
29
29
|
const header = codex.generateHeader({ ...baseSkill, model: 'opus' });
|
|
30
|
-
assert.match(header, /model: gpt-5-
|
|
30
|
+
assert.match(header, /model: gpt-5\.6-sol/);
|
|
31
31
|
});
|
|
32
32
|
|
|
33
|
-
test('sonnet translates to gpt-5', () => {
|
|
33
|
+
test('sonnet translates to gpt-5.6-terra', () => {
|
|
34
34
|
const header = codex.generateHeader({ ...baseSkill, model: 'sonnet' });
|
|
35
|
-
assert.match(header, /model: gpt-5
|
|
35
|
+
assert.match(header, /model: gpt-5\.6-terra/);
|
|
36
36
|
});
|
|
37
37
|
|
|
38
|
-
test('haiku translates to gpt-5-
|
|
38
|
+
test('haiku translates to gpt-5.6-luna', () => {
|
|
39
39
|
const header = codex.generateHeader({ ...baseSkill, model: 'haiku' });
|
|
40
|
-
assert.match(header, /model: gpt-5-
|
|
40
|
+
assert.match(header, /model: gpt-5\.6-luna/);
|
|
41
41
|
});
|
|
42
42
|
|
|
43
43
|
test('omitted model produces no model line', () => {
|
|
@@ -29,7 +29,7 @@ test('listPlatforms returns all 13 platform adapters', () => {
|
|
|
29
29
|
test('getAdapter returns adapter by name', () => {
|
|
30
30
|
const cursor = getAdapter('cursor');
|
|
31
31
|
assert.strictEqual(cursor.name, 'cursor');
|
|
32
|
-
assert.strictEqual(cursor.fileExtension, '.
|
|
32
|
+
assert.strictEqual(cursor.fileExtension, '.md');
|
|
33
33
|
});
|
|
34
34
|
|
|
35
35
|
test('getAdapter throws for unknown adapter', () => {
|
|
@@ -112,18 +112,45 @@ for (const adapterName of ADAPTER_NAMES) {
|
|
|
112
112
|
});
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
// --- Frontmatter escaping (regression: descriptions containing quoted phrases) ---
|
|
116
|
+
|
|
117
|
+
test('generateHeader escapes quoted descriptions exactly once (valid YAML)', () => {
|
|
118
|
+
// Parser hands adapters CLEAN text (interior \" already unescaped) — the
|
|
119
|
+
// adapter re-escapes exactly once. Double-escaping produced `\\"` which
|
|
120
|
+
// prematurely terminates the YAML scalar.
|
|
121
|
+
const skill = { name: 'docs', layer: 'L2', group: 'workflow', description: 'The "docs are never outdated" skill' };
|
|
122
|
+
for (const name of ['cursor', 'windsurf', 'copilot', 'codex', 'antigravity', 'opencode', 'gemini', 'qoder', 'qwen']) {
|
|
123
|
+
const header = getAdapter(name).generateHeader(skill);
|
|
124
|
+
assert.ok(header.includes('\\"docs are never outdated\\"'), `${name} should escape quotes once`);
|
|
125
|
+
assert.ok(!header.includes('\\\\"'), `${name} must not double-escape quotes`);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
115
129
|
// --- Cursor-specific ---
|
|
116
130
|
|
|
117
|
-
test('cursor adapter
|
|
131
|
+
test('cursor adapter emits Agent Skills (dir-per-skill SKILL.md)', () => {
|
|
118
132
|
const cursor = getAdapter('cursor');
|
|
119
|
-
|
|
120
|
-
|
|
133
|
+
assert.strictEqual(cursor.outputDir, '.cursor/skills');
|
|
134
|
+
assert.strictEqual(cursor.useSkillDirectories, true);
|
|
135
|
+
assert.strictEqual(cursor.skillFileName, 'SKILL.md');
|
|
136
|
+
|
|
137
|
+
const header = cursor.generateHeader({ name: 'fix', layer: 'L2', group: 'workflow', description: 'Fix code' });
|
|
138
|
+
assert.ok(header.includes('name: rune-fix'));
|
|
139
|
+
assert.ok(header.includes('description: "Fix code"'));
|
|
140
|
+
assert.ok(!header.includes('alwaysApply'), 'skills format has no alwaysApply');
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// --- Windsurf-specific ---
|
|
121
144
|
|
|
122
|
-
|
|
123
|
-
|
|
145
|
+
test('windsurf adapter emits Cascade Skills (dir-per-skill SKILL.md)', () => {
|
|
146
|
+
const windsurf = getAdapter('windsurf');
|
|
147
|
+
assert.strictEqual(windsurf.outputDir, '.windsurf/skills');
|
|
148
|
+
assert.strictEqual(windsurf.useSkillDirectories, true);
|
|
149
|
+
assert.strictEqual(windsurf.skillFileName, 'SKILL.md');
|
|
124
150
|
|
|
125
|
-
const
|
|
126
|
-
assert.ok(
|
|
151
|
+
const header = windsurf.generateHeader({ name: 'fix', layer: 'L2', group: 'workflow', description: 'Fix code' });
|
|
152
|
+
assert.ok(header.includes('name: rune-fix'));
|
|
153
|
+
assert.ok(header.includes('description: "Fix code"'));
|
|
127
154
|
});
|
|
128
155
|
|
|
129
156
|
// --- Codex-specific ---
|
|
@@ -134,28 +161,50 @@ test('codex adapter uses skill directories', () => {
|
|
|
134
161
|
assert.strictEqual(codex.skillFileName, 'SKILL.md');
|
|
135
162
|
});
|
|
136
163
|
|
|
164
|
+
test('codex AGENTS.md documents the tier→model + reasoning-effort mapping', async () => {
|
|
165
|
+
const codex = getAdapter('codex');
|
|
166
|
+
const extras = await codex.generateExtraFiles({
|
|
167
|
+
stats: { skillCount: 65, crossRefsResolved: 204, packCount: 14, files: [] },
|
|
168
|
+
});
|
|
169
|
+
const agents = extras.find((e) => e.path === 'AGENTS.md');
|
|
170
|
+
assert.ok(agents, 'codex emits AGENTS.md');
|
|
171
|
+
// Guards against a MODEL_MAP / REASONING_EFFORT_MAP interpolation swap.
|
|
172
|
+
assert.match(agents.content, /opus → `gpt-5\.6-sol` \(`model_reasoning_effort = "high"`\)/);
|
|
173
|
+
assert.match(agents.content, /sonnet → `gpt-5\.6-terra` \(`model_reasoning_effort = "medium"`\)/);
|
|
174
|
+
assert.match(agents.content, /haiku → `gpt-5\.6-luna` \(`model_reasoning_effort = "low"`\)/);
|
|
175
|
+
});
|
|
176
|
+
|
|
137
177
|
// --- New v2.18 adapters: shape + generateExtraFiles contract ---
|
|
138
178
|
|
|
139
|
-
test('qoder adapter targets .qoder/
|
|
179
|
+
test('qoder adapter targets .qoder/skills (dir-per-skill) and emits AGENTS.md', async () => {
|
|
140
180
|
const qoder = getAdapter('qoder');
|
|
141
|
-
assert.strictEqual(qoder.outputDir, '.qoder/
|
|
142
|
-
assert.strictEqual(qoder.useSkillDirectories,
|
|
181
|
+
assert.strictEqual(qoder.outputDir, '.qoder/skills');
|
|
182
|
+
assert.strictEqual(qoder.useSkillDirectories, true);
|
|
183
|
+
assert.strictEqual(qoder.skillFileName, 'SKILL.md');
|
|
143
184
|
assert.strictEqual(typeof qoder.generateExtraFiles, 'function');
|
|
144
185
|
const extras = await qoder.generateExtraFiles({ stats: { skillCount: 5, packCount: 1, files: [] } });
|
|
145
|
-
assert.ok(extras.some((e) => e.path === 'AGENTS.md' && e.content.includes('
|
|
186
|
+
assert.ok(extras.some((e) => e.path === 'AGENTS.md' && e.content.includes('.qoder/skills')));
|
|
146
187
|
});
|
|
147
188
|
|
|
148
|
-
test('copilot adapter targets .github/
|
|
189
|
+
test('copilot adapter targets .github/skills with SKILL.md format', async () => {
|
|
149
190
|
const copilot = getAdapter('copilot');
|
|
150
|
-
assert.strictEqual(copilot.outputDir, '.github/
|
|
151
|
-
assert.strictEqual(copilot.
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
191
|
+
assert.strictEqual(copilot.outputDir, '.github/skills');
|
|
192
|
+
assert.strictEqual(copilot.useSkillDirectories, true);
|
|
193
|
+
assert.strictEqual(copilot.skillFileName, 'SKILL.md');
|
|
194
|
+
const header = copilot.generateHeader({
|
|
195
|
+
name: 'cook',
|
|
196
|
+
layer: 'L1',
|
|
197
|
+
group: 'orchestrator',
|
|
198
|
+
description: 'Test',
|
|
199
|
+
model: 'opus',
|
|
200
|
+
});
|
|
201
|
+
// Agent Skills spec: name + description frontmatter; tier hint stays a body comment.
|
|
202
|
+
assert.ok(header.includes('name: rune-cook'));
|
|
203
|
+
assert.ok(header.includes('description: "Test"'));
|
|
204
|
+
assert.ok(header.includes('<!-- tier-hint: tier:heavy -->'));
|
|
205
|
+
assert.ok(!header.includes('applyTo'), 'skills format has no applyTo');
|
|
157
206
|
const extras = await copilot.generateExtraFiles({ stats: { skillCount: 5, packCount: 1, files: [] } });
|
|
158
|
-
assert.ok(extras.some((e) => e.path === '.github/copilot-instructions.md'));
|
|
207
|
+
assert.ok(extras.some((e) => e.path === '.github/copilot-instructions.md' && e.content.includes('.github/skills')));
|
|
159
208
|
assert.ok(extras.some((e) => e.path === 'AGENTS.md'));
|
|
160
209
|
});
|
|
161
210
|
|
|
@@ -201,21 +250,30 @@ test('aider adapter emits .aider.conf.yml with read array', async () => {
|
|
|
201
250
|
assert.ok(!conf.content.includes('aider/rules/index.md'));
|
|
202
251
|
});
|
|
203
252
|
|
|
204
|
-
test('qwen adapter emits QWEN.md
|
|
253
|
+
test('qwen adapter targets .qwen/skills and emits slim QWEN.md pointer', async () => {
|
|
205
254
|
const qwen = getAdapter('qwen');
|
|
206
|
-
assert.strictEqual(qwen.outputDir, 'qwen/skills');
|
|
255
|
+
assert.strictEqual(qwen.outputDir, '.qwen/skills');
|
|
256
|
+
assert.strictEqual(qwen.useSkillDirectories, true);
|
|
257
|
+
assert.strictEqual(qwen.skillFileName, 'SKILL.md');
|
|
207
258
|
const extras = await qwen.generateExtraFiles({
|
|
208
|
-
stats: { skillCount: 2, packCount: 0, files: ['rune-cook.md', 'rune-fix.md'] },
|
|
259
|
+
stats: { skillCount: 2, packCount: 0, files: ['rune-cook/SKILL.md', 'rune-fix/SKILL.md'] },
|
|
209
260
|
});
|
|
210
261
|
const qwenMd = extras.find((e) => e.path === 'QWEN.md');
|
|
211
262
|
assert.ok(qwenMd);
|
|
212
|
-
assert.ok(qwenMd.content.includes('
|
|
213
|
-
assert.ok(qwenMd.content.includes('@qwen/skills/
|
|
263
|
+
assert.ok(qwenMd.content.includes('.qwen/skills/rune-<name>/SKILL.md'));
|
|
264
|
+
assert.ok(!qwenMd.content.includes('@qwen/skills/'), 'no @import lines — skills are lazy-loaded natively');
|
|
214
265
|
});
|
|
215
266
|
|
|
216
|
-
test('gemini adapter
|
|
267
|
+
test('gemini adapter targets .gemini/skills and emits slim GEMINI.md pointer', async () => {
|
|
217
268
|
const gemini = getAdapter('gemini');
|
|
218
|
-
assert.strictEqual(gemini.outputDir, 'gemini/skills');
|
|
219
|
-
assert.strictEqual(
|
|
220
|
-
|
|
269
|
+
assert.strictEqual(gemini.outputDir, '.gemini/skills');
|
|
270
|
+
assert.strictEqual(gemini.useSkillDirectories, true);
|
|
271
|
+
assert.strictEqual(gemini.skillFileName, 'SKILL.md');
|
|
272
|
+
const extras = await gemini.generateExtraFiles({
|
|
273
|
+
stats: { skillCount: 2, packCount: 0, files: ['rune-cook/SKILL.md', 'rune-fix/SKILL.md'] },
|
|
274
|
+
});
|
|
275
|
+
const geminiMd = extras.find((e) => e.path === 'GEMINI.md');
|
|
276
|
+
assert.ok(geminiMd);
|
|
277
|
+
assert.ok(geminiMd.content.includes('.gemini/skills/rune-<name>/SKILL.md'));
|
|
278
|
+
assert.ok(!geminiMd.content.includes('## rune-cook'), 'no bundled H2 sections — skills are lazy-loaded natively');
|
|
221
279
|
});
|
|
@@ -244,7 +244,10 @@ describe('buildAll split pack auto-discovery', () => {
|
|
|
244
244
|
assert.strictEqual(stats.packCount, 1, 'should build 1 pack');
|
|
245
245
|
|
|
246
246
|
// Read the compiled pack output
|
|
247
|
-
const packOutput = await readFile(
|
|
247
|
+
const packOutput = await readFile(
|
|
248
|
+
path.join(outputRoot, adapter.outputDir, 'rune-ext-test-pack', 'SKILL.md'),
|
|
249
|
+
'utf-8',
|
|
250
|
+
);
|
|
248
251
|
|
|
249
252
|
// Should contain the pack index body
|
|
250
253
|
assert.ok(packOutput.includes('Pack index body'), 'missing pack index body');
|
|
@@ -270,7 +273,7 @@ describe('buildAll split pack auto-discovery', () => {
|
|
|
270
273
|
await buildAll({ runeRoot, outputRoot: tmp, adapter });
|
|
271
274
|
|
|
272
275
|
try {
|
|
273
|
-
const packOutput = await readFile(path.join(tmp, adapter.outputDir, 'rune-ext-ai-ml.
|
|
276
|
+
const packOutput = await readFile(path.join(tmp, adapter.outputDir, 'rune-ext-ai-ml', 'SKILL.md'), 'utf-8');
|
|
274
277
|
|
|
275
278
|
// Should be significantly longer than just the index body (~99 lines without skills)
|
|
276
279
|
const lineCount = packOutput.split('\n').length;
|
|
@@ -58,12 +58,12 @@ describe('full pipeline: cook skill', () => {
|
|
|
58
58
|
});
|
|
59
59
|
|
|
60
60
|
describe('full pipeline: fix skill', () => {
|
|
61
|
-
test('cursor: cross-refs transformed to
|
|
61
|
+
test('cursor: cross-refs transformed to skill references', () => {
|
|
62
62
|
const adapter = getAdapter('cursor');
|
|
63
63
|
const result = transformSkill(fixParsed, adapter);
|
|
64
64
|
|
|
65
65
|
// Fix references cook, debug, test, etc. — at least some should be transformed
|
|
66
|
-
assert.ok(result.body.includes('
|
|
66
|
+
assert.ok(result.body.includes('the rune-'), 'cursor output should contain skill references');
|
|
67
67
|
});
|
|
68
68
|
|
|
69
69
|
test('generic: cross-refs transformed to descriptive text', () => {
|
|
@@ -82,15 +82,15 @@ describe('full pipeline: fix skill', () => {
|
|
|
82
82
|
// --- Output assembly (header + body + footer) ---
|
|
83
83
|
|
|
84
84
|
describe('output assembly', () => {
|
|
85
|
-
test('cursor output assembles into valid .
|
|
85
|
+
test('cursor output assembles into valid SKILL.md file', () => {
|
|
86
86
|
const adapter = getAdapter('cursor');
|
|
87
87
|
const result = transformSkill(cookParsed, adapter);
|
|
88
88
|
const assembled = result.header + result.body + result.footer;
|
|
89
89
|
|
|
90
90
|
// Should start with YAML frontmatter
|
|
91
|
-
assert.ok(assembled.startsWith('---\n'), 'cursor .
|
|
92
|
-
assert.ok(assembled.includes('
|
|
93
|
-
assert.ok(assembled.includes('
|
|
91
|
+
assert.ok(assembled.startsWith('---\n'), 'cursor SKILL.md should start with YAML frontmatter');
|
|
92
|
+
assert.ok(assembled.includes('name: rune-'), 'cursor SKILL.md should have name');
|
|
93
|
+
assert.ok(assembled.includes('description:'), 'cursor SKILL.md should have description');
|
|
94
94
|
});
|
|
95
95
|
|
|
96
96
|
test('codex output has YAML frontmatter with name', () => {
|
|
@@ -54,7 +54,7 @@ describe('resolveScriptsPath', () => {
|
|
|
54
54
|
|
|
55
55
|
describe('adapter scriptsDir', () => {
|
|
56
56
|
test('flat-file adapters return sibling dir pattern', () => {
|
|
57
|
-
for (const name of ['
|
|
57
|
+
for (const name of ['generic', 'openclaw']) {
|
|
58
58
|
const adapter = getAdapter(name);
|
|
59
59
|
assert.ok(adapter.scriptsDir, `${name} should have scriptsDir`);
|
|
60
60
|
assert.strictEqual(adapter.scriptsDir('slides'), 'rune-slides-scripts', `${name} scriptsDir mismatch`);
|
|
@@ -62,7 +62,17 @@ describe('adapter scriptsDir', () => {
|
|
|
62
62
|
});
|
|
63
63
|
|
|
64
64
|
test('directory-per-skill adapters return nested dir pattern', () => {
|
|
65
|
-
for (const name of [
|
|
65
|
+
for (const name of [
|
|
66
|
+
'codex',
|
|
67
|
+
'opencode',
|
|
68
|
+
'cursor',
|
|
69
|
+
'windsurf',
|
|
70
|
+
'copilot',
|
|
71
|
+
'qoder',
|
|
72
|
+
'gemini',
|
|
73
|
+
'qwen',
|
|
74
|
+
'antigravity',
|
|
75
|
+
]) {
|
|
66
76
|
const adapter = getAdapter(name);
|
|
67
77
|
assert.ok(adapter.scriptsDir, `${name} should have scriptsDir`);
|
|
68
78
|
assert.strictEqual(adapter.scriptsDir('slides'), 'rune-slides/scripts', `${name} scriptsDir mismatch`);
|
|
@@ -143,7 +153,7 @@ describe('buildAll with scripts', () => {
|
|
|
143
153
|
return tmp;
|
|
144
154
|
}
|
|
145
155
|
|
|
146
|
-
test('cursor: scripts
|
|
156
|
+
test('cursor: scripts inside skill directory, placeholder resolved', async () => {
|
|
147
157
|
const tmp = await createTempSkillTree();
|
|
148
158
|
try {
|
|
149
159
|
const outputRoot = path.join(tmp, 'out');
|
|
@@ -153,19 +163,19 @@ describe('buildAll with scripts', () => {
|
|
|
153
163
|
// Scripts copied
|
|
154
164
|
assert.ok(stats.scriptsCopied >= 2, `expected 2+ scripts copied, got ${stats.scriptsCopied}`);
|
|
155
165
|
|
|
156
|
-
// Scripts
|
|
157
|
-
const scriptsOut = path.join(outputRoot, adapter.outputDir, 'rune-test-slide
|
|
166
|
+
// Scripts inside skill dir: .cursor/skills/rune-test-slide/scripts/
|
|
167
|
+
const scriptsOut = path.join(outputRoot, adapter.outputDir, 'rune-test-slide', 'scripts');
|
|
158
168
|
assert.ok(existsSync(path.join(scriptsOut, 'build-deck.js')), 'build-deck.js missing in output');
|
|
159
169
|
assert.ok(existsSync(path.join(scriptsOut, 'helper.py')), 'helper.py missing in output');
|
|
160
170
|
|
|
161
|
-
// Placeholder resolved in .
|
|
162
|
-
const
|
|
163
|
-
assert.ok(!
|
|
164
|
-
assert.ok(
|
|
171
|
+
// Placeholder resolved in SKILL.md output
|
|
172
|
+
const md = await readFile(path.join(outputRoot, adapter.outputDir, 'rune-test-slide', 'SKILL.md'), 'utf-8');
|
|
173
|
+
assert.ok(!md.includes('{scripts_dir}'), 'placeholder should be resolved');
|
|
174
|
+
assert.ok(md.includes('.cursor/skills/rune-test-slide/scripts/build-deck.js'), 'resolved path missing');
|
|
165
175
|
|
|
166
176
|
// Plain skill: no scripts dir created
|
|
167
177
|
assert.ok(
|
|
168
|
-
!existsSync(path.join(outputRoot, adapter.outputDir, 'rune-test-plain
|
|
178
|
+
!existsSync(path.join(outputRoot, adapter.outputDir, 'rune-test-plain', 'scripts')),
|
|
169
179
|
'plain skill should not have scripts dir',
|
|
170
180
|
);
|
|
171
181
|
} finally {
|
|
@@ -182,7 +192,7 @@ describe('buildAll with scripts', () => {
|
|
|
182
192
|
|
|
183
193
|
assert.ok(stats.scriptsCopied >= 2);
|
|
184
194
|
|
|
185
|
-
// Scripts inside skill dir: .
|
|
195
|
+
// Scripts inside skill dir: .agents/skills/rune-test-slide/scripts/
|
|
186
196
|
const scriptsOut = path.join(outputRoot, adapter.outputDir, 'rune-test-slide', 'scripts');
|
|
187
197
|
assert.ok(existsSync(path.join(scriptsOut, 'build-deck.js')), 'build-deck.js missing');
|
|
188
198
|
assert.ok(existsSync(path.join(scriptsOut, 'helper.py')), 'helper.py missing');
|
|
@@ -190,7 +200,7 @@ describe('buildAll with scripts', () => {
|
|
|
190
200
|
// Placeholder resolved
|
|
191
201
|
const md = await readFile(path.join(outputRoot, adapter.outputDir, 'rune-test-slide', 'SKILL.md'), 'utf-8');
|
|
192
202
|
assert.ok(!md.includes('{scripts_dir}'), 'placeholder should be resolved');
|
|
193
|
-
assert.ok(md.includes('.
|
|
203
|
+
assert.ok(md.includes('.agents/skills/rune-test-slide/scripts/build-deck.js'), 'resolved path missing');
|
|
194
204
|
} finally {
|
|
195
205
|
await rm(tmp, { recursive: true, force: true });
|
|
196
206
|
}
|
|
@@ -251,7 +261,7 @@ describe('buildAll with scripts', () => {
|
|
|
251
261
|
|
|
252
262
|
const original = await readFile(path.join(tmp, 'skills', 'test-slide', 'scripts', 'build-deck.js'), 'utf-8');
|
|
253
263
|
const copied = await readFile(
|
|
254
|
-
path.join(outputRoot, adapter.outputDir, 'rune-test-slide
|
|
264
|
+
path.join(outputRoot, adapter.outputDir, 'rune-test-slide', 'scripts', 'build-deck.js'),
|
|
255
265
|
'utf-8',
|
|
256
266
|
);
|
|
257
267
|
assert.strictEqual(copied, original, 'script content should be identical');
|
|
@@ -263,19 +273,19 @@ describe('buildAll with scripts', () => {
|
|
|
263
273
|
test('multiple adapters resolve different paths', async () => {
|
|
264
274
|
const tmp = await createTempSkillTree();
|
|
265
275
|
try {
|
|
266
|
-
// Cursor (
|
|
276
|
+
// Cursor (directory)
|
|
267
277
|
const cursorOut = path.join(tmp, 'out-cursor');
|
|
268
278
|
const cursorAdapter = getAdapter('cursor');
|
|
269
279
|
await buildAll({ runeRoot: tmp, outputRoot: cursorOut, adapter: cursorAdapter });
|
|
270
|
-
const
|
|
271
|
-
assert.ok(
|
|
280
|
+
const cursorMd = await readFile(path.join(cursorOut, '.cursor/skills/rune-test-slide/SKILL.md'), 'utf-8');
|
|
281
|
+
assert.ok(cursorMd.includes('.cursor/skills/rune-test-slide/scripts/'), 'cursor path');
|
|
272
282
|
|
|
273
|
-
// Codex (directory)
|
|
283
|
+
// Codex (directory, shared .agents/skills standard)
|
|
274
284
|
const codexOut = path.join(tmp, 'out-codex');
|
|
275
285
|
const codexAdapter = getAdapter('codex');
|
|
276
286
|
await buildAll({ runeRoot: tmp, outputRoot: codexOut, adapter: codexAdapter });
|
|
277
|
-
const codexMd = await readFile(path.join(codexOut, '.
|
|
278
|
-
assert.ok(codexMd.includes('.
|
|
287
|
+
const codexMd = await readFile(path.join(codexOut, '.agents/skills/rune-test-slide/SKILL.md'), 'utf-8');
|
|
288
|
+
assert.ok(codexMd.includes('.agents/skills/rune-test-slide/scripts/'), 'codex path');
|
|
279
289
|
} finally {
|
|
280
290
|
await rm(tmp, { recursive: true, force: true });
|
|
281
291
|
}
|
|
@@ -27,10 +27,10 @@ describe('transformSkill', () => {
|
|
|
27
27
|
assert.strictEqual(result.footer, '');
|
|
28
28
|
});
|
|
29
29
|
|
|
30
|
-
test('Cursor adapter transforms cross-refs to
|
|
30
|
+
test('Cursor adapter transforms cross-refs to skill references', () => {
|
|
31
31
|
const cursor = getAdapter('cursor');
|
|
32
32
|
const result = transformSkill(mockSkill, cursor);
|
|
33
|
-
assert.ok(result.body.includes('
|
|
33
|
+
assert.ok(result.body.includes('the rune-cook skill'));
|
|
34
34
|
assert.ok(!result.body.includes('rune:cook'));
|
|
35
35
|
});
|
|
36
36
|
|
|
@@ -44,11 +44,11 @@ describe('transformSkill', () => {
|
|
|
44
44
|
assert.ok(result.footer.length > 0, 'footer should not be empty for non-Claude');
|
|
45
45
|
});
|
|
46
46
|
|
|
47
|
-
test('Cursor header contains frontmatter with description', () => {
|
|
47
|
+
test('Cursor header contains SKILL.md frontmatter with name and description', () => {
|
|
48
48
|
const cursor = getAdapter('cursor');
|
|
49
49
|
const result = transformSkill(mockSkill, cursor);
|
|
50
|
+
assert.ok(result.header.includes('name: rune-'));
|
|
50
51
|
assert.ok(result.header.includes('description:'));
|
|
51
|
-
assert.ok(result.header.includes('alwaysApply:'));
|
|
52
52
|
});
|
|
53
53
|
|
|
54
54
|
test('Generic adapter transforms refs to descriptive text', () => {
|
|
@@ -1,26 +1,45 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* OpenAI Codex Adapter
|
|
3
3
|
*
|
|
4
|
-
* Emits SKILL.md files into .
|
|
4
|
+
* Emits SKILL.md files into .agents/skills/{name}/ directories.
|
|
5
5
|
* Codex uses the same SKILL.md frontmatter format (name, description)
|
|
6
6
|
* with markdown body — very close to Rune's native format.
|
|
7
7
|
*
|
|
8
8
|
* Codex project context: AGENTS.md (equivalent to CLAUDE.md)
|
|
9
|
-
* Codex skills dir: .
|
|
10
|
-
* Codex skill format: .
|
|
9
|
+
* Codex skills dir: .agents/skills/ (scanned from CWD up to repo root)
|
|
10
|
+
* Codex skill format: .agents/skills/{name}/SKILL.md
|
|
11
11
|
*
|
|
12
|
-
*
|
|
12
|
+
* NOTE: Codex dropped .codex/skills/ from its scan list — .codex/ is config
|
|
13
|
+
* only (config.toml). Repo skills MUST live in .agents/skills/ to be
|
|
14
|
+
* auto-discovered. @see https://developers.openai.com/codex/skills
|
|
15
|
+
*
|
|
16
|
+
* MODEL TIER MAPPING (v2.15+, lineup refreshed 2026-07):
|
|
13
17
|
* Skill frontmatter `model: opus|sonnet|haiku` (Anthropic naming) is
|
|
14
18
|
* translated to Codex/OpenAI provider-correct model names so the field
|
|
15
19
|
* is meaningful in the compiled output. Unknown tier values pass through.
|
|
20
|
+
*
|
|
21
|
+
* Codex GPT-5.6 tiers (verified against codex-cli 0.144.1 + developers.openai.com/codex/models):
|
|
22
|
+
* sol = flagship/complex → opus
|
|
23
|
+
* terra = balanced everyday → sonnet
|
|
24
|
+
* luna = fast/affordable → haiku
|
|
25
|
+
* Codex also exposes `model_reasoning_effort = minimal|low|medium|high|xhigh`
|
|
26
|
+
* (a config.toml key, not per-skill frontmatter) — Rune surfaces the tier→effort
|
|
27
|
+
* suggestion in AGENTS.md rather than emitting a speculative per-skill field.
|
|
16
28
|
*/
|
|
17
29
|
|
|
18
30
|
import { BRANDING_FOOTER } from '../transforms/branding.js';
|
|
19
31
|
|
|
20
32
|
const MODEL_MAP = {
|
|
21
|
-
opus: 'gpt-5-
|
|
22
|
-
sonnet: 'gpt-5',
|
|
23
|
-
haiku: 'gpt-5-
|
|
33
|
+
opus: 'gpt-5.6-sol',
|
|
34
|
+
sonnet: 'gpt-5.6-terra',
|
|
35
|
+
haiku: 'gpt-5.6-luna',
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// Suggested config.toml `model_reasoning_effort` per tier (global key, documented in AGENTS.md).
|
|
39
|
+
const REASONING_EFFORT_MAP = {
|
|
40
|
+
opus: 'high',
|
|
41
|
+
sonnet: 'medium',
|
|
42
|
+
haiku: 'low',
|
|
24
43
|
};
|
|
25
44
|
|
|
26
45
|
const TOOL_MAP = {
|
|
@@ -37,12 +56,12 @@ const TOOL_MAP = {
|
|
|
37
56
|
|
|
38
57
|
export default {
|
|
39
58
|
name: 'codex',
|
|
40
|
-
outputDir: '.
|
|
59
|
+
outputDir: '.agents/skills',
|
|
41
60
|
fileExtension: '.md',
|
|
42
61
|
skillPrefix: 'rune-',
|
|
43
62
|
skillSuffix: '',
|
|
44
63
|
|
|
45
|
-
// Codex uses directory-per-skill: .
|
|
64
|
+
// Codex uses directory-per-skill: .agents/skills/{name}/SKILL.md
|
|
46
65
|
useSkillDirectories: true,
|
|
47
66
|
skillFileName: 'SKILL.md',
|
|
48
67
|
|
|
@@ -105,7 +124,17 @@ export default {
|
|
|
105
124
|
'',
|
|
106
125
|
'## Skills Directory',
|
|
107
126
|
'',
|
|
108
|
-
'Skills are located in: .
|
|
127
|
+
'Skills are located in: .agents/skills/ (auto-discovered by Codex)',
|
|
128
|
+
'',
|
|
129
|
+
'## Model Tiers',
|
|
130
|
+
'',
|
|
131
|
+
'Rune skills carry a tier hint (`opus`/`sonnet`/`haiku`). Suggested Codex mapping:',
|
|
132
|
+
'',
|
|
133
|
+
`- opus → \`${MODEL_MAP.opus}\` (\`model_reasoning_effort = "${REASONING_EFFORT_MAP.opus}"\`) — architecture / security / planning`,
|
|
134
|
+
`- sonnet → \`${MODEL_MAP.sonnet}\` (\`model_reasoning_effort = "${REASONING_EFFORT_MAP.sonnet}"\`) — code / edit / review`,
|
|
135
|
+
`- haiku → \`${MODEL_MAP.haiku}\` (\`model_reasoning_effort = "${REASONING_EFFORT_MAP.haiku}"\`) — scan / read-only`,
|
|
136
|
+
'',
|
|
137
|
+
'Set `model` + `model_reasoning_effort` in `~/.codex/config.toml` to match the tier of the task.',
|
|
109
138
|
'',
|
|
110
139
|
'---',
|
|
111
140
|
'> Rune Skill Mesh — https://github.com/rune-kit/rune',
|