selftune 0.2.18 → 0.2.20

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 (77) hide show
  1. package/README.md +9 -4
  2. package/apps/local-dashboard/dist/assets/index-D8O-RG1I.js +60 -0
  3. package/apps/local-dashboard/dist/assets/index-_EcLywDg.css +1 -0
  4. package/apps/local-dashboard/dist/assets/vendor-table-BIiI3YhS.js +1 -0
  5. package/apps/local-dashboard/dist/assets/vendor-ui-CGEmUayx.js +12 -0
  6. package/apps/local-dashboard/dist/index.html +5 -5
  7. package/cli/selftune/alpha-upload/stage-canonical.ts +7 -6
  8. package/cli/selftune/constants.ts +10 -0
  9. package/cli/selftune/contribute/contribute.ts +30 -2
  10. package/cli/selftune/contribution-config.ts +249 -0
  11. package/cli/selftune/contribution-relay.ts +177 -0
  12. package/cli/selftune/contribution-signals.ts +219 -0
  13. package/cli/selftune/contribution-staging.ts +147 -0
  14. package/cli/selftune/contributions.ts +532 -0
  15. package/cli/selftune/creator-contributions.ts +333 -0
  16. package/cli/selftune/dashboard-contract.ts +209 -1
  17. package/cli/selftune/dashboard-server.ts +45 -11
  18. package/cli/selftune/eval/family-overlap.ts +714 -0
  19. package/cli/selftune/eval/hooks-to-evals.ts +182 -28
  20. package/cli/selftune/eval/synthetic-evals.ts +298 -11
  21. package/cli/selftune/evolution/evidence.ts +5 -0
  22. package/cli/selftune/evolution/evolve-body.ts +62 -2
  23. package/cli/selftune/evolution/evolve.ts +58 -1
  24. package/cli/selftune/evolution/validate-body.ts +10 -0
  25. package/cli/selftune/evolution/validate-host-replay.ts +236 -0
  26. package/cli/selftune/evolution/validate-proposal.ts +10 -0
  27. package/cli/selftune/evolution/validate-routing.ts +112 -5
  28. package/cli/selftune/export.ts +2 -2
  29. package/cli/selftune/index.ts +41 -5
  30. package/cli/selftune/ingestors/codex-rollout.ts +31 -35
  31. package/cli/selftune/ingestors/codex-wrapper.ts +32 -24
  32. package/cli/selftune/localdb/db.ts +2 -2
  33. package/cli/selftune/localdb/direct-write.ts +8 -3
  34. package/cli/selftune/localdb/materialize.ts +7 -2
  35. package/cli/selftune/localdb/queries.ts +712 -31
  36. package/cli/selftune/localdb/schema.ts +30 -1
  37. package/cli/selftune/recover.ts +153 -0
  38. package/cli/selftune/repair/skill-usage.ts +363 -4
  39. package/cli/selftune/routes/actions.ts +35 -1
  40. package/cli/selftune/routes/analytics.ts +14 -0
  41. package/cli/selftune/routes/index.ts +1 -0
  42. package/cli/selftune/routes/overview.ts +112 -4
  43. package/cli/selftune/routes/skill-report.ts +575 -11
  44. package/cli/selftune/status.ts +81 -2
  45. package/cli/selftune/sync.ts +56 -2
  46. package/cli/selftune/trust-model.ts +66 -0
  47. package/cli/selftune/types.ts +103 -0
  48. package/cli/selftune/utils/skill-detection.ts +43 -0
  49. package/cli/selftune/utils/text-similarity.ts +73 -0
  50. package/cli/selftune/watchlist.ts +65 -0
  51. package/package.json +1 -1
  52. package/packages/ui/src/components/ActivityTimeline.tsx +165 -150
  53. package/packages/ui/src/components/EvidenceViewer.tsx +419 -145
  54. package/packages/ui/src/components/EvolutionTimeline.tsx +81 -29
  55. package/packages/ui/src/components/OrchestrateRunsPanel.tsx +33 -16
  56. package/packages/ui/src/components/RecentActivityFeed.tsx +72 -41
  57. package/packages/ui/src/components/section-cards.tsx +12 -9
  58. package/packages/ui/src/primitives/card.tsx +1 -1
  59. package/packages/ui/src/types.ts +4 -0
  60. package/skill/SKILL.md +11 -1
  61. package/skill/Workflows/AlphaUpload.md +4 -0
  62. package/skill/Workflows/Composability.md +78 -0
  63. package/skill/Workflows/Contribute.md +6 -3
  64. package/skill/Workflows/Contributions.md +97 -0
  65. package/skill/Workflows/CreatorContributions.md +74 -0
  66. package/skill/Workflows/Dashboard.md +31 -0
  67. package/skill/Workflows/Evals.md +57 -8
  68. package/skill/Workflows/Evolve.md +23 -0
  69. package/skill/Workflows/Ingest.md +7 -0
  70. package/skill/Workflows/Initialize.md +20 -1
  71. package/skill/Workflows/Recover.md +84 -0
  72. package/skill/Workflows/RepairSkillUsage.md +12 -4
  73. package/skill/Workflows/Sync.md +18 -12
  74. package/apps/local-dashboard/dist/assets/index-BMIS6uUh.css +0 -2
  75. package/apps/local-dashboard/dist/assets/index-DOu3iLD9.js +0 -16
  76. package/apps/local-dashboard/dist/assets/vendor-table-pHbDxq36.js +0 -8
  77. package/apps/local-dashboard/dist/assets/vendor-ui-DIwlrGlb.js +0 -12
