@rune-kit/rune 2.8.0 → 2.11.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/LICENSE +21 -21
- package/README.md +68 -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/commands/rune.md +168 -168
- 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/contexts/dev.md +34 -34
- package/contexts/research.md +43 -43
- package/contexts/review.md +55 -55
- package/extensions/ai-ml/PACK.md +88 -88
- package/extensions/ai-ml/skills/ai-agents.md +172 -172
- package/extensions/ai-ml/skills/code-sandbox.md +187 -187
- package/extensions/ai-ml/skills/deep-research.md +146 -146
- package/extensions/ai-ml/skills/embedding-search.md +66 -66
- package/extensions/ai-ml/skills/fine-tuning-guide.md +74 -74
- package/extensions/ai-ml/skills/llm-architect.md +125 -125
- package/extensions/ai-ml/skills/llm-integration.md +64 -64
- package/extensions/ai-ml/skills/prompt-patterns.md +72 -72
- package/extensions/ai-ml/skills/rag-patterns.md +66 -66
- package/extensions/ai-ml/skills/web-extraction.md +114 -114
- package/extensions/analytics/PACK.md +92 -92
- package/extensions/analytics/skills/ab-testing.md +72 -72
- package/extensions/analytics/skills/dashboard-patterns.md +83 -83
- package/extensions/analytics/skills/data-validation.md +68 -68
- package/extensions/analytics/skills/funnel-analysis.md +81 -81
- package/extensions/analytics/skills/sql-patterns.md +57 -57
- package/extensions/analytics/skills/statistical-analysis.md +79 -79
- package/extensions/analytics/skills/tracking-setup.md +71 -71
- package/extensions/backend/PACK.md +104 -104
- package/extensions/backend/skills/api-patterns.md +84 -84
- package/extensions/backend/skills/async-pipeline.md +193 -193
- package/extensions/backend/skills/auth-patterns.md +97 -97
- package/extensions/backend/skills/background-jobs.md +133 -133
- package/extensions/backend/skills/caching-patterns.md +108 -108
- package/extensions/backend/skills/cli-generation.md +133 -133
- package/extensions/backend/skills/database-patterns.md +87 -87
- package/extensions/backend/skills/middleware-patterns.md +104 -104
- package/extensions/chrome-ext/PACK.md +93 -93
- package/extensions/chrome-ext/skills/cws-preflight.md +143 -143
- package/extensions/chrome-ext/skills/cws-publish.md +104 -104
- package/extensions/chrome-ext/skills/ext-ai-integration.md +251 -251
- package/extensions/chrome-ext/skills/ext-messaging.md +139 -139
- package/extensions/chrome-ext/skills/ext-storage.md +133 -133
- package/extensions/chrome-ext/skills/mv3-scaffold.md +164 -164
- package/extensions/content/PACK.md +96 -96
- package/extensions/content/skills/blog-patterns.md +88 -88
- package/extensions/content/skills/cms-integration.md +131 -131
- package/extensions/content/skills/content-scoring.md +107 -107
- package/extensions/content/skills/i18n.md +83 -83
- package/extensions/content/skills/mdx-authoring.md +137 -137
- package/extensions/content/skills/reference.md +1014 -1014
- package/extensions/content/skills/seo-patterns.md +67 -67
- package/extensions/content/skills/video-repurpose.md +153 -153
- package/extensions/devops/PACK.md +101 -101
- package/extensions/devops/skills/chaos-testing.md +67 -67
- package/extensions/devops/skills/ci-cd.md +75 -75
- package/extensions/devops/skills/docker.md +58 -58
- package/extensions/devops/skills/edge-serverless.md +163 -163
- package/extensions/devops/skills/infra-as-code.md +158 -158
- package/extensions/devops/skills/kubernetes.md +110 -110
- package/extensions/devops/skills/monitoring.md +57 -57
- package/extensions/devops/skills/server-setup.md +64 -64
- package/extensions/devops/skills/ssl-domain.md +42 -42
- package/extensions/ecommerce/PACK.md +116 -116
- package/extensions/ecommerce/skills/cart-system.md +79 -79
- package/extensions/ecommerce/skills/inventory-mgmt.md +102 -102
- package/extensions/ecommerce/skills/order-management.md +126 -126
- package/extensions/ecommerce/skills/payment-integration.md +472 -472
- package/extensions/ecommerce/skills/shopify-dev.md +69 -69
- package/extensions/ecommerce/skills/subscription-billing.md +93 -93
- package/extensions/ecommerce/skills/tax-compliance.md +117 -117
- package/extensions/gamedev/PACK.md +142 -142
- package/extensions/gamedev/skills/asset-pipeline.md +74 -74
- package/extensions/gamedev/skills/audio-system.md +129 -129
- package/extensions/gamedev/skills/camera-system.md +87 -87
- package/extensions/gamedev/skills/ecs.md +98 -98
- package/extensions/gamedev/skills/game-loops.md +72 -72
- package/extensions/gamedev/skills/input-system.md +199 -199
- package/extensions/gamedev/skills/multiplayer.md +180 -180
- package/extensions/gamedev/skills/particles.md +105 -105
- package/extensions/gamedev/skills/physics-engine.md +89 -89
- package/extensions/gamedev/skills/scene-management.md +146 -146
- package/extensions/gamedev/skills/threejs-patterns.md +90 -90
- package/extensions/gamedev/skills/webgl.md +71 -71
- package/extensions/mobile/PACK.md +106 -106
- package/extensions/mobile/skills/app-store-connect.md +152 -152
- package/extensions/mobile/skills/app-store-prep.md +66 -66
- package/extensions/mobile/skills/deep-linking.md +109 -109
- package/extensions/mobile/skills/flutter.md +60 -60
- package/extensions/mobile/skills/ios-build-pipeline.md +142 -142
- package/extensions/mobile/skills/native-bridge.md +66 -66
- package/extensions/mobile/skills/ota-updates.md +97 -97
- package/extensions/mobile/skills/push-notifications.md +111 -111
- package/extensions/mobile/skills/react-native.md +82 -82
- package/extensions/saas/PACK.md +116 -116
- package/extensions/saas/skills/billing-integration.md +200 -200
- package/extensions/saas/skills/feature-flags.md +130 -130
- package/extensions/saas/skills/multi-tenant.md +103 -103
- package/extensions/saas/skills/onboarding-flow.md +139 -139
- package/extensions/saas/skills/subscription-flow.md +95 -95
- package/extensions/saas/skills/team-management.md +144 -144
- package/extensions/security/PACK.md +99 -99
- package/extensions/security/skills/api-security.md +140 -140
- package/extensions/security/skills/compliance.md +68 -68
- package/extensions/security/skills/owasp-audit.md +64 -64
- package/extensions/security/skills/pentest-patterns.md +77 -77
- package/extensions/security/skills/secret-mgmt.md +65 -65
- package/extensions/security/skills/supply-chain.md +65 -65
- package/extensions/trading/PACK.md +80 -80
- package/extensions/trading/skills/chart-components.md +55 -55
- package/extensions/trading/skills/experiment-loop.md +125 -125
- package/extensions/trading/skills/fintech-patterns.md +47 -47
- package/extensions/trading/skills/indicator-library.md +58 -58
- package/extensions/trading/skills/quant-analysis.md +111 -111
- package/extensions/trading/skills/realtime-data.md +58 -58
- package/extensions/trading/skills/trade-logic.md +104 -104
- package/extensions/ui/PACK.md +130 -130
- package/extensions/ui/skills/a11y-audit.md +91 -91
- package/extensions/ui/skills/animation-patterns.md +127 -106
- package/extensions/ui/skills/component-patterns.md +100 -75
- package/extensions/ui/skills/design-decision.md +108 -108
- package/extensions/ui/skills/design-system.md +68 -68
- package/extensions/ui/skills/landing-patterns.md +155 -155
- package/extensions/ui/skills/palette-picker.md +173 -173
- package/extensions/ui/skills/react-health.md +90 -90
- package/extensions/ui/skills/type-system.md +125 -125
- package/extensions/ui/skills/web-vitals.md +153 -153
- package/extensions/zalo/PACK.md +145 -145
- package/extensions/zalo/skills/zalo-oa-mcp.md +317 -317
- package/extensions/zalo/skills/zalo-oa-messaging.md +429 -429
- package/extensions/zalo/skills/zalo-oa-setup.md +236 -236
- package/extensions/zalo/skills/zalo-oa-webhook.md +189 -189
- package/extensions/zalo/skills/zalo-personal-messaging.md +194 -194
- package/extensions/zalo/skills/zalo-personal-setup.md +153 -153
- package/extensions/zalo/skills/zalo-rate-guard.md +219 -219
- package/hooks/auto-format/index.cjs +48 -48
- package/hooks/context-watch/index.cjs +95 -68
- package/hooks/hooks.json +111 -111
- package/hooks/metrics-collector/index.cjs +86 -42
- package/hooks/post-session-reflect/index.cjs +189 -153
- package/hooks/pre-compact/index.cjs +95 -95
- package/hooks/run-hook.cmd +1 -1
- package/hooks/secrets-scan/index.cjs +100 -100
- package/hooks/session-start/index.cjs +71 -65
- package/hooks/typecheck/index.cjs +65 -65
- package/package.json +63 -63
- package/references/ui-pro-max-data/LICENSE-UI-PRO-MAX +21 -21
- package/references/ui-pro-max-data/charts.csv +26 -26
- package/references/ui-pro-max-data/colors.csv +161 -161
- package/references/ui-pro-max-data/styles.csv +68 -68
- package/references/ui-pro-max-data/typography.csv +74 -74
- package/references/ui-pro-max-data/ui-reasoning.csv +162 -162
- package/references/ui-pro-max-data/ux-guidelines.csv +99 -99
- package/skills/adversary/SKILL.md +283 -283
- package/skills/asset-creator/SKILL.md +157 -157
- package/skills/audit/SKILL.md +148 -2
- package/skills/autopsy/SKILL.md +335 -259
- package/skills/autopsy/references/repo-analysis-patterns.md +113 -0
- package/skills/ba/SKILL.md +72 -2
- package/skills/brainstorm/SKILL.md +342 -341
- package/skills/browser-pilot/SKILL.md +168 -168
- package/skills/constraint-check/SKILL.md +165 -165
- package/skills/context-engine/SKILL.md +404 -404
- package/skills/cook/SKILL.md +917 -834
- package/skills/cook/references/output-format.md +33 -0
- package/skills/db/SKILL.md +273 -272
- package/skills/debug/SKILL.md +465 -443
- package/skills/dependency-doctor/SKILL.md +265 -235
- package/skills/deploy/SKILL.md +274 -231
- package/skills/design/DESIGN-REFERENCE.md +365 -365
- package/skills/design/SKILL.md +589 -482
- package/skills/doc-processor/SKILL.md +254 -254
- package/skills/docs/SKILL.md +374 -373
- package/skills/docs-seeker/SKILL.md +177 -177
- package/skills/fix/SKILL.md +330 -308
- package/skills/git/SKILL.md +339 -339
- 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/hallucination-guard/SKILL.md +219 -219
- package/skills/incident/SKILL.md +254 -251
- package/skills/integrity-check/SKILL.md +169 -169
- package/skills/journal/SKILL.md +240 -238
- package/skills/launch/SKILL.md +344 -342
- package/skills/logic-guardian/SKILL.md +251 -251
- package/skills/marketing/SKILL.md +290 -245
- package/skills/mcp-builder/SKILL.md +425 -423
- package/skills/mcp-builder/references/auto-discovery-pattern.md +169 -0
- package/skills/neural-memory/SKILL.md +362 -362
- package/skills/onboard/SKILL.md +404 -403
- package/skills/perf/SKILL.md +346 -346
- package/skills/plan/SKILL.md +433 -370
- package/skills/plan/references/feature-map.md +84 -0
- package/skills/preflight/SKILL.md +415 -396
- package/skills/problem-solver/SKILL.md +380 -284
- package/skills/rescue/SKILL.md +474 -450
- package/skills/retro/SKILL.md +5 -1
- package/skills/review/SKILL.md +612 -535
- package/skills/review-intake/SKILL.md +249 -249
- package/skills/safeguard/SKILL.md +200 -200
- package/skills/sast/SKILL.md +190 -190
- package/skills/scaffold/SKILL.md +328 -286
- package/skills/scope-guard/SKILL.md +180 -162
- package/skills/scout/SKILL.md +263 -263
- package/skills/sentinel/SKILL.md +382 -353
- package/skills/sentinel-env/SKILL.md +254 -254
- package/skills/sequential-thinking/SKILL.md +234 -234
- package/skills/session-bridge/SKILL.md +543 -397
- package/skills/skill-forge/SKILL.md +581 -539
- package/skills/skill-router/{skill.md → SKILL.md} +30 -2
- package/skills/surgeon/SKILL.md +215 -215
- package/skills/team/SKILL.md +556 -514
- package/skills/test/SKILL.md +614 -587
- package/skills/trend-scout/SKILL.md +145 -145
- package/skills/verification/SKILL.md +326 -325
- package/skills/video-creator/SKILL.md +201 -201
- package/skills/watchdog/SKILL.md +168 -168
- package/skills/worktree/SKILL.md +140 -140
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { afterEach, beforeEach, describe, it } from 'node:test';
|
|
6
|
+
import {
|
|
7
|
+
getAllAnalytics,
|
|
8
|
+
getModelDistribution,
|
|
9
|
+
getSessionOverview,
|
|
10
|
+
getSessionTrend,
|
|
11
|
+
getSkillChains,
|
|
12
|
+
getSkillFrequency,
|
|
13
|
+
getToolDistribution,
|
|
14
|
+
} from '../analytics.js';
|
|
15
|
+
|
|
16
|
+
// ─── Test Helpers ───
|
|
17
|
+
|
|
18
|
+
function today() {
|
|
19
|
+
return new Date().toISOString().slice(0, 10);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function daysAgo(n) {
|
|
23
|
+
const d = new Date();
|
|
24
|
+
d.setDate(d.getDate() - n);
|
|
25
|
+
return d.toISOString().slice(0, 10);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function sessionEntry(overrides = {}) {
|
|
29
|
+
return {
|
|
30
|
+
id: `s-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
|
|
31
|
+
date: today(),
|
|
32
|
+
duration_min: 15,
|
|
33
|
+
tool_calls: 42,
|
|
34
|
+
tool_distribution: { Read: 15, Edit: 12, Bash: 8, Grep: 7 },
|
|
35
|
+
skill_invocations: 5,
|
|
36
|
+
skills_used: ['cook', 'plan', 'scout'],
|
|
37
|
+
primary_skill: 'cook',
|
|
38
|
+
models_used: { opus: 1, sonnet: 2 },
|
|
39
|
+
skill_durations: { cook: 3000, plan: 1500 },
|
|
40
|
+
...overrides,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function chainEntry(chain, session = 'test-session') {
|
|
45
|
+
return { session, chain, depth: Array.isArray(chain) ? chain.length : 0 };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function setupMetrics(tmpDir, sessions = [], chains = [], skillTotals = null) {
|
|
49
|
+
const metricsDir = path.join(tmpDir, '.rune', 'metrics');
|
|
50
|
+
await mkdir(metricsDir, { recursive: true });
|
|
51
|
+
|
|
52
|
+
if (sessions.length > 0) {
|
|
53
|
+
const lines = `${sessions.map((s) => JSON.stringify(s)).join('\n')}\n`;
|
|
54
|
+
await writeFile(path.join(metricsDir, 'sessions.jsonl'), lines);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (chains.length > 0) {
|
|
58
|
+
const lines = `${chains.map((c) => JSON.stringify(c)).join('\n')}\n`;
|
|
59
|
+
await writeFile(path.join(metricsDir, 'chains.jsonl'), lines);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (skillTotals) {
|
|
63
|
+
await writeFile(
|
|
64
|
+
path.join(metricsDir, 'skills.json'),
|
|
65
|
+
JSON.stringify({ version: 1, updated: new Date().toISOString(), skills: skillTotals }),
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ─── Tests ───
|
|
71
|
+
|
|
72
|
+
describe('analytics — empty state', () => {
|
|
73
|
+
let tmpDir;
|
|
74
|
+
beforeEach(async () => {
|
|
75
|
+
tmpDir = await mkdtemp(path.join(os.tmpdir(), 'rune-analytics-test-'));
|
|
76
|
+
});
|
|
77
|
+
afterEach(async () => {
|
|
78
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('returns zeros when no .rune/metrics/ exists', async () => {
|
|
82
|
+
const overview = await getSessionOverview(tmpDir, 30);
|
|
83
|
+
assert.equal(overview.total_sessions, 0);
|
|
84
|
+
assert.equal(overview.avg_duration_min, 0);
|
|
85
|
+
assert.equal(overview.total_tool_calls, 0);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('returns empty arrays for frequency/distribution', async () => {
|
|
89
|
+
const skills = await getSkillFrequency(tmpDir, 30);
|
|
90
|
+
assert.deepEqual(skills, []);
|
|
91
|
+
const models = await getModelDistribution(tmpDir, 30);
|
|
92
|
+
assert.deepEqual(models, []);
|
|
93
|
+
const tools = await getToolDistribution(tmpDir, 30);
|
|
94
|
+
assert.deepEqual(tools, []);
|
|
95
|
+
const chains = await getSkillChains(tmpDir, 30);
|
|
96
|
+
assert.deepEqual(chains, []);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('returns empty trend', async () => {
|
|
100
|
+
const trend = await getSessionTrend(tmpDir, 30);
|
|
101
|
+
assert.deepEqual(trend, []);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('getAllAnalytics works with empty data', async () => {
|
|
105
|
+
const data = await getAllAnalytics(tmpDir, 30);
|
|
106
|
+
assert.equal(data.overview.total_sessions, 0);
|
|
107
|
+
assert.ok(data.generated);
|
|
108
|
+
assert.equal(data.days, 30);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe('analytics — with data', () => {
|
|
113
|
+
let tmpDir;
|
|
114
|
+
beforeEach(async () => {
|
|
115
|
+
tmpDir = await mkdtemp(path.join(os.tmpdir(), 'rune-analytics-test-'));
|
|
116
|
+
});
|
|
117
|
+
afterEach(async () => {
|
|
118
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('getSessionOverview aggregates correctly', async () => {
|
|
122
|
+
await setupMetrics(tmpDir, [
|
|
123
|
+
sessionEntry({ duration_min: 10, tool_calls: 30, skill_invocations: 3 }),
|
|
124
|
+
sessionEntry({ duration_min: 20, tool_calls: 50, skill_invocations: 7 }),
|
|
125
|
+
]);
|
|
126
|
+
const overview = await getSessionOverview(tmpDir, 30);
|
|
127
|
+
assert.equal(overview.total_sessions, 2);
|
|
128
|
+
assert.equal(overview.avg_duration_min, 15);
|
|
129
|
+
assert.equal(overview.total_tool_calls, 80);
|
|
130
|
+
assert.equal(overview.total_skill_invocations, 10);
|
|
131
|
+
assert.equal(overview.active_days, 1);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('getSkillFrequency counts sessions per skill', async () => {
|
|
135
|
+
await setupMetrics(tmpDir, [
|
|
136
|
+
sessionEntry({ skills_used: ['cook', 'plan'] }),
|
|
137
|
+
sessionEntry({ skills_used: ['cook', 'debug'] }),
|
|
138
|
+
sessionEntry({ skills_used: ['plan'] }),
|
|
139
|
+
]);
|
|
140
|
+
const freq = await getSkillFrequency(tmpDir, 30);
|
|
141
|
+
assert.equal(freq[0].skill, 'cook');
|
|
142
|
+
assert.equal(freq[0].sessions_count, 2);
|
|
143
|
+
assert.equal(freq[1].skill, 'plan');
|
|
144
|
+
assert.equal(freq[1].sessions_count, 2);
|
|
145
|
+
assert.equal(freq[2].skill, 'debug');
|
|
146
|
+
assert.equal(freq[2].sessions_count, 1);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('getModelDistribution aggregates model counts', async () => {
|
|
150
|
+
await setupMetrics(tmpDir, [
|
|
151
|
+
sessionEntry({ models_used: { opus: 2, sonnet: 3 } }),
|
|
152
|
+
sessionEntry({ models_used: { sonnet: 1, haiku: 5 } }),
|
|
153
|
+
]);
|
|
154
|
+
const models = await getModelDistribution(tmpDir, 30);
|
|
155
|
+
const map = Object.fromEntries(models.map((m) => [m.model, m.skill_count]));
|
|
156
|
+
assert.equal(map.sonnet, 4);
|
|
157
|
+
assert.equal(map.haiku, 5);
|
|
158
|
+
assert.equal(map.opus, 2);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('getSessionTrend groups by date', async () => {
|
|
162
|
+
await setupMetrics(tmpDir, [
|
|
163
|
+
sessionEntry({ date: daysAgo(1), skill_invocations: 3 }),
|
|
164
|
+
sessionEntry({ date: daysAgo(1), skill_invocations: 5 }),
|
|
165
|
+
sessionEntry({ date: today(), skill_invocations: 2 }),
|
|
166
|
+
]);
|
|
167
|
+
const trend = await getSessionTrend(tmpDir, 30);
|
|
168
|
+
assert.equal(trend.length, 2);
|
|
169
|
+
assert.equal(trend[0].sessions, 2);
|
|
170
|
+
assert.equal(trend[0].skill_invocations, 8);
|
|
171
|
+
assert.equal(trend[1].sessions, 1);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('getSkillChains counts pattern frequency', async () => {
|
|
175
|
+
await setupMetrics(
|
|
176
|
+
tmpDir,
|
|
177
|
+
[sessionEntry({ id: 's1' }), sessionEntry({ id: 's2' })],
|
|
178
|
+
[
|
|
179
|
+
chainEntry(['cook', 'plan', 'scout'], 's1'),
|
|
180
|
+
chainEntry(['cook', 'plan', 'scout'], 's2'),
|
|
181
|
+
chainEntry(['debug', 'fix'], 's1'),
|
|
182
|
+
],
|
|
183
|
+
);
|
|
184
|
+
const chains = await getSkillChains(tmpDir, 30);
|
|
185
|
+
assert.equal(chains[0].chain, 'cook → plan → scout');
|
|
186
|
+
assert.equal(chains[0].count, 2);
|
|
187
|
+
assert.equal(chains[1].chain, 'debug → fix');
|
|
188
|
+
assert.equal(chains[1].count, 1);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('getToolDistribution aggregates tool counts', async () => {
|
|
192
|
+
await setupMetrics(tmpDir, [
|
|
193
|
+
sessionEntry({ tool_distribution: { Read: 10, Edit: 5 } }),
|
|
194
|
+
sessionEntry({ tool_distribution: { Read: 3, Bash: 7 } }),
|
|
195
|
+
]);
|
|
196
|
+
const tools = await getToolDistribution(tmpDir, 30);
|
|
197
|
+
const map = Object.fromEntries(tools.map((t) => [t.tool, t.count]));
|
|
198
|
+
assert.equal(map.Read, 13);
|
|
199
|
+
assert.equal(map.Edit, 5);
|
|
200
|
+
assert.equal(map.Bash, 7);
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
describe('analytics — date filtering', () => {
|
|
205
|
+
let tmpDir;
|
|
206
|
+
beforeEach(async () => {
|
|
207
|
+
tmpDir = await mkdtemp(path.join(os.tmpdir(), 'rune-analytics-test-'));
|
|
208
|
+
});
|
|
209
|
+
afterEach(async () => {
|
|
210
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('filters sessions by day range', async () => {
|
|
214
|
+
await setupMetrics(tmpDir, [
|
|
215
|
+
sessionEntry({ date: today() }),
|
|
216
|
+
sessionEntry({ date: daysAgo(5) }),
|
|
217
|
+
sessionEntry({ date: daysAgo(15) }),
|
|
218
|
+
sessionEntry({ date: daysAgo(45) }),
|
|
219
|
+
]);
|
|
220
|
+
const all = await getSessionOverview(tmpDir, 0);
|
|
221
|
+
assert.equal(all.total_sessions, 4);
|
|
222
|
+
|
|
223
|
+
const week = await getSessionOverview(tmpDir, 7);
|
|
224
|
+
assert.equal(week.total_sessions, 2);
|
|
225
|
+
|
|
226
|
+
const month = await getSessionOverview(tmpDir, 30);
|
|
227
|
+
assert.equal(month.total_sessions, 3);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('days=0 returns all sessions', async () => {
|
|
231
|
+
await setupMetrics(tmpDir, [sessionEntry({ date: daysAgo(100) }), sessionEntry({ date: today() })]);
|
|
232
|
+
const all = await getSessionOverview(tmpDir, 0);
|
|
233
|
+
assert.equal(all.total_sessions, 2);
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
describe('analytics — edge cases', () => {
|
|
238
|
+
let tmpDir;
|
|
239
|
+
beforeEach(async () => {
|
|
240
|
+
tmpDir = await mkdtemp(path.join(os.tmpdir(), 'rune-analytics-test-'));
|
|
241
|
+
});
|
|
242
|
+
afterEach(async () => {
|
|
243
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('handles malformed JSONL lines gracefully', async () => {
|
|
247
|
+
const metricsDir = path.join(tmpDir, '.rune', 'metrics');
|
|
248
|
+
await mkdir(metricsDir, { recursive: true });
|
|
249
|
+
await writeFile(
|
|
250
|
+
path.join(metricsDir, 'sessions.jsonl'),
|
|
251
|
+
`${JSON.stringify(sessionEntry())}\nNOT VALID JSON\n${JSON.stringify(sessionEntry())}\n`,
|
|
252
|
+
);
|
|
253
|
+
const overview = await getSessionOverview(tmpDir, 30);
|
|
254
|
+
assert.equal(overview.total_sessions, 2); // skips malformed line
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it('handles empty JSONL files', async () => {
|
|
258
|
+
const metricsDir = path.join(tmpDir, '.rune', 'metrics');
|
|
259
|
+
await mkdir(metricsDir, { recursive: true });
|
|
260
|
+
await writeFile(path.join(metricsDir, 'sessions.jsonl'), '');
|
|
261
|
+
const overview = await getSessionOverview(tmpDir, 30);
|
|
262
|
+
assert.equal(overview.total_sessions, 0);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('excludes orphaned chains (no matching session)', async () => {
|
|
266
|
+
await setupMetrics(
|
|
267
|
+
tmpDir,
|
|
268
|
+
[sessionEntry({ id: 's1' })],
|
|
269
|
+
[
|
|
270
|
+
chainEntry(['cook', 'plan'], 's1'),
|
|
271
|
+
chainEntry(['debug', 'fix'], 'orphaned-session'), // no matching session
|
|
272
|
+
chainEntry(null, 's1'), // malformed chain
|
|
273
|
+
],
|
|
274
|
+
);
|
|
275
|
+
const chains = await getSkillChains(tmpDir, 30);
|
|
276
|
+
assert.equal(chains.length, 1);
|
|
277
|
+
assert.equal(chains[0].chain, 'cook → plan');
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it('handles sessions without optional fields', async () => {
|
|
281
|
+
await setupMetrics(tmpDir, [
|
|
282
|
+
{ id: 's1', date: today(), duration_min: 5, tool_calls: 10 },
|
|
283
|
+
// Missing: skill_invocations, skills_used, primary_skill, models_used, skill_durations
|
|
284
|
+
]);
|
|
285
|
+
const overview = await getSessionOverview(tmpDir, 30);
|
|
286
|
+
assert.equal(overview.total_sessions, 1);
|
|
287
|
+
const models = await getModelDistribution(tmpDir, 30);
|
|
288
|
+
assert.deepEqual(models, []);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it('handles corrupted skills.json', async () => {
|
|
292
|
+
const metricsDir = path.join(tmpDir, '.rune', 'metrics');
|
|
293
|
+
await mkdir(metricsDir, { recursive: true });
|
|
294
|
+
await writeFile(path.join(metricsDir, 'skills.json'), 'NOT JSON');
|
|
295
|
+
// Should not throw
|
|
296
|
+
const data = await getAllAnalytics(tmpDir, 30);
|
|
297
|
+
assert.ok(data);
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
describe('analytics — dashboard HTML generation', () => {
|
|
302
|
+
it('generates valid HTML from analytics data', async () => {
|
|
303
|
+
const { generateDashboardHTML } = await import('../dashboard.js');
|
|
304
|
+
|
|
305
|
+
const data = {
|
|
306
|
+
overview: {
|
|
307
|
+
total_sessions: 5,
|
|
308
|
+
avg_duration_min: 12,
|
|
309
|
+
total_tool_calls: 200,
|
|
310
|
+
total_skill_invocations: 30,
|
|
311
|
+
active_days: 3,
|
|
312
|
+
},
|
|
313
|
+
skillFrequency: [
|
|
314
|
+
{ skill: 'cook', sessions_count: 4 },
|
|
315
|
+
{ skill: 'plan', sessions_count: 2 },
|
|
316
|
+
],
|
|
317
|
+
modelDistribution: [
|
|
318
|
+
{ model: 'sonnet', skill_count: 8 },
|
|
319
|
+
{ model: 'opus', skill_count: 3 },
|
|
320
|
+
],
|
|
321
|
+
sessionTrend: [{ date: '2026-03-30', sessions: 2, duration_min: 20, skill_invocations: 10, tool_calls: 80 }],
|
|
322
|
+
skillChains: [{ chain: 'cook → plan → scout', count: 3 }],
|
|
323
|
+
toolDistribution: [
|
|
324
|
+
{ tool: 'Read', count: 50 },
|
|
325
|
+
{ tool: 'Edit', count: 30 },
|
|
326
|
+
],
|
|
327
|
+
skillHeatmap: { heatmap: [], dates: [], maxCount: 1 },
|
|
328
|
+
sessionTimeline: [],
|
|
329
|
+
skillMesh: { nodes: [], edges: [], maxCount: 1 },
|
|
330
|
+
generated: new Date().toISOString(),
|
|
331
|
+
days: 30,
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
const html = generateDashboardHTML(data);
|
|
335
|
+
assert.ok(html.includes('<!DOCTYPE html>'));
|
|
336
|
+
assert.ok(html.includes('Rune Analytics'));
|
|
337
|
+
assert.ok(html.includes('Skill Mesh'));
|
|
338
|
+
assert.ok(html.includes('Models'));
|
|
339
|
+
assert.ok(html.includes('Workflow Chains'));
|
|
340
|
+
assert.ok(html.includes('Recent Sessions'));
|
|
341
|
+
assert.ok(html.includes('cook'));
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
it('generates empty state HTML when no data', async () => {
|
|
345
|
+
const { generateDashboardHTML } = await import('../dashboard.js');
|
|
346
|
+
|
|
347
|
+
const data = {
|
|
348
|
+
overview: {
|
|
349
|
+
total_sessions: 0,
|
|
350
|
+
avg_duration_min: 0,
|
|
351
|
+
total_tool_calls: 0,
|
|
352
|
+
total_skill_invocations: 0,
|
|
353
|
+
active_days: 0,
|
|
354
|
+
},
|
|
355
|
+
skillFrequency: [],
|
|
356
|
+
modelDistribution: [],
|
|
357
|
+
sessionTrend: [],
|
|
358
|
+
skillChains: [],
|
|
359
|
+
toolDistribution: [],
|
|
360
|
+
skillHeatmap: { heatmap: [], dates: [], maxCount: 1 },
|
|
361
|
+
sessionTimeline: [],
|
|
362
|
+
skillMesh: { nodes: [], edges: [], maxCount: 1 },
|
|
363
|
+
generated: new Date().toISOString(),
|
|
364
|
+
days: 30,
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
const html = generateDashboardHTML(data);
|
|
368
|
+
assert.ok(html.includes('No analytics data yet'));
|
|
369
|
+
});
|
|
370
|
+
});
|
|
@@ -73,7 +73,7 @@ export default {
|
|
|
73
73
|
name: 'Rune',
|
|
74
74
|
kind: 'skills',
|
|
75
75
|
description:
|
|
76
|
-
'
|
|
76
|
+
'62-skill mesh for AI coding assistants. Routes all code tasks through specialized skills. 215+ connections, 14 extension packs.',
|
|
77
77
|
version: pluginJson.version || '0.0.0',
|
|
78
78
|
skills: ['./skills'],
|
|
79
79
|
configSchema: {
|
|
@@ -116,7 +116,7 @@ export default {
|
|
|
116
116
|
|
|
117
117
|
> Less skills. Deeper connections.
|
|
118
118
|
|
|
119
|
-
**${skills.length}-skill mesh** for AI coding assistants — 5-layer architecture,
|
|
119
|
+
**${skills.length}-skill mesh** for AI coding assistants — 5-layer architecture, 215+ connections, 14 extension packs.
|
|
120
120
|
|
|
121
121
|
## Install
|
|
122
122
|
|