selftune 0.2.21 → 0.2.23

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 (108) hide show
  1. package/README.md +15 -8
  2. package/apps/local-dashboard/dist/assets/index-CwOtTrUS.css +1 -0
  3. package/apps/local-dashboard/dist/assets/index-f1HQpbeH.js +59 -0
  4. package/apps/local-dashboard/dist/assets/vendor-ui-jVSaIZey.js +12 -0
  5. package/apps/local-dashboard/dist/index.html +3 -3
  6. package/cli/selftune/adapters/cline/hook.ts +167 -0
  7. package/cli/selftune/adapters/cline/install.ts +197 -0
  8. package/cli/selftune/adapters/codex/hook.ts +296 -0
  9. package/cli/selftune/adapters/codex/install.ts +289 -0
  10. package/cli/selftune/adapters/opencode/hook.ts +222 -0
  11. package/cli/selftune/adapters/opencode/install.ts +543 -0
  12. package/cli/selftune/adapters/pi/hook.ts +273 -0
  13. package/cli/selftune/adapters/pi/install.ts +207 -0
  14. package/cli/selftune/constants.ts +10 -1
  15. package/cli/selftune/dashboard-contract.ts +14 -0
  16. package/cli/selftune/evolution/engines/judge-engine.ts +96 -0
  17. package/cli/selftune/evolution/engines/replay-engine.ts +158 -0
  18. package/cli/selftune/evolution/evidence.ts +2 -6
  19. package/cli/selftune/evolution/evolve-body.ts +73 -20
  20. package/cli/selftune/evolution/validate-body.ts +78 -42
  21. package/cli/selftune/evolution/validate-routing.ts +45 -104
  22. package/cli/selftune/hooks/auto-activate.ts +43 -37
  23. package/cli/selftune/hooks/skill-eval.ts +2 -1
  24. package/cli/selftune/hooks-shared/git-metadata.ts +149 -0
  25. package/cli/selftune/hooks-shared/hook-output.ts +105 -0
  26. package/cli/selftune/hooks-shared/normalize.ts +196 -0
  27. package/cli/selftune/hooks-shared/session-state.ts +76 -0
  28. package/cli/selftune/hooks-shared/skill-paths.ts +50 -0
  29. package/cli/selftune/hooks-shared/stdin-dispatch.ts +59 -0
  30. package/cli/selftune/hooks-shared/types.ts +91 -0
  31. package/cli/selftune/index.ts +76 -6
  32. package/cli/selftune/ingestors/pi-ingest.ts +726 -0
  33. package/cli/selftune/init.ts +11 -1
  34. package/cli/selftune/localdb/direct-write.ts +85 -0
  35. package/cli/selftune/localdb/materialize.ts +6 -7
  36. package/cli/selftune/localdb/queries.ts +126 -0
  37. package/cli/selftune/localdb/schema.ts +38 -0
  38. package/cli/selftune/observability.ts +8 -1
  39. package/cli/selftune/orchestrate.ts +43 -0
  40. package/cli/selftune/registry/client.ts +74 -0
  41. package/cli/selftune/registry/history.ts +54 -0
  42. package/cli/selftune/registry/index.ts +90 -0
  43. package/cli/selftune/registry/install.ts +141 -0
  44. package/cli/selftune/registry/list.ts +44 -0
  45. package/cli/selftune/registry/push.ts +171 -0
  46. package/cli/selftune/registry/rollback.ts +49 -0
  47. package/cli/selftune/registry/status.ts +62 -0
  48. package/cli/selftune/registry/sync.ts +125 -0
  49. package/cli/selftune/repair/skill-usage.ts +4 -1
  50. package/cli/selftune/status.ts +31 -0
  51. package/cli/selftune/sync.ts +127 -23
  52. package/cli/selftune/types.ts +2 -1
  53. package/cli/selftune/utils/jsonl.ts +1 -30
  54. package/cli/selftune/utils/llm-call.ts +99 -34
  55. package/cli/selftune/utils/skill-discovery.ts +22 -0
  56. package/node_modules/@selftune/telemetry-contract/fixtures/evidence-only-push.ts +1 -1
  57. package/node_modules/@selftune/telemetry-contract/fixtures/golden.test.ts +0 -1
  58. package/node_modules/@selftune/telemetry-contract/fixtures/partial-push-unresolved-parents.ts +1 -1
  59. package/node_modules/@selftune/telemetry-contract/package.json +1 -1
  60. package/node_modules/@selftune/telemetry-contract/src/index.ts +1 -0
  61. package/node_modules/@selftune/telemetry-contract/src/schemas.ts +22 -4
  62. package/node_modules/@selftune/telemetry-contract/src/types.ts +1 -12
  63. package/node_modules/@selftune/telemetry-contract/tests/compatibility.test.ts +0 -1
  64. package/package.json +1 -1
  65. package/packages/telemetry-contract/fixtures/evidence-only-push.ts +1 -1
  66. package/packages/telemetry-contract/fixtures/golden.test.ts +0 -1
  67. package/packages/telemetry-contract/fixtures/partial-push-unresolved-parents.ts +1 -1
  68. package/packages/telemetry-contract/package.json +1 -1
  69. package/packages/telemetry-contract/src/index.ts +1 -0
  70. package/packages/telemetry-contract/src/schemas.ts +22 -4
  71. package/packages/telemetry-contract/src/types.ts +1 -12
  72. package/packages/telemetry-contract/tests/compatibility.test.ts +0 -1
  73. package/packages/ui/AGENTS.md +16 -0
  74. package/packages/ui/README.md +1 -1
  75. package/packages/ui/package.json +1 -1
  76. package/packages/ui/src/components/ActivityTimeline.tsx +152 -168
  77. package/packages/ui/src/components/AnalyticsCharts.tsx +344 -0
  78. package/packages/ui/src/components/EvidenceViewer.tsx +153 -443
  79. package/packages/ui/src/components/EvolutionTimeline.tsx +34 -87
  80. package/packages/ui/src/components/InfoTip.tsx +1 -2
  81. package/packages/ui/src/components/InvocationsPanel.tsx +413 -0
  82. package/packages/ui/src/components/JobHistoryTimeline.tsx +156 -0
  83. package/packages/ui/src/components/OrchestrateRunsPanel.tsx +18 -36
  84. package/packages/ui/src/components/OverviewPanels.tsx +652 -0
  85. package/packages/ui/src/components/PipelineStatusBar.tsx +65 -0
  86. package/packages/ui/src/components/SkillReportGuide.tsx +215 -0
  87. package/packages/ui/src/components/SkillReportPanels.tsx +919 -0
  88. package/packages/ui/src/components/SkillsLibrary.tsx +437 -0
  89. package/packages/ui/src/components/index.ts +56 -1
  90. package/packages/ui/src/components/section-cards.tsx +18 -35
  91. package/packages/ui/src/components/skill-health-grid.tsx +47 -37
  92. package/packages/ui/src/lib/constants.tsx +0 -1
  93. package/packages/ui/src/primitives/card.tsx +1 -1
  94. package/packages/ui/src/primitives/checkbox.tsx +1 -1
  95. package/packages/ui/src/primitives/dropdown-menu.tsx +2 -2
  96. package/packages/ui/src/primitives/select.tsx +2 -2
  97. package/packages/ui/src/types.ts +172 -4
  98. package/skill/SKILL.md +26 -2
  99. package/skill/Workflows/Ingest.md +60 -2
  100. package/skill/Workflows/Initialize.md +54 -9
  101. package/skill/Workflows/PlatformHooks.md +109 -0
  102. package/skill/Workflows/Registry.md +99 -0
  103. package/skill/Workflows/Sync.md +3 -1
  104. package/apps/local-dashboard/dist/assets/index-D8O-RG1I.js +0 -60
  105. package/apps/local-dashboard/dist/assets/index-_EcLywDg.css +0 -1
  106. package/apps/local-dashboard/dist/assets/vendor-ui-CGEmUayx.js +0 -12
  107. package/cli/selftune/utils/html.ts +0 -27
  108. package/packages/ui/src/components/RecentActivityFeed.tsx +0 -117
