selftune 0.2.22 → 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.
- package/README.md +4 -2
- package/apps/local-dashboard/dist/assets/index-CwOtTrUS.css +1 -0
- package/apps/local-dashboard/dist/assets/index-f1HQpbeH.js +59 -0
- package/apps/local-dashboard/dist/assets/vendor-ui-jVSaIZey.js +12 -0
- package/apps/local-dashboard/dist/index.html +3 -3
- package/cli/selftune/adapters/pi/hook.ts +273 -0
- package/cli/selftune/adapters/pi/install.ts +207 -0
- package/cli/selftune/constants.ts +10 -1
- package/cli/selftune/dashboard-contract.ts +14 -0
- package/cli/selftune/evolution/engines/judge-engine.ts +96 -0
- package/cli/selftune/evolution/engines/replay-engine.ts +158 -0
- package/cli/selftune/evolution/evidence.ts +2 -6
- package/cli/selftune/evolution/evolve-body.ts +73 -20
- package/cli/selftune/evolution/validate-body.ts +78 -42
- package/cli/selftune/evolution/validate-routing.ts +45 -104
- package/cli/selftune/hooks/skill-eval.ts +2 -1
- package/cli/selftune/hooks-shared/types.ts +1 -0
- package/cli/selftune/index.ts +23 -5
- package/cli/selftune/ingestors/pi-ingest.ts +726 -0
- package/cli/selftune/init.ts +11 -1
- package/cli/selftune/localdb/direct-write.ts +85 -0
- package/cli/selftune/localdb/materialize.ts +6 -7
- package/cli/selftune/localdb/queries.ts +126 -0
- package/cli/selftune/localdb/schema.ts +38 -0
- package/cli/selftune/observability.ts +8 -1
- package/cli/selftune/orchestrate.ts +43 -0
- package/cli/selftune/registry/client.ts +74 -0
- package/cli/selftune/registry/history.ts +54 -0
- package/cli/selftune/registry/index.ts +90 -0
- package/cli/selftune/registry/install.ts +141 -0
- package/cli/selftune/registry/list.ts +44 -0
- package/cli/selftune/registry/push.ts +171 -0
- package/cli/selftune/registry/rollback.ts +49 -0
- package/cli/selftune/registry/status.ts +62 -0
- package/cli/selftune/registry/sync.ts +125 -0
- package/cli/selftune/repair/skill-usage.ts +4 -1
- package/cli/selftune/status.ts +31 -0
- package/cli/selftune/sync.ts +127 -23
- package/cli/selftune/types.ts +2 -1
- package/cli/selftune/utils/jsonl.ts +1 -30
- package/cli/selftune/utils/skill-discovery.ts +22 -0
- package/node_modules/@selftune/telemetry-contract/fixtures/evidence-only-push.ts +1 -1
- package/node_modules/@selftune/telemetry-contract/fixtures/golden.test.ts +0 -1
- package/node_modules/@selftune/telemetry-contract/fixtures/partial-push-unresolved-parents.ts +1 -1
- package/node_modules/@selftune/telemetry-contract/package.json +1 -1
- package/node_modules/@selftune/telemetry-contract/src/index.ts +1 -0
- package/node_modules/@selftune/telemetry-contract/src/schemas.ts +22 -4
- package/node_modules/@selftune/telemetry-contract/src/types.ts +1 -12
- package/node_modules/@selftune/telemetry-contract/tests/compatibility.test.ts +0 -1
- package/package.json +1 -1
- package/packages/telemetry-contract/fixtures/evidence-only-push.ts +1 -1
- package/packages/telemetry-contract/fixtures/golden.test.ts +0 -1
- package/packages/telemetry-contract/fixtures/partial-push-unresolved-parents.ts +1 -1
- package/packages/telemetry-contract/package.json +1 -1
- package/packages/telemetry-contract/src/index.ts +1 -0
- package/packages/telemetry-contract/src/schemas.ts +22 -4
- package/packages/telemetry-contract/src/types.ts +1 -12
- package/packages/telemetry-contract/tests/compatibility.test.ts +0 -1
- package/packages/ui/AGENTS.md +16 -0
- package/packages/ui/README.md +1 -1
- package/packages/ui/package.json +1 -1
- package/packages/ui/src/components/ActivityTimeline.tsx +152 -168
- package/packages/ui/src/components/AnalyticsCharts.tsx +344 -0
- package/packages/ui/src/components/EvidenceViewer.tsx +153 -443
- package/packages/ui/src/components/EvolutionTimeline.tsx +34 -87
- package/packages/ui/src/components/InfoTip.tsx +1 -2
- package/packages/ui/src/components/InvocationsPanel.tsx +413 -0
- package/packages/ui/src/components/JobHistoryTimeline.tsx +156 -0
- package/packages/ui/src/components/OrchestrateRunsPanel.tsx +18 -36
- package/packages/ui/src/components/OverviewPanels.tsx +652 -0
- package/packages/ui/src/components/PipelineStatusBar.tsx +65 -0
- package/packages/ui/src/components/SkillReportGuide.tsx +215 -0
- package/packages/ui/src/components/SkillReportPanels.tsx +919 -0
- package/packages/ui/src/components/SkillsLibrary.tsx +437 -0
- package/packages/ui/src/components/index.ts +56 -1
- package/packages/ui/src/components/section-cards.tsx +18 -35
- package/packages/ui/src/components/skill-health-grid.tsx +47 -37
- package/packages/ui/src/lib/constants.tsx +0 -1
- package/packages/ui/src/primitives/card.tsx +1 -1
- package/packages/ui/src/primitives/checkbox.tsx +1 -1
- package/packages/ui/src/primitives/dropdown-menu.tsx +2 -2
- package/packages/ui/src/primitives/select.tsx +2 -2
- package/packages/ui/src/types.ts +172 -4
- package/skill/SKILL.md +18 -4
- package/skill/Workflows/Ingest.md +60 -2
- package/skill/Workflows/Initialize.md +8 -5
- package/skill/Workflows/PlatformHooks.md +19 -3
- package/skill/Workflows/Registry.md +99 -0
- package/skill/Workflows/Sync.md +3 -1
- package/apps/local-dashboard/dist/assets/index-D8O-RG1I.js +0 -60
- package/apps/local-dashboard/dist/assets/index-_EcLywDg.css +0 -1
- package/apps/local-dashboard/dist/assets/vendor-ui-CGEmUayx.js +0 -12
- package/cli/selftune/utils/html.ts +0 -27
- 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 {
|
|
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
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
125
|
+
<CardDescription className="flex items-center gap-1.5 font-headline">
|
|
143
126
|
<AlertTriangleIcon className="size-3.5" />
|
|
144
|
-
|
|
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">
|
|
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=
|
|
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." />
|