@rune-kit/rune 2.12.2 → 2.13.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 +26 -3
- package/compiler/__tests__/hooks-doctor-tier.test.js +214 -0
- package/compiler/__tests__/openclaw-adapter.test.js +26 -0
- package/compiler/adapters/openclaw.js +43 -3
- package/compiler/commands/hooks/tiers.js +42 -0
- package/compiler/doctor.js +131 -0
- package/package.json +1 -1
- package/skills/adversary/SKILL.md +1 -1
- package/skills/autopsy/SKILL.md +1 -1
- package/skills/ba/SKILL.md +32 -8
- package/skills/context-pack/SKILL.md +1 -1
- package/skills/cook/SKILL.md +6 -5
- package/skills/design/SKILL.md +95 -2
- package/skills/graft/SKILL.md +20 -4
- package/skills/journal/SKILL.md +1 -1
- package/skills/marketing/SKILL.md +2 -1
- package/skills/preflight/SKILL.md +13 -3
- package/skills/review/SKILL.md +53 -1
- package/skills/safeguard/SKILL.md +1 -1
- package/skills/sentinel-env/SKILL.md +24 -13
- package/skills/skill-forge/SKILL.md +108 -1
- package/skills/surgeon/SKILL.md +1 -1
package/README.md
CHANGED
|
@@ -83,7 +83,15 @@ _Methodology: Claude Code CLI headless mode (`claude -p --output-format json`),
|
|
|
83
83
|
|
|
84
84
|
---
|
|
85
85
|
|
|
86
|
-
## What's New (v2.
|
|
86
|
+
## What's New (v2.13.0 — Script Contract + Media Pack)
|
|
87
|
+
|
|
88
|
+
- **`@rune-pro/media` pack v1.0.0** — new Pro pack: raster image generation across 5 providers (Codex CLI, DALL-E, Replicate, Stability AI, local SD), prompt engineering with 4-gate safety check (trademark, public-figure, prompt-injection, uncanny-precondition), batch asset pipeline with multi-resolution variants + WebP/AVIF conversion + EXIF strip. Fills the raster gap left by `asset-creator` (SVG/HTML only) and `video-creator` (planning only).
|
|
89
|
+
- **`sentinel-env` v0.3.0** — 9-tier binary detection for hard-dependency checks. Catches desktop-app bundles (macOS `.app`), npm-global on Windows (PATH oversight), platform release archives. Eliminates the "binary installed but not detected" failure class.
|
|
90
|
+
- **`skill-forge` v1.8.0** — new Phase 5.25 "Script Contract" — helper scripts must follow stdout=paths / stderr=diagnostics / `--json` opt-in / `--debug` opt-in pattern, implement semantic exit codes (including `4` timeout-partial vs `124` timeout-zero), honor OpenClaw artifact-directory fallback chain. HARD-GATE on pre-ship verification.
|
|
91
|
+
- **OpenClaw adapter** — `generateManifest` now declares `artifactConvention` (output-dir fallback order, output contract, exit-code vocabulary). Formalizes the de-facto in-the-wild convention from [codex-imagen](https://github.com/darkamenosa/codex-imagen).
|
|
92
|
+
- **Skill-count scaling** — OpenClaw manifest description now scales with actual skill count rather than hardcoded.
|
|
93
|
+
|
|
94
|
+
### Previous (v2.12.0 — Auto-Discipline)
|
|
87
95
|
|
|
88
96
|
- **Runtime auto-discipline** — `rune hooks install` wires native hooks on Claude Code, Cursor, Windsurf, Antigravity so `preflight`, `sentinel`, `completion-gate` auto-fire before tool use. No more "remember to invoke the skill."
|
|
89
97
|
- **Three presets** — `strict` (blocking gates), `gentle` (warnings, default), `off` (uninstall). Idempotent install / uninstall with full restore of user hooks.
|
|
@@ -548,17 +556,32 @@ npx @rune-kit/rune doctor
|
|
|
548
556
|
|
|
549
557
|
See [docs/MULTI-PLATFORM.md](docs/MULTI-PLATFORM.md) for the full architecture.
|
|
550
558
|
|
|
559
|
+
## Documentation
|
|
560
|
+
|
|
561
|
+
| Doc | What's inside |
|
|
562
|
+
|-----|---------------|
|
|
563
|
+
| [`docs/GETTING_STARTED.md`](docs/GETTING_STARTED.md) | Your first 5 minutes with Rune — install to first `/rune cook` |
|
|
564
|
+
| [`docs/SKILLS.md`](docs/SKILLS.md) | All 62 skills, searchable by intent and layer |
|
|
565
|
+
| [`docs/SIGNALS.md`](docs/SIGNALS.md) | Canonical signal inventory — 25 events, emit/listen graph |
|
|
566
|
+
| [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) | 5-layer mesh architecture reference |
|
|
567
|
+
| [`docs/VISION.md`](docs/VISION.md) | Philosophy — what Rune is and isn't |
|
|
568
|
+
| [`docs/HOOKS.md`](docs/HOOKS.md) | Auto-discipline hooks per platform |
|
|
569
|
+
| [`docs/TROUBLESHOOTING.md`](docs/TROUBLESHOOTING.md) | Common issues + fixes |
|
|
570
|
+
| [`CONTRIBUTING.md`](CONTRIBUTING.md) | How to contribute skills, packs, fixes |
|
|
571
|
+
| [`CHANGELOG.md`](CHANGELOG.md) | Release history |
|
|
572
|
+
| [`ROADMAP.md`](ROADMAP.md) | What's next |
|
|
573
|
+
|
|
551
574
|
## Numbers
|
|
552
575
|
|
|
553
576
|
```
|
|
554
577
|
Core Skills: 62 (L0: 1 │ L1: 5 │ L2: 29 │ L3: 27)
|
|
555
578
|
Extension Packs: 14 free + 5 pro + 4 business
|
|
556
579
|
Mesh Connections: 215+ cross-references
|
|
557
|
-
Mesh Signals:
|
|
580
|
+
Mesh Signals: 25 signals across 62 skills (emit/listen graph)
|
|
558
581
|
Connections/Skill: 3.4 avg
|
|
559
582
|
Platforms: 8 (Claude Code, Cursor, Windsurf, Antigravity, Codex, OpenCode, OpenClaw, Generic)
|
|
560
583
|
Compiler: ~1400 LOC (parser + 8 transforms + 8 adapters + CLI)
|
|
561
|
-
Tests:
|
|
584
|
+
Tests: 1,152+ (compiler + signals + status + visualizer + hooks + scripts + tier-hooks)
|
|
562
585
|
Pack Depth: 23 packs total (14 free + 5 pro + 4 business, all free packs rated Deep)
|
|
563
586
|
```
|
|
564
587
|
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Doctor tier-coverage check — warns when a tier's hooks are installed on
|
|
3
|
+
* Claude but missing on other detected platforms, or when required env vars
|
|
4
|
+
* are unset so installed hooks would fail at runtime.
|
|
5
|
+
*
|
|
6
|
+
* Checks are advisory (WARN) — never block the doctor pipeline.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import assert from 'node:assert';
|
|
10
|
+
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
11
|
+
import { tmpdir } from 'node:os';
|
|
12
|
+
import path from 'node:path';
|
|
13
|
+
import { afterEach, beforeEach, describe, test } from 'node:test';
|
|
14
|
+
import { listDetectedTiers } from '../commands/hooks/tiers.js';
|
|
15
|
+
import { checkTierCoverage } from '../doctor.js';
|
|
16
|
+
|
|
17
|
+
const PRO_MANIFEST = {
|
|
18
|
+
name: 'Rune Pro Hooks',
|
|
19
|
+
description: 'Pro tier hooks',
|
|
20
|
+
tier: 'pro',
|
|
21
|
+
version: '1.0.0',
|
|
22
|
+
requires: ['RUNE_PRO_ROOT'],
|
|
23
|
+
entries: [
|
|
24
|
+
{
|
|
25
|
+
id: 'context-inject',
|
|
26
|
+
skill: 'context-inject',
|
|
27
|
+
event: 'UserPromptSubmit',
|
|
28
|
+
matcher: '.*',
|
|
29
|
+
command: 'node "${RUNE_PRO_ROOT}/hooks/run-hook.cjs" context-inject',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: 'context-sense',
|
|
33
|
+
skill: 'context-sense',
|
|
34
|
+
event: 'PreToolUse',
|
|
35
|
+
matcher: 'Edit|Write',
|
|
36
|
+
command: 'node "${RUNE_PRO_ROOT}/hooks/run-hook.cjs" context-sense',
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
let tmpRoot;
|
|
42
|
+
let tierRoot;
|
|
43
|
+
let originalRunePro;
|
|
44
|
+
|
|
45
|
+
async function seedTierRoot(tier, manifest) {
|
|
46
|
+
const dir = await mkdtemp(path.join(tmpdir(), `rune-doc-${tier}-`));
|
|
47
|
+
await mkdir(path.join(dir, 'hooks'), { recursive: true });
|
|
48
|
+
await writeFile(path.join(dir, 'hooks', 'manifest.json'), JSON.stringify(manifest, null, 2));
|
|
49
|
+
return dir;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function seedPlatform(root, platform) {
|
|
53
|
+
const map = {
|
|
54
|
+
claude: { dir: '.claude', settings: true },
|
|
55
|
+
cursor: { dir: '.cursor/rules' },
|
|
56
|
+
windsurf: { dir: '.windsurf/workflows' },
|
|
57
|
+
antigravity: { dir: '.antigravity/rules' },
|
|
58
|
+
};
|
|
59
|
+
const spec = map[platform];
|
|
60
|
+
await mkdir(path.join(root, spec.dir), { recursive: true });
|
|
61
|
+
if (spec.settings) {
|
|
62
|
+
await writeFile(path.join(root, '.claude', 'settings.json'), '{}');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function seedTierHooksOn(root, platform, tier) {
|
|
67
|
+
if (platform === 'claude') {
|
|
68
|
+
const settingsPath = path.join(root, '.claude', 'settings.json');
|
|
69
|
+
const settings = {
|
|
70
|
+
hooks: {
|
|
71
|
+
UserPromptSubmit: [
|
|
72
|
+
{
|
|
73
|
+
matcher: '.*',
|
|
74
|
+
hooks: [
|
|
75
|
+
{ type: 'command', command: 'node "${RUNE_' + tier.toUpperCase() + '_ROOT}/hooks/run-hook.cjs" ctx' },
|
|
76
|
+
],
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
await mkdir(path.dirname(settingsPath), { recursive: true });
|
|
82
|
+
await writeFile(settingsPath, JSON.stringify(settings, null, 2));
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const dirs = {
|
|
86
|
+
cursor: path.join(root, '.cursor/rules'),
|
|
87
|
+
windsurf: path.join(root, '.windsurf/workflows'),
|
|
88
|
+
antigravity: path.join(root, '.antigravity/rules'),
|
|
89
|
+
};
|
|
90
|
+
const ext = { cursor: '.mdc', windsurf: '.md', antigravity: '.md' }[platform];
|
|
91
|
+
const dir = dirs[platform];
|
|
92
|
+
await mkdir(dir, { recursive: true });
|
|
93
|
+
await writeFile(path.join(dir, `rune-${tier}-context-inject${ext}`), '# tier hook\n');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
beforeEach(async () => {
|
|
97
|
+
tmpRoot = await mkdtemp(path.join(tmpdir(), 'rune-doctor-tier-'));
|
|
98
|
+
tierRoot = await seedTierRoot('pro', PRO_MANIFEST);
|
|
99
|
+
originalRunePro = process.env.RUNE_PRO_ROOT;
|
|
100
|
+
process.env.RUNE_PRO_ROOT = tierRoot;
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
afterEach(async () => {
|
|
104
|
+
if (tmpRoot) await rm(tmpRoot, { recursive: true, force: true });
|
|
105
|
+
if (tierRoot) await rm(tierRoot, { recursive: true, force: true });
|
|
106
|
+
if (originalRunePro === undefined) delete process.env.RUNE_PRO_ROOT;
|
|
107
|
+
else process.env.RUNE_PRO_ROOT = originalRunePro;
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe('listDetectedTiers', () => {
|
|
111
|
+
test('returns empty array when no tier env vars set and no monorepo sibling', async () => {
|
|
112
|
+
delete process.env.RUNE_PRO_ROOT;
|
|
113
|
+
const result = await listDetectedTiers(tmpRoot);
|
|
114
|
+
assert.deepStrictEqual(result, []);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test('returns Pro tier when RUNE_PRO_ROOT resolves', async () => {
|
|
118
|
+
const result = await listDetectedTiers(tmpRoot);
|
|
119
|
+
assert.strictEqual(result.length, 1);
|
|
120
|
+
assert.strictEqual(result[0].tier, 'pro');
|
|
121
|
+
assert.strictEqual(result[0].found, true);
|
|
122
|
+
assert.strictEqual(result[0].requiresOk, true);
|
|
123
|
+
assert.deepStrictEqual(result[0].requiresMissing, []);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test('reports requiresMissing when env var gets unset after locate', async () => {
|
|
127
|
+
// Manifest locates via a secondary path (env set at locate, unset at requires check).
|
|
128
|
+
// Simulate by forcing a manifest with extra required env.
|
|
129
|
+
const extraReq = { ...PRO_MANIFEST, requires: ['RUNE_PRO_ROOT', 'SOME_OTHER_VAR'] };
|
|
130
|
+
await writeFile(path.join(tierRoot, 'hooks', 'manifest.json'), JSON.stringify(extraReq, null, 2));
|
|
131
|
+
const result = await listDetectedTiers(tmpRoot);
|
|
132
|
+
assert.strictEqual(result[0].requiresOk, false);
|
|
133
|
+
assert.deepStrictEqual(result[0].requiresMissing, ['SOME_OTHER_VAR']);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe('checkTierCoverage', () => {
|
|
138
|
+
test('silent when no tiers detected', async () => {
|
|
139
|
+
delete process.env.RUNE_PRO_ROOT;
|
|
140
|
+
const result = await checkTierCoverage({ projectRoot: tmpRoot });
|
|
141
|
+
assert.strictEqual(result.checks.length, 0);
|
|
142
|
+
assert.strictEqual(result.warnings.length, 0);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test('silent when tier detected but no platform dirs exist', async () => {
|
|
146
|
+
const result = await checkTierCoverage({ projectRoot: tmpRoot });
|
|
147
|
+
assert.strictEqual(result.checks.length, 0);
|
|
148
|
+
assert.strictEqual(result.warnings.length, 0);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test('pass when Pro installed on every detected platform', async () => {
|
|
152
|
+
await seedPlatform(tmpRoot, 'claude');
|
|
153
|
+
await seedPlatform(tmpRoot, 'cursor');
|
|
154
|
+
await seedTierHooksOn(tmpRoot, 'claude', 'pro');
|
|
155
|
+
await seedTierHooksOn(tmpRoot, 'cursor', 'pro');
|
|
156
|
+
|
|
157
|
+
const result = await checkTierCoverage({ projectRoot: tmpRoot });
|
|
158
|
+
const tierCheck = result.checks.find((c) => c.name === 'Tier coverage');
|
|
159
|
+
assert.ok(tierCheck, 'should emit a tier-coverage check');
|
|
160
|
+
assert.strictEqual(tierCheck.status, 'pass');
|
|
161
|
+
assert.strictEqual(result.warnings.length, 0);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test('warns when Pro on Claude but missing on detected Cursor', async () => {
|
|
165
|
+
await seedPlatform(tmpRoot, 'claude');
|
|
166
|
+
await seedPlatform(tmpRoot, 'cursor');
|
|
167
|
+
await seedTierHooksOn(tmpRoot, 'claude', 'pro');
|
|
168
|
+
// Cursor platform seeded but NO pro-prefixed files — the gap we want to catch.
|
|
169
|
+
|
|
170
|
+
const result = await checkTierCoverage({ projectRoot: tmpRoot });
|
|
171
|
+
const tierCheck = result.checks.find((c) => c.name === 'Tier coverage');
|
|
172
|
+
assert.strictEqual(tierCheck.status, 'warn');
|
|
173
|
+
assert.strictEqual(result.warnings.length >= 1, true);
|
|
174
|
+
const msg = result.warnings.join('\n');
|
|
175
|
+
assert.match(msg, /pro/);
|
|
176
|
+
assert.match(msg, /cursor/);
|
|
177
|
+
assert.match(msg, /rune hooks install/);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test('warns when tier manifest requires unset env var', async () => {
|
|
181
|
+
await seedPlatform(tmpRoot, 'claude');
|
|
182
|
+
await seedTierHooksOn(tmpRoot, 'claude', 'pro');
|
|
183
|
+
// Rewrite manifest to add a req that is definitely unset.
|
|
184
|
+
const extraReq = { ...PRO_MANIFEST, requires: ['RUNE_PRO_ROOT', 'DOCTOR_TIER_MISSING_ENV'] };
|
|
185
|
+
await writeFile(path.join(tierRoot, 'hooks', 'manifest.json'), JSON.stringify(extraReq, null, 2));
|
|
186
|
+
|
|
187
|
+
const result = await checkTierCoverage({ projectRoot: tmpRoot });
|
|
188
|
+
const envWarn = result.warnings.find((w) => w.includes('DOCTOR_TIER_MISSING_ENV'));
|
|
189
|
+
assert.ok(envWarn, `expected missing-env warning, got: ${result.warnings.join('\n')}`);
|
|
190
|
+
assert.match(envWarn, /pro/);
|
|
191
|
+
assert.match(envWarn, /will FAIL at runtime/);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test('no false-positive: Pro on Cursor alone (no Claude dir) is not a gap', async () => {
|
|
195
|
+
await seedPlatform(tmpRoot, 'cursor');
|
|
196
|
+
await seedTierHooksOn(tmpRoot, 'cursor', 'pro');
|
|
197
|
+
|
|
198
|
+
const result = await checkTierCoverage({ projectRoot: tmpRoot });
|
|
199
|
+
const tierCheck = result.checks.find((c) => c.name === 'Tier coverage');
|
|
200
|
+
// Installed on every detected platform (just cursor) → pass, not warn.
|
|
201
|
+
assert.strictEqual(tierCheck.status, 'pass');
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test('windsurf workflow OR rule file counts as installed', async () => {
|
|
205
|
+
await seedPlatform(tmpRoot, 'windsurf');
|
|
206
|
+
const rulesDir = path.join(tmpRoot, '.windsurf/rules');
|
|
207
|
+
await mkdir(rulesDir, { recursive: true });
|
|
208
|
+
await writeFile(path.join(rulesDir, 'rune-pro-context-inject-rule.md'), '# rule\n');
|
|
209
|
+
|
|
210
|
+
const result = await checkTierCoverage({ projectRoot: tmpRoot });
|
|
211
|
+
const tierCheck = result.checks.find((c) => c.name === 'Tier coverage');
|
|
212
|
+
assert.strictEqual(tierCheck.status, 'pass');
|
|
213
|
+
});
|
|
214
|
+
});
|
|
@@ -114,6 +114,32 @@ test('generateManifest defaults version when missing', () => {
|
|
|
114
114
|
assert.strictEqual(manifest.version, '0.0.0');
|
|
115
115
|
});
|
|
116
116
|
|
|
117
|
+
test('generateManifest declares artifact convention for OpenClaw skills', () => {
|
|
118
|
+
const manifest = openclaw.generateManifest(
|
|
119
|
+
[{ name: 'cook', layer: 'L1', group: 'orchestrator' }],
|
|
120
|
+
{ version: '1.0.0' },
|
|
121
|
+
);
|
|
122
|
+
assert.ok(manifest.artifactConvention, 'artifactConvention field exists');
|
|
123
|
+
assert.ok(Array.isArray(manifest.artifactConvention.outputDirPriority), 'outputDirPriority is array');
|
|
124
|
+
assert.ok(manifest.artifactConvention.outputDirPriority.length >= 4, 'at least 4 fallback tiers');
|
|
125
|
+
assert.ok(
|
|
126
|
+
manifest.artifactConvention.outputDirPriority.some((tier) => tier.includes('OPENCLAW_AGENT_DIR')),
|
|
127
|
+
'OPENCLAW_AGENT_DIR is a tier',
|
|
128
|
+
);
|
|
129
|
+
assert.ok(manifest.artifactConvention.outputContract, 'outputContract documented');
|
|
130
|
+
assert.strictEqual(manifest.artifactConvention.outputContract.exitCodes[0], 'success');
|
|
131
|
+
assert.strictEqual(manifest.artifactConvention.outputContract.exitCodes[4], 'timeout with partial results (accept)');
|
|
132
|
+
assert.strictEqual(manifest.artifactConvention.outputContract.exitCodes[124], 'timeout with zero results (retry or abort)');
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test('generateManifest description scales with skill count', () => {
|
|
136
|
+
const fewSkills = openclaw.generateManifest(
|
|
137
|
+
[{ name: 'cook', layer: 'L1' }, { name: 'plan', layer: 'L2' }],
|
|
138
|
+
{ version: '1.0.0' },
|
|
139
|
+
);
|
|
140
|
+
assert.ok(fewSkills.description.includes('2-skill'), 'description reflects actual skill count');
|
|
141
|
+
});
|
|
142
|
+
|
|
117
143
|
// --- generateEntryPoint ---
|
|
118
144
|
|
|
119
145
|
test('generateEntryPoint returns valid TypeScript with register(api)', () => {
|
|
@@ -7,6 +7,25 @@
|
|
|
7
7
|
* .openclaw/rune/skills/*.md (transformed skill files)
|
|
8
8
|
*
|
|
9
9
|
* Follows the NeuralMemory OpenClaw plugin pattern.
|
|
10
|
+
*
|
|
11
|
+
* ARTIFACT CONVENTION (v2.13+):
|
|
12
|
+
* OpenClaw skills that produce file artifacts (images, reports, data) should
|
|
13
|
+
* resolve output directory in this fallback order — honored by the Rune script
|
|
14
|
+
* output contract (see skills/skill-forge Phase 5.25):
|
|
15
|
+
*
|
|
16
|
+
* 1. --out-dir <path> (explicit caller intent)
|
|
17
|
+
* 2. <SKILL>_OUT_DIR (skill-specific env var)
|
|
18
|
+
* 3. OPENCLAW_OUTPUT_DIR (platform-wide override)
|
|
19
|
+
* 4. OPENCLAW_AGENT_DIR/artifacts/<skill> (per-agent scoped default)
|
|
20
|
+
* 5. OPENCLAW_STATE_DIR/artifacts/<skill> (state-scoped fallback)
|
|
21
|
+
* 6. ./.rune/<skill>/ (project-local default)
|
|
22
|
+
*
|
|
23
|
+
* Reference implementations in @rune-pro/media/scripts/:
|
|
24
|
+
* - codex_imagen_bridge.mjs (full resolution + 9-tier binary detection)
|
|
25
|
+
* - image_optimizer.py (Python equivalent)
|
|
26
|
+
*
|
|
27
|
+
* Reference codex-imagen repo (darkamenosa/codex-imagen) documents the
|
|
28
|
+
* de-facto in-the-wild convention this adapter formalizes.
|
|
10
29
|
*/
|
|
11
30
|
|
|
12
31
|
import { BRANDING_FOOTER } from '../transforms/branding.js';
|
|
@@ -67,15 +86,36 @@ export default {
|
|
|
67
86
|
* @param {object} pluginJson - Rune's .claude-plugin/plugin.json
|
|
68
87
|
* @returns {object} manifest object
|
|
69
88
|
*/
|
|
70
|
-
generateManifest(
|
|
89
|
+
generateManifest(skills, pluginJson) {
|
|
71
90
|
return {
|
|
72
91
|
id: 'rune',
|
|
73
92
|
name: 'Rune',
|
|
74
93
|
kind: 'skills',
|
|
75
|
-
description:
|
|
76
|
-
'62-skill mesh for AI coding assistants. Routes all code tasks through specialized skills. 215+ connections, 14 extension packs.',
|
|
94
|
+
description: `${skills.length}-skill mesh for AI coding assistants. Routes all code tasks through specialized skills. 215+ connections, 14 extension packs.`,
|
|
77
95
|
version: pluginJson.version || '0.0.0',
|
|
78
96
|
skills: ['./skills'],
|
|
97
|
+
artifactConvention: {
|
|
98
|
+
outputDirPriority: [
|
|
99
|
+
'--out-dir <path>',
|
|
100
|
+
'<SKILL>_OUT_DIR',
|
|
101
|
+
'OPENCLAW_OUTPUT_DIR',
|
|
102
|
+
'OPENCLAW_AGENT_DIR/artifacts/<skill>',
|
|
103
|
+
'OPENCLAW_STATE_DIR/artifacts/<skill>',
|
|
104
|
+
'./.rune/<skill>/',
|
|
105
|
+
],
|
|
106
|
+
outputContract: {
|
|
107
|
+
stdout: 'one artifact path per line (default) or JSON (--json mode)',
|
|
108
|
+
stderr: 'diagnostics + warnings',
|
|
109
|
+
exitCodes: {
|
|
110
|
+
0: 'success',
|
|
111
|
+
1: 'execution failed (retryable)',
|
|
112
|
+
2: 'usage error (bug)',
|
|
113
|
+
3: 'data-integrity error (halt)',
|
|
114
|
+
4: 'timeout with partial results (accept)',
|
|
115
|
+
124: 'timeout with zero results (retry or abort)',
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
},
|
|
79
119
|
configSchema: {
|
|
80
120
|
jsonSchema: {
|
|
81
121
|
type: 'object',
|
|
@@ -255,6 +255,48 @@ export function checkManifestRequires(manifest) {
|
|
|
255
255
|
return { ok: missing.length === 0, missing };
|
|
256
256
|
}
|
|
257
257
|
|
|
258
|
+
/**
|
|
259
|
+
* Enumerate known tiers and return status for each that can be located.
|
|
260
|
+
* Tiers that can't be found (no env var, no monorepo sibling) are skipped.
|
|
261
|
+
* Invalid/corrupt manifests are reported with { found: false, error }.
|
|
262
|
+
*
|
|
263
|
+
* @param {string} projectRoot
|
|
264
|
+
* @returns {Promise<Array<{tier: string, found: boolean, manifestPath: string|null, version?: string, requires: string[], requiresOk: boolean, requiresMissing: string[], entries: number, error?: string}>>}
|
|
265
|
+
*/
|
|
266
|
+
export async function listDetectedTiers(projectRoot) {
|
|
267
|
+
const out = [];
|
|
268
|
+
for (const tier of Object.keys(TIER_ENV_VARS)) {
|
|
269
|
+
const manifestPath = locateTierManifest(tier, projectRoot);
|
|
270
|
+
if (!manifestPath) continue;
|
|
271
|
+
try {
|
|
272
|
+
const manifest = await loadTierManifest(manifestPath);
|
|
273
|
+
const req = checkManifestRequires(manifest);
|
|
274
|
+
out.push({
|
|
275
|
+
tier,
|
|
276
|
+
found: true,
|
|
277
|
+
manifestPath,
|
|
278
|
+
version: manifest.version,
|
|
279
|
+
requires: [...(manifest.requires || [])],
|
|
280
|
+
requiresOk: req.ok,
|
|
281
|
+
requiresMissing: req.missing,
|
|
282
|
+
entries: manifest.entries.length,
|
|
283
|
+
});
|
|
284
|
+
} catch (err) {
|
|
285
|
+
out.push({
|
|
286
|
+
tier,
|
|
287
|
+
found: false,
|
|
288
|
+
manifestPath,
|
|
289
|
+
requires: [],
|
|
290
|
+
requiresOk: false,
|
|
291
|
+
requiresMissing: [],
|
|
292
|
+
entries: 0,
|
|
293
|
+
error: err.message,
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return out;
|
|
298
|
+
}
|
|
299
|
+
|
|
258
300
|
/**
|
|
259
301
|
* Resolve a tier request (`--tier pro`) into a loaded, validated manifest,
|
|
260
302
|
* or throw with a helpful upgrade/missing-env message.
|
package/compiler/doctor.js
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
import { existsSync } from 'node:fs';
|
|
9
9
|
import { readdir, readFile } from 'node:fs/promises';
|
|
10
10
|
import path from 'node:path';
|
|
11
|
+
import { detectPlatforms } from './adapters/hooks/index.js';
|
|
12
|
+
import { listDetectedTiers } from './commands/hooks/tiers.js';
|
|
11
13
|
import { parseOrgConfig, parsePack, parseTemplate } from './parser.js';
|
|
12
14
|
|
|
13
15
|
/**
|
|
@@ -46,6 +48,9 @@ export async function runDoctor({ outputRoot, adapter, config, runeRoot }) {
|
|
|
46
48
|
// Check 2: Output directory exists
|
|
47
49
|
if (adapter.name === 'claude') {
|
|
48
50
|
results.checks.push({ name: 'Output directory', status: 'skip', detail: 'Claude Code uses source directly' });
|
|
51
|
+
const tierCoverage = await checkTierCoverage({ projectRoot: outputRoot });
|
|
52
|
+
for (const check of tierCoverage.checks) results.checks.push(check);
|
|
53
|
+
for (const warn of tierCoverage.warnings) results.warnings.push(warn);
|
|
49
54
|
return results;
|
|
50
55
|
}
|
|
51
56
|
|
|
@@ -164,11 +169,137 @@ export async function runDoctor({ outputRoot, adapter, config, runeRoot }) {
|
|
|
164
169
|
results.warnings.push(...orgErrors);
|
|
165
170
|
}
|
|
166
171
|
|
|
172
|
+
// Check 11: Tier hook coverage across detected platforms (advisory)
|
|
173
|
+
const tierCoverage = await checkTierCoverage({ projectRoot: outputRoot });
|
|
174
|
+
for (const check of tierCoverage.checks) results.checks.push(check);
|
|
175
|
+
for (const warn of tierCoverage.warnings) results.warnings.push(warn);
|
|
176
|
+
|
|
167
177
|
if (results.errors.length > 0) results.healthy = false;
|
|
168
178
|
|
|
169
179
|
return results;
|
|
170
180
|
}
|
|
171
181
|
|
|
182
|
+
const PLATFORM_TIER_DIRS = Object.freeze({
|
|
183
|
+
cursor: '.cursor/rules',
|
|
184
|
+
windsurf: '.windsurf/workflows',
|
|
185
|
+
antigravity: '.antigravity/rules',
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
const PLATFORM_TIER_EXTS = Object.freeze({
|
|
189
|
+
cursor: '.mdc',
|
|
190
|
+
windsurf: '.md',
|
|
191
|
+
antigravity: '.md',
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* True if the platform's config shows tier hooks installed for `tier`.
|
|
196
|
+
* Claude: scans `.claude/settings.json` for the `${RUNE_<TIER>_ROOT}` substring
|
|
197
|
+
* that the installer emits into every tier command string.
|
|
198
|
+
* Others: scans the platform's rules/workflow dir for a `rune-<tier>-*` file.
|
|
199
|
+
* Windsurf additionally checks `.windsurf/rules` since a tier entry
|
|
200
|
+
* writes both a workflow and a rule file.
|
|
201
|
+
*/
|
|
202
|
+
async function platformHasTier(platformId, projectRoot, tier) {
|
|
203
|
+
if (platformId === 'claude') {
|
|
204
|
+
const settingsPath = path.join(projectRoot, '.claude', 'settings.json');
|
|
205
|
+
if (!existsSync(settingsPath)) return false;
|
|
206
|
+
const raw = await readFile(settingsPath, 'utf-8');
|
|
207
|
+
return raw.includes(`\${RUNE_${tier.toUpperCase()}_ROOT}`);
|
|
208
|
+
}
|
|
209
|
+
const relDir = PLATFORM_TIER_DIRS[platformId];
|
|
210
|
+
if (!relDir) return false;
|
|
211
|
+
const ext = PLATFORM_TIER_EXTS[platformId];
|
|
212
|
+
const prefix = `rune-${tier}-`;
|
|
213
|
+
|
|
214
|
+
const scanDir = async (dir) => {
|
|
215
|
+
if (!existsSync(dir)) return false;
|
|
216
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
217
|
+
return entries.some((e) => e.isFile() && e.name.startsWith(prefix) && e.name.endsWith(ext));
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
if (await scanDir(path.join(projectRoot, relDir))) return true;
|
|
221
|
+
if (platformId === 'windsurf') {
|
|
222
|
+
if (await scanDir(path.join(projectRoot, '.windsurf/rules'))) return true;
|
|
223
|
+
}
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Advisory tier-coverage check. Silent when no tiers are detected or no
|
|
229
|
+
* tier hooks are installed anywhere. Warns when:
|
|
230
|
+
* 1. A tier's hooks are installed on some detected platforms but missing
|
|
231
|
+
* on others — suggests re-running `rune hooks install --tier ... --platform all`.
|
|
232
|
+
* 2. A tier manifest requires env vars that are unset — installed hooks
|
|
233
|
+
* will fail at runtime, and `rune hooks install` surfaces this too.
|
|
234
|
+
*
|
|
235
|
+
* Advisory only: emits WARN, never blocks the doctor pipeline.
|
|
236
|
+
*
|
|
237
|
+
* @param {{projectRoot: string}} options
|
|
238
|
+
* @returns {Promise<{checks: object[], warnings: string[]}>}
|
|
239
|
+
*/
|
|
240
|
+
export async function checkTierCoverage({ projectRoot }) {
|
|
241
|
+
const result = { checks: [], warnings: [] };
|
|
242
|
+
const tiers = await listDetectedTiers(projectRoot);
|
|
243
|
+
if (tiers.length === 0) return result;
|
|
244
|
+
|
|
245
|
+
const detected = detectPlatforms(projectRoot);
|
|
246
|
+
const gaps = [];
|
|
247
|
+
let installedSomewhere = false;
|
|
248
|
+
const coverageByTier = [];
|
|
249
|
+
|
|
250
|
+
for (const tierInfo of tiers) {
|
|
251
|
+
if (!tierInfo.found) {
|
|
252
|
+
result.warnings.push(
|
|
253
|
+
`tier ${tierInfo.tier}: manifest at ${tierInfo.manifestPath} failed to load — ${tierInfo.error}`,
|
|
254
|
+
);
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const coverage = { tier: tierInfo.tier, installedOn: [], missingOn: [] };
|
|
259
|
+
for (const id of detected) {
|
|
260
|
+
const has = await platformHasTier(id, projectRoot, tierInfo.tier);
|
|
261
|
+
if (has) coverage.installedOn.push(id);
|
|
262
|
+
else coverage.missingOn.push(id);
|
|
263
|
+
}
|
|
264
|
+
if (coverage.installedOn.length > 0) installedSomewhere = true;
|
|
265
|
+
if (coverage.installedOn.length > 0 && coverage.missingOn.length > 0) {
|
|
266
|
+
gaps.push(coverage);
|
|
267
|
+
}
|
|
268
|
+
coverageByTier.push(coverage);
|
|
269
|
+
|
|
270
|
+
if (!tierInfo.requiresOk && coverage.installedOn.length > 0) {
|
|
271
|
+
result.warnings.push(
|
|
272
|
+
`tier ${tierInfo.tier}: required env var${tierInfo.requiresMissing.length === 1 ? '' : 's'} unset (${tierInfo.requiresMissing.join(', ')}) — hooks will FAIL at runtime. Fix: export ${tierInfo.requiresMissing[0]}=/path/to/${tierInfo.tier} then re-run your shell.`,
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (!installedSomewhere) return result;
|
|
278
|
+
|
|
279
|
+
if (gaps.length === 0) {
|
|
280
|
+
result.checks.push({
|
|
281
|
+
name: 'Tier coverage',
|
|
282
|
+
status: 'pass',
|
|
283
|
+
detail: coverageByTier.map((c) => `${c.tier}:${c.installedOn.join('+') || 'none'}`).join(', '),
|
|
284
|
+
});
|
|
285
|
+
return result;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const detailParts = gaps.map((g) => `${g.tier} missing on ${g.missingOn.join(', ')}`);
|
|
289
|
+
result.checks.push({
|
|
290
|
+
name: 'Tier coverage',
|
|
291
|
+
status: 'warn',
|
|
292
|
+
detail: detailParts.join('; '),
|
|
293
|
+
});
|
|
294
|
+
for (const gap of gaps) {
|
|
295
|
+
const plural = gap.missingOn.length === 1 ? 'platform' : 'platforms';
|
|
296
|
+
result.warnings.push(
|
|
297
|
+
`tier ${gap.tier} installed on ${gap.installedOn.join(', ')} but missing on detected ${plural} ${gap.missingOn.join(', ')}. Re-run: rune hooks install --preset gentle --tier ${gap.tier} --platform all`,
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
return result;
|
|
301
|
+
}
|
|
302
|
+
|
|
172
303
|
/**
|
|
173
304
|
* Check that all cross-references in compiled files point to existing files
|
|
174
305
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rune-kit/rune",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.13.0",
|
|
4
4
|
"description": "62-skill mesh for AI coding assistants — runtime auto-discipline via native hooks (Claude/Cursor/Windsurf/Antigravity), 5-layer architecture, 215+ connections, multi-platform compiler.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: adversary
|
|
3
|
-
description: Pre-implementation red-team analysis. Challenges plans before code is written — finds edge cases, security holes, scalability bottlenecks, error propagation risks, and integration conflicts. Catches flaws at plan time (10x cheaper than post-implementation).
|
|
3
|
+
description: "Pre-implementation red-team analysis. Use when a plan is high-risk, critical path, or expensive to reverse. Challenges plans before code is written — finds edge cases, security holes, scalability bottlenecks, error propagation risks, and integration conflicts. Catches flaws at plan time (10x cheaper than post-implementation)."
|
|
4
4
|
metadata:
|
|
5
5
|
author: runedev
|
|
6
6
|
version: "0.1.0"
|
package/skills/autopsy/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: autopsy
|
|
3
|
-
description: Full codebase health assessment. Analyzes complexity, dependencies, dead code, tech debt, and git hotspots. Produces a health score and rescue plan.
|
|
3
|
+
description: Full codebase health assessment. Use when diagnosing project health or starting a rescue workflow on legacy code. Analyzes complexity, dependencies, dead code, tech debt, and git hotspots. Produces a health score and rescue plan.
|
|
4
4
|
metadata:
|
|
5
5
|
author: runedev
|
|
6
6
|
version: "0.4.0"
|
package/skills/ba/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: ba
|
|
|
3
3
|
description: Business Analyst agent. Deeply understands user requirements before any planning or coding begins. Asks probing questions, identifies hidden requirements, maps stakeholders, defines scope boundaries, and produces a structured Requirements Document that plan and cook consume.
|
|
4
4
|
metadata:
|
|
5
5
|
author: runedev
|
|
6
|
-
version: "0.
|
|
6
|
+
version: "0.7.0"
|
|
7
7
|
layer: L2
|
|
8
8
|
model: opus
|
|
9
9
|
group: creation
|
|
@@ -87,6 +87,23 @@ Each question must be asked separately, wait for answer before next.
|
|
|
87
87
|
Exception: if user provides a detailed spec/PRD → extract answers from it, confirm with user.
|
|
88
88
|
</HARD-GATE>
|
|
89
89
|
|
|
90
|
+
#### Question Discipline (MANDATORY)
|
|
91
|
+
|
|
92
|
+
Every question the user answers burns attention you don't get back. Protect it.
|
|
93
|
+
|
|
94
|
+
1. **Max 5 questions total across the whole BA session.** If you find yourself wanting a 6th, the answer is in the first 5 or you're stalling — re-read, don't re-ask.
|
|
95
|
+
2. **Prefer yes/no or multiple-choice over open-ended.** An open-ended question is a last resort when no reasonable option set exists.
|
|
96
|
+
- BAD: "What auth strategy do you want?"
|
|
97
|
+
- GOOD: "Auth: **(a)** email+password with JWT, **(b)** OAuth (Google/GitHub), **(c)** magic link, **(d)** I'll decide — pick one."
|
|
98
|
+
3. **Never ask what you can infer.** If the answer is in the repo, the user's message, or the classification from Step 1 — don't ask it.
|
|
99
|
+
- Wrong stack? → read `package.json`, don't ask.
|
|
100
|
+
- Wrong audience? → check the `README`, don't ask.
|
|
101
|
+
- Wrong framework? → check config files, don't ask.
|
|
102
|
+
4. **Cache the answer.** Write each Q→A pair into the Requirements Document verbatim. If the user restarts the BA session on the same feature, reuse the cached answers — never re-ask what was already answered.
|
|
103
|
+
5. **Bundle yes/no questions after Q1 if the user is concise.** A user who replies "y" / "n" / "skip" in 1-2 words tolerates a bundle. A user who replies with paragraphs wants the slow pace — keep one-at-a-time.
|
|
104
|
+
|
|
105
|
+
Every Q should earn its slot: removing it must leave the Requirements Document materially worse. If it wouldn't, cut the question.
|
|
106
|
+
|
|
90
107
|
#### Structured Elicitation Frameworks
|
|
91
108
|
|
|
92
109
|
Choose the framework that fits the requirement type. Use it to STRUCTURE the 5 Questions above, not replace them.
|
|
@@ -449,13 +466,16 @@ Saved to `.rune/features/<feature-name>/requirements.md`
|
|
|
449
466
|
|
|
450
467
|
## Constraints
|
|
451
468
|
|
|
452
|
-
1. MUST ask 5 probing questions before producing requirements —
|
|
453
|
-
2. MUST
|
|
454
|
-
3. MUST
|
|
455
|
-
4. MUST
|
|
456
|
-
5. MUST
|
|
457
|
-
6. MUST
|
|
458
|
-
7. MUST
|
|
469
|
+
1. MUST ask up to 5 probing questions before producing requirements — never more, skip any you can infer from context
|
|
470
|
+
2. MUST prefer yes/no or multiple-choice questions — open-ended only when no reasonable option set exists
|
|
471
|
+
3. MUST NOT ask for information already present in the user's message, the repo, or classification — read/grep first, ask second
|
|
472
|
+
4. MUST cache each Q→A pair in the Requirements Document and reuse on subsequent BA sessions for the same feature
|
|
473
|
+
5. MUST identify hidden requirements — the obvious ones are never the full picture
|
|
474
|
+
6. MUST define out-of-scope explicitly — prevents scope creep
|
|
475
|
+
7. MUST produce testable acceptance criteria — they become test cases
|
|
476
|
+
8. MUST NOT write code or plan implementation — BA produces WHAT, plan produces HOW
|
|
477
|
+
9. MUST ask ONE question at a time by default; bundle yes/no batches only after user shows concise replies
|
|
478
|
+
10. MUST NOT skip BA for non-trivial tasks — "just build it" gets redirected to Question 1
|
|
459
479
|
|
|
460
480
|
## Returns
|
|
461
481
|
|
|
@@ -476,6 +496,10 @@ Known failure modes for this skill. Check these before declaring done.
|
|
|
476
496
|
| Answering own questions instead of asking user | HIGH | Questions require USER input — BA doesn't guess |
|
|
477
497
|
| Producing implementation details (HOW) instead of requirements (WHAT) | HIGH | BA outputs requirements doc → plan outputs implementation |
|
|
478
498
|
| All-at-once question dump (asking 5 questions in one message) | MEDIUM | One question at a time, wait for answer before next |
|
|
499
|
+
| Asking open-ended questions when yes/no or multiple-choice would work | HIGH | Question Discipline rule 2 — option sets are faster to answer and easier to cache |
|
|
500
|
+
| Asking for info already in the repo/message (stack, framework, audience) | HIGH | Question Discipline rule 3 — read `package.json`/README/config first, ask only what you genuinely can't find |
|
|
501
|
+
| Exceeding 5 questions when the user seems "engaged" | MEDIUM | Question Discipline rule 1 — hard cap at 5. A 6th question is a sign of stalling, not thoroughness |
|
|
502
|
+
| Re-asking on session restart when answers were cached | MEDIUM | Question Discipline rule 4 — load `.rune/features/<name>/requirements.md` and reuse cached Q→A pairs |
|
|
479
503
|
| Missing hidden requirements (auth, error handling, edge cases) | HIGH | Step 3 checklist is mandatory scan |
|
|
480
504
|
| Requirements doc too verbose (>500 lines) | MEDIUM | Max 200 lines — concise, actionable, testable |
|
|
481
505
|
| Skipping BA for "simple" features that turn out complex | HIGH | Let cook's complexity detection trigger BA, not user judgment |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: context-pack
|
|
3
|
-
description: "Creates structured handoff briefings between agents. Packages task context, constraints, and progress into a compact packet that subagents can consume without re-reading the full conversation. Prevents the 'lost context' problem in multi-agent delegation."
|
|
3
|
+
description: "Creates structured handoff briefings between agents. Use when delegating complex work to subagents that would otherwise lose context. Packages task context, constraints, and progress into a compact packet that subagents can consume without re-reading the full conversation. Prevents the 'lost context' problem in multi-agent delegation."
|
|
4
4
|
user-invocable: false
|
|
5
5
|
metadata:
|
|
6
6
|
author: runedev
|
package/skills/cook/SKILL.md
CHANGED
|
@@ -5,7 +5,7 @@ context: fork
|
|
|
5
5
|
agent: general-purpose
|
|
6
6
|
metadata:
|
|
7
7
|
author: runedev
|
|
8
|
-
version: "2.
|
|
8
|
+
version: "2.5.0"
|
|
9
9
|
layer: L1
|
|
10
10
|
model: sonnet
|
|
11
11
|
group: orchestrator
|
|
@@ -307,9 +307,10 @@ Contract violations are NON-NEGOTIABLE. If `.rune/contract.md` exists and a plan
|
|
|
307
307
|
2. **Feature workspace** (opt-in) — for non-trivial features (3+ phases), suggest creating `.rune/features/<feature-name>/` with `spec.md`, `plan.md`, `decisions.md`, `status.md`. Skip for simple bug fixes, fast mode.
|
|
308
308
|
3. Create implementation plan: exact files to create/modify, change order, dependencies, active decision constraints
|
|
309
309
|
4. If multiple valid approaches exist → invoke `rune:brainstorm` for trade-off analysis
|
|
310
|
-
5.
|
|
311
|
-
6.
|
|
312
|
-
7.
|
|
310
|
+
5. **Frontend detection** — if task touches `.tsx/.jsx/.vue/.svelte/.css`, component files, or mentions "UI/page/screen/design/layout/landing": invoke `rune:design` BEFORE plan approval. Pass hint `mode: "tweaks-default"` — design proposes ONE opinionated default per `.rune/design-system.md` (Step 2.7), not a 5-option menu. User replies with tweaks ("more professional", "darker") rather than picking from a list. If `.rune/design-system.md` is missing, design creates it first.
|
|
311
|
+
6. Present plan to user for approval
|
|
312
|
+
7. If feature workspace was created, write approved plan to `.rune/features/<name>/plan.md`
|
|
313
|
+
8. Mark Phase 2 as `completed`
|
|
313
314
|
|
|
314
315
|
**Gate**: User MUST approve the plan before proceeding. Do NOT skip this.
|
|
315
316
|
|
|
@@ -751,7 +752,7 @@ Mentally track tool call fingerprints. 3 identical calls → WARN. 5 identical c
|
|
|
751
752
|
| 1 | `logic-guardian` | L2 | Conditional: when `.rune/logic-manifest.json` exists — protect complex business logic before any edits |
|
|
752
753
|
| 2 | `plan` | L2 | Create implementation plan |
|
|
753
754
|
| 2 | `brainstorm` | L2 | Trade-off analysis / rescue mode |
|
|
754
|
-
| 2 | `design` | L2 | UI/design phase for frontend features |
|
|
755
|
+
| 2 | `design` | L2 | UI/design phase for frontend features — invoke with `mode: "tweaks-default"` (one opinionated default + accept natural-language tweaks, not a 5-option menu) |
|
|
755
756
|
| 2.5 | `adversary` | L2 | Red-team challenge on approved plan |
|
|
756
757
|
| 3 | `test` | L2 | Write failing tests (RED phase) |
|
|
757
758
|
| 4 | `fix` | L2 | Implement code changes (GREEN phase) |
|
package/skills/design/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: design
|
|
|
3
3
|
description: Design system reasoning. Maps product domain to style, palette, typography, and platform-specific patterns. Generates .rune/design-system.md as the shared design contract for all UI-generating skills.
|
|
4
4
|
metadata:
|
|
5
5
|
author: runedev
|
|
6
|
-
version: "0.
|
|
6
|
+
version: "0.5.0"
|
|
7
7
|
layer: L2
|
|
8
8
|
model: sonnet
|
|
9
9
|
group: creation
|
|
@@ -125,6 +125,84 @@ Accept a single mood keyword (or infer from context if obvious). Map mood to con
|
|
|
125
125
|
|
|
126
126
|
**Skip if**: User says "no preference" or "just follow domain defaults" — proceed to Step 3 with domain-only reasoning.
|
|
127
127
|
|
|
128
|
+
### Step 2.7 — Tweaks, Not Menus (Default Style Pattern)
|
|
129
|
+
|
|
130
|
+
Picking from a 10-option style menu is how AI UI gets generic. Instead:
|
|
131
|
+
|
|
132
|
+
1. **Propose ONE opinionated default** based on domain + mood (from Step 2.5). Describe it in 2-3 lines — style, palette direction, typography pairing.
|
|
133
|
+
2. **Ask for tweaks, not choices.** The question is **"Any tweaks to this, or ship it?"** — not "Which of these do you prefer?"
|
|
134
|
+
3. **Accept natural-language adjustments.** Map phrases → design system edits:
|
|
135
|
+
- "more professional" → heavier type weights, reduce saturation, tighter spacing
|
|
136
|
+
- "less corporate" → looser weights, brighter accent, more whitespace
|
|
137
|
+
- "darker" → swap base for darker neutral, raise contrast on elevated surfaces
|
|
138
|
+
- "more playful" → add subtle animation, soften corners, bolder accent
|
|
139
|
+
- "more trust" → cooler palette (slate/blue), heavier headers, smaller radius
|
|
140
|
+
4. **If the user asks for a menu**, provide max 3 options — but mark one as the recommended default. Never present a neutral list of 5+ equivalent styles.
|
|
141
|
+
|
|
142
|
+
Why: Every menu option dilutes commitment. A single confident default gets committed, tweaked, and shipped. A menu gets deliberated, A/B'd, and abandoned. This is the **Tweaks Default** pattern from Anthropic's design system guidance — the AI commits first, humans steer second.
|
|
143
|
+
|
|
144
|
+
### Step 2.9 — Universal Anti-AI Rules (apply to ALL domains)
|
|
145
|
+
|
|
146
|
+
These rules apply regardless of domain, mood, or platform. Every generated design system MUST comply.
|
|
147
|
+
|
|
148
|
+
**Enforcement**: `rune:review` v1.1.0+ reads `.rune/design-system.md` § Scale Minimums and flags violations of all 3 rules below as MEDIUM/HIGH findings. Design defines, review enforces — this is the contract.
|
|
149
|
+
|
|
150
|
+
#### Rule 1 — Scale Minimums
|
|
151
|
+
|
|
152
|
+
Below these thresholds, designs read as "AI boilerplate" no matter how good the palette is.
|
|
153
|
+
|
|
154
|
+
| Element | Minimum | Ideal |
|
|
155
|
+
|---------|---------|-------|
|
|
156
|
+
| Hero/display text | 48px | 56-72px |
|
|
157
|
+
| H1 (page title) | 32px | 36-40px |
|
|
158
|
+
| Body text | 16px (never 14px for primary content) | 16-18px |
|
|
159
|
+
| Secondary/meta text | 14px | 14-15px |
|
|
160
|
+
| Touch targets (mobile) | 44×44px | 48×48px |
|
|
161
|
+
| Touch target gap (mobile) | 8px | 12px |
|
|
162
|
+
| Focus-visible ring | 2px | 3px |
|
|
163
|
+
|
|
164
|
+
Write these minimums to `.rune/design-system.md` under `## Scale Minimums`. Downstream skills (`cook`, `fix`) treat violations as review findings.
|
|
165
|
+
|
|
166
|
+
#### Rule 2 — Placeholder Over Bad SVG
|
|
167
|
+
|
|
168
|
+
If the design calls for an icon, illustration, or graphic that the agent cannot generate at high quality, **ship a boxed placeholder, not a malformed SVG**.
|
|
169
|
+
|
|
170
|
+
```html
|
|
171
|
+
<!-- GOOD: placeholder -->
|
|
172
|
+
<div class="placeholder" data-icon="dashboard" aria-label="Dashboard icon — design pass needed">
|
|
173
|
+
[ ICON: dashboard ]
|
|
174
|
+
</div>
|
|
175
|
+
|
|
176
|
+
<!-- BAD: AI-generated SVG with broken geometry -->
|
|
177
|
+
<svg viewBox="0 0 24 24">
|
|
178
|
+
<path d="M12 2L2 7l10 5 10-5-10-5z M2 17l10 5 10-5 M2 12l10 5 10-5"/>
|
|
179
|
+
</svg>
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
- Use **Phosphor Icons** (`@phosphor-icons/react`) or **Huge Icons** as the library default. Never generate custom SVG for standard iconography.
|
|
183
|
+
- For illustrations, reference a placeholder string (e.g., `[ILLUSTRATION: empty-state-dashboard]`) that a human or asset-creator pass fills in later.
|
|
184
|
+
- Malformed SVG is the #1 AI tell. A clean labeled placeholder is honest and professional.
|
|
185
|
+
|
|
186
|
+
#### Rule 3 — Color Derivation via oklch(), not Manual Shading
|
|
187
|
+
|
|
188
|
+
When the design needs a darker hover, lighter surface, or tinted state, **derive from the accent via oklch()** — never eyeball a hex value.
|
|
189
|
+
|
|
190
|
+
```css
|
|
191
|
+
/* GOOD: relative derivation */
|
|
192
|
+
--accent: oklch(65% 0.2 255);
|
|
193
|
+
--accent-hover: oklch(from var(--accent) calc(l - 0.08) c h);
|
|
194
|
+
--accent-pressed: oklch(from var(--accent) calc(l - 0.15) c h);
|
|
195
|
+
--accent-subtle: oklch(from var(--accent) calc(l + 0.3) calc(c * 0.4) h);
|
|
196
|
+
|
|
197
|
+
/* BAD: manual hex shading — breaks hue/chroma consistency */
|
|
198
|
+
--accent: #3b82f6;
|
|
199
|
+
--accent-hover: #2563eb; /* guessed darker */
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Why: HSL shading distorts perceived brightness at different hues. oklch() keeps perceptual lightness consistent, so derived states look intentional rather than "kinda close." Write derived tokens to `.rune/design-system.md` — downstream skills reuse these, not re-derive.
|
|
203
|
+
|
|
204
|
+
Bonus: use `text-wrap: pretty` on headings to prevent widow words. One line, zero ceremony.
|
|
205
|
+
|
|
128
206
|
### Step 3 — Apply Domain Reasoning Rules
|
|
129
207
|
|
|
130
208
|
Map domain to design system parameters:
|
|
@@ -532,6 +610,10 @@ Trading/Fintech — Data-Dense Dark — Web
|
|
|
532
610
|
4. MUST write `.rune/design-system.md` — ephemeral design decisions evaporate; persistence is the point
|
|
533
611
|
5. MUST NOT overwrite existing design-system.md without user confirmation
|
|
534
612
|
6. MUST include platform-specific overrides when platform is iOS or Android
|
|
613
|
+
7. MUST propose ONE opinionated default and ask for tweaks — never present a neutral 5+ option menu (Step 2.7 Tweaks Default)
|
|
614
|
+
8. MUST enforce Scale Minimums (hero ≥48px, body ≥16px, touch targets ≥44px) in every design system (Step 2.9 Rule 1)
|
|
615
|
+
9. MUST use Phosphor/Huge icons or boxed placeholders — NEVER generate custom SVG for standard iconography (Step 2.9 Rule 2)
|
|
616
|
+
10. MUST derive accent variants via `oklch(from var(--accent) ...)` — NEVER hand-shade hex values (Step 2.9 Rule 3)
|
|
535
617
|
|
|
536
618
|
## Mesh Gates (L1/L2 only)
|
|
537
619
|
|
|
@@ -541,6 +623,10 @@ Trading/Fintech — Data-Dense Dark — Web
|
|
|
541
623
|
| Anti-Pattern Gate | Anti-pattern list derived from domain rules (not generic) | Domain-specific list required |
|
|
542
624
|
| Persistence Gate | .rune/design-system.md written before reporting done | Write file first |
|
|
543
625
|
| Platform Gate | Platform detected before generating tokens | Default to web, note assumption |
|
|
626
|
+
| Tweaks-Default Gate | One opinionated default proposed before asking for tweaks | Do NOT present neutral 5-option menus |
|
|
627
|
+
| Scale-Minimums Gate | Hero ≥48px, body ≥16px, touch ≥44px written into design-system.md | Emit minimums block in output |
|
|
628
|
+
| SVG-Placeholder Gate | No hand-rolled SVG for standard icons — Phosphor/Huge or placeholder | Swap to icon library or `[ ICON: name ]` box |
|
|
629
|
+
| oklch-Derivation Gate | All accent variants derived via `oklch(from ...)` | Rewrite manual hex shades as relative oklch |
|
|
544
630
|
|
|
545
631
|
## Returns
|
|
546
632
|
|
|
@@ -568,13 +654,20 @@ Known failure modes for this skill. Check these before declaring done.
|
|
|
568
654
|
| Visual audit score < 18 shipped without improvement plan | MEDIUM | Step 6.5 flags weak pillars and creates backlog items |
|
|
569
655
|
| iOS target generating solid-background cards | MEDIUM | Platform Gate: iOS 26 Liquid Glass deprecates this pattern |
|
|
570
656
|
| Android target using hardcoded hex colors | MEDIUM | Platform Gate: MaterialTheme.colorScheme is mandatory for dynamic color |
|
|
657
|
+
| Presenting a neutral 5+ option style menu (deliberation death) | HIGH | Step 2.7 Tweaks Default — propose ONE opinionated default, ask for tweaks |
|
|
658
|
+
| Body text at 14px or hero at <40px (AI boilerplate scale) | HIGH | Step 2.9 Rule 1 — enforce Scale Minimums table in every design system |
|
|
659
|
+
| Hand-rolled SVG for dashboard/menu/close icons (malformed geometry) | HIGH | Step 2.9 Rule 2 — Phosphor/Huge Icons or `[ ICON: name ]` placeholder, never custom |
|
|
660
|
+
| Accent variants shaded by eyeball (inconsistent perceived brightness) | MEDIUM | Step 2.9 Rule 3 — `oklch(from var(--accent) calc(l - 0.1) c h)` |
|
|
661
|
+
| Missing `text-wrap: pretty` on headings (widow words) | LOW | One-line CSS — add to base heading styles |
|
|
571
662
|
|
|
572
663
|
## Done When
|
|
573
664
|
|
|
574
665
|
- Design reference loaded (user override or baseline)
|
|
575
666
|
- Domain classified (one of the 10 categories or explicit custom reasoning)
|
|
576
667
|
- Mood mapped to constraints (or explicitly skipped with "domain defaults")
|
|
577
|
-
-
|
|
668
|
+
- Opinionated default proposed (Step 2.7) — user confirmed or requested tweaks
|
|
669
|
+
- Universal anti-AI rules applied (Step 2.9): Scale Minimums, Placeholder-over-bad-SVG, oklch() color derivation
|
|
670
|
+
- Design system generated with: colors (primitive + semantic, oklch-derived variants), typography, spacing, effects, anti-patterns
|
|
578
671
|
- Platform-specific overrides applied (if iOS/Android target)
|
|
579
672
|
- UI-SPEC.md written with locked layout, hierarchy, and component decisions
|
|
580
673
|
- Accessibility review completed (6 checks: contrast, focus, touch targets, labels, semantic HTML, motion)
|
package/skills/graft/SKILL.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: graft
|
|
3
|
-
description: "Clone, port, or convert features from any GitHub repo into your project. Understand before copy, challenge before implement. 4 modes: port (rewrite), compare (analysis), copy (transplant), improve (copy + optimize)."
|
|
3
|
+
description: "Clone, port, or convert features from any GitHub repo into your project. Use when stealing patterns from external repos or porting proven code. Understand before copy, challenge before implement. 4 modes: port (rewrite), compare (analysis), copy (transplant), improve (copy + optimize)."
|
|
4
4
|
metadata:
|
|
5
5
|
author: runedev
|
|
6
|
-
version: "0.
|
|
6
|
+
version: "0.2.0"
|
|
7
7
|
layer: L2
|
|
8
8
|
model: sonnet
|
|
9
9
|
group: creation
|
|
@@ -18,6 +18,17 @@ metadata:
|
|
|
18
18
|
|
|
19
19
|
External code intelligence — structured workflow for learning from, adapting, and integrating features from any public repository into your project. Graft is NOT a copy-paste tool. It enforces understanding before adoption through a mandatory challenge gate that evaluates license compatibility, stack fit, scope, quality, and maintenance health before any code touches your codebase.
|
|
20
20
|
|
|
21
|
+
## Core Rule: The Tree is a Menu, Not the Meal
|
|
22
|
+
|
|
23
|
+
When you clone a repo you see hundreds of files. **That tree is a menu — options to order from, not a meal to eat.** Grafting the whole tree is how context windows die and foreign patterns leak into your codebase.
|
|
24
|
+
|
|
25
|
+
- Read the README + the **2-5 files that implement the target feature**. Skip the rest.
|
|
26
|
+
- If you cannot name the specific files you need before reading, you do not know what you want yet — go back to Step 0 and narrow scope.
|
|
27
|
+
- `WebFetch` on raw GitHub URLs beats `git clone` whenever you know the exact files. Use clone only when discovery is genuinely needed.
|
|
28
|
+
- A graft that reads >10 source files is almost always a scoping failure, not a thorough one.
|
|
29
|
+
|
|
30
|
+
This rule applies to ALL modes. Copy mode is not an excuse to import a directory wholesale — you still select files deliberately.
|
|
31
|
+
|
|
21
32
|
<HARD-GATE>
|
|
22
33
|
Challenge gate (Step 4) MUST complete before adaptation planning (Step 5).
|
|
23
34
|
No implementation without confronting trade-offs. This applies to ALL modes except compare.
|
|
@@ -123,14 +134,16 @@ git sparse-checkout set <target-dir>
|
|
|
123
134
|
|
|
124
135
|
For specific files or small repos: use `WebFetch` on raw GitHub URLs instead of cloning.
|
|
125
136
|
|
|
126
|
-
**Read in this order** (stop when you have enough context):
|
|
137
|
+
**Read in this order** (stop when you have enough context — see Core Rule: the tree is a menu):
|
|
127
138
|
1. README.md — purpose, architecture overview
|
|
128
|
-
2. Target dir's files — the actual code to graft
|
|
139
|
+
2. Target dir's files — the actual code to graft (aim for 2-5 files, hard-cap at 10)
|
|
129
140
|
3. package.json / pyproject.toml / Cargo.toml — dependencies and stack
|
|
130
141
|
4. Tests for target feature — understand expected behavior
|
|
131
142
|
|
|
132
143
|
**Scope guard**: If target feature spans >15 files or >2000 LOC → WARN user: "Feature is large. Suggest narrowing to [specific module]. Continue anyway?"
|
|
133
144
|
|
|
145
|
+
**Menu discipline**: Before reading file #6, pause and ask "do I actually need this, or am I eating the menu?" If the answer isn't a concrete reason tied to the target feature, stop reading and move to Step 2.
|
|
146
|
+
|
|
134
147
|
### Step 2 — Analyze Source
|
|
135
148
|
|
|
136
149
|
Understand the target feature's architecture:
|
|
@@ -289,6 +302,7 @@ graft.complete:
|
|
|
289
302
|
5. MUST respect local conventions — grafted code should look native, not foreign
|
|
290
303
|
6. MUST NOT modify the source repository — read-only access only
|
|
291
304
|
7. MUST NOT graft without scoping — always narrow to specific feature/module
|
|
305
|
+
8. MUST treat the source file tree as a menu, not a meal — read the 2-5 files the feature actually needs, not every file you can see
|
|
292
306
|
|
|
293
307
|
## Mesh Gates
|
|
294
308
|
|
|
@@ -305,6 +319,7 @@ graft.complete:
|
|
|
305
319
|
| Grafting GPL code into MIT project | CRITICAL | Challenge gate checks license — blocks incompatible |
|
|
306
320
|
| Blindly copying code without understanding | CRITICAL | HARD-GATE: challenge before implement |
|
|
307
321
|
| Context overflow from large source files | HIGH | Scope guard: >15 files or >2000 LOC triggers warning |
|
|
322
|
+
| Reading the whole repo instead of the feature | HIGH | "Tree is a menu" rule — pause before file #6, justify each read |
|
|
308
323
|
| Grafted code doesn't match local conventions | HIGH | Step 3 scans local patterns, Step 5 plans adaptation |
|
|
309
324
|
| Stale source (abandoned repo) | MEDIUM | Maintenance dimension in challenge gate |
|
|
310
325
|
| Private repo URL fails | MEDIUM | Fallback to WebFetch raw URLs or manual paste |
|
|
@@ -320,6 +335,7 @@ SELF-VALIDATION (run before emitting graft.complete):
|
|
|
320
335
|
- [ ] License compatibility confirmed (or user override documented)
|
|
321
336
|
- [ ] Temp clone directory cleaned up
|
|
322
337
|
- [ ] Grafted code compiles/lints without new errors
|
|
338
|
+
- [ ] Source files read count ≤10 (menu discipline) — if >10, document why in the output
|
|
323
339
|
IF ANY check fails → fix before reporting done. Do NOT defer to completion-gate.
|
|
324
340
|
```
|
|
325
341
|
|
package/skills/journal/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: journal
|
|
3
|
-
description: Persistent state tracking and Architecture Decision Records across sessions. Manages progress state, module health, dependency graphs, and ADRs for any workflow.
|
|
3
|
+
description: Persistent state tracking and Architecture Decision Records across sessions. Use when recording a decision, ADR, or progress that must survive session boundaries. Manages progress state, module health, dependency graphs, and ADRs for any workflow.
|
|
4
4
|
metadata:
|
|
5
5
|
author: runedev
|
|
6
6
|
version: "0.3.0"
|
|
@@ -3,11 +3,12 @@ name: marketing
|
|
|
3
3
|
description: Create marketing assets and execute launch strategy. Generates landing copy, social banners, SEO meta, blog posts, and video scripts.
|
|
4
4
|
metadata:
|
|
5
5
|
author: runedev
|
|
6
|
-
version: "0.
|
|
6
|
+
version: "0.6.0"
|
|
7
7
|
layer: L2
|
|
8
8
|
model: sonnet
|
|
9
9
|
group: delivery
|
|
10
10
|
tools: "Read, Write, Edit, Glob, Grep, WebFetch, WebSearch"
|
|
11
|
+
emit: media.request
|
|
11
12
|
---
|
|
12
13
|
|
|
13
14
|
# marketing
|
|
@@ -3,7 +3,7 @@ name: preflight
|
|
|
3
3
|
description: Pre-commit quality gate that catches "almost right" code. Goes beyond linting — checks logic correctness, error handling, regressions, and completeness.
|
|
4
4
|
metadata:
|
|
5
5
|
author: runedev
|
|
6
|
-
version: "1.
|
|
6
|
+
version: "1.1.0"
|
|
7
7
|
layer: L2
|
|
8
8
|
model: sonnet
|
|
9
9
|
group: quality
|
|
@@ -224,14 +224,20 @@ For each detected domain, run its checks on the relevant files in the diff:
|
|
|
224
224
|
|
|
225
225
|
#### UI/Frontend Domain Checks
|
|
226
226
|
|
|
227
|
-
When UI/Frontend hook is triggered, run these checks on all `.tsx`/`.jsx`/`.svelte`/`.vue` files in the diff
|
|
227
|
+
When UI/Frontend hook is triggered, run these checks on all `.tsx`/`.jsx`/`.svelte`/`.vue` files in the diff.
|
|
228
|
+
|
|
229
|
+
**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."
|
|
228
230
|
|
|
229
231
|
| Check | What to Scan | Severity |
|
|
230
232
|
|-------|-------------|----------|
|
|
231
233
|
| **Design token compliance** | Hardcoded colors (`#fff`, `rgb(`, `hsl(`) instead of CSS variables or Tailwind tokens | WARN: "Hardcoded color at {file}:{line} — use design token" |
|
|
232
234
|
| **UI-SPEC drift** | If `.rune/ui-spec.md` exists, compare component decisions (card style, form layout, nav type) against spec | BLOCK: "Component at {file} uses bordered cards but UI-SPEC locks elevated cards" |
|
|
233
235
|
| **Animation accessibility** | Animations/transitions without `prefers-reduced-motion` guard | WARN: "Animation at {file}:{line} missing reduced-motion check" |
|
|
234
|
-
| **Touch target size** | Interactive elements with explicit small sizing (`w-5 h-5`, `p-0.5` on buttons/links) < 44×44px | WARN: "Touch target too small at {file}:{line}" |
|
|
236
|
+
| **Touch target size** | Interactive elements with explicit small sizing (`w-5 h-5`, `p-0.5` on buttons/links) < 44×44px (or project override from design-system.md) | WARN: "Touch target too small at {file}:{line}" |
|
|
237
|
+
| **Scale Minimum — body text** | `text-sm` / `text-xs` / explicit `font-size: 14px` on `<p>` or primary body regions (not meta/secondary) | WARN: "Body text below 16px at {file}:{line} — reads as AI boilerplate" |
|
|
238
|
+
| **Scale Minimum — hero display** | `<h1>` with `text-3xl` or smaller (30px) when the heading is in a hero/landing section | WARN: "Hero heading below 48px at {file}:{line} — insufficient visual hierarchy" |
|
|
239
|
+
| **Hand-rolled SVG for standard icons** | Inline `<svg viewBox=` in JSX when the surrounding comment/class names indicate standard iconography (dashboard, menu, close, chevron, arrow, search, home, user, settings, bell, trash) | WARN: "Hand-rolled SVG at {file}:{line} — use @phosphor-icons/react or huge-icons, or ship boxed placeholder" |
|
|
240
|
+
| **Manual hex accent shading** | CSS/Tailwind config defining 2+ sibling `--accent-hover` / `--accent-pressed` / `--accent-active` with hex literals (no `oklch(from ...)` or design-token chain) | WARN: "Manual hex shade at {file}:{line} — derive via oklch(from var(--accent) calc(l - 0.08) c h)" |
|
|
235
241
|
| **Missing states** | Components fetching data without loading/error/empty states | WARN: "Async component at {file} missing [loading|error|empty] state" |
|
|
236
242
|
| **Icon accessibility** | Decorative icons without `aria-hidden="true"`, functional icons without `aria-label` | WARN: "Icon at {file}:{line} missing aria attribute" |
|
|
237
243
|
| **Inline styles** | `style={{` or `style=` attribute usage instead of classes/tokens | WARN: "Inline style at {file}:{line} — use CSS class or Tailwind" |
|
|
@@ -240,6 +246,10 @@ When UI/Frontend hook is triggered, run these checks on all `.tsx`/`.jsx`/`.svel
|
|
|
240
246
|
|
|
241
247
|
**Skip if**: Diff contains only test files, config files, or non-UI code (detected by absence of JSX/template syntax).
|
|
242
248
|
|
|
249
|
+
**Exception for Scale Minimums**: Secondary/meta text (`<time>`, `<small>`, form hints, table captions) is allowed at 14px. The check only fires on primary body regions — paragraphs inside `<main>`, `<article>`, card body, marketing hero/features. Use common sense or an explicit `data-scale="meta"` attribute to opt out.
|
|
250
|
+
|
|
251
|
+
**Exception for hand-rolled SVG**: Project logos, data visualizations (charts/graphs via d3/recharts/visx), and human-designed illustrations are never flagged. The check fires only when class/comment context names a standard icon.
|
|
252
|
+
|
|
243
253
|
#### Pack Integration
|
|
244
254
|
|
|
245
255
|
When a domain pack is installed (e.g., `@rune-pro/finance`, `@rune-pro/legal`), preflight checks the pack's **Hard-Stop Thresholds** table and applies matching rules to staged files. This means:
|
package/skills/review/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: review
|
|
|
3
3
|
description: Code quality review — patterns, security, performance, correctness. Finds bugs, suggests improvements, triggers fix for issues found. Escalates to opus for security-critical code.
|
|
4
4
|
metadata:
|
|
5
5
|
author: runedev
|
|
6
|
-
version: "1.
|
|
6
|
+
version: "1.1.0"
|
|
7
7
|
layer: L2
|
|
8
8
|
model: sonnet
|
|
9
9
|
group: development
|
|
@@ -381,6 +381,9 @@ Apply **only** when `.tsx`, `.jsx`, `.svelte`, `.vue`, or `.html` files are in t
|
|
|
381
381
|
|
|
382
382
|
These are the **"AI UI signature"** — patterns that make AI-generated frontends visually identifiable as non-human-designed. Flag each as MEDIUM severity.
|
|
383
383
|
|
|
384
|
+
**Preamble — load design contract first:**
|
|
385
|
+
If `.rune/design-system.md` exists, read it first. Pull the project's **Scale Minimums** block (if authored by `rune:design` v0.5.0+) and apply those thresholds instead of the defaults below. Missing design-system.md → use defaults and add a LOW finding: "Project has no design-system.md — run `rune design` to lock visual decisions." Never enforce stale defaults against a project that has already declared stricter/looser minimums.
|
|
386
|
+
|
|
384
387
|
**AI_ANTIPATTERN — Purple/indigo default accent with no domain justification:**
|
|
385
388
|
```tsx
|
|
386
389
|
// BAD: LLM default color bias — signals "AI-generated" to experienced designers
|
|
@@ -415,6 +418,55 @@ className="bg-indigo-600 text-white" // every button/CTA is indigo
|
|
|
415
418
|
<span className="font-mono text-2xl font-bold">${price}</span>
|
|
416
419
|
```
|
|
417
420
|
|
|
421
|
+
**AI_ANTIPATTERN — Scale Minimum violations (AI boilerplate tell):**
|
|
422
|
+
```tsx
|
|
423
|
+
// BAD: body text at 14px (AI default) — primary content must be ≥16px
|
|
424
|
+
<p className="text-sm">Welcome to the dashboard.</p>
|
|
425
|
+
|
|
426
|
+
// BAD: hero/display text below 40px — reads as "section heading", not "hero"
|
|
427
|
+
<h1 className="text-3xl font-bold">Ship Faster</h1> // 30px
|
|
428
|
+
|
|
429
|
+
// BAD: touch target below 44×44px on mobile
|
|
430
|
+
<button className="w-8 h-8"><XIcon /></button> // 32px — WCAG 2.5.8 failure
|
|
431
|
+
|
|
432
|
+
// GOOD: hero ≥48px, body ≥16px, touch ≥44×44px
|
|
433
|
+
<h1 className="text-5xl md:text-6xl font-bold">Ship Faster</h1> // 48-60px
|
|
434
|
+
<p className="text-base">Welcome to the dashboard.</p> // 16px
|
|
435
|
+
<button className="w-11 h-11"><XIcon /></button> // 44px
|
|
436
|
+
```
|
|
437
|
+
Pull project-specific overrides from `.rune/design-system.md` § Scale Minimums.
|
|
438
|
+
|
|
439
|
+
**AI_ANTIPATTERN — Hand-rolled SVG for standard iconography:**
|
|
440
|
+
```tsx
|
|
441
|
+
// BAD: custom <svg> for dashboard/menu/close/chevron — AI geometry almost always malformed
|
|
442
|
+
<svg viewBox="0 0 24 24"><path d="M3 3h18v18H3z M3 9h18 M9 3v18"/></svg>
|
|
443
|
+
|
|
444
|
+
// GOOD: Phosphor Icons (preferred) or Huge Icons
|
|
445
|
+
import { House, List, X } from '@phosphor-icons/react';
|
|
446
|
+
<House weight="bold" size={24} />
|
|
447
|
+
|
|
448
|
+
// GOOD: labeled placeholder when no icon library available yet
|
|
449
|
+
<span className="icon-placeholder" aria-label="Dashboard icon — design pass needed">
|
|
450
|
+
[ ICON: dashboard ]
|
|
451
|
+
</span>
|
|
452
|
+
```
|
|
453
|
+
Exceptions: inline SVG for project-unique logos, data visualizations (charts/graphs), or decorative illustrations generated by a human designer — these are not "standard iconography."
|
|
454
|
+
|
|
455
|
+
**AI_ANTIPATTERN — Manual hex shading for accent states (oklch() violation):**
|
|
456
|
+
```css
|
|
457
|
+
/* BAD: hand-darkened hex — breaks perceived lightness consistency */
|
|
458
|
+
--accent: #3b82f6;
|
|
459
|
+
--accent-hover: #2563eb; /* guessed darker */
|
|
460
|
+
--accent-pressed: #1d4ed8; /* guessed even darker */
|
|
461
|
+
|
|
462
|
+
/* GOOD: relative oklch() derivation */
|
|
463
|
+
--accent: oklch(62% 0.19 258);
|
|
464
|
+
--accent-hover: oklch(from var(--accent) calc(l - 0.08) c h);
|
|
465
|
+
--accent-pressed: oklch(from var(--accent) calc(l - 0.15) c h);
|
|
466
|
+
--accent-subtle: oklch(from var(--accent) calc(l + 0.3) calc(c * 0.4) h);
|
|
467
|
+
```
|
|
468
|
+
Flag any CSS file defining 2+ hover/pressed/active variants with sibling hex literals. Not a finding if accent uses a design-token library (Radix Colors, Tailwind palette) that already ships perceptually-tuned scales.
|
|
469
|
+
|
|
418
470
|
**AI_ANTIPATTERN — Missing UI states (only happy path rendered):**
|
|
419
471
|
```tsx
|
|
420
472
|
// BAD: data rendering without empty/error/loading states
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: safeguard
|
|
3
|
-
description: Build safety nets before refactoring. Creates characterization tests, boundary markers, config freezes, and rollback points.
|
|
3
|
+
description: Build safety nets before refactoring. Use when running surgeon or any risky refactor that needs a rollback point. Creates characterization tests, boundary markers, config freezes, and rollback points.
|
|
4
4
|
metadata:
|
|
5
5
|
author: runedev
|
|
6
6
|
version: "0.2.0"
|
|
@@ -3,7 +3,7 @@ name: sentinel-env
|
|
|
3
3
|
description: Environment-aware pre-flight check. Validates OS, runtime versions, installed tools, port availability, env vars, and disk space BEFORE coding starts. Prevents "works on my machine" failures. Like sentinel but for the environment, not the code.
|
|
4
4
|
metadata:
|
|
5
5
|
author: runedev
|
|
6
|
-
version: "0.
|
|
6
|
+
version: "0.3.0"
|
|
7
7
|
layer: L3
|
|
8
8
|
model: haiku
|
|
9
9
|
group: validation
|
|
@@ -117,25 +117,36 @@ Detect and verify tools the project depends on:
|
|
|
117
117
|
- `Grep` for `subprocess.run(`, `child_process.exec(`, `Deno.Command(` → project invokes external CLI
|
|
118
118
|
- `Read` README/docs for "requires X installed" or "depends on X"
|
|
119
119
|
|
|
120
|
-
For each detected hard dependency
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
120
|
+
For each detected hard dependency, apply the **9-tier binary detection** below — checking only `which`/`where` is insufficient and produces the largest category of "works on my machine" false-negatives (user has binary installed but PATH is stale, or installed via a package manager that didn't register it, or installed as a desktop app with a bundled binary).
|
|
121
|
+
|
|
122
|
+
**9-Tier Binary Detection** (stop at first hit):
|
|
123
|
+
|
|
124
|
+
| Tier | Source | Catches |
|
|
125
|
+
|------|--------|---------|
|
|
126
|
+
| 1 | Explicit `--<tool>-bin <path>` flag | CI, automation, manual override |
|
|
127
|
+
| 2 | Skill-specific env var `<SKILL>_<TOOL>_BIN` | Per-project pinning |
|
|
128
|
+
| 3 | Tool-family env var `<TOOL>_APP_BIN` | Ecosystem conventions |
|
|
129
|
+
| 4 | Generic tool env var `<TOOL>_BIN` | Legacy overrides |
|
|
130
|
+
| 5 | Platform desktop-app bundle (macOS `.app/Contents/Resources`, Windows `%LOCALAPPDATA%\Programs`, Linux `/opt`) | Desktop app users (~40% of population) |
|
|
131
|
+
| 6 | PATH lookup (`which`/`where.exe`) | Standard shell users |
|
|
132
|
+
| 7 | Package manager global bin (`npm config get prefix`, `pnpm`, `pipx --list`, `cargo install --root`) | npm-global on Windows (PATH oversight) |
|
|
133
|
+
| 8 | Platform common directories (`~/.local/bin`, `~/.npm-global/bin`, Homebrew, `%APPDATA%\npm`, `%LOCALAPPDATA%\Microsoft\WindowsApps`, `%ProgramFiles%\nodejs`) | Manual installers |
|
|
134
|
+
| 9 | Platform release archive names (e.g., `codex-x86_64-unknown-linux-musl`, `<tool>-aarch64-apple-darwin`) | Release-tarball downloaders |
|
|
127
135
|
|
|
128
136
|
**Verdict:**
|
|
129
|
-
- Tool found
|
|
130
|
-
- Tool NOT found → **BLOCK** with
|
|
137
|
+
- Tool found via any tier → PASS (log which tier + version)
|
|
138
|
+
- Tool NOT found → **BLOCK** with per-OS install guidance:
|
|
131
139
|
```
|
|
132
|
-
[ENV-XXX] Required tool '<tool>' not found
|
|
140
|
+
[ENV-XXX] Required tool '<tool>' not found (9-tier lookup exhausted)
|
|
133
141
|
→ Debian/Ubuntu: sudo apt install <tool>
|
|
134
|
-
→ macOS: brew install <tool>
|
|
142
|
+
→ macOS: brew install <tool> (or desktop app: <URL>)
|
|
135
143
|
→ Windows: winget install <tool> (or choco install <tool>)
|
|
136
|
-
→
|
|
144
|
+
→ Any platform: npm install -g <package> (if Node tool)
|
|
145
|
+
→ Manual: <download URL>
|
|
146
|
+
→ Pin explicitly: export <TOOL>_BIN=/path/to/binary
|
|
137
147
|
```
|
|
138
148
|
- This prevents the entire class of "it worked in CI but not locally" failures where `subprocess.run()` silently fails
|
|
149
|
+
- Reference implementation: `scripts/codex_imagen_bridge.mjs` in `@rune-pro/media` ports this pattern
|
|
139
150
|
|
|
140
151
|
### Step 4: Port Availability Check
|
|
141
152
|
|
|
@@ -3,7 +3,7 @@ name: skill-forge
|
|
|
3
3
|
description: Use when creating new Rune skills, editing existing skills, or verifying skill quality before deployment. Applies TDD discipline to skill authoring — test before write, verify before ship.
|
|
4
4
|
metadata:
|
|
5
5
|
author: runedev
|
|
6
|
-
version: "1.
|
|
6
|
+
version: "1.8.0"
|
|
7
7
|
layer: L2
|
|
8
8
|
model: opus
|
|
9
9
|
group: creation
|
|
@@ -277,6 +277,113 @@ Research (Meincke et al., 2025, 28,000 conversations) shows 33% → 72% complian
|
|
|
277
277
|
|
|
278
278
|
**Ethical test**: Would this serve the user's genuine interests if they fully understood the technique?
|
|
279
279
|
|
|
280
|
+
### Phase 5.25 — SCRIPT CONTRACT (skills with helper scripts only)
|
|
281
|
+
|
|
282
|
+
If the skill bundles executable scripts in its `scripts/` directory, those scripts MUST follow the Rune script output contract. This is a testable contract — orchestrators (cook, team, marketing) rely on it for piping and retry logic.
|
|
283
|
+
|
|
284
|
+
#### The Three-Mode Contract
|
|
285
|
+
|
|
286
|
+
Every helper script supports three output modes:
|
|
287
|
+
|
|
288
|
+
| Mode | Stdout | Stderr | File Artifacts |
|
|
289
|
+
|------|--------|--------|----------------|
|
|
290
|
+
| default | One artifact path per line | Diagnostics + warnings | Artifacts in declared out-dir |
|
|
291
|
+
| `--json` | Structured JSON summary | Diagnostics (unchanged) | Artifacts (unchanged) |
|
|
292
|
+
| `--debug` | Default stdout (paths) | Verbose trace + diagnostics | Default + JSONL redacted trace at `<out-dir>/<slug>.jsonl` |
|
|
293
|
+
|
|
294
|
+
**Why**: default-mode stdout-as-paths is the Unix way. Downstream skills pipe directly without log-parsing. `--json` is opt-in for callers that need metadata.
|
|
295
|
+
|
|
296
|
+
#### Required Flags
|
|
297
|
+
|
|
298
|
+
Every helper script MUST accept at least these flags:
|
|
299
|
+
|
|
300
|
+
```
|
|
301
|
+
--help Print usage + exit 0
|
|
302
|
+
--version Print version + exit 0
|
|
303
|
+
--json Structured JSON on stdout
|
|
304
|
+
--debug Write JSONL redacted trace
|
|
305
|
+
--dry-run Report plan, make no changes, exit 0
|
|
306
|
+
--smoke Pre-flight check (validate deps, exit 0 if healthy)
|
|
307
|
+
--out-dir <path> Override default artifact directory
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
And SHOULD accept when applicable:
|
|
311
|
+
```
|
|
312
|
+
--prompt-file <path> Read long text input from file (avoids shell-quoting hell on Windows)
|
|
313
|
+
--confirm Skip confirmation gate for expensive/destructive ops
|
|
314
|
+
--timeout-ms <n> Operation timeout (with semantic exit codes below)
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
#### Semantic Exit Codes
|
|
318
|
+
|
|
319
|
+
Adopt the standard Rune exit-code vocabulary:
|
|
320
|
+
|
|
321
|
+
| Code | Meaning | Orchestrator Response |
|
|
322
|
+
|------|---------|-----------------------|
|
|
323
|
+
| `0` | Success | Accept + chain to next |
|
|
324
|
+
| `1` | Execution failed (retryable) | Log + retry with alternate config |
|
|
325
|
+
| `2` | Usage error (bug) | Abort — don't retry |
|
|
326
|
+
| `3` | Data-integrity error | Halt — don't retry |
|
|
327
|
+
| `4` | Timeout with partial results | **Accept partial + continue** |
|
|
328
|
+
| `124` | Timeout with zero results | Retry with longer timeout or alternate provider |
|
|
329
|
+
|
|
330
|
+
Codes `5-63` are skill-specific. Document every code used in `references/<skill>/exit-codes.md`.
|
|
331
|
+
|
|
332
|
+
**Why `4` vs `124` matters**: Standard Unix collapses "timeout-with-2-of-3-images" and "timeout-with-0-images" into `124`. They are fundamentally different outcomes. Split them.
|
|
333
|
+
|
|
334
|
+
#### Default Artifact Directory Resolution
|
|
335
|
+
|
|
336
|
+
Resolve `--out-dir` in this fallback order:
|
|
337
|
+
|
|
338
|
+
1. `--out-dir <path>` explicit flag
|
|
339
|
+
2. `<SKILL>_OUT_DIR` env var (skill-specific)
|
|
340
|
+
3. `OPENCLAW_OUTPUT_DIR` (OpenClaw platform convention)
|
|
341
|
+
4. `OPENCLAW_AGENT_DIR/artifacts/<skill>` (OpenClaw default)
|
|
342
|
+
5. `OPENCLAW_STATE_DIR/artifacts/<skill>` (OpenClaw state fallback)
|
|
343
|
+
6. `./.rune/<skill>/` (project-local default)
|
|
344
|
+
|
|
345
|
+
**Why**: OpenClaw is one of Rune's adapter targets. Scripts that honor this convention work across adapters without modification.
|
|
346
|
+
|
|
347
|
+
#### Sensitive-Data Redaction
|
|
348
|
+
|
|
349
|
+
`--debug` trace MUST redact sensitive fields before write:
|
|
350
|
+
- Regex: `/authorization|bearer|token|api[_-]?key|secret|cookie|session[_-]?id|chatgpt[_-]?account/i` (key names)
|
|
351
|
+
- Any value exceeding 500 chars truncates to `<first-500>...`
|
|
352
|
+
- Never log env var VALUES — only presence check
|
|
353
|
+
|
|
354
|
+
#### Contract Test
|
|
355
|
+
|
|
356
|
+
Before shipping a helper script, verify:
|
|
357
|
+
|
|
358
|
+
```bash
|
|
359
|
+
# Contract smoke test:
|
|
360
|
+
node scripts/<script>.mjs --help # exit 0
|
|
361
|
+
node scripts/<script>.mjs --version # exit 0, prints version only
|
|
362
|
+
node scripts/<script>.mjs --smoke # exit 0 or 1, human-readable stderr
|
|
363
|
+
node scripts/<script>.mjs --dry-run ... # exit 0, no side effects
|
|
364
|
+
node scripts/<script>.mjs ... --json # stdout is parseable JSON
|
|
365
|
+
node scripts/<script>.mjs ... | head -1 # stdout default mode = path
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
<HARD-GATE>
|
|
369
|
+
Scripts that don't honor the contract cannot be shipped.
|
|
370
|
+
Specifically:
|
|
371
|
+
- Mixing paths and progress on stdout = BLOCK
|
|
372
|
+
- Silent failure (no install guidance on miss) = BLOCK
|
|
373
|
+
- Logging credentials in trace = CRITICAL-BLOCK
|
|
374
|
+
- Binary exit code (0/1 only) when timeout semantics apply = BLOCK
|
|
375
|
+
</HARD-GATE>
|
|
376
|
+
|
|
377
|
+
**Reference implementations**:
|
|
378
|
+
- `@rune-pro/media/scripts/codex_imagen_bridge.mjs` — full 9-tier binary detection + contract
|
|
379
|
+
- `@rune-pro/media/scripts/provider_probe.mjs` — `--smoke` convention exemplar
|
|
380
|
+
- `@rune-pro/media/scripts/image_optimizer.py` — Python contract implementation
|
|
381
|
+
|
|
382
|
+
**Reference docs**:
|
|
383
|
+
- `references/image-generator/script-contract.md` (pack-level contract)
|
|
384
|
+
- `references/image-generator/exit-codes.md` (exit-code vocabulary)
|
|
385
|
+
- `references/image-generator/binary-detection.md` (9-tier lookup)
|
|
386
|
+
|
|
280
387
|
### Phase 5.5 — SECURITY MODEL
|
|
281
388
|
|
|
282
389
|
Every skill that touches external systems, user data, or destructive operations MUST define an explicit Security Model section. This is a contract — not aspirational, but testable.
|
package/skills/surgeon/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: surgeon
|
|
3
|
-
description: Incremental refactorer. Refactors ONE module per session using proven patterns — Strangler Fig, Branch by Abstraction, Expand-Migrate-Contract.
|
|
3
|
+
description: Incremental refactorer. Use within a rescue workflow after safeguard has set up safety nets. Refactors ONE module per session using proven patterns — Strangler Fig, Branch by Abstraction, Expand-Migrate-Contract.
|
|
4
4
|
metadata:
|
|
5
5
|
author: runedev
|
|
6
6
|
version: "0.2.0"
|