@@ -0,0 +1,437 @@
1
+ import { Badge } from "../primitives/badge";
2
+ import { Card } from "../primitives/card";
3
+ import { deriveStatus, formatRate, sortByPassRateAndChecks, timeAgo } from "../lib/format";
4
+ import type { SkillHealthStatus } from "../types";
5
+ import {
6
+ AlertCircleIcon,
7
+ ArrowUpDownIcon,
8
+ BrainCircuitIcon,
9
+ CircleDotIcon,
10
+ RefreshCwIcon,
11
+ } from "lucide-react";
12
+ import { type ReactNode } from "react";
13
+
14
+ /* ── Types ─────────────────────────────────────────────────── */
15
+
16
+ export type FilterTab = "ALL" | "HEALTHY" | "WARNING" | "CRITICAL" | "UNGRADED";
17
+
18
+ export interface DerivedSkill {
19
+ name: string;
20
+ scope: string | null;
21
+ platforms: string[];
22
+ passRate: number | null;
23
+ checks: number;
24
+ status: SkillHealthStatus;
25
+ uniqueSessions: number;
26
+ triggeredCount: number;
27
+ lastSeen: string | null;
28
+ }
29
+
30
+ export interface SkillHeroCardProps {
31
+ skillName: string;
32
+ skillScope: string | null;
33
+ platforms?: string[];
34
+ passRate: number | null;
35
+ totalChecks: number;
36
+ uniqueSessions: number;
37
+ status: SkillHealthStatus;
38
+ latestEvolutionTimestamp: string | null;
39
+ /** Render prop for action buttons (link component varies by framework) */
40
+ renderActions?: (skillName: string) => ReactNode;
41
+ }
42
+
43
+ export interface LibraryHealthCardProps {
44
+ aggregatePassRate: number | null;
45
+ gradedCount: number;
46
+ }
47
+
48
+ export interface PendingProposalsCardProps {
49
+ proposals: Array<{
50
+ id: string;
51
+ skillName: string | null;
52
+ action: string;
53
+ }>;
54
+ }
55
+
56
+ export interface SkillCardProps {
57
+ skill: DerivedSkill;
58
+ /** Render prop for action buttons (link component varies by framework) */
59
+ renderActions?: (skillName: string) => ReactNode;
60
+ }
61
+
62
+ export interface SkillFilterTabsProps {
63
+ filter: FilterTab;
64
+ onFilterChange: (tab: FilterTab) => void;
65
+ counts: Record<FilterTab, number>;
66
+ sortDesc: boolean;
67
+ onSortToggle: () => void;
68
+ }
69
+
70
+ /* ── Constants ─────────────────────────────────────────────── */
71
+
72
+ const FILTER_TABS: { key: FilterTab; label: string }[] = [
73
+ { key: "ALL", label: "All Skills" },
74
+ { key: "HEALTHY", label: "Healthy" },
75
+ { key: "WARNING", label: "Warning" },
76
+ { key: "CRITICAL", label: "Critical" },
77
+ { key: "UNGRADED", label: "Ungraded" },
78
+ ];
79
+
80
+ const STATUS_STYLE: Record<SkillHealthStatus, { text: string; bg: string; label: string }> = {
81
+ HEALTHY: { text: "text-primary", bg: "bg-primary", label: "Deployed" },
82
+ WARNING: { text: "text-primary-accent", bg: "bg-primary-accent", label: "Needs Attention" },
83
+ CRITICAL: { text: "text-destructive", bg: "bg-destructive", label: "Critical" },
84
+ UNGRADED: { text: "text-muted-foreground", bg: "bg-muted-foreground", label: "Ungraded" },
85
+ UNKNOWN: { text: "text-muted-foreground", bg: "bg-muted-foreground", label: "Unknown" },
86
+ };
87
+
88
+ function getPassRatePercent(passRate: number | null): number {
89
+ return passRate !== null ? Math.round(passRate * 100) : 0;
90
+ }
91
+
92
+ /* ── Loading Skeleton ──────────────────────────────────────── */
93
+
94
+ export function SkillsLibrarySkeleton() {
95
+ return (
96
+ <div className="flex flex-1 flex-col gap-8 p-6 md:p-10 animate-in fade-in duration-500">
97
+ <div className="space-y-2">
98
+ <div className="h-12 w-64 rounded-lg bg-muted animate-pulse" />
99
+ <div className="h-5 w-96 rounded-lg bg-muted animate-pulse" />
100
+ </div>
101
+ <div className="grid grid-cols-12 gap-6">
102
+ <div className="col-span-12 lg:col-span-8 h-72 rounded-xl bg-muted animate-pulse" />
103
+ <div className="col-span-12 lg:col-span-4 flex flex-col gap-6">
104
+ <div className="h-32 rounded-xl bg-muted animate-pulse" />
105
+ <div className="h-32 rounded-xl bg-muted animate-pulse" />
106
+ </div>
107
+ </div>
108
+ <div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6">
109
+ {Array.from({ length: 6 }).map((_, i) => (
110
+ <div key={`skel-${i}`} className="h-52 rounded-xl bg-muted animate-pulse" />
111
+ ))}
112
+ </div>
113
+ </div>
114
+ );
115
+ }
116
+
117
+ /* ── Hero Card ─────────────────────────────────────────────── */
118
+
119
+ export function SkillHeroCard({
120
+ skillName,
121
+ skillScope,
122
+ platforms,
123
+ passRate,
124
+ totalChecks,
125
+ uniqueSessions,
126
+ status,
127
+ latestEvolutionTimestamp,
128
+ renderActions,
129
+ }: SkillHeroCardProps) {
130
+ const passRatePct = getPassRatePercent(totalChecks > 0 ? passRate : null);
131
+ const style = STATUS_STYLE[status];
132
+
133
+ return (
134
+ <Card className="col-span-12 lg:col-span-8 rounded-3xl border border-border/15 p-8 relative overflow-hidden flex flex-col">
135
+ {/* Top progress bar */}
136
+ <div className="absolute top-0 left-0 w-full h-1 bg-input">
137
+ <div
138
+ className="h-full bg-primary shadow-[0_0_15px_rgba(79,242,255,0.6)] transition-all duration-700"
139
+ style={{ width: `${passRatePct}%` }}
140
+ />
141
+ </div>
142
+
143
+ {/* Header row */}
144
+ <div className="flex justify-between items-start mb-6">
145
+ <div>
146
+ <div className="flex items-center gap-3 mb-2">
147
+ <span className="px-2 py-0.5 rounded text-[10px] font-bold bg-primary/10 text-primary uppercase tracking-widest">
148
+ {status === "HEALTHY" ? "Deployed" : "Evolving"}
149
+ </span>
150
+ {platforms && platforms.length > 0 ? (
151
+ <span className="flex items-center gap-1">
152
+ {platforms.map((p) => (
153
+ <span
154
+ key={p}
155
+ className="px-1.5 py-0.5 rounded bg-muted text-muted-foreground font-mono text-[10px]"
156
+ >
157
+ {p}
158
+ </span>
159
+ ))}
160
+ </span>
161
+ ) : (
162
+ <span className="text-muted-foreground font-mono text-xs">
163
+ {skillScope ?? "global"} scope
164
+ </span>
165
+ )}
166
+ </div>
167
+ <h2 className="font-headline text-3xl font-bold text-foreground">{skillName}</h2>
168
+ </div>
169
+ <div className="text-right">
170
+ <span className="text-4xl font-headline font-light text-primary">
171
+ {formatRate(totalChecks > 0 ? passRate : null)}
172
+ </span>
173
+ <p className="text-[10px] text-muted-foreground uppercase tracking-widest mt-1">
174
+ {latestEvolutionTimestamp
175
+ ? `Evolved ${timeAgo(latestEvolutionTimestamp)}`
176
+ : "Pass Rate"}
177
+ </p>
178
+ </div>
179
+ </div>
180
+
181
+ {/* Stats grid */}
182
+ <div className="grid grid-cols-3 gap-8 mb-8">
183
+ <div className="bg-muted p-4 rounded-2xl border border-border/15">
184
+ <p className="text-[10px] text-muted-foreground uppercase tracking-widest mb-1">
185
+ Total Checks
186
+ </p>
187
+ <p className="text-xl font-bold font-headline tabular-nums">
188
+ {totalChecks.toLocaleString()}
189
+ </p>
190
+ </div>
191
+ <div className="bg-muted p-4 rounded-2xl border border-border/15">
192
+ <p className="text-[10px] text-muted-foreground uppercase tracking-widest mb-1">
193
+ Pass Rate
194
+ </p>
195
+ <p className={`text-xl font-bold font-headline tabular-nums ${style.text}`}>
196
+ {formatRate(totalChecks > 0 ? passRate : null)}
197
+ </p>
198
+ </div>
199
+ <div className="bg-muted p-4 rounded-2xl border border-border/15">
200
+ <p className="text-[10px] text-muted-foreground uppercase tracking-widest mb-1">
201
+ Unique Sessions
202
+ </p>
203
+ <p className="text-xl font-bold font-headline tabular-nums">
204
+ {uniqueSessions.toLocaleString()}
205
+ </p>
206
+ </div>
207
+ </div>
208
+
209
+ {/* Action buttons */}
210
+ <div className="flex justify-end gap-4">{renderActions?.(skillName)}</div>
211
+ </Card>
212
+ );
213
+ }
214
+
215
+ /* ── Library Health Card ───────────────────────────────────── */
216
+
217
+ export function LibraryHealthCard({ aggregatePassRate, gradedCount }: LibraryHealthCardProps) {
218
+ return (
219
+ <Card className="rounded-3xl border border-border/15 p-6 flex flex-col justify-center">
220
+ <p className="text-[10px] text-muted-foreground uppercase tracking-widest mb-4">
221
+ Library Health
222
+ </p>
223
+ <div className="flex items-end gap-2 mb-2">
224
+ <span className="text-5xl font-headline font-bold tabular-nums">
225
+ {aggregatePassRate !== null ? `${Math.round(aggregatePassRate * 100)}%` : "--"}
226
+ </span>
227
+ </div>
228
+ <p className="text-sm text-muted-foreground">
229
+ Aggregate pass rate across {gradedCount} graded skill{gradedCount !== 1 ? "s" : ""}.
230
+ </p>
231
+ </Card>
232
+ );
233
+ }
234
+
235
+ /* ── Pending Proposals Card ────────────────────────────────── */
236
+
237
+ export function PendingProposalsCard({ proposals }: PendingProposalsCardProps) {
238
+ if (proposals.length === 0) {
239
+ return (
240
+ <Card className="rounded-3xl p-6 flex flex-col gap-3 border border-border/15 border-l-4 border-l-primary/40">
241
+ <p className="text-[10px] text-muted-foreground uppercase tracking-widest mb-2">
242
+ Pending Proposals
243
+ </p>
244
+ <h3 className="font-headline font-bold text-lg">No proposals pending</h3>
245
+ </Card>
246
+ );
247
+ }
248
+
249
+ return (
250
+ <Card className="rounded-3xl p-6 flex flex-col gap-3 border border-border/15 border-l-4 border-l-primary/40">
251
+ <p className="text-[10px] text-muted-foreground uppercase tracking-widest mb-2">
252
+ Pending Proposals
253
+ </p>
254
+ <h3 className="font-headline font-bold text-lg mb-4">
255
+ Awaiting Review
256
+ <Badge
257
+ variant="secondary"
258
+ className="ml-2 h-5 px-2 text-[10px] bg-primary/15 text-primary border-none align-middle"
259
+ >
260
+ {proposals.length}
261
+ </Badge>
262
+ </h3>
263
+ <div className="space-y-3 max-h-32 overflow-y-auto themed-scroll">
264
+ {proposals.map((p) => (
265
+ <div key={p.id} className="flex items-center justify-between p-3 bg-muted rounded-xl">
266
+ <span className="text-sm truncate">{p.skillName ?? "Unknown"}</span>
267
+ <span className="text-xs text-muted-foreground shrink-0">{p.action}</span>
268
+ </div>
269
+ ))}
270
+ </div>
271
+ </Card>
272
+ );
273
+ }
274
+
275
+ /* ── Skill Card ────────────────────────────────────────────── */
276
+
277
+ export function SkillCardItem({ skill, renderActions }: SkillCardProps) {
278
+ const passRatePct = getPassRatePercent(skill.passRate);
279
+ const style = STATUS_STYLE[skill.status];
280
+
281
+ return (
282
+ <Card className="border border-border/15 p-6 hover:border-border/30 transition-all duration-300 flex flex-col">
283
+ {/* Top row: status dot in box (left) + metric (right) */}
284
+ <div className="flex justify-between items-start mb-4">
285
+ <div className="w-12 h-12 rounded-xl bg-muted flex items-center justify-center">
286
+ <span className={`size-3 rounded-full ${style.bg}`} />
287
+ </div>
288
+ <div className="text-right">
289
+ <div className="flex flex-wrap justify-end gap-1">
290
+ {skill.platforms && skill.platforms.length > 0 ? (
291
+ skill.platforms.map((p) => (
292
+ <span
293
+ key={p}
294
+ className="text-[10px] text-muted-foreground uppercase tracking-widest"
295
+ >
296
+ {p}
297
+ </span>
298
+ ))
299
+ ) : (
300
+ <span className="text-[10px] text-muted-foreground uppercase tracking-widest">
301
+ {skill.scope ?? "unknown"}
302
+ </span>
303
+ )}
304
+ </div>
305
+ <p className="text-sm font-bold tabular-nums">{skill.checks.toLocaleString()}</p>
306
+ </div>
307
+ </div>
308
+
309
+ {/* Title + description */}
310
+ <h3 className="font-headline font-bold text-xl tracking-tight text-foreground truncate mb-1">
311
+ {skill.name}
312
+ </h3>
313
+ <p className="text-sm text-muted-foreground mb-6">
314
+ {skill.uniqueSessions} sessions · Last seen{" "}
315
+ {skill.lastSeen ? timeAgo(skill.lastSeen) : "never"}
316
+ </p>
317
+
318
+ {/* Progress section */}
319
+ <div className="space-y-4">
320
+ <div className="flex justify-between items-end text-xs uppercase tracking-tighter">
321
+ <span className="text-muted-foreground">Pass Rate</span>
322
+ <span className={`font-bold ${style.text}`}>{style.label}</span>
323
+ </div>
324
+ <div className="w-full h-1 bg-input rounded-full overflow-hidden">
325
+ <div
326
+ className={`h-full rounded-full transition-all duration-500 ${style.bg}`}
327
+ style={{ width: `${passRatePct}%` }}
328
+ />
329
+ </div>
330
+
331
+ {/* Buttons */}
332
+ <div className="pt-4 flex gap-3">{renderActions?.(skill.name)}</div>
333
+ </div>
334
+ </Card>
335
+ );
336
+ }
337
+
338
+ /* ── Filter Tabs ───────────────────────────────────────────── */
339
+
340
+ export function SkillFilterTabs({
341
+ filter,
342
+ onFilterChange,
343
+ counts,
344
+ sortDesc,
345
+ onSortToggle,
346
+ }: SkillFilterTabsProps) {
347
+ return (
348
+ <div className="flex items-center justify-between gap-4 flex-wrap">
349
+ <div className="flex gap-1 bg-muted rounded-xl p-1">
350
+ {FILTER_TABS.map((tab) => (
351
+ <button
352
+ key={tab.key}
353
+ type="button"
354
+ onClick={() => onFilterChange(tab.key)}
355
+ className={`px-4 py-2 rounded-lg text-sm font-headline font-semibold transition-all duration-200 ${
356
+ filter === tab.key
357
+ ? "bg-card text-foreground shadow-sm"
358
+ : "text-muted-foreground hover:text-foreground"
359
+ }`}
360
+ >
361
+ {tab.label}
362
+ <span className="ml-1.5 text-xs opacity-60">{counts[tab.key]}</span>
363
+ </button>
364
+ ))}
365
+ </div>
366
+
367
+ <button
368
+ type="button"
369
+ onClick={onSortToggle}
370
+ className="flex items-center gap-2 px-3 py-2 text-sm text-muted-foreground hover:text-foreground transition-colors font-headline"
371
+ title={sortDesc ? "Highest pass rate first" : "Lowest pass rate first"}
372
+ >
373
+ <ArrowUpDownIcon className="size-4" />
374
+ <span>Sort by Performance</span>
375
+ </button>
376
+ </div>
377
+ );
378
+ }
379
+
380
+ /* ── Empty Hero Placeholder ────────────────────────────────── */
381
+
382
+ export function SkillHeroEmpty() {
383
+ return (
384
+ <Card className="col-span-12 lg:col-span-8 border border-border/15 p-8 flex items-center justify-center">
385
+ <div className="text-center space-y-2">
386
+ <BrainCircuitIcon className="size-10 text-muted-foreground mx-auto" />
387
+ <p className="text-muted-foreground">
388
+ No evolution activity yet. Run an evolution cycle to see your most active skill.
389
+ </p>
390
+ </div>
391
+ </Card>
392
+ );
393
+ }
394
+
395
+ /* ── Empty Grid Placeholder ────────────────────────────────── */
396
+
397
+ export function SkillGridEmpty() {
398
+ return (
399
+ <Card className="border border-border/15 p-12 text-center">
400
+ <CircleDotIcon className="size-8 text-muted-foreground mx-auto mb-3" />
401
+ <p className="text-muted-foreground font-headline">No skills match the current filter</p>
402
+ </Card>
403
+ );
404
+ }
405
+
406
+ /* ── Error State ───────────────────────────────────────────── */
407
+
408
+ export function SkillsLibraryError({
409
+ message,
410
+ onRetry,
411
+ }: {
412
+ message: string;
413
+ onRetry?: () => void;
414
+ }) {
415
+ return (
416
+ <div className="flex flex-1 flex-col items-center justify-center gap-4 py-16">
417
+ <AlertCircleIcon className="size-10 text-destructive" />
418
+ <p className="text-sm font-medium text-destructive">{message}</p>
419
+ {onRetry && (
420
+ <button
421
+ type="button"
422
+ onClick={onRetry}
423
+ className="inline-flex items-center gap-2 rounded-md border border-border px-3 py-1.5 text-sm font-medium hover:bg-muted transition-colors"
424
+ >
425
+ <RefreshCwIcon className="size-3.5" />
426
+ Retry
427
+ </button>
428
+ )}
429
+ </div>
430
+ );
431
+ }
432
+
433
+ /* ── Utility re-exports for consumers ──────────────────────── */
434
+
435
+ export { deriveStatus, formatRate, sortByPassRateAndChecks, timeAgo };
436
+ export { STATUS_STYLE, FILTER_TABS, getPassRatePercent };
437
+ export type { SkillHealthStatus };
@@ -1,8 +1,63 @@
1
1
  export { ActivityPanel } from "./ActivityTimeline";
2
+ export { JobHistoryTimeline } from "./JobHistoryTimeline";
3
+ export type { JobHistoryFilters } from "./JobHistoryTimeline";
4
+ export { PipelineStatusBar } from "./PipelineStatusBar";
5
+ export {
6
+ PassRateTrendChart,
7
+ SkillRankingsList,
8
+ ActivityHeatmap,
9
+ EvolutionROIList,
10
+ } from "./AnalyticsCharts";
11
+ export type {
12
+ PassRateTrendPoint,
13
+ SkillRanking,
14
+ DailyActivity,
15
+ EvolutionImpact,
16
+ AnalyticsSummary,
17
+ AnalyticsResponse,
18
+ } from "./AnalyticsCharts";
2
19
  export { EvidenceViewer } from "./EvidenceViewer";
3
20
  export { EvolutionTimeline } from "./EvolutionTimeline";
4
21
  export { InfoTip } from "./InfoTip";
22
+ export { InvocationsPanel } from "./InvocationsPanel";
23
+ export type { InvocationRow, SessionMeta, InvocationFilter } from "./InvocationsPanel";
5
24
  export { OrchestrateRunsPanel } from "./OrchestrateRunsPanel";
6
- export { RecentActivityFeed } from "./RecentActivityFeed";
25
+ export {
26
+ AutonomyHeroCard,
27
+ TrustWatchlistRail,
28
+ SupervisionFeed,
29
+ SkillComparisonGrid,
30
+ } from "./OverviewPanels";
31
+ export type { AutonomyHeroCardProps, SkillComparisonRow } from "./OverviewPanels";
7
32
  export { SectionCards } from "./section-cards";
8
33
  export { SkillHealthGrid } from "./skill-health-grid";
34
+ export {
35
+ SkillReportTopRow,
36
+ SkillTrustNarrativePanel,
37
+ TrustSignalsGrid,
38
+ PromptEvidencePanel,
39
+ DataQualityPanel,
40
+ observationBadge,
41
+ historicalContextBadge,
42
+ } from "./SkillReportPanels";
43
+ export { SkillReportGuideSheet, SkillReportOnboardingBanner } from "./SkillReportGuide";
44
+ export {
45
+ SkillHeroCard,
46
+ LibraryHealthCard,
47
+ PendingProposalsCard,
48
+ SkillCardItem,
49
+ SkillFilterTabs,
50
+ SkillHeroEmpty,
51
+ SkillGridEmpty,
52
+ SkillsLibrarySkeleton,
53
+ SkillsLibraryError,
54
+ } from "./SkillsLibrary";
55
+ export type {
56
+ DerivedSkill,
57
+ FilterTab,
58
+ SkillHeroCardProps,
59
+ LibraryHealthCardProps,
60
+ PendingProposalsCardProps,
61
+ SkillCardProps,
62
+ SkillFilterTabsProps,
63
+ } from "./SkillsLibrary";
@@ -1,3 +1,6 @@
1
+ import { Badge } from "../primitives/badge";
2
+ import { Card, CardAction, CardDescription, CardHeader, CardTitle } from "../primitives/card";
3
+ import { InfoTip } from "./InfoTip";
1
4
  import {
2
5
  TrendingUpIcon,
3
6
  TrendingDownIcon,
@@ -9,10 +12,6 @@ import {
9
12
  SearchXIcon,
10
13
  } from "lucide-react";
11
14
 
12
- import { Badge } from "../primitives/badge";
13
- import { Card, CardAction, CardDescription, CardHeader, CardTitle } from "../primitives/card";
14
- import { InfoTip } from "./InfoTip";
15
-
16
15
  interface SectionCardsProps {
17
16
  skillsCount: number;
18
17
  avgPassRate: number | null;
@@ -21,12 +20,8 @@ interface SectionCardsProps {
21
20
  pendingCount: number;
22
21
  evidenceCount: number;
23
22
  hasEvolution?: boolean;
24
- activeSessionsCount?: number;
25
23
  }
26
24
 
27
- const CARD_DESCRIPTION_CLASS =
28
- "flex items-center gap-1.5 text-[10px] uppercase tracking-widest text-slate-500";
29
-
30
25
  export function SectionCards({
31
26
  skillsCount,
32
27
  avgPassRate,
@@ -35,16 +30,15 @@ export function SectionCards({
35
30
  pendingCount,
36
31
  evidenceCount,
37
32
  hasEvolution = true,
38
- activeSessionsCount = 0,
39
33
  }: SectionCardsProps) {
40
34
  const passRateStr = avgPassRate !== null ? `${Math.round(avgPassRate * 100)}%` : "--";
41
35
  const passRateGood = avgPassRate !== null && avgPassRate >= 0.7;
42
36
 
43
37
  return (
44
- <div className="grid grid-cols-1 gap-4 px-4 *:data-[slot=card]:shadow-none lg:px-6 @xl/main:grid-cols-2 @5xl/main:grid-cols-3">
45
- <Card className="@container/card">
38
+ <div className="grid grid-cols-1 gap-4 px-4 lg:px-6 @xl/main:grid-cols-2 @5xl/main:grid-cols-3">
39
+ <Card className="@container/card bg-muted border-none shadow-none ring-0">
46
40
  <CardHeader>
47
- <CardDescription className={CARD_DESCRIPTION_CLASS}>
41
+ <CardDescription className="flex items-center gap-1.5 font-headline">
48
42
  <LayersIcon className="size-3.5" />
49
43
  Skills Monitored
50
44
  <InfoTip text="Total number of skills detected and being tracked by selftune" />
@@ -61,9 +55,9 @@ export function SectionCards({
61
55
  </CardHeader>
62
56
  </Card>
63
57
 
64
- <Card className="@container/card">
58
+ <Card className="@container/card bg-muted border-none shadow-none ring-0">
65
59
  <CardHeader>
66
- <CardDescription className={CARD_DESCRIPTION_CLASS}>
60
+ <CardDescription className="flex items-center gap-1.5 font-headline">
67
61
  <FlaskConicalIcon className="size-3.5" />
68
62
  Avg Trigger Rate
69
63
  <InfoTip text="Average percentage of skill checks that resulted in a trigger across all graded skills (5+ checks). Run selftune evolve to improve this." />
@@ -92,9 +86,9 @@ export function SectionCards({
92
86
  </CardHeader>
93
87
  </Card>
94
88
 
95
- <Card className="@container/card">
89
+ <Card className="@container/card bg-muted border-none shadow-none ring-0">
96
90
  <CardHeader>
97
- <CardDescription className={CARD_DESCRIPTION_CLASS}>
91
+ <CardDescription className="flex items-center gap-1.5 font-headline">
98
92
  <SearchXIcon className="size-3.5" />
99
93
  Unmatched Queries
100
94
  <InfoTip text="User prompts that didn't match any skill's trigger criteria — potential gaps in coverage" />
@@ -113,9 +107,9 @@ export function SectionCards({
113
107
  </CardHeader>
114
108
  </Card>
115
109
 
116
- <Card className="@container/card">
110
+ <Card className="@container/card bg-muted border-none shadow-none ring-0">
117
111
  <CardHeader>
118
- <CardDescription className={CARD_DESCRIPTION_CLASS}>
112
+ <CardDescription className="flex items-center gap-1.5 font-headline">
119
113
  <ActivityIcon className="size-3.5" />
120
114
  Sessions
121
115
  <InfoTip text="Total agent sessions that have been recorded and analyzed" />
@@ -123,25 +117,14 @@ export function SectionCards({
123
117
  <CardTitle className="text-2xl font-semibold tabular-nums @[250px]/card:text-3xl">
124
118
  {sessionsCount}
125
119
  </CardTitle>
126
- {activeSessionsCount > 0 && (
127
- <CardAction>
128
- <Badge variant="outline" className="gap-1.5">
129
- <span className="relative flex size-2">
130
- <span className="absolute inline-flex size-full animate-ping rounded-full bg-primary opacity-75" />
131
- <span className="relative inline-flex size-2 rounded-full bg-primary shadow-[0_0_8px_color-mix(in_srgb,var(--primary)_60%,transparent)]" />
132
- </span>
133
- {activeSessionsCount} in progress
134
- </Badge>
135
- </CardAction>
136
- )}
137
120
  </CardHeader>
138
121
  </Card>
139
122
 
140
- <Card className="@container/card">
123
+ <Card className="@container/card bg-muted border-none shadow-none ring-0">
141
124
  <CardHeader>
142
- <CardDescription className={CARD_DESCRIPTION_CLASS}>
125
+ <CardDescription className="flex items-center gap-1.5 font-headline">
143
126
  <AlertTriangleIcon className="size-3.5" />
144
- Undeployed Proposals
127
+ Pending Proposals
145
128
  <InfoTip text="Evolution proposals that have been generated but not yet validated or deployed. Requires running selftune evolve." />
146
129
  </CardDescription>
147
130
  <CardTitle className="text-2xl font-semibold tabular-nums @[250px]/card:text-3xl">
@@ -153,15 +136,15 @@ export function SectionCards({
153
136
  no evolution runs yet
154
137
  </Badge>
155
138
  ) : pendingCount > 0 ? (
156
- <Badge variant="secondary">not yet deployed</Badge>
139
+ <Badge variant="secondary">awaiting review</Badge>
157
140
  ) : null}
158
141
  </CardAction>
159
142
  </CardHeader>
160
143
  </Card>
161
144
 
162
- <Card className="@container/card">
145
+ <Card className="@container/card bg-muted border-none shadow-none ring-0">
163
146
  <CardHeader>
164
- <CardDescription className={CARD_DESCRIPTION_CLASS}>
147
+ <CardDescription className="flex items-center gap-1.5 font-headline">
165
148
  <EyeIcon className="size-3.5" />
166
149
  Total Evidence
167
150
  <InfoTip text="Number of evidence entries documenting skill changes with before/after validation results. Requires running selftune evolve." />