@rune-kit/rune 2.8.0 → 2.10.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 +66 -34
- package/agents/adversary.md +27 -0
- package/agents/architect.md +19 -29
- package/agents/asset-creator.md +18 -4
- package/agents/audit.md +25 -4
- package/agents/autopsy.md +19 -4
- package/agents/ba.md +35 -0
- package/agents/brainstorm.md +31 -4
- package/agents/browser-pilot.md +21 -4
- package/agents/coder.md +21 -29
- package/agents/completion-gate.md +20 -4
- package/agents/constraint-check.md +18 -4
- package/agents/context-engine.md +22 -4
- package/agents/context-pack.md +32 -0
- package/agents/cook.md +41 -4
- package/agents/db.md +19 -4
- package/agents/debug.md +33 -4
- package/agents/dependency-doctor.md +20 -4
- package/agents/deploy.md +27 -4
- package/agents/design.md +22 -4
- package/agents/doc-processor.md +27 -0
- package/agents/docs-seeker.md +19 -4
- package/agents/docs.md +31 -0
- package/agents/fix.md +37 -4
- package/agents/git.md +29 -0
- package/agents/hallucination-guard.md +20 -4
- package/agents/incident.md +21 -4
- package/agents/integrity-check.md +18 -4
- package/agents/journal.md +19 -4
- package/agents/launch.md +32 -4
- package/agents/logic-guardian.md +26 -11
- package/agents/marketing.md +23 -4
- package/agents/mcp-builder.md +26 -0
- package/agents/neural-memory.md +30 -0
- package/agents/onboard.md +22 -4
- package/agents/perf.md +21 -4
- package/agents/plan.md +29 -4
- package/agents/preflight.md +22 -4
- package/agents/problem-solver.md +20 -4
- package/agents/rescue.md +23 -4
- package/agents/research.md +19 -4
- package/agents/researcher.md +19 -29
- package/agents/retro.md +32 -0
- package/agents/review-intake.md +20 -4
- package/agents/review.md +32 -4
- package/agents/reviewer.md +20 -28
- package/agents/safeguard.md +19 -4
- package/agents/sast.md +18 -4
- package/agents/scaffold.md +41 -0
- package/agents/scanner.md +19 -28
- package/agents/scope-guard.md +18 -4
- package/agents/scout.md +23 -4
- package/agents/sentinel-env.md +26 -0
- package/agents/sentinel.md +33 -4
- package/agents/sequential-thinking.md +20 -4
- package/agents/session-bridge.md +24 -4
- package/agents/skill-forge.md +22 -4
- package/agents/skill-router.md +26 -4
- package/agents/slides.md +24 -0
- package/agents/surgeon.md +19 -4
- package/agents/team.md +30 -4
- package/agents/test.md +36 -4
- package/agents/trend-scout.md +17 -4
- package/agents/verification.md +20 -4
- package/agents/video-creator.md +20 -4
- package/agents/watchdog.md +19 -4
- package/agents/worktree.md +17 -4
- package/compiler/__tests__/analytics.test.js +370 -0
- package/compiler/adapters/openclaw.js +2 -2
- package/compiler/analytics.js +385 -0
- package/compiler/bin/rune.js +68 -2
- package/compiler/dashboard.js +883 -0
- package/compiler/transforms/branding.js +1 -1
- package/extensions/ui/skills/animation-patterns.md +21 -0
- package/extensions/ui/skills/component-patterns.md +25 -0
- package/extensions/ui/skills/landing-patterns.md +1 -1
- package/hooks/context-watch/index.cjs +95 -68
- package/hooks/metrics-collector/index.cjs +86 -42
- package/hooks/post-session-reflect/index.cjs +41 -5
- package/hooks/session-start/index.cjs +8 -2
- package/package.json +2 -2
- package/skills/audit/SKILL.md +1 -0
- package/skills/autopsy/SKILL.md +78 -2
- package/skills/autopsy/references/repo-analysis-patterns.md +113 -0
- package/skills/ba/SKILL.md +72 -2
- package/skills/brainstorm/SKILL.md +1 -0
- package/skills/cook/SKILL.md +32 -3
- package/skills/cook/references/output-format.md +33 -0
- package/skills/db/SKILL.md +1 -0
- package/skills/debug/SKILL.md +23 -1
- package/skills/deploy/SKILL.md +1 -1
- package/skills/design/SKILL.md +110 -3
- package/skills/docs/SKILL.md +2 -1
- package/skills/fix/SKILL.md +23 -1
- package/skills/graft/SKILL.md +352 -0
- package/skills/graft/references/challenge-framework.md +98 -0
- package/skills/graft/references/mode-decision.md +44 -0
- package/skills/incident/SKILL.md +2 -0
- package/skills/journal/SKILL.md +2 -0
- package/skills/launch/SKILL.md +2 -0
- package/skills/marketing/SKILL.md +46 -2
- package/skills/mcp-builder/SKILL.md +3 -1
- package/skills/mcp-builder/references/auto-discovery-pattern.md +169 -0
- package/skills/plan/SKILL.md +60 -2
- package/skills/plan/references/feature-map.md +84 -0
- package/skills/preflight/SKILL.md +20 -1
- package/skills/rescue/SKILL.md +25 -1
- package/skills/retro/SKILL.md +2 -0
- package/skills/review/SKILL.md +55 -2
- package/skills/scaffold/SKILL.md +1 -0
- package/skills/scope-guard/SKILL.md +35 -17
- package/skills/sentinel/SKILL.md +29 -1
- package/skills/session-bridge/SKILL.md +155 -9
- package/skills/skill-forge/SKILL.md +43 -1
- package/skills/skill-router/{skill.md → SKILL.md} +27 -2
- package/skills/team/SKILL.md +24 -1
- package/skills/test/SKILL.md +29 -2
- package/skills/verification/SKILL.md +1 -0
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
export const BRANDING_FOOTER = [
|
|
13
13
|
'',
|
|
14
14
|
'---',
|
|
15
|
-
'> **Rune Skill Mesh** —
|
|
15
|
+
'> **Rune Skill Mesh** — 62 skills, 215+ connections, 14 extension packs',
|
|
16
16
|
'> [Landing Page](https://rune-kit.github.io/rune) · [Source](https://github.com/rune-kit/rune) (MIT)',
|
|
17
17
|
'> **Rune Pro** ($49 lifetime) — product, sales, data-science, support packs → [rune-kit/rune-pro](https://github.com/rune-kit/rune-pro)',
|
|
18
18
|
'> **Rune Business** ($149 lifetime) — finance, legal, HR, enterprise-search packs → [rune-kit/rune-business](https://github.com/rune-kit/rune-business)',
|
|
@@ -24,6 +24,27 @@ Use Grep to find every animation/transition declaration. Verify each is wrapped
|
|
|
24
24
|
**Step 4 — Page transition patterns**
|
|
25
25
|
Apply View Transitions API for same-document navigations (SvelteKit, Astro, vanilla JS). For React/Next.js, use Framer Motion `AnimatePresence` + `layoutId` for shared layout animations. Emit transition wrapper component with both strategies.
|
|
26
26
|
|
|
27
|
+
**Step 5 — Mood-to-Animation Timing**
|
|
28
|
+
|
|
29
|
+
If `.rune/design-system.md` contains a `## Mood` section, read the selected mood and apply the matching motion profile. This ensures animation feel aligns with the product's emotional intent — not just technical correctness.
|
|
30
|
+
|
|
31
|
+
| Mood | Duration | Easing | Hover | Enter/Exit | Scroll | Signature |
|
|
32
|
+
|------|----------|--------|-------|------------|--------|-----------|
|
|
33
|
+
| **Impressed** | 0.8-1.2s | `ease-out` | Scale 1.03 + deep shadow | Fade-up 24px | Parallax layers | Staggered reveals with 100ms delay between items |
|
|
34
|
+
| **Excited** | 0.4-0.6s | `spring(1, 80, 10)` | Scale 1.06 + color shift | Slide-in from edge | Snap scroll | Overshoot on entry (1.56 bounce), pulse on data change |
|
|
35
|
+
| **Calm** | 0.6-0.8s | `ease-out-quad` | Subtle opacity 0.8→1 | Slow fade 300ms | Gentle float | Breathing rhythm on idle elements (opacity 0.7↔1, 4s loop) |
|
|
36
|
+
| **Confident** | 0.3-0.5s | `ease` | Precise underline/border | Clean slide 16px | None or minimal | Sharp, decisive — no overshoot, no bounce |
|
|
37
|
+
| **Playful** | 0.4-0.6s | `spring(1, 100, 12)` | Wobble or tilt (rotate ±2°) | Bounce-in from bottom | Elastic snap | Squish on click (scaleX 1.05, scaleY 0.95), emoji-like feedback |
|
|
38
|
+
| **Techy** | 0.15-0.3s | `ease-out` | Glow border or underline | Instant or 100ms fade | Sticky headers | Typewriter text, cursor blink, terminal-feel transitions |
|
|
39
|
+
| **Professional** | 0.2-0.3s | `ease` | Background tint only | Simple fade | Fixed header | Minimal — motion serves function, never decoration |
|
|
40
|
+
| **Inspired** | 0.5-0.8s | `cubic-bezier(0.4, 0, 0.2, 1)` | Reveal hidden detail | Scroll-driven enter | Parallax + reveal | Cinematic — content appears as user discovers, like turning pages |
|
|
41
|
+
|
|
42
|
+
**Usage rules:**
|
|
43
|
+
1. Read mood from `.rune/design-system.md` → select matching row → apply as default motion tokens
|
|
44
|
+
2. If no mood defined, fall back to **Professional** (safest, least opinionated)
|
|
45
|
+
3. ALL timing values must be wrapped in `prefers-reduced-motion` check — mood doesn't override accessibility
|
|
46
|
+
4. Mood overrides generic Step 2 micro-interactions where they conflict
|
|
47
|
+
|
|
27
48
|
#### Example
|
|
28
49
|
|
|
29
50
|
```tsx
|
|
@@ -24,6 +24,31 @@ Write the refactored component skeleton following the compound component pattern
|
|
|
24
24
|
**Step 4 — Composition vs inheritance + slot patterns**
|
|
25
25
|
After structural refactor, audit for slot opportunities (Svelte `<slot>`, Vue `v-slot`, React `children` with typed slots). Enforce: prefer composition (pass components as props) over inheritance (extend base class). Flag any `extends React.Component` or class-based patterns for migration.
|
|
26
26
|
|
|
27
|
+
**Step 5 — Bento Card Archetypes**
|
|
28
|
+
|
|
29
|
+
When designing card-based layouts, apply named archetypes instead of uniform grids. Each archetype has a specific interaction model and animation signature. Mix archetypes within a page to create visual hierarchy.
|
|
30
|
+
|
|
31
|
+
| Archetype | Content Type | Layout | Interaction | Animation |
|
|
32
|
+
|-----------|-------------|--------|-------------|-----------|
|
|
33
|
+
| **Intelligent List** | Ranked/sortable items (leaderboard, top assets, recent activity) | Vertical stack, auto-reorders | Drag to reorder, click to expand | `layoutId` shared animation on reorder — items slide to new position (Framer Motion `layout` prop) |
|
|
34
|
+
| **Command Input** | Search, AI prompt, quick actions | Single input + dropdown results | Type to filter, keyboard nav, Enter to execute | Typewriter placeholder text + shimmer gradient on focus + results fade-in with 50ms stagger |
|
|
35
|
+
| **Live Status** | Real-time metrics (uptime, price, active users) | Compact card, number-dominant | Hover for sparkline/history | Breathing pulse on idle (opacity 0.85↔1, 3s), overshoot scale on value change (spring) |
|
|
36
|
+
| **Wide Data Stream** | Horizontal feed (news, transactions, timeline) | Full-width horizontal scroll or infinite carousel | Swipe/drag, auto-advance optional | Infinite scroll with momentum, snap to item, fade edges to signal overflow |
|
|
37
|
+
| **Contextual Panel** | Detail view, settings, metadata | Expandable from parent card or sidebar | Click parent → panel slides in | Stagger children (50ms each), float-in from right (translateX 24px → 0) |
|
|
38
|
+
|
|
39
|
+
**Anti-patterns for card layouts:**
|
|
40
|
+
- ❌ **Uniform grid** — all cards same size, same padding, same content structure = AI signature
|
|
41
|
+
- ❌ **Card soup** — cards with no clear grouping or hierarchy
|
|
42
|
+
- ❌ **Static bento** — bento layout without interaction model = decorative, not functional
|
|
43
|
+
|
|
44
|
+
**Composition rules:**
|
|
45
|
+
1. A page should use 2-4 archetypes max — more = visual noise
|
|
46
|
+
2. **Live Status** cards cluster together (dashboard KPI row)
|
|
47
|
+
3. **Intelligent List** is the primary content area — usually takes 60%+ of viewport
|
|
48
|
+
4. **Command Input** is a singleton — ONE per page, always accessible (often pinned top or Cmd+K)
|
|
49
|
+
5. **Wide Data Stream** breaks vertical rhythm — use as section separator between dense blocks
|
|
50
|
+
6. **Contextual Panel** is secondary — triggered by interaction, never shown by default on load
|
|
51
|
+
|
|
27
52
|
#### Example
|
|
28
53
|
|
|
29
54
|
```tsx
|
|
@@ -84,7 +84,7 @@ export function SplitHero() {
|
|
|
84
84
|
<em className="not-italic text-[var(--primary)]">before the sprint</em>
|
|
85
85
|
</h1>
|
|
86
86
|
<p className="text-[var(--text-secondary)] text-body leading-relaxed mb-8 max-w-md">
|
|
87
|
-
Rune wires your AI coding assistant to a mesh of
|
|
87
|
+
Rune wires your AI coding assistant to a mesh of 62 skills so you spend time building, not prompting.
|
|
88
88
|
</p>
|
|
89
89
|
<div className="flex flex-wrap gap-3">
|
|
90
90
|
<a
|
|
@@ -1,68 +1,95 @@
|
|
|
1
|
-
// Rune Context Watch Hook
|
|
2
|
-
// Lightweight tool call counter — detects context pressure and suggests rune:context-engine
|
|
3
|
-
// Runs as PreToolUse hook on Edit/Write (high-cost operations)
|
|
4
|
-
//
|
|
5
|
-
// H3 Intelligence: also tracks tool type distribution and session start timestamp
|
|
6
|
-
// for metrics aggregation at session end.
|
|
7
|
-
//
|
|
8
|
-
// Uses a temp file counter (survives across hook invocations within same session).
|
|
9
|
-
// Zero overhead: just reads/increments a number. No token cost.
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
} catch {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
//
|
|
42
|
-
state
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
if (
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
1
|
+
// Rune Context Watch Hook
|
|
2
|
+
// Lightweight tool call counter — detects context pressure and suggests rune:context-engine
|
|
3
|
+
// Runs as PreToolUse hook on Edit/Write (high-cost operations)
|
|
4
|
+
//
|
|
5
|
+
// H3 Intelligence: also tracks tool type distribution and session start timestamp
|
|
6
|
+
// for metrics aggregation at session end.
|
|
7
|
+
//
|
|
8
|
+
// Uses a temp file counter (survives across hook invocations within same session).
|
|
9
|
+
// Zero overhead: just reads/increments a number. No token cost.
|
|
10
|
+
//
|
|
11
|
+
// Data source: stdin JSON from Claude Code (not env vars)
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const os = require('os');
|
|
16
|
+
|
|
17
|
+
// Counter file scoped to current working directory (hash of cwd)
|
|
18
|
+
const cwd = process.cwd();
|
|
19
|
+
const hash = Buffer.from(cwd).toString('base64url').slice(0, 16);
|
|
20
|
+
const counterFile = path.join(os.tmpdir(), `rune-context-watch-${hash}.json`);
|
|
21
|
+
|
|
22
|
+
// Thresholds
|
|
23
|
+
const FIRST_WARNING = 40;
|
|
24
|
+
const REPEAT_INTERVAL = 20;
|
|
25
|
+
const CRITICAL_THRESHOLD = 80;
|
|
26
|
+
|
|
27
|
+
// Read stdin JSON to get tool name (Claude Code passes hook data via stdin)
|
|
28
|
+
let stdinData = '';
|
|
29
|
+
process.stdin.setEncoding('utf-8');
|
|
30
|
+
process.stdin.on('data', chunk => { stdinData += chunk; });
|
|
31
|
+
process.stdin.on('end', () => {
|
|
32
|
+
let toolName = 'unknown';
|
|
33
|
+
try {
|
|
34
|
+
const parsed = JSON.parse(stdinData);
|
|
35
|
+
toolName = parsed.tool || parsed.tool_name || 'unknown';
|
|
36
|
+
} catch {
|
|
37
|
+
// Fallback to env var for backwards compatibility
|
|
38
|
+
toolName = process.env.CLAUDE_TOOL_NAME || 'unknown';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Read current state
|
|
42
|
+
let state = { count: 0, lastWarning: 0, sessionStart: null, sessionId: null, toolCounts: {} };
|
|
43
|
+
try {
|
|
44
|
+
const raw = fs.readFileSync(counterFile, 'utf-8');
|
|
45
|
+
state = JSON.parse(raw);
|
|
46
|
+
// Ensure fields exist (upgrade from old format)
|
|
47
|
+
if (!state.toolCounts) state.toolCounts = {};
|
|
48
|
+
if (!state.sessionStart) state.sessionStart = new Date().toISOString();
|
|
49
|
+
if (!state.sessionId) {
|
|
50
|
+
const s = state.sessionStart;
|
|
51
|
+
state.sessionId = `s-${s.slice(0, 10).replace(/-/g, '')}-${s.slice(11, 19).replace(/:/g, '')}`;
|
|
52
|
+
}
|
|
53
|
+
} catch {
|
|
54
|
+
// First run or corrupted — start fresh
|
|
55
|
+
const now = new Date().toISOString();
|
|
56
|
+
state.sessionStart = now;
|
|
57
|
+
state.sessionId = `s-${now.slice(0, 10).replace(/-/g, '')}-${now.slice(11, 19).replace(/:/g, '')}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Increment total and per-tool counters
|
|
61
|
+
state.count += 1;
|
|
62
|
+
state.toolCounts[toolName] = (state.toolCounts[toolName] || 0) + 1;
|
|
63
|
+
|
|
64
|
+
// Check thresholds
|
|
65
|
+
const count = state.count;
|
|
66
|
+
const sinceLast = count - state.lastWarning;
|
|
67
|
+
|
|
68
|
+
if (count >= CRITICAL_THRESHOLD && sinceLast >= REPEAT_INTERVAL) {
|
|
69
|
+
console.log(`\n🔴 [Rune context-watch] ${count} tool calls — context likely RED (>85%).`);
|
|
70
|
+
console.log(' RECOMMENDED: Invoke rune:context-engine for state save + /compact.');
|
|
71
|
+
console.log(' Risk: auto-compaction may lose critical decisions without state save.\n');
|
|
72
|
+
state.lastWarning = count;
|
|
73
|
+
} else if (count >= FIRST_WARNING && sinceLast >= REPEAT_INTERVAL) {
|
|
74
|
+
console.log(`\n🟡 [Rune context-watch] ${count} tool calls — context filling up.`);
|
|
75
|
+
console.log(' Consider invoking rune:context-engine at the next logical boundary.');
|
|
76
|
+
console.log(' Or run /compact manually if at a good stopping point.\n');
|
|
77
|
+
state.lastWarning = count;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Pass through stdin to stdout (required for PreToolUse hooks)
|
|
81
|
+
if (stdinData) process.stdout.write(stdinData);
|
|
82
|
+
|
|
83
|
+
// Persist
|
|
84
|
+
try {
|
|
85
|
+
fs.writeFileSync(counterFile, JSON.stringify(state));
|
|
86
|
+
} catch {
|
|
87
|
+
// Non-critical — counter resets next run
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
process.exit(0);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Handle empty stdin (pipe closed immediately)
|
|
94
|
+
process.stdin.on('error', () => process.exit(0));
|
|
95
|
+
process.stdin.resume();
|
|
@@ -1,42 +1,86 @@
|
|
|
1
|
-
// Rune Metrics Collector Hook
|
|
2
|
-
// PostToolUse on Skill — captures skill invocations for H3 mesh analytics
|
|
3
|
-
// Append-only JSONL to tmpdir. Flushed to .rune/metrics/ at session end.
|
|
4
|
-
// Async: true — never blocks skill execution.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
|
|
1
|
+
// Rune Metrics Collector Hook
|
|
2
|
+
// PostToolUse on Skill — captures skill invocations for H3 mesh analytics
|
|
3
|
+
// Append-only JSONL to tmpdir. Flushed to .rune/metrics/ at session end.
|
|
4
|
+
// Async: true — never blocks skill execution.
|
|
5
|
+
//
|
|
6
|
+
// Data source: stdin JSON from Claude Code (not env vars)
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const os = require('os');
|
|
11
|
+
|
|
12
|
+
const cwd = process.cwd();
|
|
13
|
+
const hash = Buffer.from(cwd).toString('base64url').slice(0, 16);
|
|
14
|
+
const metricsFile = path.join(os.tmpdir(), `rune-metrics-${hash}.jsonl`);
|
|
15
|
+
const watchFile = path.join(os.tmpdir(), `rune-context-watch-${hash}.json`);
|
|
16
|
+
|
|
17
|
+
// Read stdin JSON to get tool input (Claude Code passes hook data via stdin)
|
|
18
|
+
let stdinData = '';
|
|
19
|
+
process.stdin.setEncoding('utf-8');
|
|
20
|
+
process.stdin.on('data', chunk => { stdinData += chunk; });
|
|
21
|
+
process.stdin.on('end', () => {
|
|
22
|
+
let skillName = 'unknown';
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
const hookData = JSON.parse(stdinData);
|
|
26
|
+
// PostToolUse stdin: { tool: "Skill", tool_input: { skill: "rune:cook", ... }, tool_result: ... }
|
|
27
|
+
const toolInput = hookData.tool_input || {};
|
|
28
|
+
const raw = toolInput.skill || toolInput.name || '';
|
|
29
|
+
skillName = raw.replace(/^rune:/, '') || 'unknown';
|
|
30
|
+
} catch {
|
|
31
|
+
// Fallback: try env var for backwards compatibility
|
|
32
|
+
const toolInput = process.env.CLAUDE_TOOL_INPUT || '';
|
|
33
|
+
try {
|
|
34
|
+
const parsed = JSON.parse(toolInput);
|
|
35
|
+
const raw = parsed.skill || parsed.name || '';
|
|
36
|
+
skillName = raw.replace(/^rune:/, '');
|
|
37
|
+
} catch {
|
|
38
|
+
const match = toolInput.match(/(?:rune:)?([a-z][\w-]*)/i);
|
|
39
|
+
if (match) skillName = match[1];
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (skillName && skillName !== 'unknown') {
|
|
44
|
+
const now = Date.now();
|
|
45
|
+
const ts = new Date(now).toISOString();
|
|
46
|
+
|
|
47
|
+
// Read session ID from context-watch state (shared tmpdir file)
|
|
48
|
+
let sessionId = null;
|
|
49
|
+
try {
|
|
50
|
+
const watchState = JSON.parse(fs.readFileSync(watchFile, 'utf-8'));
|
|
51
|
+
sessionId = watchState.sessionId || null;
|
|
52
|
+
} catch { /* no watch state yet */ }
|
|
53
|
+
|
|
54
|
+
// Compute duration since last skill event (approximate skill execution time)
|
|
55
|
+
let durationMs = null;
|
|
56
|
+
try {
|
|
57
|
+
const lines = fs.readFileSync(metricsFile, 'utf-8').trim().split('\n').filter(Boolean);
|
|
58
|
+
if (lines.length > 0) {
|
|
59
|
+
const last = JSON.parse(lines[lines.length - 1]);
|
|
60
|
+
durationMs = now - new Date(last.ts).getTime();
|
|
61
|
+
// Cap at 10 minutes — longer gaps are idle time, not skill duration
|
|
62
|
+
if (durationMs > 600000) durationMs = null;
|
|
63
|
+
}
|
|
64
|
+
} catch { /* first event or read error */ }
|
|
65
|
+
|
|
66
|
+
const entry = JSON.stringify({
|
|
67
|
+
ts,
|
|
68
|
+
skill: skillName,
|
|
69
|
+
event: 'invoke',
|
|
70
|
+
session_id: sessionId,
|
|
71
|
+
duration_ms: durationMs
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
fs.appendFileSync(metricsFile, entry + '\n');
|
|
76
|
+
} catch {
|
|
77
|
+
// Non-critical — metrics are best-effort
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
process.exit(0);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Handle empty stdin (pipe closed immediately)
|
|
85
|
+
process.stdin.on('error', () => process.exit(0));
|
|
86
|
+
process.stdin.resume();
|
|
@@ -15,6 +15,32 @@ const metricsJsonl = path.join(os.tmpdir(), `rune-metrics-${hash}.jsonl`);
|
|
|
15
15
|
const counterFile = path.join(os.tmpdir(), `rune-context-watch-${hash}.json`);
|
|
16
16
|
const runeMetricsDir = path.join(cwd, '.rune', 'metrics');
|
|
17
17
|
|
|
18
|
+
// Resolve skill names → expected model from agent frontmatter
|
|
19
|
+
// Reads agents/*.md at flush time — no runtime model detection needed
|
|
20
|
+
// Map skill names → models, weighted by invocation count
|
|
21
|
+
// skillCounts: { skillName: invocationCount }
|
|
22
|
+
function resolveSkillModels(skillCounts) {
|
|
23
|
+
const models = {};
|
|
24
|
+
const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT || path.join(__dirname, '..', '..');
|
|
25
|
+
const agentsDir = path.join(pluginRoot, 'agents');
|
|
26
|
+
|
|
27
|
+
if (!fs.existsSync(agentsDir)) return models;
|
|
28
|
+
|
|
29
|
+
for (const [skill, count] of Object.entries(skillCounts)) {
|
|
30
|
+
try {
|
|
31
|
+
const agentFile = path.join(agentsDir, `${skill}.md`);
|
|
32
|
+
if (!fs.existsSync(agentFile)) continue;
|
|
33
|
+
const content = fs.readFileSync(agentFile, 'utf-8');
|
|
34
|
+
const match = content.match(/^model:\s*(\w+)/m);
|
|
35
|
+
if (match) {
|
|
36
|
+
const model = match[1];
|
|
37
|
+
models[model] = (models[model] || 0) + count;
|
|
38
|
+
}
|
|
39
|
+
} catch { /* best-effort */ }
|
|
40
|
+
}
|
|
41
|
+
return models;
|
|
42
|
+
}
|
|
43
|
+
|
|
18
44
|
try {
|
|
19
45
|
flushMetrics();
|
|
20
46
|
} catch (e) {
|
|
@@ -33,7 +59,7 @@ function flushMetrics() {
|
|
|
33
59
|
}
|
|
34
60
|
|
|
35
61
|
// Read context-watch state for tool counts and session timing
|
|
36
|
-
let watchState = { count: 0, sessionStart: null, toolCounts: {} };
|
|
62
|
+
let watchState = { count: 0, sessionStart: null, sessionId: null, toolCounts: {} };
|
|
37
63
|
if (fs.existsSync(counterFile)) {
|
|
38
64
|
try {
|
|
39
65
|
watchState = JSON.parse(fs.readFileSync(counterFile, 'utf-8'));
|
|
@@ -50,20 +76,28 @@ function flushMetrics() {
|
|
|
50
76
|
const sessionStart = watchState.sessionStart || now;
|
|
51
77
|
const durationMin = Math.round((new Date(now) - new Date(sessionStart)) / 60000);
|
|
52
78
|
|
|
53
|
-
// Build skill usage map
|
|
79
|
+
// Build skill usage map and duration aggregation
|
|
54
80
|
const skillCounts = {};
|
|
81
|
+
const skillDurations = {};
|
|
55
82
|
const skillChain = [];
|
|
56
83
|
for (const evt of skillEvents) {
|
|
57
84
|
skillCounts[evt.skill] = (skillCounts[evt.skill] || 0) + 1;
|
|
58
85
|
skillChain.push(evt.skill);
|
|
86
|
+
if (evt.duration_ms != null) {
|
|
87
|
+
skillDurations[evt.skill] = (skillDurations[evt.skill] || 0) + evt.duration_ms;
|
|
88
|
+
}
|
|
59
89
|
}
|
|
60
90
|
|
|
61
91
|
// Determine primary skill (most invoked)
|
|
62
92
|
const primarySkill = Object.entries(skillCounts)
|
|
63
93
|
.sort((a, b) => b[1] - a[1])[0]?.[0] || 'none';
|
|
64
94
|
|
|
65
|
-
//
|
|
66
|
-
const
|
|
95
|
+
// Map skills to expected models from agent definitions (weighted by invocation count)
|
|
96
|
+
const modelsUsed = resolveSkillModels(skillCounts);
|
|
97
|
+
|
|
98
|
+
// Use session ID from context-watch (shared) or generate new
|
|
99
|
+
const sessionId = watchState.sessionId
|
|
100
|
+
|| `s-${now.slice(0, 10).replace(/-/g, '')}-${now.slice(11, 19).replace(/:/g, '')}`;
|
|
67
101
|
|
|
68
102
|
// 1. Append to sessions.jsonl
|
|
69
103
|
const sessionEntry = {
|
|
@@ -74,7 +108,9 @@ function flushMetrics() {
|
|
|
74
108
|
tool_distribution: watchState.toolCounts,
|
|
75
109
|
skill_invocations: skillEvents.length,
|
|
76
110
|
skills_used: Object.keys(skillCounts),
|
|
77
|
-
primary_skill: primarySkill
|
|
111
|
+
primary_skill: primarySkill,
|
|
112
|
+
models_used: modelsUsed,
|
|
113
|
+
skill_durations: Object.keys(skillDurations).length > 0 ? skillDurations : undefined
|
|
78
114
|
};
|
|
79
115
|
|
|
80
116
|
const sessionsFile = path.join(runeMetricsDir, 'sessions.jsonl');
|
|
@@ -8,10 +8,16 @@ const os = require('os');
|
|
|
8
8
|
const cwd = process.cwd();
|
|
9
9
|
const runeDir = path.join(cwd, '.rune');
|
|
10
10
|
|
|
11
|
-
//
|
|
11
|
+
// Initialize fresh session state (shared between context-watch and metrics-collector)
|
|
12
12
|
const hash = Buffer.from(cwd).toString('base64url').slice(0, 16);
|
|
13
13
|
const counterFile = path.join(os.tmpdir(), `rune-context-watch-${hash}.json`);
|
|
14
|
-
|
|
14
|
+
const now = new Date().toISOString();
|
|
15
|
+
const sessionId = `s-${now.slice(0, 10).replace(/-/g, '')}-${now.slice(11, 19).replace(/:/g, '')}`;
|
|
16
|
+
try {
|
|
17
|
+
fs.writeFileSync(counterFile, JSON.stringify({
|
|
18
|
+
count: 0, lastWarning: 0, sessionStart: now, sessionId, toolCounts: {}
|
|
19
|
+
}));
|
|
20
|
+
} catch { /* non-critical */ }
|
|
15
21
|
|
|
16
22
|
if (fs.existsSync(runeDir)) {
|
|
17
23
|
const stateFiles = [
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rune-kit/rune",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.10.0",
|
|
4
|
+
"description": "62-skill mesh for AI coding assistants — 5-layer architecture, 215+ connections, 8 platforms (Claude Code, Cursor, Windsurf, Antigravity, Codex, OpenCode, OpenClaw, Generic)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"rune": "./compiler/bin/rune.js"
|
package/skills/audit/SKILL.md
CHANGED
package/skills/autopsy/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: autopsy
|
|
|
3
3
|
description: Full codebase health assessment. 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
|
-
version: "0.
|
|
6
|
+
version: "0.4.0"
|
|
7
7
|
layer: L2
|
|
8
8
|
model: opus
|
|
9
9
|
group: rescue
|
|
@@ -32,6 +32,32 @@ Full codebase health assessment for legacy projects. Autopsy analyzes complexity
|
|
|
32
32
|
|
|
33
33
|
## Execution Steps
|
|
34
34
|
|
|
35
|
+
### Step 0 — Repo intelligence (if GitHub-hosted)
|
|
36
|
+
|
|
37
|
+
If the project is a GitHub repository, gather repo-level metrics before diving into code:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# Fetch via GitHub API (requires gh CLI or curl + GITHUB_TOKEN)
|
|
41
|
+
gh api repos/{owner}/{repo} --jq '{stars: .stargazers_count, forks: .forks_count, open_issues: .open_issues_count, license: .license.spdx_id, language: .language, topics: .topics, created: .created_at, pushed: .pushed_at}'
|
|
42
|
+
|
|
43
|
+
# Contributor count and top contributors
|
|
44
|
+
gh api repos/{owner}/{repo}/contributors --jq 'length'
|
|
45
|
+
gh api repos/{owner}/{repo}/contributors --jq '.[0:5] | .[] | "\(.login): \(.contributions)"'
|
|
46
|
+
|
|
47
|
+
# Commit frequency (last 52 weeks)
|
|
48
|
+
gh api repos/{owner}/{repo}/stats/commit_activity --jq '[.[] | .total] | add'
|
|
49
|
+
|
|
50
|
+
# Language byte distribution
|
|
51
|
+
gh api repos/{owner}/{repo}/languages
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Record in working notes:
|
|
55
|
+
- **Activity signal**: commits/week (>5 = active, 1-5 = maintained, <1 = stale)
|
|
56
|
+
- **Bus factor**: contributor count (1 = critical risk, 2-3 = low, >5 = healthy)
|
|
57
|
+
- **Community signal**: stars/forks ratio, open issue count, staleness of latest push
|
|
58
|
+
|
|
59
|
+
Skip this step for local-only projects with no remote.
|
|
60
|
+
|
|
35
61
|
### Step 1 — Structure scan
|
|
36
62
|
|
|
37
63
|
Call `rune:scout` with a request for a full project map. Ask scout to return:
|
|
@@ -84,6 +110,12 @@ git log --before="1 year ago" --format="%H" | head -1 | xargs -I{} git diff --na
|
|
|
84
110
|
|
|
85
111
|
# Authors per file (high author count = high churn risk)
|
|
86
112
|
git log --format="%an" -- <file> | sort -u | wc -l
|
|
113
|
+
|
|
114
|
+
# Commit velocity by month (trend detection)
|
|
115
|
+
git log --format="%Y-%m" | sort | uniq -c | tail -12
|
|
116
|
+
|
|
117
|
+
# Issue/PR close rate (GitHub only)
|
|
118
|
+
gh api repos/{owner}/{repo}/issues --jq '[.[] | select(.pull_request == null)] | length'
|
|
87
119
|
```
|
|
88
120
|
|
|
89
121
|
Identify:
|
|
@@ -91,6 +123,7 @@ Identify:
|
|
|
91
123
|
- God files (> 500 LOC with many importers)
|
|
92
124
|
- Hotspot files (changed most often = highest bug density)
|
|
93
125
|
- Dead files (no importers, no recent commits)
|
|
126
|
+
- Velocity trend: accelerating, stable, or decelerating (compare last 3 months)
|
|
94
127
|
|
|
95
128
|
### Step 5 — Generate RESCUE-REPORT.md
|
|
96
129
|
|
|
@@ -108,7 +141,22 @@ Generated: [date]
|
|
|
108
141
|
| [name] | [n] | [low/med/high] | [%] | [low/med/high] | [tier] | [1-N] |
|
|
109
142
|
|
|
110
143
|
## Dependency Graph
|
|
111
|
-
[Mermaid
|
|
144
|
+
[Mermaid flowchart of module coupling — use subgraphs for clusters]
|
|
145
|
+
|
|
146
|
+
## Language Distribution
|
|
147
|
+
[Mermaid pie chart — e.g., pie title Languages "TypeScript" : 65 "JavaScript" : 20 "CSS" : 15]
|
|
148
|
+
|
|
149
|
+
## Commit Velocity (Last 12 Months)
|
|
150
|
+
[Trend: accelerating / stable / decelerating — include monthly commit counts]
|
|
151
|
+
|
|
152
|
+
## Repo Intelligence (GitHub only)
|
|
153
|
+
| Metric | Value | Signal |
|
|
154
|
+
|--------|-------|--------|
|
|
155
|
+
| Stars | [n] | [community interest level] |
|
|
156
|
+
| Contributors | [n] | [bus factor: critical/low/healthy] |
|
|
157
|
+
| Open issues | [n] | [maintenance signal] |
|
|
158
|
+
| Commits/week | [n] | [activity: active/maintained/stale] |
|
|
159
|
+
| Last push | [date] | [freshness] |
|
|
112
160
|
|
|
113
161
|
## Surgery Queue (Priority Order)
|
|
114
162
|
1. [module] — Score: [n] — [primary reason] — Suggested pattern: [pattern]
|
|
@@ -136,6 +184,34 @@ Output a summary of the findings:
|
|
|
136
184
|
- Confirm RESCUE-REPORT.md was saved
|
|
137
185
|
- Recommended next step: call `rune:safeguard` on the top-priority module
|
|
138
186
|
|
|
187
|
+
## Confidence Scoring
|
|
188
|
+
|
|
189
|
+
Every finding in the autopsy report MUST carry a confidence level:
|
|
190
|
+
|
|
191
|
+
| Level | Range | Criteria |
|
|
192
|
+
|-------|-------|----------|
|
|
193
|
+
| High | 90-100% | Measured directly from code/git — LOC counted, tests run, deps parsed |
|
|
194
|
+
| Medium | 70-89% | Inferred from strong signals — file patterns, naming conventions, partial git data |
|
|
195
|
+
| Low | 50-69% | Estimated from weak signals — no git history, binary files, generated code |
|
|
196
|
+
|
|
197
|
+
Rules:
|
|
198
|
+
- Health scores backed by actual code metrics → High confidence
|
|
199
|
+
- Health scores using git archaeology only (no code read) → Medium confidence
|
|
200
|
+
- Health scores for modules where files couldn't be read (binary, encrypted, too large) → Low confidence
|
|
201
|
+
- **Overall report confidence** = weighted average of module confidences (by LOC weight)
|
|
202
|
+
- Include confidence in RESCUE-REPORT.md header: `Confidence: [High|Medium|Low] ([n]%)`
|
|
203
|
+
|
|
204
|
+
## Multi-Round Analysis
|
|
205
|
+
|
|
206
|
+
Autopsy follows a broad-to-narrow pattern to avoid missing systemic issues:
|
|
207
|
+
|
|
208
|
+
1. **Round 1 — Surface scan** (Steps 0-1): Repo metrics + structure map. Goal: identify scope and major clusters.
|
|
209
|
+
2. **Round 2 — Module deep dive** (Steps 2-3): Read and score each module. Goal: quantified health per module.
|
|
210
|
+
3. **Round 3 — Cross-cutting analysis** (Step 4): Git archaeology + dependency graph. Goal: find systemic risks invisible at module level (circular deps, hotspot clusters, bus factor).
|
|
211
|
+
4. **Round 4 — Synthesis** (Steps 5-6): Combine all rounds into prioritized report. Findings from later rounds may revise earlier scores.
|
|
212
|
+
|
|
213
|
+
Do NOT skip rounds. Round 3 cross-cutting analysis frequently reveals risks that per-module analysis misses (e.g., a "healthy" module that is the single point of failure for 10 others).
|
|
214
|
+
|
|
139
215
|
## Health Score Factors
|
|
140
216
|
|
|
141
217
|
```
|