@@ -10,13 +10,14 @@ import {
10
10
  ChevronRightIcon,
11
11
  } from "lucide-react";
12
12
  import { useState } from "react";
13
+ import type { ReactNode } from "react";
13
14
 
14
15
  import { timeAgo } from "../lib/format";
15
16
  import { cn } from "../lib/utils";
16
17
  import { Badge } from "../primitives/badge";
17
18
  import type { EvalSnapshot, EvolutionEntry } from "../types";
18
19
 
19
- const ACTION_ICON: Record<string, React.ReactNode> = {
20
+ const ACTION_ICON: Record<string, ReactNode> = {
20
21
  created: <CircleDotIcon className="size-3.5" />,
21
22
  validated: <ShieldCheckIcon className="size-3.5" />,
22
23
  deployed: <RocketIcon className="size-3.5" />,
@@ -25,27 +26,43 @@ const ACTION_ICON: Record<string, React.ReactNode> = {
25
26
  };
26
27
 
27
28
  const ACTION_COLOR: Record<string, string> = {
28
- created: "bg-blue-500",
29
- validated: "bg-amber-500",
30
- deployed: "bg-emerald-500",
31
- rejected: "bg-red-500",
32
- rolled_back: "bg-red-400",
29
+ created: "bg-primary/15",
30
+ validated: "bg-primary/25",
31
+ deployed: "bg-primary/30",
32
+ rejected: "bg-destructive/20",
33
+ rolled_back: "bg-destructive/15",
34
+ };
35
+
36
+ const ACTION_ICON_COLOR: Record<string, string> = {
37
+ created: "text-primary/70",
38
+ validated: "text-primary/85",
39
+ deployed: "text-primary",
40
+ rejected: "text-destructive",
41
+ rolled_back: "text-destructive/70",
33
42
  };
34
43
 
35
44
  const ACTION_RING: Record<string, string> = {
36
- created: "ring-blue-500/30",
37
- validated: "ring-amber-500/30",
38
- deployed: "ring-emerald-500/30",
39
- rejected: "ring-red-500/30",
40
- rolled_back: "ring-red-400/30",
45
+ created: "ring-primary/15",
46
+ validated: "ring-primary/25",
47
+ deployed: "ring-primary/30",
48
+ rejected: "ring-destructive/25",
49
+ rolled_back: "ring-destructive/15",
50
+ };
51
+
52
+ const ACTION_DOT: Record<string, string> = {
53
+ created: "bg-primary/40 ring-primary/30",
54
+ validated: "bg-primary/60 ring-primary/40",
55
+ deployed: "bg-primary ring-primary/50",
56
+ rejected: "bg-destructive/60 ring-destructive/40",
57
+ rolled_back: "bg-destructive/40 ring-destructive/30",
41
58
  };
42
59
 
43
60
  const ACTION_LINE: Record<string, string> = {
44
- created: "bg-blue-500/30",
45
- validated: "bg-amber-500/30",
46
- deployed: "bg-emerald-500/30",
47
- rejected: "bg-red-500/30",
48
- rolled_back: "bg-red-400/30",
61
+ created: "bg-primary/15",
62
+ validated: "bg-primary/20",
63
+ deployed: "bg-primary/25",
64
+ rejected: "bg-destructive/20",
65
+ rolled_back: "bg-destructive/15",
49
66
  };
50
67
 
51
68
  interface Props {
@@ -54,6 +71,21 @@ interface Props {
54
71
  onSelect: (proposalId: string) => void;
55
72
  }
56
73
 
74
+ function validationModeBadge(
75
+ mode?: string | null,
76
+ ): { label: string; variant: "default" | "secondary" | "outline" } | null {
77
+ switch (mode) {
78
+ case "host_replay":
79
+ return { label: "replay", variant: "default" };
80
+ case "llm_judge":
81
+ return { label: "judge", variant: "secondary" };
82
+ case "structural_guard":
83
+ return { label: "structural", variant: "outline" };
84
+ default:
85
+ return null;
86
+ }
87
+ }
88
+
57
89
  /** Group evolution entries by proposal_id, ordered newest-first. */
58
90
  function groupByProposal(entries: EvolutionEntry[]) {
59
91
  const map = new Map<string, EvolutionEntry[]>();
@@ -93,7 +125,7 @@ function PassRateDelta({ snapshot }: { snapshot: EvalSnapshot }) {
93
125
  <span
94
126
  className={cn(
95
127
  "inline-flex items-center gap-0.5 text-[10px] font-mono font-medium",
96
- isPositive ? "text-emerald-600 dark:text-emerald-400" : "text-red-500",
128
+ isPositive ? "text-primary" : "text-destructive",
97
129
  )}
98
130
  >
99
131
  {isPositive ? (
@@ -119,23 +151,35 @@ function LifecycleLegend() {
119
151
  const [open, setOpen] = useState(false);
120
152
 
121
153
  return (
122
- <div className="px-2 pb-2">
154
+ <div className="px-2 pb-3">
123
155
  <button
124
156
  type="button"
125
157
  onClick={() => setOpen(!open)}
126
- className="flex items-center gap-1 text-[10px] text-muted-foreground/70 hover:text-muted-foreground transition-colors w-full"
158
+ aria-expanded={open}
159
+ aria-controls="evolution-lifecycle-stages"
160
+ className="flex w-full items-center gap-1 text-[10px] text-muted-foreground/70 transition-colors hover:text-muted-foreground"
127
161
  >
128
162
  {open ? <ChevronDownIcon className="size-3" /> : <ChevronRightIcon className="size-3" />}
129
163
  Lifecycle stages
130
164
  </button>
131
165
  {open && (
132
- <div className="mt-1.5 space-y-1.5 rounded-md border bg-muted/30 p-2">
166
+ <div
167
+ id="evolution-lifecycle-stages"
168
+ className="mt-1.5 space-y-2.5 rounded-md border bg-muted/30 p-2"
169
+ >
133
170
  {LIFECYCLE_STEPS.map((step) => (
134
171
  <div key={step.action} className="flex items-start gap-2">
135
- <div className={cn("size-2 rounded-full mt-1 shrink-0", ACTION_COLOR[step.action])} />
136
- <div className="min-w-0">
137
- <span className="text-[10px] font-medium">{step.label}</span>
138
- <p className="text-[10px] text-muted-foreground/70 leading-tight">{step.desc}</p>
172
+ <div
173
+ className={cn(
174
+ "size-2 rounded-full shrink-0 ring-1 mt-[3px]",
175
+ ACTION_DOT[step.action],
176
+ )}
177
+ />
178
+ <div className="min-w-0 flex flex-col gap-0.5">
179
+ <span className="text-[10px] font-medium leading-none">{step.label}</span>
180
+ <span className="text-[10px] text-muted-foreground/70 leading-tight">
181
+ {step.desc}
182
+ </span>
139
183
  </div>
140
184
  </div>
141
185
  ))}
@@ -158,7 +202,7 @@ export function EvolutionTimeline({ entries, selectedProposalId, onSelect }: Pro
158
202
 
159
203
  return (
160
204
  <div className="flex flex-col gap-0">
161
- <h2 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider px-2 pb-2 sticky top-0 z-10 bg-background">
205
+ <h2 className="px-2 pb-2 text-[10px] font-semibold uppercase tracking-[0.18em] text-muted-foreground/80">
162
206
  Evolution
163
207
  </h2>
164
208
  <LifecycleLegend />
@@ -167,11 +211,13 @@ export function EvolutionTimeline({ entries, selectedProposalId, onSelect }: Pro
167
211
  const terminal = terminalAction(steps);
168
212
  const isSelected = selectedProposalId === proposalId;
169
213
  const lastStep = steps[steps.length - 1];
170
- const dotColor = ACTION_COLOR[terminal] ?? "bg-muted-foreground";
214
+ const dotColor = ACTION_COLOR[terminal] ?? "bg-muted-foreground/20";
215
+ const iconColor = ACTION_ICON_COLOR[terminal] ?? "text-muted-foreground";
171
216
  const ringColor = ACTION_RING[terminal] ?? "ring-muted-foreground/30";
172
217
  const lineColor = ACTION_LINE[terminal] ?? "bg-border";
173
218
  const isLast = groupIdx === groups.length - 1;
174
219
  const snapshot = findEvalSnapshot(steps);
220
+ const validationBadge = validationModeBadge(lastStep.validation_mode);
175
221
 
176
222
  return (
177
223
  <div key={proposalId} className="relative flex gap-3">
@@ -179,14 +225,15 @@ export function EvolutionTimeline({ entries, selectedProposalId, onSelect }: Pro
179
225
  <div className="flex flex-col items-center">
180
226
  <div
181
227
  className={cn(
182
- "flex items-center justify-center size-7 rounded-full ring-2 text-white shrink-0 z-10",
228
+ "flex items-center justify-center size-7 rounded-full ring-2 shrink-0 z-10",
183
229
  dotColor,
184
230
  ringColor,
231
+ iconColor,
185
232
  )}
186
233
  >
187
234
  {ACTION_ICON[terminal] ?? <CircleDotIcon className="size-3.5" />}
188
235
  </div>
189
- {!isLast && <div className={cn("w-0.5 flex-1 min-h-[16px]", lineColor)} />}
236
+ {!isLast && <div className={cn("w-0.5 flex-1 min-h-[8px] my-1", lineColor)} />}
190
237
  </div>
191
238
 
192
239
  {/* Content */}
@@ -215,6 +262,11 @@ export function EvolutionTimeline({ entries, selectedProposalId, onSelect }: Pro
215
262
  <span className="text-[10px] text-muted-foreground">
216
263
  {timeAgo(lastStep.timestamp)}
217
264
  </span>
265
+ {validationBadge && (
266
+ <Badge variant={validationBadge.variant} className="text-[9px] uppercase">
267
+ {validationBadge.label}
268
+ </Badge>
269
+ )}
218
270
  </div>
219
271
  {/* Pass rate delta from eval snapshot */}
220
272
  {snapshot && (
@@ -241,7 +293,7 @@ export function EvolutionTimeline({ entries, selectedProposalId, onSelect }: Pro
241
293
  key={`${s.action}-${i}`}
242
294
  className={cn(
243
295
  "size-1.5 rounded-full",
244
- ACTION_COLOR[s.action] ?? "bg-muted-foreground",
296
+ ACTION_DOT[s.action] ?? "bg-muted-foreground/40",
245
297
  )}
246
298
  />
247
299
  ))}
@@ -8,7 +8,7 @@ import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "../primitiv
8
8
  import type { OrchestrateRunReport, OrchestrateRunSkillAction } from "../types";
9
9
 
10
10
  const ACTION_ICON: Record<string, React.ReactNode> = {
11
- evolve: <ZapIcon className="size-3 text-amber-500" />,
11
+ evolve: <ZapIcon className="size-3 text-primary-accent" />,
12
12
  watch: <EyeIcon className="size-3 text-blue-500" />,
13
13
  skip: <SkipForwardIcon className="size-3 text-muted-foreground" />,
14
14
  };
@@ -66,17 +66,15 @@ function RunCard({ run }: { run: OrchestrateRunReport }) {
66
66
  <div
67
67
  className={`mt-1.5 size-2 shrink-0 rounded-full ${
68
68
  run.deployed > 0
69
- ? "bg-emerald-500"
69
+ ? "bg-primary"
70
70
  : run.evolved > 0
71
- ? "bg-amber-400"
71
+ ? "bg-primary-accent"
72
72
  : "bg-muted-foreground/40"
73
73
  }`}
74
74
  />
75
75
  <div className="flex-1 min-w-0">
76
76
  <div className="flex items-center gap-2">
77
- <span className="text-xs font-mono text-muted-foreground">
78
- {timeAgo(run.timestamp)}
79
- </span>
77
+ <span className="text-[10px] font-mono text-slate-500">{timeAgo(run.timestamp)}</span>
80
78
  {run.dry_run && (
81
79
  <Badge variant="outline" className="text-[10px] h-4 px-1.5">
82
80
  dry-run
@@ -90,7 +88,7 @@ function RunCard({ run }: { run: OrchestrateRunReport }) {
90
88
  </div>
91
89
  <div className="flex items-center gap-3 mt-1 text-xs text-muted-foreground">
92
90
  {run.deployed > 0 && (
93
- <span className="text-emerald-600 font-medium">{run.deployed} deployed</span>
91
+ <span className="text-primary font-medium">{run.deployed} deployed</span>
94
92
  )}
95
93
  {run.evolved > 0 && <span>{run.evolved} evolved</span>}
96
94
  {run.watched > 0 && <span>{run.watched} watched</span>}
@@ -104,7 +102,7 @@ function RunCard({ run }: { run: OrchestrateRunReport }) {
104
102
  </div>
105
103
  </CollapsibleTrigger>
106
104
  <CollapsibleContent>
107
- <div className="ml-5 pl-3 border-l border-border space-y-0.5 pb-2">
105
+ <div className="ml-5 pl-3 border-l border-border/15 space-y-0.5 pb-2">
108
106
  {nonSkipActions.map((action, i) => (
109
107
  <SkillActionRow key={`${action.skill}-${i}`} action={action} />
110
108
  ))}
@@ -126,7 +124,32 @@ function RunCard({ run }: { run: OrchestrateRunReport }) {
126
124
  );
127
125
  }
128
126
 
129
- export function OrchestrateRunsPanel({ runs }: { runs: OrchestrateRunReport[] }) {
127
+ export function OrchestrateRunsPanel({
128
+ runs,
129
+ embedded = false,
130
+ }: {
131
+ runs: OrchestrateRunReport[];
132
+ embedded?: boolean;
133
+ }) {
134
+ const totalDeployed = runs.reduce((sum, r) => sum + r.deployed, 0);
135
+ const content =
136
+ runs.length === 0 ? (
137
+ <p className="py-4 text-center text-sm text-muted-foreground">
138
+ No orchestrate runs yet. Run{" "}
139
+ <code className="rounded bg-muted px-1 py-0.5 text-xs">selftune orchestrate</code> to start.
140
+ </p>
141
+ ) : (
142
+ <div className="space-y-0">
143
+ {runs.slice(0, 10).map((run) => (
144
+ <RunCard key={run.run_id} run={run} />
145
+ ))}
146
+ </div>
147
+ );
148
+
149
+ if (embedded) {
150
+ return <div>{content}</div>;
151
+ }
152
+
130
153
  if (runs.length === 0) {
131
154
  return (
132
155
  <Card>
@@ -147,8 +170,6 @@ export function OrchestrateRunsPanel({ runs }: { runs: OrchestrateRunReport[] })
147
170
  );
148
171
  }
149
172
 
150
- const totalDeployed = runs.reduce((sum, r) => sum + r.deployed, 0);
151
-
152
173
  return (
153
174
  <Card>
154
175
  <CardHeader>
@@ -161,11 +182,7 @@ export function OrchestrateRunsPanel({ runs }: { runs: OrchestrateRunReport[] })
161
182
  {totalDeployed > 0 && <> &middot; {totalDeployed} total deployments</>}
162
183
  </CardDescription>
163
184
  </CardHeader>
164
- <CardContent className="space-y-0">
165
- {runs.slice(0, 10).map((run) => (
166
- <RunCard key={run.run_id} run={run} />
167
- ))}
168
- </CardContent>
185
+ <CardContent>{content}</CardContent>
169
186
  </Card>
170
187
  );
171
188
  }
@@ -13,7 +13,77 @@ export interface RecentActivityItem {
13
13
  is_live: boolean;
14
14
  }
15
15
 
16
- export function RecentActivityFeed({ items }: { items: RecentActivityItem[] }) {
16
+ export function RecentActivityFeed({
17
+ items,
18
+ embedded = false,
19
+ }: {
20
+ items: RecentActivityItem[];
21
+ embedded?: boolean;
22
+ }) {
23
+ const content =
24
+ items.length === 0 ? (
25
+ <p className="py-6 text-center text-sm text-muted-foreground">No recent skill invocations</p>
26
+ ) : (
27
+ <div className="space-y-2.5">
28
+ {items.slice(0, 20).map((item, i) => (
29
+ <div
30
+ key={`${item.session_id}-${item.skill_name}-${i}`}
31
+ className="flex gap-3 rounded-md p-1.5"
32
+ >
33
+ <div
34
+ className={`mt-0.5 w-10 h-10 shrink-0 rounded-xl bg-input flex items-center justify-center`}
35
+ >
36
+ <div
37
+ className={`size-2 rounded-full ${
38
+ item.triggered
39
+ ? "bg-primary shadow-[0_0_8px_rgba(79,242,255,0.6)]"
40
+ : "bg-muted-foreground/40"
41
+ }`}
42
+ />
43
+ </div>
44
+ <div className="flex-1 min-w-0 space-y-0.5">
45
+ <div className="flex flex-wrap items-center gap-2">
46
+ <span className="truncate font-bold text-sm">{item.skill_name}</span>
47
+ {item.is_live && (
48
+ <Badge variant="outline" className="h-4 gap-1 px-1 text-[10px]">
49
+ <CircleDotIcon className="size-2.5 text-primary" />
50
+ live
51
+ </Badge>
52
+ )}
53
+ {item.triggered ? (
54
+ <Badge
55
+ variant="default"
56
+ className="h-4 px-1 text-[10px] font-bold uppercase tracking-tighter bg-primary/10 text-primary"
57
+ >
58
+ triggered
59
+ </Badge>
60
+ ) : (
61
+ <Badge
62
+ variant="secondary"
63
+ className="h-4 px-1 text-[10px] font-bold uppercase tracking-tighter"
64
+ >
65
+ checked
66
+ </Badge>
67
+ )}
68
+ <span className="ml-auto shrink-0 font-mono text-[10px] text-slate-500">
69
+ {timeAgo(item.timestamp)}
70
+ </span>
71
+ </div>
72
+ {item.query && (
73
+ <p className="line-clamp-1 text-sm text-card-foreground leading-relaxed">
74
+ {item.query}
75
+ </p>
76
+ )}
77
+ </div>
78
+ </div>
79
+ ))}
80
+ </div>
81
+ );
82
+
83
+ if (embedded) {
84
+ return <div>{content}</div>;
85
+ }
86
+
17
87
  if (items.length === 0) {
18
88
  return (
19
89
  <Card>
@@ -41,46 +111,7 @@ export function RecentActivityFeed({ items }: { items: RecentActivityItem[] }) {
41
111
  </CardTitle>
42
112
  <CardDescription>Latest skill invocations across sessions</CardDescription>
43
113
  </CardHeader>
44
- <CardContent className="space-y-2.5">
45
- {items.slice(0, 20).map((item, i) => (
46
- <div
47
- key={`${item.session_id}-${item.skill_name}-${i}`}
48
- className="flex gap-3 rounded-md p-1.5"
49
- >
50
- <div
51
- className={`mt-1 size-2 shrink-0 rounded-full ${
52
- item.triggered ? "bg-emerald-500" : "bg-muted-foreground/40"
53
- }`}
54
- />
55
- <div className="flex-1 min-w-0 space-y-0.5">
56
- <div className="flex items-center gap-2 flex-wrap">
57
- <span className="text-xs font-medium truncate">{item.skill_name}</span>
58
- {item.is_live && (
59
- <Badge variant="outline" className="h-4 px-1 text-[10px] gap-1">
60
- <CircleDotIcon className="size-2.5 text-emerald-500" />
61
- live
62
- </Badge>
63
- )}
64
- {item.triggered ? (
65
- <Badge variant="default" className="h-4 px-1 text-[10px]">
66
- triggered
67
- </Badge>
68
- ) : (
69
- <Badge variant="secondary" className="h-4 px-1 text-[10px]">
70
- checked
71
- </Badge>
72
- )}
73
- <span className="text-[10px] text-muted-foreground font-mono ml-auto shrink-0">
74
- {timeAgo(item.timestamp)}
75
- </span>
76
- </div>
77
- {item.query && (
78
- <p className="text-xs text-muted-foreground line-clamp-1 font-mono">{item.query}</p>
79
- )}
80
- </div>
81
- </div>
82
- ))}
83
- </CardContent>
114
+ <CardContent>{content}</CardContent>
84
115
  </Card>
85
116
  );
86
117
  }
@@ -24,6 +24,9 @@ interface SectionCardsProps {
24
24
  activeSessionsCount?: number;
25
25
  }
26
26
 
27
+ const CARD_DESCRIPTION_CLASS =
28
+ "flex items-center gap-1.5 text-[10px] uppercase tracking-widest text-slate-500";
29
+
27
30
  export function SectionCards({
28
31
  skillsCount,
29
32
  avgPassRate,
@@ -38,10 +41,10 @@ export function SectionCards({
38
41
  const passRateGood = avgPassRate !== null && avgPassRate >= 0.7;
39
42
 
40
43
  return (
41
- <div className="grid grid-cols-1 gap-4 px-4 *:data-[slot=card]:shadow-xs lg:px-6 @xl/main:grid-cols-2 @5xl/main:grid-cols-3">
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">
42
45
  <Card className="@container/card">
43
46
  <CardHeader>
44
- <CardDescription className="flex items-center gap-1.5">
47
+ <CardDescription className={CARD_DESCRIPTION_CLASS}>
45
48
  <LayersIcon className="size-3.5" />
46
49
  Skills Monitored
47
50
  <InfoTip text="Total number of skills detected and being tracked by selftune" />
@@ -60,7 +63,7 @@ export function SectionCards({
60
63
 
61
64
  <Card className="@container/card">
62
65
  <CardHeader>
63
- <CardDescription className="flex items-center gap-1.5">
66
+ <CardDescription className={CARD_DESCRIPTION_CLASS}>
64
67
  <FlaskConicalIcon className="size-3.5" />
65
68
  Avg Trigger Rate
66
69
  <InfoTip text="Average percentage of skill checks that resulted in a trigger across all graded skills (5+ checks). Run selftune evolve to improve this." />
@@ -91,7 +94,7 @@ export function SectionCards({
91
94
 
92
95
  <Card className="@container/card">
93
96
  <CardHeader>
94
- <CardDescription className="flex items-center gap-1.5">
97
+ <CardDescription className={CARD_DESCRIPTION_CLASS}>
95
98
  <SearchXIcon className="size-3.5" />
96
99
  Unmatched Queries
97
100
  <InfoTip text="User prompts that didn't match any skill's trigger criteria — potential gaps in coverage" />
@@ -112,7 +115,7 @@ export function SectionCards({
112
115
 
113
116
  <Card className="@container/card">
114
117
  <CardHeader>
115
- <CardDescription className="flex items-center gap-1.5">
118
+ <CardDescription className={CARD_DESCRIPTION_CLASS}>
116
119
  <ActivityIcon className="size-3.5" />
117
120
  Sessions
118
121
  <InfoTip text="Total agent sessions that have been recorded and analyzed" />
@@ -124,8 +127,8 @@ export function SectionCards({
124
127
  <CardAction>
125
128
  <Badge variant="outline" className="gap-1.5">
126
129
  <span className="relative flex size-2">
127
- <span className="absolute inline-flex size-full animate-ping rounded-full bg-emerald-400 opacity-75" />
128
- <span className="relative inline-flex size-2 rounded-full bg-emerald-500" />
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)]" />
129
132
  </span>
130
133
  {activeSessionsCount} in progress
131
134
  </Badge>
@@ -136,7 +139,7 @@ export function SectionCards({
136
139
 
137
140
  <Card className="@container/card">
138
141
  <CardHeader>
139
- <CardDescription className="flex items-center gap-1.5">
142
+ <CardDescription className={CARD_DESCRIPTION_CLASS}>
140
143
  <AlertTriangleIcon className="size-3.5" />
141
144
  Undeployed Proposals
142
145
  <InfoTip text="Evolution proposals that have been generated but not yet validated or deployed. Requires running selftune evolve." />
@@ -158,7 +161,7 @@ export function SectionCards({
158
161
 
159
162
  <Card className="@container/card">
160
163
  <CardHeader>
161
- <CardDescription className="flex items-center gap-1.5">
164
+ <CardDescription className={CARD_DESCRIPTION_CLASS}>
162
165
  <EyeIcon className="size-3.5" />
163
166
  Total Evidence
164
167
  <InfoTip text="Number of evidence entries documenting skill changes with before/after validation results. Requires running selftune evolve." />
@@ -12,7 +12,7 @@ function Card({
12
12
  data-slot="card"
13
13
  data-size={size}
14
14
  className={cn(
15
- "group/card flex flex-col gap-4 overflow-hidden rounded-xl bg-card py-4 text-sm text-card-foreground ring-1 ring-foreground/10 has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl",
15
+ "group/card flex flex-col gap-4 overflow-hidden rounded-2xl bg-card py-4 text-sm text-card-foreground ring-1 ring-foreground/5 has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl",
16
16
  className,
17
17
  )}
18
18
  {...props}
@@ -31,6 +31,10 @@ export interface EvolutionEntry {
31
31
  action: string;
32
32
  details: string;
33
33
  eval_snapshot?: EvalSnapshot | null;
34
+ validation_mode?: "structural_guard" | "host_replay" | "llm_judge" | null;
35
+ validation_agent?: string | null;
36
+ validation_fixture_id?: string | null;
37
+ validation_evidence_ref?: string | null;
34
38
  }
35
39
 
36
40
  export interface UnmatchedQuery {
package/skill/SKILL.md CHANGED
@@ -89,6 +89,7 @@ selftune eval generate --skill <name> [--list-skills] [--stats] [--max N] [
89
89
  selftune eval unit-test --skill <name> --tests <path> [--run-agent] [--generate]
90
90
  selftune eval import --dir <path> --skill <name> --output <path> [--match-strategy exact|fuzzy]
91
91
  selftune eval composability --skill <name> [--window N] [--telemetry-log <path>]
92
+ selftune eval family-overlap --prefix <family-> | --skills <a,b,c> [--parent-skill <name>] [--min-overlap 0.3] [--min-shared 2]
92
93
 
93
94
  # Other commands
94
95
  selftune watch --skill <name> --skill-path <path> [--auto-rollback]
@@ -96,6 +97,8 @@ selftune status
96
97
  selftune last
97
98
  selftune doctor
98
99
  selftune dashboard [--port <port>] [--no-open]
100
+ selftune contributions [status|preview <skill>|upload [--dry-run]|approve <skill>|revoke <skill>|default <ask|always|never>|reset]
101
+ selftune creator-contributions [status|enable --skill <name>|enable --all [--prefix <value>]|disable --skill <name>]
99
102
  selftune contribute [--skill NAME] [--preview] [--sanitize LEVEL] [--submit]
100
103
  selftune cron setup [--dry-run] # auto-detect platform (cron/launchd/systemd)
101
104
  selftune cron setup --platform openclaw [--dry-run] [--tz <timezone>] # OpenClaw-specific
@@ -115,6 +118,7 @@ selftune badge --skill <name> [--format svg|markdown|url] [--output PATH]
115
118
  # Maintenance
116
119
  selftune quickstart
117
120
  selftune repair-skill-usage [--since DATE] [--dry-run]
121
+ selftune recover [--full] [--force] [--since DATE]
118
122
  selftune export-canonical [--out FILE] [--platform NAME] [--record-kind KIND] [--pretty] [--push-payload]
119
123
  selftune uninstall [--dry-run] [--keep-logs] [--npm-uninstall]
120
124
 
@@ -141,6 +145,8 @@ selftune status # shows c
141
145
  | doctor, health, hooks, broken, diagnose, not working, something wrong | Doctor | Workflows/Doctor.md |
142
146
  | ingest, import, codex logs, opencode, openclaw, wrap codex | Ingest | Workflows/Ingest.md |
143
147
  | replay, backfill, claude transcripts, historical sessions | Replay | Workflows/Replay.md |
148
+ | contributions, sharing preferences, opt in creator sharing, opt out creator sharing, approve contributions, revoke contributions, preview contributions, upload contributions, relay queue, contribution upload, contribution preview | Contributions | Workflows/Contributions.md |
149
+ | creator contributions, bundle contribution config, selftune.contribute.json, enable creator contribution, disable creator contribution, bulk enable creator contribution, enable all creator contributions, creator prefix config, --all, --prefix | CreatorContributions | Workflows/CreatorContributions.md |
144
150
  | contribute, share, community, export data, anonymized, give back | Contribute | Workflows/Contribute.md |
145
151
  | init, setup, set up, bootstrap, first time, install, configure selftune, alpha, enroll, alpha enrollment, cloud link, upload credential | Initialize | Workflows/Initialize.md |
146
152
  | cron, schedule, automate evolution, run automatically | Cron | Workflows/Cron.md |
@@ -149,7 +155,7 @@ selftune status # shows c
149
155
  | evolution memory, session continuity, what happened last | EvolutionMemory | Workflows/EvolutionMemory.md |
150
156
  | grade baseline, baseline lift, adds value, skill value, no-skill comparison | Baseline | Workflows/Baseline.md |
151
157
  | eval unit-test, skill test, test skill, generate tests, run tests | UnitTest | Workflows/UnitTest.md |
152
- | eval composability, co-occurrence, skill conflicts, skills together | Composability | Workflows/Composability.md |
158
+ | eval composability, co-occurrence, skill conflicts, skills together, family overlap, sibling confusion, consolidate skill family | Composability | Workflows/Composability.md |
153
159
  | eval import, skillsbench, external evals, benchmark tasks | ImportSkillsBench | Workflows/ImportSkillsBench.md |
154
160
  | telemetry, analytics, disable analytics, opt out, tracking, privacy | Telemetry | Workflows/Telemetry.md |
155
161
  | orchestrate, autonomous, full loop, improve all skills, run selftune loop | Orchestrate | Workflows/Orchestrate.md |
@@ -157,6 +163,7 @@ selftune status # shows c
157
163
  | badge, readme badge, skill badge, health badge | Badge | Workflows/Badge.md |
158
164
  | workflows, discover workflows, list workflows, multi-skill workflows | Workflows | Workflows/Workflows.md |
159
165
  | alpha upload, upload data, send alpha data, manual upload, dry run upload | AlphaUpload | Workflows/AlphaUpload.md |
166
+ | recover, rebuild sqlite, recover db, legacy backfill, restore from export snapshot | Recover | Workflows/Recover.md |
160
167
  | quickstart, getting started, onboard, first time setup, new user | Quickstart | Workflows/Quickstart.md |
161
168
  | uninstall, remove selftune, clean up, teardown | Uninstall | Workflows/Uninstall.md |
162
169
  | repair, rebuild usage, fix skill usage, trustworthy usage, repair-skill-usage | RepairSkillUsage | Workflows/RepairSkillUsage.md |
@@ -345,6 +352,9 @@ accomplish a task _using_ a skill, route to that skill instead.
345
352
  | `Workflows/Quickstart.md` | Guided onboarding: init, ingest, status | First-time setup for new users |
346
353
  | `Workflows/Uninstall.md` | Clean removal of selftune data and config | When removing selftune completely |
347
354
  | `Workflows/RepairSkillUsage.md` | Rebuild skill usage from source transcripts | When skill usage data seems inaccurate |
355
+ | `Workflows/Recover.md` | Recover SQLite from legacy/exported JSONL | When rebuilding or backfilling SQLite |
356
+ | `Workflows/Contributions.md` | Manage creator-directed sharing preferences | When approving or revoking creator contribution |
357
+ | `Workflows/CreatorContributions.md` | Manage bundled `selftune.contribute.json` configs | When preparing a skill package for creator contributions |
348
358
  | `Workflows/ExportCanonical.md` | Export canonical telemetry for downstream use | When exporting data for external consumption |
349
359
  | `Workflows/Hook.md` | Manual hook invocation for debugging | When debugging or testing hooks manually |
350
360
  | `references/logs.md` | Log file formats (telemetry, usage, queries, audit) | When parsing or debugging log files |
@@ -24,6 +24,10 @@ selftune alpha upload [--dry-run]
24
24
  4. Build V2 push envelopes and flush them to the cloud API
25
25
  5. Print a JSON summary with `enrolled`, `prepared`, `sent`, `failed`, `skipped`, and optional `guidance`
26
26
 
27
+ `selftune sync` already triggers an upload cycle automatically when alpha is
28
+ enrolled. Use this workflow when you want a manual upload now or want to see a
29
+ dry-run summary of what SQLite-backed staging would send.
30
+
27
31
  ## Examples
28
32
 
29
33
  Preview the upload: