@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.
Files changed (118) hide show
  1. package/README.md +66 -34
  2. package/agents/adversary.md +27 -0
  3. package/agents/architect.md +19 -29
  4. package/agents/asset-creator.md +18 -4
  5. package/agents/audit.md +25 -4
  6. package/agents/autopsy.md +19 -4
  7. package/agents/ba.md +35 -0
  8. package/agents/brainstorm.md +31 -4
  9. package/agents/browser-pilot.md +21 -4
  10. package/agents/coder.md +21 -29
  11. package/agents/completion-gate.md +20 -4
  12. package/agents/constraint-check.md +18 -4
  13. package/agents/context-engine.md +22 -4
  14. package/agents/context-pack.md +32 -0
  15. package/agents/cook.md +41 -4
  16. package/agents/db.md +19 -4
  17. package/agents/debug.md +33 -4
  18. package/agents/dependency-doctor.md +20 -4
  19. package/agents/deploy.md +27 -4
  20. package/agents/design.md +22 -4
  21. package/agents/doc-processor.md +27 -0
  22. package/agents/docs-seeker.md +19 -4
  23. package/agents/docs.md +31 -0
  24. package/agents/fix.md +37 -4
  25. package/agents/git.md +29 -0
  26. package/agents/hallucination-guard.md +20 -4
  27. package/agents/incident.md +21 -4
  28. package/agents/integrity-check.md +18 -4
  29. package/agents/journal.md +19 -4
  30. package/agents/launch.md +32 -4
  31. package/agents/logic-guardian.md +26 -11
  32. package/agents/marketing.md +23 -4
  33. package/agents/mcp-builder.md +26 -0
  34. package/agents/neural-memory.md +30 -0
  35. package/agents/onboard.md +22 -4
  36. package/agents/perf.md +21 -4
  37. package/agents/plan.md +29 -4
  38. package/agents/preflight.md +22 -4
  39. package/agents/problem-solver.md +20 -4
  40. package/agents/rescue.md +23 -4
  41. package/agents/research.md +19 -4
  42. package/agents/researcher.md +19 -29
  43. package/agents/retro.md +32 -0
  44. package/agents/review-intake.md +20 -4
  45. package/agents/review.md +32 -4
  46. package/agents/reviewer.md +20 -28
  47. package/agents/safeguard.md +19 -4
  48. package/agents/sast.md +18 -4
  49. package/agents/scaffold.md +41 -0
  50. package/agents/scanner.md +19 -28
  51. package/agents/scope-guard.md +18 -4
  52. package/agents/scout.md +23 -4
  53. package/agents/sentinel-env.md +26 -0
  54. package/agents/sentinel.md +33 -4
  55. package/agents/sequential-thinking.md +20 -4
  56. package/agents/session-bridge.md +24 -4
  57. package/agents/skill-forge.md +22 -4
  58. package/agents/skill-router.md +26 -4
  59. package/agents/slides.md +24 -0
  60. package/agents/surgeon.md +19 -4
  61. package/agents/team.md +30 -4
  62. package/agents/test.md +36 -4
  63. package/agents/trend-scout.md +17 -4
  64. package/agents/verification.md +20 -4
  65. package/agents/video-creator.md +20 -4
  66. package/agents/watchdog.md +19 -4
  67. package/agents/worktree.md +17 -4
  68. package/compiler/__tests__/analytics.test.js +370 -0
  69. package/compiler/adapters/openclaw.js +2 -2
  70. package/compiler/analytics.js +385 -0
  71. package/compiler/bin/rune.js +68 -2
  72. package/compiler/dashboard.js +883 -0
  73. package/compiler/transforms/branding.js +1 -1
  74. package/extensions/ui/skills/animation-patterns.md +21 -0
  75. package/extensions/ui/skills/component-patterns.md +25 -0
  76. package/extensions/ui/skills/landing-patterns.md +1 -1
  77. package/hooks/context-watch/index.cjs +95 -68
  78. package/hooks/metrics-collector/index.cjs +86 -42
  79. package/hooks/post-session-reflect/index.cjs +41 -5
  80. package/hooks/session-start/index.cjs +8 -2
  81. package/package.json +2 -2
  82. package/skills/audit/SKILL.md +1 -0
  83. package/skills/autopsy/SKILL.md +78 -2
  84. package/skills/autopsy/references/repo-analysis-patterns.md +113 -0
  85. package/skills/ba/SKILL.md +72 -2
  86. package/skills/brainstorm/SKILL.md +1 -0
  87. package/skills/cook/SKILL.md +32 -3
  88. package/skills/cook/references/output-format.md +33 -0
  89. package/skills/db/SKILL.md +1 -0
  90. package/skills/debug/SKILL.md +23 -1
  91. package/skills/deploy/SKILL.md +1 -1
  92. package/skills/design/SKILL.md +110 -3
  93. package/skills/docs/SKILL.md +2 -1
  94. package/skills/fix/SKILL.md +23 -1
  95. package/skills/graft/SKILL.md +352 -0
  96. package/skills/graft/references/challenge-framework.md +98 -0
  97. package/skills/graft/references/mode-decision.md +44 -0
  98. package/skills/incident/SKILL.md +2 -0
  99. package/skills/journal/SKILL.md +2 -0
  100. package/skills/launch/SKILL.md +2 -0
  101. package/skills/marketing/SKILL.md +46 -2
  102. package/skills/mcp-builder/SKILL.md +3 -1
  103. package/skills/mcp-builder/references/auto-discovery-pattern.md +169 -0
  104. package/skills/plan/SKILL.md +60 -2
  105. package/skills/plan/references/feature-map.md +84 -0
  106. package/skills/preflight/SKILL.md +20 -1
  107. package/skills/rescue/SKILL.md +25 -1
  108. package/skills/retro/SKILL.md +2 -0
  109. package/skills/review/SKILL.md +55 -2
  110. package/skills/scaffold/SKILL.md +1 -0
  111. package/skills/scope-guard/SKILL.md +35 -17
  112. package/skills/sentinel/SKILL.md +29 -1
  113. package/skills/session-bridge/SKILL.md +155 -9
  114. package/skills/skill-forge/SKILL.md +43 -1
  115. package/skills/skill-router/{skill.md → SKILL.md} +27 -2
  116. package/skills/team/SKILL.md +24 -1
  117. package/skills/test/SKILL.md +29 -2
  118. 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** — 59 skills, 200+ connections, 14 extension packs',
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 55 skills so you spend time building, not prompting.
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
- const fs = require('fs');
12
- const path = require('path');
13
- const os = require('os');
14
-
15
- // Counter file scoped to current working directory (hash of cwd)
16
- const cwd = process.cwd();
17
- const hash = Buffer.from(cwd).toString('base64url').slice(0, 16);
18
- const counterFile = path.join(os.tmpdir(), `rune-context-watch-${hash}.json`);
19
-
20
- // Thresholds
21
- const FIRST_WARNING = 40;
22
- const REPEAT_INTERVAL = 20;
23
- const CRITICAL_THRESHOLD = 80;
24
-
25
- // Detect tool type from environment
26
- const toolName = process.env.CLAUDE_TOOL_NAME || 'unknown';
27
-
28
- // Read current state
29
- let state = { count: 0, lastWarning: 0, sessionStart: null, toolCounts: {} };
30
- try {
31
- const raw = fs.readFileSync(counterFile, 'utf-8');
32
- state = JSON.parse(raw);
33
- // Ensure toolCounts exists (upgrade from old format)
34
- if (!state.toolCounts) state.toolCounts = {};
35
- if (!state.sessionStart) state.sessionStart = new Date().toISOString();
36
- } catch {
37
- // First run or corrupted start fresh
38
- state.sessionStart = new Date().toISOString();
39
- }
40
-
41
- // Increment total and per-tool counters
42
- state.count += 1;
43
- state.toolCounts[toolName] = (state.toolCounts[toolName] || 0) + 1;
44
-
45
- // Check thresholds
46
- const count = state.count;
47
- const sinceLast = count - state.lastWarning;
48
-
49
- if (count >= CRITICAL_THRESHOLD && sinceLast >= REPEAT_INTERVAL) {
50
- console.log(`\n🔴 [Rune context-watch] ${count} tool calls — context likely RED (>85%).`);
51
- console.log(' RECOMMENDED: Invoke rune:context-engine for state save + /compact.');
52
- console.log(' Risk: auto-compaction may lose critical decisions without state save.\n');
53
- state.lastWarning = count;
54
- } else if (count >= FIRST_WARNING && sinceLast >= REPEAT_INTERVAL) {
55
- console.log(`\n🟡 [Rune context-watch] ${count} tool calls — context filling up.`);
56
- console.log(' Consider invoking rune:context-engine at the next logical boundary.');
57
- console.log(' Or run /compact manually if at a good stopping point.\n');
58
- state.lastWarning = count;
59
- }
60
-
61
- // Persist
62
- try {
63
- fs.writeFileSync(counterFile, JSON.stringify(state));
64
- } catch {
65
- // Non-critical counter resets next run
66
- }
67
-
68
- process.exit(0);
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
- const fs = require('fs');
7
- const path = require('path');
8
- const os = require('os');
9
-
10
- const cwd = process.cwd();
11
- const hash = Buffer.from(cwd).toString('base64url').slice(0, 16);
12
- const metricsFile = path.join(os.tmpdir(), `rune-metrics-${hash}.jsonl`);
13
-
14
- // Extract skill name from tool input
15
- const toolInput = process.env.CLAUDE_TOOL_INPUT || '';
16
- let skillName = 'unknown';
17
- try {
18
- const parsed = JSON.parse(toolInput);
19
- // Skill tool input has { skill: "rune:cook" } or { skill: "cook" }
20
- const raw = parsed.skill || parsed.name || '';
21
- skillName = raw.replace(/^rune:/, '');
22
- } catch {
23
- // If not JSON, try raw string match
24
- const match = toolInput.match(/(?:rune:)?([a-z][\w-]*)/i);
25
- if (match) skillName = match[1];
26
- }
27
-
28
- if (skillName && skillName !== 'unknown') {
29
- const entry = JSON.stringify({
30
- ts: new Date().toISOString(),
31
- skill: skillName,
32
- event: 'invoke'
33
- });
34
-
35
- try {
36
- fs.appendFileSync(metricsFile, entry + '\n');
37
- } catch {
38
- // Non-critical metrics are best-effort
39
- }
40
- }
41
-
42
- process.exit(0);
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
- // Generate session ID
66
- const sessionId = `s-${now.slice(0, 10).replace(/-/g, '')}-${now.slice(11, 19).replace(/:/g, '')}`;
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
- // Reset context-watch counter on session start (fresh context window)
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
- try { fs.unlinkSync(counterFile); } catch { /* no counter yet — fine */ }
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.8.0",
4
- "description": "61-skill mesh for AI coding assistants — 5-layer architecture, 200+ connections, 8 platforms (Claude Code, Cursor, Windsurf, Antigravity, Codex, OpenCode, OpenClaw, Generic)",
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"
@@ -8,6 +8,7 @@ metadata:
8
8
  model: sonnet
9
9
  group: quality
10
10
  tools: "Read, Bash, Glob, Grep"
11
+ emit: audit.complete
11
12
  ---
12
13
 
13
14
  # audit
@@ -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.3.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 diagram of module coupling]
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
  ```