selftune 0.2.18 → 0.2.19

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 (65) hide show
  1. package/README.md +9 -4
  2. package/apps/local-dashboard/dist/assets/index-DnhnXQm6.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 +205 -1
  17. package/cli/selftune/dashboard-server.ts +45 -11
  18. package/cli/selftune/eval/family-overlap.ts +395 -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/export.ts +2 -2
  22. package/cli/selftune/index.ts +41 -5
  23. package/cli/selftune/ingestors/codex-rollout.ts +31 -35
  24. package/cli/selftune/ingestors/codex-wrapper.ts +32 -24
  25. package/cli/selftune/localdb/db.ts +2 -2
  26. package/cli/selftune/localdb/queries.ts +701 -30
  27. package/cli/selftune/localdb/schema.ts +20 -0
  28. package/cli/selftune/recover.ts +153 -0
  29. package/cli/selftune/repair/skill-usage.ts +363 -4
  30. package/cli/selftune/routes/actions.ts +35 -1
  31. package/cli/selftune/routes/analytics.ts +14 -0
  32. package/cli/selftune/routes/index.ts +1 -0
  33. package/cli/selftune/routes/overview.ts +112 -4
  34. package/cli/selftune/routes/skill-report.ts +569 -10
  35. package/cli/selftune/status.ts +81 -2
  36. package/cli/selftune/sync.ts +56 -2
  37. package/cli/selftune/trust-model.ts +66 -0
  38. package/cli/selftune/types.ts +49 -0
  39. package/cli/selftune/utils/skill-detection.ts +43 -0
  40. package/cli/selftune/watchlist.ts +65 -0
  41. package/package.json +1 -1
  42. package/packages/ui/src/components/ActivityTimeline.tsx +165 -150
  43. package/packages/ui/src/components/EvidenceViewer.tsx +335 -144
  44. package/packages/ui/src/components/EvolutionTimeline.tsx +58 -28
  45. package/packages/ui/src/components/OrchestrateRunsPanel.tsx +33 -16
  46. package/packages/ui/src/components/RecentActivityFeed.tsx +72 -41
  47. package/packages/ui/src/components/section-cards.tsx +12 -9
  48. package/packages/ui/src/primitives/card.tsx +1 -1
  49. package/skill/SKILL.md +11 -1
  50. package/skill/Workflows/AlphaUpload.md +4 -0
  51. package/skill/Workflows/Composability.md +64 -0
  52. package/skill/Workflows/Contribute.md +6 -3
  53. package/skill/Workflows/Contributions.md +97 -0
  54. package/skill/Workflows/CreatorContributions.md +74 -0
  55. package/skill/Workflows/Dashboard.md +31 -0
  56. package/skill/Workflows/Evals.md +57 -8
  57. package/skill/Workflows/Ingest.md +7 -0
  58. package/skill/Workflows/Initialize.md +20 -1
  59. package/skill/Workflows/Recover.md +84 -0
  60. package/skill/Workflows/RepairSkillUsage.md +12 -4
  61. package/skill/Workflows/Sync.md +18 -12
  62. package/apps/local-dashboard/dist/assets/index-BMIS6uUh.css +0 -2
  63. package/apps/local-dashboard/dist/assets/index-DOu3iLD9.js +0 -16
  64. package/apps/local-dashboard/dist/assets/vendor-table-pHbDxq36.js +0 -8
  65. package/apps/local-dashboard/dist/assets/vendor-ui-DIwlrGlb.js +0 -12
@@ -25,27 +25,43 @@ const ACTION_ICON: Record<string, React.ReactNode> = {
25
25
  };
26
26
 
27
27
  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",
28
+ created: "bg-primary/15",
29
+ validated: "bg-primary/25",
30
+ deployed: "bg-primary/30",
31
+ rejected: "bg-destructive/20",
32
+ rolled_back: "bg-destructive/15",
33
+ };
34
+
35
+ const ACTION_ICON_COLOR: Record<string, string> = {
36
+ created: "text-primary/70",
37
+ validated: "text-primary/85",
38
+ deployed: "text-primary",
39
+ rejected: "text-destructive",
40
+ rolled_back: "text-destructive/70",
33
41
  };
34
42
 
35
43
  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",
44
+ created: "ring-primary/15",
45
+ validated: "ring-primary/25",
46
+ deployed: "ring-primary/30",
47
+ rejected: "ring-destructive/25",
48
+ rolled_back: "ring-destructive/15",
49
+ };
50
+
51
+ const ACTION_DOT: Record<string, string> = {
52
+ created: "bg-primary/40 ring-primary/30",
53
+ validated: "bg-primary/60 ring-primary/40",
54
+ deployed: "bg-primary ring-primary/50",
55
+ rejected: "bg-destructive/60 ring-destructive/40",
56
+ rolled_back: "bg-destructive/40 ring-destructive/30",
41
57
  };
42
58
 
43
59
  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",
60
+ created: "bg-primary/15",
61
+ validated: "bg-primary/20",
62
+ deployed: "bg-primary/25",
63
+ rejected: "bg-destructive/20",
64
+ rolled_back: "bg-destructive/15",
49
65
  };
50
66
 
51
67
  interface Props {
@@ -93,7 +109,7 @@ function PassRateDelta({ snapshot }: { snapshot: EvalSnapshot }) {
93
109
  <span
94
110
  className={cn(
95
111
  "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",
112
+ isPositive ? "text-primary" : "text-destructive",
97
113
  )}
98
114
  >
99
115
  {isPositive ? (
@@ -119,23 +135,35 @@ function LifecycleLegend() {
119
135
  const [open, setOpen] = useState(false);
120
136
 
121
137
  return (
122
- <div className="px-2 pb-2">
138
+ <div className="px-2 pb-3">
123
139
  <button
124
140
  type="button"
125
141
  onClick={() => setOpen(!open)}
126
- className="flex items-center gap-1 text-[10px] text-muted-foreground/70 hover:text-muted-foreground transition-colors w-full"
142
+ aria-expanded={open}
143
+ aria-controls="evolution-lifecycle-stages"
144
+ className="flex w-full items-center gap-1 text-[10px] text-muted-foreground/70 transition-colors hover:text-muted-foreground"
127
145
  >
128
146
  {open ? <ChevronDownIcon className="size-3" /> : <ChevronRightIcon className="size-3" />}
129
147
  Lifecycle stages
130
148
  </button>
131
149
  {open && (
132
- <div className="mt-1.5 space-y-1.5 rounded-md border bg-muted/30 p-2">
150
+ <div
151
+ id="evolution-lifecycle-stages"
152
+ className="mt-1.5 space-y-2.5 rounded-md border bg-muted/30 p-2"
153
+ >
133
154
  {LIFECYCLE_STEPS.map((step) => (
134
155
  <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>
156
+ <div
157
+ className={cn(
158
+ "size-2 rounded-full shrink-0 ring-1 mt-[3px]",
159
+ ACTION_DOT[step.action],
160
+ )}
161
+ />
162
+ <div className="min-w-0 flex flex-col gap-0.5">
163
+ <span className="text-[10px] font-medium leading-none">{step.label}</span>
164
+ <span className="text-[10px] text-muted-foreground/70 leading-tight">
165
+ {step.desc}
166
+ </span>
139
167
  </div>
140
168
  </div>
141
169
  ))}
@@ -158,7 +186,7 @@ export function EvolutionTimeline({ entries, selectedProposalId, onSelect }: Pro
158
186
 
159
187
  return (
160
188
  <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">
189
+ <h2 className="px-2 pb-2 text-[10px] font-semibold uppercase tracking-[0.18em] text-muted-foreground/80">
162
190
  Evolution
163
191
  </h2>
164
192
  <LifecycleLegend />
@@ -167,7 +195,8 @@ export function EvolutionTimeline({ entries, selectedProposalId, onSelect }: Pro
167
195
  const terminal = terminalAction(steps);
168
196
  const isSelected = selectedProposalId === proposalId;
169
197
  const lastStep = steps[steps.length - 1];
170
- const dotColor = ACTION_COLOR[terminal] ?? "bg-muted-foreground";
198
+ const dotColor = ACTION_COLOR[terminal] ?? "bg-muted-foreground/20";
199
+ const iconColor = ACTION_ICON_COLOR[terminal] ?? "text-muted-foreground";
171
200
  const ringColor = ACTION_RING[terminal] ?? "ring-muted-foreground/30";
172
201
  const lineColor = ACTION_LINE[terminal] ?? "bg-border";
173
202
  const isLast = groupIdx === groups.length - 1;
@@ -179,14 +208,15 @@ export function EvolutionTimeline({ entries, selectedProposalId, onSelect }: Pro
179
208
  <div className="flex flex-col items-center">
180
209
  <div
181
210
  className={cn(
182
- "flex items-center justify-center size-7 rounded-full ring-2 text-white shrink-0 z-10",
211
+ "flex items-center justify-center size-7 rounded-full ring-2 shrink-0 z-10",
183
212
  dotColor,
184
213
  ringColor,
214
+ iconColor,
185
215
  )}
186
216
  >
187
217
  {ACTION_ICON[terminal] ?? <CircleDotIcon className="size-3.5" />}
188
218
  </div>
189
- {!isLast && <div className={cn("w-0.5 flex-1 min-h-[16px]", lineColor)} />}
219
+ {!isLast && <div className={cn("w-0.5 flex-1 min-h-[8px] my-1", lineColor)} />}
190
220
  </div>
191
221
 
192
222
  {/* Content */}
@@ -241,7 +271,7 @@ export function EvolutionTimeline({ entries, selectedProposalId, onSelect }: Pro
241
271
  key={`${s.action}-${i}`}
242
272
  className={cn(
243
273
  "size-1.5 rounded-full",
244
- ACTION_COLOR[s.action] ?? "bg-muted-foreground",
274
+ ACTION_DOT[s.action] ?? "bg-muted-foreground/40",
245
275
  )}
246
276
  />
247
277
  ))}
@@ -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}
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:
@@ -4,12 +4,27 @@ Analyze how skills interact when triggered together in the same session.
4
4
  Detects conflict candidates — skill pairs that produce more errors when
5
5
  co-occurring than when used alone.
6
6
 
7
+ Use the same workflow when the user is asking whether a sibling skill family
8
+ should stay split apart or be consolidated under one parent skill.
9
+
7
10
  ## Default Command
8
11
 
9
12
  ```bash
10
13
  selftune eval composability --skill <name> [options]
11
14
  ```
12
15
 
16
+ ## Family Overlap Command
17
+
18
+ ```bash
19
+ selftune eval family-overlap --prefix <family-> [options]
20
+ ```
21
+
22
+ Or analyze an explicit set of siblings:
23
+
24
+ ```bash
25
+ selftune eval family-overlap --skills <skill-a,skill-b,skill-c> [options]
26
+ ```
27
+
13
28
  ## Options
14
29
 
15
30
  | Flag | Description | Default |
@@ -18,6 +33,16 @@ selftune eval composability --skill <name> [options]
18
33
  | `--window <n>` | Only analyze sessions from last N days | All sessions |
19
34
  | `--telemetry-log <path>` | Path to telemetry log | `~/.claude/session_telemetry_log.jsonl` |
20
35
 
36
+ ### Family Overlap Options
37
+
38
+ | Flag | Description | Default |
39
+ | ----------------------- | ------------------------------------------------------------------ | ------- |
40
+ | `--prefix <family->` | Analyze all installed/observed sibling skills with this prefix | Required unless `--skills` |
41
+ | `--skills <a,b,c>` | Analyze a specific skill family | Required unless `--prefix` |
42
+ | `--parent-skill <name>` | Override the suggested consolidated parent skill name | Derived from prefix |
43
+ | `--min-overlap <pct>` | Minimum positive-query overlap to flag consolidation pressure | `0.3` |
44
+ | `--min-shared <n>` | Minimum shared positive queries to flag a sibling pair | `2` |
45
+
21
46
  ## Output Format
22
47
 
23
48
  ```json
@@ -60,6 +85,25 @@ The analyzer is a pure function that computes conflict scores from telemetry:
60
85
  3. Pairs with `conflict_score > 0.3` are flagged as conflict candidates
61
86
  4. Results sorted by co-occurrence count (most common first)
62
87
 
88
+ ## How Family Overlap Works
89
+
90
+ The family-overlap analyzer answers a different question:
91
+
92
+ 1. Build a trusted positive query set for each sibling skill
93
+ 2. Compare every pair of siblings using exact-query overlap
94
+ 3. Flag pairs whose overlap crosses the configured threshold
95
+ 4. If overlap is persistent across the family, emit:
96
+ - consolidation recommendation
97
+ - draft parent skill name
98
+ - internal workflow mapping
99
+ - compatibility alias / migration notes
100
+
101
+ This is for packaging questions like:
102
+
103
+ - "Should `sc-search`, `sc-model`, and `sc-compare` really be one parent skill?"
104
+ - "Are my sibling skills competing for the same user intent?"
105
+ - "Should I stop evolving these independently and redesign the family?"
106
+
63
107
  ## Steps
64
108
 
65
109
  ### 1. Run Analysis
@@ -86,6 +130,18 @@ When conflict candidates are identified, present them to the user with recommend
86
130
  - Consider evolving descriptions to reduce false triggers
87
131
  - Use the `pattern-analyst` agent for deeper cross-skill analysis
88
132
 
133
+ ### 4. Investigate Family Consolidation
134
+
135
+ ```bash
136
+ selftune eval family-overlap --prefix sc-
137
+ ```
138
+
139
+ Interpretation:
140
+
141
+ - `consolidation_candidate: false` means keep improving the sibling descriptions/workflows separately
142
+ - `consolidation_candidate: true` means the problem is likely packaging, not just wording
143
+ - `refactor_proposal` is a draft for human review only; do not auto-deploy a family rewrite
144
+
89
145
  ## Subagent Escalation
90
146
 
91
147
  For deep cross-skill analysis beyond what the composability command provides,
@@ -110,3 +166,11 @@ resolution plan with trigger ownership recommendations.
110
166
  **"Why are sessions with multiple skills failing?"**
111
167
 
112
168
  > Run composability for each skill involved, look for high conflict scores.
169
+
170
+ **"Are my State Change skills too fragmented?"**
171
+
172
+ > `selftune eval family-overlap --prefix sc-`
173
+
174
+ **"Should I consolidate this sibling skill family?"**
175
+
176
+ > Run `selftune eval family-overlap` and look for `consolidation_candidate` plus the `refactor_proposal`.