selftune 0.2.27 → 0.2.29

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.
@@ -1,12 +1,4 @@
1
- import type { ReactNode } from "react";
2
- import {
3
- EyeIcon,
4
- ShieldAlertIcon,
5
- ShieldCheckIcon,
6
- ShieldIcon,
7
- ShieldQuestionIcon,
8
- } from "lucide-react";
9
-
1
+ import { cn } from "@selftune/ui/lib";
10
2
  import { Badge } from "@selftune/ui/primitives";
11
3
  import type { TrustState } from "@selftune/ui/types";
12
4
 
@@ -14,8 +6,8 @@ export function SkillReportTrustBadge({ state }: { state: TrustState }) {
14
6
  const config = getSkillReportTrustBadgeConfig(state);
15
7
 
16
8
  return (
17
- <Badge variant={config.variant} className="gap-1 shrink-0 text-[10px]">
18
- {config.icon}
9
+ <Badge variant={config.variant} className="gap-1.5 shrink-0 text-[10px]">
10
+ <span className={cn("size-1.5 shrink-0 rounded-full", config.dotClassName)} />
19
11
  {config.label}
20
12
  </Badge>
21
13
  );
@@ -24,44 +16,44 @@ export function SkillReportTrustBadge({ state }: { state: TrustState }) {
24
16
  export function getSkillReportTrustBadgeConfig(state: TrustState): {
25
17
  label: string;
26
18
  variant: "default" | "secondary" | "destructive" | "outline";
27
- icon: ReactNode;
19
+ dotClassName: string;
28
20
  } {
29
21
  switch (state) {
30
22
  case "low_sample":
31
23
  return {
32
24
  label: "Low Sample",
33
25
  variant: "secondary",
34
- icon: <ShieldQuestionIcon className="size-3" />,
26
+ dotClassName: "bg-muted-foreground/60",
35
27
  };
36
28
  case "observed":
37
29
  return {
38
30
  label: "Observed",
39
31
  variant: "outline",
40
- icon: <EyeIcon className="size-3" />,
32
+ dotClassName: "bg-muted-foreground",
41
33
  };
42
34
  case "watch":
43
35
  return {
44
36
  label: "Watch",
45
37
  variant: "secondary",
46
- icon: <ShieldAlertIcon className="size-3" />,
38
+ dotClassName: "bg-amber-400",
47
39
  };
48
40
  case "validated":
49
41
  return {
50
42
  label: "Validated",
51
43
  variant: "default",
52
- icon: <ShieldCheckIcon className="size-3" />,
44
+ dotClassName: "bg-primary",
53
45
  };
54
46
  case "deployed":
55
47
  return {
56
48
  label: "Deployed",
57
49
  variant: "default",
58
- icon: <ShieldCheckIcon className="size-3" />,
50
+ dotClassName: "bg-primary",
59
51
  };
60
52
  case "rolled_back":
61
53
  return {
62
54
  label: "Rolled Back",
63
55
  variant: "destructive",
64
- icon: <ShieldIcon className="size-3" />,
56
+ dotClassName: "bg-destructive",
65
57
  };
66
58
  }
67
59
  }
@@ -4,40 +4,14 @@ import { Card, CardContent, CardHeader, CardTitle } from "../primitives/card";
4
4
  import type { EvidenceEntry, EvolutionEntry } from "../types";
5
5
  import { formatRate, timeAgo } from "../lib/format";
6
6
  import {
7
- CheckCircleIcon,
8
7
  ChevronDownIcon,
9
8
  ChevronRightIcon,
10
- CircleDotIcon,
11
9
  FileTextIcon,
12
- InfoIcon,
13
- RocketIcon,
14
- ShieldCheckIcon,
15
10
  ShieldAlertIcon,
16
- XCircleIcon,
17
- UndoIcon,
18
- ArrowRightIcon,
19
- TrendingUpIcon,
20
- TrendingDownIcon,
21
11
  ListChecksIcon,
22
12
  } from "lucide-react";
23
13
  import Markdown from "react-markdown";
24
14
 
25
- const ACTION_ICON: Record<string, React.ReactNode> = {
26
- created: <CircleDotIcon className="size-3.5" />,
27
- validated: <ShieldCheckIcon className="size-3.5" />,
28
- deployed: <RocketIcon className="size-3.5" />,
29
- rejected: <XCircleIcon className="size-3.5" />,
30
- rolled_back: <UndoIcon className="size-3.5" />,
31
- };
32
-
33
- const ACTION_VARIANT: Record<string, "default" | "secondary" | "destructive" | "outline"> = {
34
- created: "outline",
35
- validated: "secondary",
36
- deployed: "default",
37
- rejected: "destructive",
38
- rolled_back: "destructive",
39
- };
40
-
41
15
  interface Props {
42
16
  proposalId: string;
43
17
  evolution: EvolutionEntry[];
@@ -123,9 +97,9 @@ function formatValidationValue(key: string, val: unknown): React.ReactNode {
123
97
  // Booleans
124
98
  if (typeof val === "boolean") {
125
99
  return val ? (
126
- <CheckCircleIcon className="size-3.5 text-emerald-500 inline" />
100
+ <span className="inline-block size-2 rounded-full bg-primary align-middle" />
127
101
  ) : (
128
- <XCircleIcon className="size-3.5 text-red-500 inline" />
102
+ <span className="inline-block size-2 rounded-full bg-destructive align-middle" />
129
103
  );
130
104
  }
131
105
  // Numbers that look like rates (0-1 range, or key contains "rate"/"change")
@@ -212,12 +186,12 @@ function PerEntryResult({ entry }: { entry: Record<string, unknown> }) {
212
186
  <div className="flex items-start gap-2 text-xs py-1.5 border-b border-border/50 last:border-0">
213
187
  {isPass !== null ? (
214
188
  isPass ? (
215
- <CheckCircleIcon className="size-3.5 text-emerald-500 shrink-0 mt-0.5" />
189
+ <span className="mt-1 size-2 shrink-0 rounded-full bg-primary" />
216
190
  ) : (
217
- <XCircleIcon className="size-3.5 text-red-500 shrink-0 mt-0.5" />
191
+ <span className="mt-1 size-2 shrink-0 rounded-full bg-destructive" />
218
192
  )
219
193
  ) : (
220
- <CircleDotIcon className="size-3.5 text-muted-foreground shrink-0 mt-0.5" />
194
+ <span className="mt-1 size-2 shrink-0 rounded-full bg-muted-foreground/60" />
221
195
  )}
222
196
  <span className="flex-1 min-w-0 line-clamp-2">
223
197
  {query ? String(query) : JSON.stringify(entry)}
@@ -307,7 +281,7 @@ function ValidationResults({ validation }: { validation: Record<string, unknown>
307
281
  )}
308
282
  {typeof net_change === "number" && (
309
283
  <span
310
- className={`text-xs font-mono font-semibold ${net_change > 0 ? "text-emerald-600 dark:text-emerald-400" : "text-red-500"}`}
284
+ className={`text-xs font-mono font-semibold ${net_change > 0 ? "text-primary" : "text-destructive"}`}
311
285
  >
312
286
  {net_change > 0 ? "+" : ""}
313
287
  {(net_change * 100).toFixed(1)}%
@@ -324,7 +298,7 @@ function ValidationResults({ validation }: { validation: Record<string, unknown>
324
298
  {/* New passes */}
325
299
  {newPassesArr.length > 0 && (
326
300
  <div>
327
- <p className="text-[11px] font-medium text-emerald-600 dark:text-emerald-400 mb-1">
301
+ <p className="mb-1 text-[11px] font-medium text-primary">
328
302
  New Passes ({newPassesArr.length})
329
303
  </p>
330
304
  <div className="rounded border bg-card p-2">
@@ -345,10 +319,10 @@ function ValidationResults({ validation }: { validation: Record<string, unknown>
345
319
  {/* Regressions */}
346
320
  {regressionsArr.length > 0 && (
347
321
  <div>
348
- <p className="text-[11px] font-medium text-red-500 mb-1">
322
+ <p className="text-[11px] font-medium text-destructive mb-1">
349
323
  Regressions ({regressionsArr.length})
350
324
  </p>
351
- <div className="rounded border border-red-200 dark:border-red-900/50 bg-card p-2">
325
+ <div className="rounded border border-destructive/20 bg-card p-2">
352
326
  {regressionsArr.map((entry) => (
353
327
  <PerEntryResult
354
328
  key={getEvidenceListKey("regression", entry)}
@@ -406,7 +380,7 @@ function PerEntryResultsSection({ entries }: { entries: unknown[] }) {
406
380
  {/* Pass rate bar */}
407
381
  <div className="h-1.5 rounded-full bg-muted overflow-hidden mb-2">
408
382
  <div
409
- className="h-full rounded-full bg-emerald-500 transition-all"
383
+ className="h-full rounded-full bg-primary transition-all"
410
384
  style={{ width: `${entries.length > 0 ? (passCount / entries.length) * 100 : 0}%` }}
411
385
  />
412
386
  </div>
@@ -442,7 +416,7 @@ function DeltaBadge({ prev, curr }: { prev: number | null; curr: number | null }
442
416
  const positive = delta > 0;
443
417
  return (
444
418
  <span
445
- className={`text-[10px] font-mono font-semibold ${positive ? "text-emerald-600 dark:text-emerald-400" : "text-red-500"}`}
419
+ className={`text-[10px] font-mono font-semibold ${positive ? "text-primary" : "text-destructive"}`}
446
420
  >
447
421
  {positive ? "+" : ""}
448
422
  {pct}% vs previous
@@ -487,12 +461,12 @@ function EvalSetSection({ evalSet }: { evalSet: Array<Record<string, unknown>> }
487
461
  >
488
462
  {typeof passed === "boolean" ? (
489
463
  passed ? (
490
- <CheckCircleIcon className="size-3.5 text-emerald-500 shrink-0 mt-0.5" />
464
+ <span className="mt-1 size-2 shrink-0 rounded-full bg-primary" />
491
465
  ) : (
492
- <XCircleIcon className="size-3.5 text-red-500 shrink-0 mt-0.5" />
466
+ <span className="mt-1 size-2 shrink-0 rounded-full bg-destructive" />
493
467
  )
494
468
  ) : (
495
- <CircleDotIcon className="size-3.5 text-muted-foreground shrink-0 mt-0.5" />
469
+ <span className="mt-1 size-2 shrink-0 rounded-full bg-muted-foreground/60" />
496
470
  )}
497
471
  <span className="flex-1 min-w-0 line-clamp-2">
498
472
  {String(query ?? JSON.stringify(evalEntry))}
@@ -648,15 +622,7 @@ function CollapsedEvidenceCard({
648
622
  );
649
623
  }
650
624
 
651
- export function EvidenceViewer({ proposalId, evolution, evidence }: Props) {
652
- const steps = useMemo(
653
- () =>
654
- evolution
655
- .filter((e) => e.proposal_id === proposalId)
656
- .sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()),
657
- [evolution, proposalId],
658
- );
659
-
625
+ export function EvidenceViewer({ proposalId, evidence }: Props) {
660
626
  const entries = useMemo(
661
627
  () =>
662
628
  evidence
@@ -677,13 +643,6 @@ export function EvidenceViewer({ proposalId, evolution, evidence }: Props) {
677
643
  });
678
644
  };
679
645
 
680
- const snapshot = useMemo(() => {
681
- for (let i = steps.length - 1; i >= 0; i--) {
682
- if (steps[i].eval_snapshot) return steps[i].eval_snapshot as Record<string, unknown>;
683
- }
684
- return null;
685
- }, [steps]);
686
-
687
646
  // Separate proposal-stage entries from validation-stage entries, then group validations by target
688
647
  const { proposalEntries, validationsByTarget } = useMemo(() => {
689
648
  const proposals: EvidenceEntry[] = [];
@@ -702,92 +661,6 @@ export function EvidenceViewer({ proposalId, evolution, evidence }: Props) {
702
661
 
703
662
  return (
704
663
  <div className="space-y-4">
705
- {/* Context banner */}
706
- <div className="flex items-start gap-2.5 rounded-lg border border-primary/20 bg-primary/5 px-3.5 py-2.5">
707
- <InfoIcon className="size-4 text-primary/60 shrink-0 mt-0.5" />
708
- <p className="text-xs text-muted-foreground leading-relaxed">
709
- This view shows the complete evidence trail for a skill evolution proposal &mdash; how the
710
- skill was changed, the eval test results before and after, and whether the change improved
711
- performance.
712
- </p>
713
- </div>
714
-
715
- {/* Proposal journey */}
716
- <Card>
717
- <CardHeader className="pb-3">
718
- <CardTitle className="text-sm flex items-center gap-2">
719
- <span>Proposal Journey</span>
720
- <span className="font-mono text-xs text-muted-foreground">
721
- #{proposalId.slice(0, 12)}
722
- </span>
723
- </CardTitle>
724
- </CardHeader>
725
- <CardContent className="space-y-3">
726
- <div className="flex items-center gap-2 flex-wrap">
727
- {steps.map((step, i) => (
728
- <div key={`${step.action}-${step.timestamp}`} className="contents">
729
- {i > 0 && <ArrowRightIcon className="size-3 text-muted-foreground/50 shrink-0" />}
730
- <div className="flex items-center gap-1.5 rounded-md border px-2.5 py-1.5 bg-card">
731
- {ACTION_ICON[step.action]}
732
- <Badge
733
- variant={ACTION_VARIANT[step.action] ?? "secondary"}
734
- className="text-[10px] capitalize"
735
- >
736
- {step.action.replace("_", " ")}
737
- </Badge>
738
- <span className="text-[10px] text-muted-foreground">
739
- {timeAgo(step.timestamp)}
740
- </span>
741
- </div>
742
- </div>
743
- ))}
744
- </div>
745
-
746
- {/* Eval snapshot — pass rate change */}
747
- {snapshot && (
748
- <div className="flex items-center gap-3 rounded-md border bg-muted/20 px-3 py-2">
749
- {typeof snapshot.net_change === "number" && (
750
- <div className="flex items-center gap-1">
751
- {(snapshot.net_change as number) > 0 ? (
752
- <TrendingUpIcon className="size-3.5 text-emerald-500" />
753
- ) : (
754
- <TrendingDownIcon className="size-3.5 text-red-500" />
755
- )}
756
- <span
757
- className={`text-sm font-semibold font-mono ${(snapshot.net_change as number) > 0 ? "text-emerald-600 dark:text-emerald-400" : "text-red-500"}`}
758
- >
759
- {(snapshot.net_change as number) > 0 ? "+" : ""}
760
- {Math.round((snapshot.net_change as number) * 100)}%
761
- </span>
762
- </div>
763
- )}
764
- {typeof snapshot.before_pass_rate === "number" &&
765
- typeof snapshot.after_pass_rate === "number" && (
766
- <span className="text-xs text-muted-foreground font-mono">
767
- {Math.round((snapshot.before_pass_rate as number) * 100)}% &rarr;{" "}
768
- {Math.round((snapshot.after_pass_rate as number) * 100)}%
769
- </span>
770
- )}
771
- {snapshot.improved !== undefined && (
772
- <Badge
773
- variant={snapshot.improved ? "default" : "destructive"}
774
- className="text-[10px]"
775
- >
776
- {snapshot.improved ? "Improved" : "Regressed"}
777
- </Badge>
778
- )}
779
- </div>
780
- )}
781
-
782
- {/* Details from last step */}
783
- {steps.length > 0 && steps[steps.length - 1].details && (
784
- <p className="text-xs text-muted-foreground leading-relaxed">
785
- {steps[steps.length - 1].details}
786
- </p>
787
- )}
788
- </CardContent>
789
- </Card>
790
-
791
664
  {/* Proposal-stage evidence — standalone cards showing original/proposed text */}
792
665
  {proposalEntries.map((entry) => (
793
666
  <EvidenceCard
@@ -3,48 +3,30 @@ import { Badge } from "../primitives/badge";
3
3
  import { cn } from "../lib/utils";
4
4
  import type { EvalSnapshot, EvolutionEntry } from "../types";
5
5
  import { timeAgo } from "../lib/format";
6
- import {
7
- CircleDotIcon,
8
- RocketIcon,
9
- ShieldCheckIcon,
10
- XCircleIcon,
11
- UndoIcon,
12
- TrendingUpIcon,
13
- TrendingDownIcon,
14
- ChevronDownIcon,
15
- ChevronRightIcon,
16
- } from "lucide-react";
17
-
18
- const ACTION_ICON: Record<string, React.ReactNode> = {
19
- created: <CircleDotIcon className="size-3.5" />,
20
- validated: <ShieldCheckIcon className="size-3.5" />,
21
- deployed: <RocketIcon className="size-3.5" />,
22
- rejected: <XCircleIcon className="size-3.5" />,
23
- rolled_back: <UndoIcon className="size-3.5" />,
24
- };
6
+ import { TrendingUpIcon, TrendingDownIcon, ChevronDownIcon, ChevronRightIcon } from "lucide-react";
25
7
 
26
8
  const ACTION_COLOR: Record<string, string> = {
27
- created: "bg-blue-500",
28
- validated: "bg-amber-500",
29
- deployed: "bg-emerald-500",
30
- rejected: "bg-red-500",
31
- rolled_back: "bg-red-400",
9
+ created: "bg-primary/35",
10
+ validated: "bg-primary/65",
11
+ deployed: "bg-primary",
12
+ rejected: "bg-destructive/85",
13
+ rolled_back: "bg-destructive/45",
32
14
  };
33
15
 
34
16
  const ACTION_RING: Record<string, string> = {
35
- created: "ring-blue-500/30",
36
- validated: "ring-amber-500/30",
37
- deployed: "ring-emerald-500/30",
38
- rejected: "ring-red-500/30",
39
- rolled_back: "ring-red-400/30",
17
+ created: "ring-primary/15",
18
+ validated: "ring-primary/20",
19
+ deployed: "ring-primary/30",
20
+ rejected: "ring-destructive/20",
21
+ rolled_back: "ring-destructive/15",
40
22
  };
41
23
 
42
24
  const ACTION_LINE: Record<string, string> = {
43
- created: "bg-blue-500/30",
44
- validated: "bg-amber-500/30",
45
- deployed: "bg-emerald-500/30",
46
- rejected: "bg-red-500/30",
47
- rolled_back: "bg-red-400/30",
25
+ created: "bg-primary/12",
26
+ validated: "bg-primary/18",
27
+ deployed: "bg-primary/30",
28
+ rejected: "bg-destructive/18",
29
+ rolled_back: "bg-destructive/12",
48
30
  };
49
31
 
50
32
  interface Props {
@@ -92,7 +74,7 @@ function PassRateDelta({ snapshot }: { snapshot: EvalSnapshot }) {
92
74
  <span
93
75
  className={cn(
94
76
  "inline-flex items-center gap-0.5 text-[10px] font-mono font-medium",
95
- isPositive ? "text-emerald-600 dark:text-emerald-400" : "text-red-500",
77
+ isPositive ? "text-primary" : "text-destructive",
96
78
  )}
97
79
  >
98
80
  {isPositive ? (
@@ -157,7 +139,7 @@ export function EvolutionTimeline({ entries, selectedProposalId, onSelect }: Pro
157
139
 
158
140
  return (
159
141
  <div className="flex flex-col gap-0">
160
- <h2 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider px-2 pb-2 sticky top-0 z-10 bg-background">
142
+ <h2 className="sticky top-0 z-10 bg-background px-2 pb-2 text-xs font-semibold uppercase tracking-wider text-muted-foreground">
161
143
  Evolution
162
144
  </h2>
163
145
  <LifecycleLegend />
@@ -177,14 +159,8 @@ export function EvolutionTimeline({ entries, selectedProposalId, onSelect }: Pro
177
159
  {/* Vertical connector line */}
178
160
  <div className="flex flex-col items-center">
179
161
  <div
180
- className={cn(
181
- "flex items-center justify-center size-7 rounded-full ring-2 text-white shrink-0 z-10",
182
- dotColor,
183
- ringColor,
184
- )}
185
- >
186
- {ACTION_ICON[terminal] ?? <CircleDotIcon className="size-3.5" />}
187
- </div>
162
+ className={cn("size-3 shrink-0 rounded-full ring-2 z-10", dotColor, ringColor)}
163
+ />
188
164
  {!isLast && <div className={cn("w-0.5 flex-1 min-h-[16px]", lineColor)} />}
189
165
  </div>
190
166
 
@@ -84,10 +84,7 @@ function ExampleRowItem({ row }: { row: ExampleRow }) {
84
84
  <TableCell className="py-2">
85
85
  <div className="flex items-center gap-1.5">
86
86
  {row.triggered ? (
87
- <Badge
88
- variant="outline"
89
- className="border-green-600/30 text-[10px] font-normal text-green-600"
90
- >
87
+ <Badge variant="outline" className="text-[10px] font-normal">
91
88
  triggered
92
89
  </Badge>
93
90
  ) : (
package/skill/SKILL.md CHANGED
@@ -13,7 +13,7 @@ description: >
13
13
  even if they don't say "selftune" explicitly.
14
14
  metadata:
15
15
  author: selftune-dev
16
- version: 0.2.27
16
+ version: 0.2.29
17
17
  category: developer-tools
18
18
  ---
19
19
 
@@ -55,7 +55,7 @@ Manage versioned skill distribution across your team. Push skill folders to the
55
55
  ## Prerequisites
56
56
 
57
57
  - Must be authenticated (`selftune alpha upload` to set up API key)
58
- - Push and rollback require Team plan and admin role
58
+ - Push and rollback require Pro plan or higher and admin role
59
59
  - Install requires Pro plan or higher
60
60
 
61
61
  ## Output Format