selftune 0.2.9 → 0.2.12

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 (140) hide show
  1. package/README.md +35 -35
  2. package/apps/local-dashboard/dist/assets/index-4_dAY17K.js +16 -0
  3. package/apps/local-dashboard/dist/assets/index-BxV5WZHc.css +2 -0
  4. package/apps/local-dashboard/dist/assets/rolldown-runtime-Dw2cE7zH.js +1 -0
  5. package/apps/local-dashboard/dist/assets/vendor-react-CKkiCskZ.js +11 -0
  6. package/apps/local-dashboard/dist/assets/vendor-table-pHbDxq36.js +8 -0
  7. package/apps/local-dashboard/dist/assets/vendor-ui-7xD7fNEU.js +12 -0
  8. package/apps/local-dashboard/dist/index.html +16 -15
  9. package/bin/selftune.cjs +1 -1
  10. package/cli/selftune/activation-rules.ts +1 -0
  11. package/cli/selftune/alpha-upload/build-payloads.ts +18 -2
  12. package/cli/selftune/alpha-upload/stage-canonical.ts +94 -0
  13. package/cli/selftune/auth/device-code.ts +32 -0
  14. package/cli/selftune/auto-update.ts +12 -0
  15. package/cli/selftune/badge/badge.ts +1 -0
  16. package/cli/selftune/canonical-export.ts +5 -0
  17. package/cli/selftune/claude-agents.ts +154 -0
  18. package/cli/selftune/contribute/bundle.ts +1 -0
  19. package/cli/selftune/contribute/contribute.ts +1 -0
  20. package/cli/selftune/cron/setup.ts +2 -2
  21. package/cli/selftune/dashboard-server.ts +1 -0
  22. package/cli/selftune/eval/hooks-to-evals.ts +1 -0
  23. package/cli/selftune/eval/import-skillsbench.ts +1 -0
  24. package/cli/selftune/eval/synthetic-evals.ts +2 -3
  25. package/cli/selftune/eval/unit-test.ts +1 -0
  26. package/cli/selftune/evolution/deploy-proposal.ts +9 -238
  27. package/cli/selftune/evolution/evolve-body.ts +93 -6
  28. package/cli/selftune/evolution/evolve.ts +3 -7
  29. package/cli/selftune/evolution/propose-body.ts +3 -2
  30. package/cli/selftune/evolution/propose-routing.ts +3 -2
  31. package/cli/selftune/evolution/refine-body.ts +3 -2
  32. package/cli/selftune/evolution/rollback.ts +1 -1
  33. package/cli/selftune/export.ts +1 -0
  34. package/cli/selftune/grading/grade-session.ts +8 -0
  35. package/cli/selftune/hooks/auto-activate.ts +1 -0
  36. package/cli/selftune/hooks/evolution-guard.ts +1 -1
  37. package/cli/selftune/hooks/prompt-log.ts +1 -0
  38. package/cli/selftune/hooks/session-stop.ts +34 -40
  39. package/cli/selftune/hooks/skill-change-guard.ts +1 -0
  40. package/cli/selftune/hooks/skill-eval.ts +1 -1
  41. package/cli/selftune/index.ts +23 -14
  42. package/cli/selftune/ingestors/claude-replay.ts +1 -0
  43. package/cli/selftune/ingestors/codex-rollout.ts +1 -0
  44. package/cli/selftune/ingestors/codex-wrapper.ts +1 -0
  45. package/cli/selftune/ingestors/openclaw-ingest.ts +1 -0
  46. package/cli/selftune/ingestors/opencode-ingest.ts +1 -0
  47. package/cli/selftune/init.ts +121 -29
  48. package/cli/selftune/localdb/db.ts +1 -0
  49. package/cli/selftune/localdb/direct-write.ts +39 -0
  50. package/cli/selftune/localdb/materialize.ts +2 -0
  51. package/cli/selftune/localdb/queries.ts +53 -0
  52. package/cli/selftune/localdb/schema.ts +28 -0
  53. package/cli/selftune/normalization.ts +1 -0
  54. package/cli/selftune/observability.ts +1 -0
  55. package/cli/selftune/repair/skill-usage.ts +1 -0
  56. package/cli/selftune/routes/orchestrate-runs.ts +1 -0
  57. package/cli/selftune/routes/overview.ts +1 -0
  58. package/cli/selftune/routes/report.ts +1 -1
  59. package/cli/selftune/routes/skill-report.ts +2 -1
  60. package/cli/selftune/status.ts +1 -1
  61. package/cli/selftune/sync.ts +30 -1
  62. package/cli/selftune/uninstall.ts +412 -0
  63. package/cli/selftune/utils/canonical-log.ts +2 -0
  64. package/cli/selftune/utils/frontmatter.ts +50 -7
  65. package/cli/selftune/utils/jsonl.ts +1 -0
  66. package/cli/selftune/utils/llm-call.ts +131 -3
  67. package/cli/selftune/utils/skill-log.ts +1 -0
  68. package/cli/selftune/utils/transcript.ts +1 -0
  69. package/cli/selftune/utils/trigger-check.ts +1 -1
  70. package/cli/selftune/workflows/skill-md-writer.ts +5 -5
  71. package/cli/selftune/workflows/workflows.ts +1 -0
  72. package/package.json +37 -33
  73. package/packages/telemetry-contract/fixtures/golden.test.ts +1 -0
  74. package/packages/telemetry-contract/package.json +1 -1
  75. package/packages/telemetry-contract/src/schemas.ts +1 -0
  76. package/packages/telemetry-contract/tests/compatibility.test.ts +1 -0
  77. package/packages/ui/README.md +35 -34
  78. package/packages/ui/package.json +3 -3
  79. package/packages/ui/src/components/ActivityTimeline.tsx +50 -43
  80. package/packages/ui/src/components/EvidenceViewer.tsx +306 -182
  81. package/packages/ui/src/components/EvolutionTimeline.tsx +83 -72
  82. package/packages/ui/src/components/InfoTip.tsx +4 -3
  83. package/packages/ui/src/components/OrchestrateRunsPanel.tsx +60 -53
  84. package/packages/ui/src/components/section-cards.tsx +20 -25
  85. package/packages/ui/src/components/skill-health-grid.tsx +213 -193
  86. package/packages/ui/src/lib/constants.tsx +1 -0
  87. package/packages/ui/src/primitives/badge.tsx +12 -15
  88. package/packages/ui/src/primitives/button.tsx +7 -7
  89. package/packages/ui/src/primitives/card.tsx +15 -26
  90. package/packages/ui/src/primitives/checkbox.tsx +7 -8
  91. package/packages/ui/src/primitives/collapsible.tsx +5 -5
  92. package/packages/ui/src/primitives/dropdown-menu.tsx +45 -55
  93. package/packages/ui/src/primitives/label.tsx +6 -6
  94. package/packages/ui/src/primitives/select.tsx +28 -37
  95. package/packages/ui/src/primitives/table.tsx +17 -44
  96. package/packages/ui/src/primitives/tabs.tsx +14 -21
  97. package/packages/ui/src/primitives/tooltip.tsx +10 -22
  98. package/skill/SKILL.md +70 -57
  99. package/skill/Workflows/AlphaUpload.md +4 -4
  100. package/skill/Workflows/AutoActivation.md +11 -6
  101. package/skill/Workflows/Badge.md +22 -16
  102. package/skill/Workflows/Baseline.md +34 -36
  103. package/skill/Workflows/Composability.md +16 -11
  104. package/skill/Workflows/Contribute.md +26 -21
  105. package/skill/Workflows/Cron.md +23 -22
  106. package/skill/Workflows/Dashboard.md +32 -27
  107. package/skill/Workflows/Doctor.md +33 -27
  108. package/skill/Workflows/Evals.md +48 -47
  109. package/skill/Workflows/EvolutionMemory.md +31 -21
  110. package/skill/Workflows/Evolve.md +84 -82
  111. package/skill/Workflows/EvolveBody.md +58 -47
  112. package/skill/Workflows/Grade.md +16 -13
  113. package/skill/Workflows/ImportSkillsBench.md +9 -6
  114. package/skill/Workflows/Ingest.md +36 -21
  115. package/skill/Workflows/Initialize.md +108 -40
  116. package/skill/Workflows/Orchestrate.md +22 -16
  117. package/skill/Workflows/Replay.md +12 -7
  118. package/skill/Workflows/Rollback.md +13 -6
  119. package/skill/Workflows/Schedule.md +6 -6
  120. package/skill/Workflows/Sync.md +18 -11
  121. package/skill/Workflows/UnitTest.md +28 -17
  122. package/skill/Workflows/Watch.md +28 -21
  123. package/skill/agents/diagnosis-analyst.md +11 -0
  124. package/skill/agents/evolution-reviewer.md +15 -1
  125. package/skill/agents/integration-guide.md +10 -0
  126. package/skill/agents/pattern-analyst.md +12 -1
  127. package/skill/references/grading-methodology.md +23 -24
  128. package/skill/references/interactive-config.md +7 -7
  129. package/skill/references/invocation-taxonomy.md +22 -20
  130. package/skill/references/logs.md +14 -6
  131. package/skill/references/setup-patterns.md +4 -2
  132. package/.claude/agents/diagnosis-analyst.md +0 -156
  133. package/.claude/agents/evolution-reviewer.md +0 -180
  134. package/.claude/agents/integration-guide.md +0 -212
  135. package/.claude/agents/pattern-analyst.md +0 -160
  136. package/apps/local-dashboard/dist/assets/index-Bs3Y4ixf.css +0 -1
  137. package/apps/local-dashboard/dist/assets/index-C4UYGWKr.js +0 -15
  138. package/apps/local-dashboard/dist/assets/vendor-react-BQH_6WrG.js +0 -60
  139. package/apps/local-dashboard/dist/assets/vendor-table-dK1QMLq9.js +0 -26
  140. package/apps/local-dashboard/dist/assets/vendor-ui-CO2mrx6e.js +0 -341
@@ -1,8 +1,3 @@
1
- import { useState } from "react"
2
- import { Badge } from "../primitives/badge"
3
- import { cn } from "../lib/utils"
4
- import type { EvalSnapshot, EvolutionEntry } from "../types"
5
- import { timeAgo } from "../lib/format"
6
1
  import {
7
2
  CircleDotIcon,
8
3
  RocketIcon,
@@ -13,7 +8,13 @@ import {
13
8
  TrendingDownIcon,
14
9
  ChevronDownIcon,
15
10
  ChevronRightIcon,
16
- } from "lucide-react"
11
+ } from "lucide-react";
12
+ import { useState } from "react";
13
+
14
+ import { timeAgo } from "../lib/format";
15
+ import { cn } from "../lib/utils";
16
+ import { Badge } from "../primitives/badge";
17
+ import type { EvalSnapshot, EvolutionEntry } from "../types";
17
18
 
18
19
  const ACTION_ICON: Record<string, React.ReactNode> = {
19
20
  created: <CircleDotIcon className="size-3.5" />,
@@ -21,7 +22,7 @@ const ACTION_ICON: Record<string, React.ReactNode> = {
21
22
  deployed: <RocketIcon className="size-3.5" />,
22
23
  rejected: <XCircleIcon className="size-3.5" />,
23
24
  rolled_back: <UndoIcon className="size-3.5" />,
24
- }
25
+ };
25
26
 
26
27
  const ACTION_COLOR: Record<string, string> = {
27
28
  created: "bg-blue-500",
@@ -29,7 +30,7 @@ const ACTION_COLOR: Record<string, string> = {
29
30
  deployed: "bg-emerald-500",
30
31
  rejected: "bg-red-500",
31
32
  rolled_back: "bg-red-400",
32
- }
33
+ };
33
34
 
34
35
  const ACTION_RING: Record<string, string> = {
35
36
  created: "ring-blue-500/30",
@@ -37,7 +38,7 @@ const ACTION_RING: Record<string, string> = {
37
38
  deployed: "ring-emerald-500/30",
38
39
  rejected: "ring-red-500/30",
39
40
  rolled_back: "ring-red-400/30",
40
- }
41
+ };
41
42
 
42
43
  const ACTION_LINE: Record<string, string> = {
43
44
  created: "bg-blue-500/30",
@@ -45,58 +46,65 @@ const ACTION_LINE: Record<string, string> = {
45
46
  deployed: "bg-emerald-500/30",
46
47
  rejected: "bg-red-500/30",
47
48
  rolled_back: "bg-red-400/30",
48
- }
49
+ };
49
50
 
50
51
  interface Props {
51
- entries: EvolutionEntry[]
52
- selectedProposalId: string | null
53
- onSelect: (proposalId: string) => void
52
+ entries: EvolutionEntry[];
53
+ selectedProposalId: string | null;
54
+ onSelect: (proposalId: string) => void;
54
55
  }
55
56
 
56
57
  /** Group evolution entries by proposal_id, ordered newest-first. */
57
58
  function groupByProposal(entries: EvolutionEntry[]) {
58
- const map = new Map<string, EvolutionEntry[]>()
59
+ const map = new Map<string, EvolutionEntry[]>();
59
60
  for (const e of entries) {
60
- const group = map.get(e.proposal_id) ?? []
61
- group.push(e)
62
- map.set(e.proposal_id, group)
61
+ const group = map.get(e.proposal_id) ?? [];
62
+ group.push(e);
63
+ map.set(e.proposal_id, group);
63
64
  }
64
65
  for (const group of map.values()) {
65
- group.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime())
66
+ group.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
66
67
  }
67
68
  return Array.from(map.entries()).sort((a, b) => {
68
- const aLast = a[1][a[1].length - 1]
69
- const bLast = b[1][b[1].length - 1]
70
- return new Date(bLast.timestamp).getTime() - new Date(aLast.timestamp).getTime()
71
- })
69
+ const aLast = a[1][a[1].length - 1];
70
+ const bLast = b[1][b[1].length - 1];
71
+ return new Date(bLast.timestamp).getTime() - new Date(aLast.timestamp).getTime();
72
+ });
72
73
  }
73
74
 
74
75
  function terminalAction(entries: EvolutionEntry[]): string {
75
- return entries[entries.length - 1].action
76
+ return entries[entries.length - 1].action;
76
77
  }
77
78
 
78
79
  /** Find the best eval_snapshot across all steps in a proposal group */
79
80
  function findEvalSnapshot(steps: EvolutionEntry[]): EvalSnapshot | null {
80
81
  for (let i = steps.length - 1; i >= 0; i--) {
81
- if (steps[i].eval_snapshot) return steps[i].eval_snapshot!
82
+ if (steps[i].eval_snapshot) return steps[i].eval_snapshot!;
82
83
  }
83
- return null
84
+ return null;
84
85
  }
85
86
 
86
87
  function PassRateDelta({ snapshot }: { snapshot: EvalSnapshot }) {
87
- const net = snapshot.net_change
88
- if (net === undefined || net === null) return null
89
- const pct = Math.round(net * 100)
90
- const isPositive = pct > 0
88
+ const net = snapshot.net_change;
89
+ if (net === undefined || net === null) return null;
90
+ const pct = Math.round(net * 100);
91
+ const isPositive = pct > 0;
91
92
  return (
92
- <span className={cn(
93
- "inline-flex items-center gap-0.5 text-[10px] font-mono font-medium",
94
- isPositive ? "text-emerald-600 dark:text-emerald-400" : "text-red-500",
95
- )}>
96
- {isPositive ? <TrendingUpIcon className="size-2.5" /> : <TrendingDownIcon className="size-2.5" />}
97
- {isPositive ? "+" : ""}{pct}%
93
+ <span
94
+ className={cn(
95
+ "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",
97
+ )}
98
+ >
99
+ {isPositive ? (
100
+ <TrendingUpIcon className="size-2.5" />
101
+ ) : (
102
+ <TrendingDownIcon className="size-2.5" />
103
+ )}
104
+ {isPositive ? "+" : ""}
105
+ {pct}%
98
106
  </span>
99
- )
107
+ );
100
108
  }
101
109
 
102
110
  const LIFECYCLE_STEPS = [
@@ -105,10 +113,10 @@ const LIFECYCLE_STEPS = [
105
113
  { action: "deployed", label: "Deployed", desc: "Accepted and applied to skill file" },
106
114
  { action: "rejected", label: "Rejected", desc: "Failed validation criteria" },
107
115
  { action: "rolled_back", label: "Rolled Back", desc: "Reverted after deployment" },
108
- ]
116
+ ];
109
117
 
110
118
  function LifecycleLegend() {
111
- const [open, setOpen] = useState(false)
119
+ const [open, setOpen] = useState(false);
112
120
 
113
121
  return (
114
122
  <div className="px-2 pb-2">
@@ -124,10 +132,7 @@ function LifecycleLegend() {
124
132
  <div className="mt-1.5 space-y-1.5 rounded-md border bg-muted/30 p-2">
125
133
  {LIFECYCLE_STEPS.map((step) => (
126
134
  <div key={step.action} className="flex items-start gap-2">
127
- <div className={cn(
128
- "size-2 rounded-full mt-1 shrink-0",
129
- ACTION_COLOR[step.action],
130
- )} />
135
+ <div className={cn("size-2 rounded-full mt-1 shrink-0", ACTION_COLOR[step.action])} />
131
136
  <div className="min-w-0">
132
137
  <span className="text-[10px] font-medium">{step.label}</span>
133
138
  <p className="text-[10px] text-muted-foreground/70 leading-tight">{step.desc}</p>
@@ -137,18 +142,18 @@ function LifecycleLegend() {
137
142
  </div>
138
143
  )}
139
144
  </div>
140
- )
145
+ );
141
146
  }
142
147
 
143
148
  export function EvolutionTimeline({ entries, selectedProposalId, onSelect }: Props) {
144
- const groups = groupByProposal(entries)
149
+ const groups = groupByProposal(entries);
145
150
 
146
151
  if (groups.length === 0) {
147
152
  return (
148
153
  <div className="flex items-center justify-center rounded-lg border border-dashed py-6 px-3">
149
154
  <p className="text-xs text-muted-foreground">No evolution history yet</p>
150
155
  </div>
151
- )
156
+ );
152
157
  }
153
158
 
154
159
  return (
@@ -159,29 +164,29 @@ export function EvolutionTimeline({ entries, selectedProposalId, onSelect }: Pro
159
164
  <LifecycleLegend />
160
165
  <nav className="flex flex-col">
161
166
  {groups.map(([proposalId, steps], groupIdx) => {
162
- const terminal = terminalAction(steps)
163
- const isSelected = selectedProposalId === proposalId
164
- const lastStep = steps[steps.length - 1]
165
- const dotColor = ACTION_COLOR[terminal] ?? "bg-muted-foreground"
166
- const ringColor = ACTION_RING[terminal] ?? "ring-muted-foreground/30"
167
- const lineColor = ACTION_LINE[terminal] ?? "bg-border"
168
- const isLast = groupIdx === groups.length - 1
169
- const snapshot = findEvalSnapshot(steps)
167
+ const terminal = terminalAction(steps);
168
+ const isSelected = selectedProposalId === proposalId;
169
+ const lastStep = steps[steps.length - 1];
170
+ const dotColor = ACTION_COLOR[terminal] ?? "bg-muted-foreground";
171
+ const ringColor = ACTION_RING[terminal] ?? "ring-muted-foreground/30";
172
+ const lineColor = ACTION_LINE[terminal] ?? "bg-border";
173
+ const isLast = groupIdx === groups.length - 1;
174
+ const snapshot = findEvalSnapshot(steps);
170
175
 
171
176
  return (
172
177
  <div key={proposalId} className="relative flex gap-3">
173
178
  {/* Vertical connector line */}
174
179
  <div className="flex flex-col items-center">
175
- <div className={cn(
176
- "flex items-center justify-center size-7 rounded-full ring-2 text-white shrink-0 z-10",
177
- dotColor,
178
- ringColor,
179
- )}>
180
+ <div
181
+ className={cn(
182
+ "flex items-center justify-center size-7 rounded-full ring-2 text-white shrink-0 z-10",
183
+ dotColor,
184
+ ringColor,
185
+ )}
186
+ >
180
187
  {ACTION_ICON[terminal] ?? <CircleDotIcon className="size-3.5" />}
181
188
  </div>
182
- {!isLast && (
183
- <div className={cn("w-0.5 flex-1 min-h-[16px]", lineColor)} />
184
- )}
189
+ {!isLast && <div className={cn("w-0.5 flex-1 min-h-[16px]", lineColor)} />}
185
190
  </div>
186
191
 
187
192
  {/* Content */}
@@ -191,14 +196,18 @@ export function EvolutionTimeline({ entries, selectedProposalId, onSelect }: Pro
191
196
  className={cn(
192
197
  "flex-1 min-w-0 rounded-md px-2.5 py-2 text-left transition-all mb-1",
193
198
  "hover:bg-accent/50",
194
- isSelected
195
- ? "bg-primary/5 ring-1 ring-primary/20"
196
- : "",
199
+ isSelected ? "bg-primary/5 ring-1 ring-primary/20" : "",
197
200
  )}
198
201
  >
199
202
  <div className="flex items-center gap-1.5">
200
203
  <Badge
201
- variant={terminal === "deployed" ? "default" : terminal === "rejected" || terminal === "rolled_back" ? "destructive" : "secondary"}
204
+ variant={
205
+ terminal === "deployed"
206
+ ? "default"
207
+ : terminal === "rejected" || terminal === "rolled_back"
208
+ ? "destructive"
209
+ : "secondary"
210
+ }
202
211
  className="text-[10px] capitalize"
203
212
  >
204
213
  {terminal.replace("_", " ")}
@@ -211,11 +220,13 @@ export function EvolutionTimeline({ entries, selectedProposalId, onSelect }: Pro
211
220
  {snapshot && (
212
221
  <div className="flex items-center gap-1.5 mt-1">
213
222
  <PassRateDelta snapshot={snapshot} />
214
- {snapshot.before_pass_rate !== undefined && snapshot.after_pass_rate !== undefined && (
215
- <span className="text-[10px] text-muted-foreground/60 font-mono">
216
- {Math.round(snapshot.before_pass_rate * 100)}&rarr;{Math.round(snapshot.after_pass_rate * 100)}%
217
- </span>
218
- )}
223
+ {snapshot.before_pass_rate !== undefined &&
224
+ snapshot.after_pass_rate !== undefined && (
225
+ <span className="text-[10px] text-muted-foreground/60 font-mono">
226
+ {Math.round(snapshot.before_pass_rate * 100)}&rarr;
227
+ {Math.round(snapshot.after_pass_rate * 100)}%
228
+ </span>
229
+ )}
219
230
  </div>
220
231
  )}
221
232
  <div className="flex items-center gap-1.5 mt-1">
@@ -244,9 +255,9 @@ export function EvolutionTimeline({ entries, selectedProposalId, onSelect }: Pro
244
255
  )}
245
256
  </button>
246
257
  </div>
247
- )
258
+ );
248
259
  })}
249
260
  </nav>
250
261
  </div>
251
- )
262
+ );
252
263
  }
@@ -1,5 +1,6 @@
1
- import { Tooltip, TooltipContent, TooltipTrigger } from "../primitives/tooltip"
2
- import { InfoIcon } from "lucide-react"
1
+ import { InfoIcon } from "lucide-react";
2
+
3
+ import { Tooltip, TooltipContent, TooltipTrigger } from "../primitives/tooltip";
3
4
 
4
5
  /** Small info icon that shows a tooltip on hover. Used to explain metrics and concepts. */
5
6
  export function InfoTip({ text }: { text: string }) {
@@ -15,5 +16,5 @@ export function InfoTip({ text }: { text: string }) {
15
16
  {text}
16
17
  </TooltipContent>
17
18
  </Tooltip>
18
- )
19
+ );
19
20
  }
@@ -1,33 +1,17 @@
1
- import { useState } from "react"
2
- import { Badge } from "../primitives/badge"
3
- import {
4
- Card,
5
- CardContent,
6
- CardDescription,
7
- CardHeader,
8
- CardTitle,
9
- } from "../primitives/card"
10
- import {
11
- Collapsible,
12
- CollapsibleContent,
13
- CollapsibleTrigger,
14
- } from "../primitives/collapsible"
15
- import type { OrchestrateRunReport, OrchestrateRunSkillAction } from "../types"
16
- import { timeAgo } from "../lib/format"
17
- import {
18
- BotIcon,
19
- CheckCircleIcon,
20
- ChevronRightIcon,
21
- EyeIcon,
22
- SkipForwardIcon,
23
- ZapIcon,
24
- } from "lucide-react"
1
+ import { BotIcon, ChevronRightIcon, EyeIcon, SkipForwardIcon, ZapIcon } from "lucide-react";
2
+ import { useState } from "react";
3
+
4
+ import { timeAgo } from "../lib/format";
5
+ import { Badge } from "../primitives/badge";
6
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../primitives/card";
7
+ import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "../primitives/collapsible";
8
+ import type { OrchestrateRunReport, OrchestrateRunSkillAction } from "../types";
25
9
 
26
10
  const ACTION_ICON: Record<string, React.ReactNode> = {
27
11
  evolve: <ZapIcon className="size-3 text-amber-500" />,
28
12
  watch: <EyeIcon className="size-3 text-blue-500" />,
29
13
  skip: <SkipForwardIcon className="size-3 text-muted-foreground" />,
30
- }
14
+ };
31
15
 
32
16
  function SkillActionRow({ action }: { action: OrchestrateRunSkillAction }) {
33
17
  return (
@@ -38,18 +22,25 @@ function SkillActionRow({ action }: { action: OrchestrateRunSkillAction }) {
38
22
  <span className="text-xs font-medium truncate">{action.skill}</span>
39
23
  <Badge
40
24
  variant={
41
- action.rolledBack ? "destructive"
42
- : action.action === "evolve" && action.deployed ? "default"
43
- : action.action === "evolve" ? "secondary"
44
- : action.action === "watch" ? "outline"
45
- : "secondary"
25
+ action.rolledBack
26
+ ? "destructive"
27
+ : action.action === "evolve" && action.deployed
28
+ ? "default"
29
+ : action.action === "evolve"
30
+ ? "secondary"
31
+ : action.action === "watch"
32
+ ? "outline"
33
+ : "secondary"
46
34
  }
47
35
  className="text-[10px] h-4 px-1.5 shrink-0"
48
36
  >
49
- {action.rolledBack ? "rolled back"
50
- : action.action === "evolve" && action.deployed ? "deployed"
51
- : action.action === "evolve" ? "evolved"
52
- : action.action}
37
+ {action.rolledBack
38
+ ? "rolled back"
39
+ : action.action === "evolve" && action.deployed
40
+ ? "deployed"
41
+ : action.action === "evolve"
42
+ ? "evolved"
43
+ : action.action}
53
44
  </Badge>
54
45
  {action.alert && (
55
46
  <Badge variant="destructive" className="text-[10px] h-4 px-1.5 shrink-0">
@@ -60,42 +51,56 @@ function SkillActionRow({ action }: { action: OrchestrateRunSkillAction }) {
60
51
  <p className="text-[11px] text-muted-foreground line-clamp-1">{action.reason}</p>
61
52
  </div>
62
53
  </div>
63
- )
54
+ );
64
55
  }
65
56
 
66
57
  function RunCard({ run }: { run: OrchestrateRunReport }) {
67
- const [open, setOpen] = useState(false)
68
- const nonSkipActions = run.skill_actions.filter((a) => a.action !== "skip")
69
- const skipActions = run.skill_actions.filter((a) => a.action === "skip")
58
+ const [open, setOpen] = useState(false);
59
+ const nonSkipActions = run.skill_actions.filter((a) => a.action !== "skip");
60
+ const skipActions = run.skill_actions.filter((a) => a.action === "skip");
70
61
 
71
62
  return (
72
63
  <Collapsible open={open} onOpenChange={setOpen}>
73
64
  <CollapsibleTrigger className="w-full text-left">
74
65
  <div className="flex items-start gap-3 py-2 hover:bg-muted/50 rounded-md px-2 -mx-2 transition-colors">
75
- <div className={`mt-1.5 size-2 shrink-0 rounded-full ${
76
- run.deployed > 0 ? "bg-emerald-500"
77
- : run.evolved > 0 ? "bg-amber-400"
78
- : "bg-muted-foreground/40"
79
- }`} />
66
+ <div
67
+ className={`mt-1.5 size-2 shrink-0 rounded-full ${
68
+ run.deployed > 0
69
+ ? "bg-emerald-500"
70
+ : run.evolved > 0
71
+ ? "bg-amber-400"
72
+ : "bg-muted-foreground/40"
73
+ }`}
74
+ />
80
75
  <div className="flex-1 min-w-0">
81
76
  <div className="flex items-center gap-2">
82
- <span className="text-xs font-mono text-muted-foreground">{timeAgo(run.timestamp)}</span>
77
+ <span className="text-xs font-mono text-muted-foreground">
78
+ {timeAgo(run.timestamp)}
79
+ </span>
83
80
  {run.dry_run && (
84
- <Badge variant="outline" className="text-[10px] h-4 px-1.5">dry-run</Badge>
81
+ <Badge variant="outline" className="text-[10px] h-4 px-1.5">
82
+ dry-run
83
+ </Badge>
85
84
  )}
86
85
  {run.approval_mode === "review" && (
87
- <Badge variant="outline" className="text-[10px] h-4 px-1.5">review</Badge>
86
+ <Badge variant="outline" className="text-[10px] h-4 px-1.5">
87
+ review
88
+ </Badge>
88
89
  )}
89
90
  </div>
90
91
  <div className="flex items-center gap-3 mt-1 text-xs text-muted-foreground">
91
- {run.deployed > 0 && <span className="text-emerald-600 font-medium">{run.deployed} deployed</span>}
92
+ {run.deployed > 0 && (
93
+ <span className="text-emerald-600 font-medium">{run.deployed} deployed</span>
94
+ )}
92
95
  {run.evolved > 0 && <span>{run.evolved} evolved</span>}
93
96
  {run.watched > 0 && <span>{run.watched} watched</span>}
94
97
  {run.skipped > 0 && <span>{run.skipped} skipped</span>}
95
98
  <span>{(run.elapsed_ms / 1000).toFixed(1)}s</span>
96
99
  </div>
97
100
  </div>
98
- <ChevronRightIcon className={`size-4 text-muted-foreground shrink-0 mt-1 transition-transform duration-200 ${open ? "rotate-90" : ""}`} />
101
+ <ChevronRightIcon
102
+ className={`size-4 text-muted-foreground shrink-0 mt-1 transition-transform duration-200 ${open ? "rotate-90" : ""}`}
103
+ />
99
104
  </div>
100
105
  </CollapsibleTrigger>
101
106
  <CollapsibleContent>
@@ -118,7 +123,7 @@ function RunCard({ run }: { run: OrchestrateRunReport }) {
118
123
  </div>
119
124
  </CollapsibleContent>
120
125
  </Collapsible>
121
- )
126
+ );
122
127
  }
123
128
 
124
129
  export function OrchestrateRunsPanel({ runs }: { runs: OrchestrateRunReport[] }) {
@@ -133,14 +138,16 @@ export function OrchestrateRunsPanel({ runs }: { runs: OrchestrateRunReport[] })
133
138
  </CardHeader>
134
139
  <CardContent>
135
140
  <p className="text-sm text-muted-foreground text-center py-4">
136
- No orchestrate runs yet. Run <code className="text-xs bg-muted px-1 py-0.5 rounded">selftune orchestrate</code> to start.
141
+ No orchestrate runs yet. Run{" "}
142
+ <code className="text-xs bg-muted px-1 py-0.5 rounded">selftune orchestrate</code> to
143
+ start.
137
144
  </p>
138
145
  </CardContent>
139
146
  </Card>
140
- )
147
+ );
141
148
  }
142
149
 
143
- const totalDeployed = runs.reduce((sum, r) => sum + r.deployed, 0)
150
+ const totalDeployed = runs.reduce((sum, r) => sum + r.deployed, 0);
144
151
 
145
152
  return (
146
153
  <Card>
@@ -160,5 +167,5 @@ export function OrchestrateRunsPanel({ runs }: { runs: OrchestrateRunReport[] })
160
167
  ))}
161
168
  </CardContent>
162
169
  </Card>
163
- )
170
+ );
164
171
  }
@@ -1,12 +1,3 @@
1
- import { Badge } from "../primitives/badge"
2
- import {
3
- Card,
4
- CardAction,
5
- CardDescription,
6
- CardHeader,
7
- CardTitle,
8
- } from "../primitives/card"
9
- import { InfoTip } from "./InfoTip"
10
1
  import {
11
2
  TrendingUpIcon,
12
3
  TrendingDownIcon,
@@ -16,16 +7,20 @@ import {
16
7
  FlaskConicalIcon,
17
8
  LayersIcon,
18
9
  SearchXIcon,
19
- } from "lucide-react"
10
+ } from "lucide-react";
11
+
12
+ import { Badge } from "../primitives/badge";
13
+ import { Card, CardAction, CardDescription, CardHeader, CardTitle } from "../primitives/card";
14
+ import { InfoTip } from "./InfoTip";
20
15
 
21
16
  interface SectionCardsProps {
22
- skillsCount: number
23
- avgPassRate: number | null
24
- unmatchedCount: number
25
- sessionsCount: number
26
- pendingCount: number
27
- evidenceCount: number
28
- hasEvolution?: boolean
17
+ skillsCount: number;
18
+ avgPassRate: number | null;
19
+ unmatchedCount: number;
20
+ sessionsCount: number;
21
+ pendingCount: number;
22
+ evidenceCount: number;
23
+ hasEvolution?: boolean;
29
24
  }
30
25
 
31
26
  export function SectionCards({
@@ -37,8 +32,8 @@ export function SectionCards({
37
32
  evidenceCount,
38
33
  hasEvolution = true,
39
34
  }: SectionCardsProps) {
40
- const passRateStr = avgPassRate !== null ? `${Math.round(avgPassRate * 100)}%` : "--"
41
- const passRateGood = avgPassRate !== null && avgPassRate >= 0.7
35
+ const passRateStr = avgPassRate !== null ? `${Math.round(avgPassRate * 100)}%` : "--";
36
+ const passRateGood = avgPassRate !== null && avgPassRate >= 0.7;
42
37
 
43
38
  return (
44
39
  <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">
@@ -68,7 +63,9 @@ export function SectionCards({
68
63
  Avg Trigger Rate
69
64
  <InfoTip text="Average percentage of skill checks that resulted in a trigger across all graded skills (5+ checks). Run selftune evolve to improve this." />
70
65
  </CardDescription>
71
- <CardTitle className={`text-2xl font-semibold tabular-nums @[250px]/card:text-3xl ${!passRateGood && avgPassRate !== null ? "text-red-600" : ""}`}>
66
+ <CardTitle
67
+ className={`text-2xl font-semibold tabular-nums @[250px]/card:text-3xl ${!passRateGood && avgPassRate !== null ? "text-red-600" : ""}`}
68
+ >
72
69
  {passRateStr}
73
70
  </CardTitle>
74
71
  <CardAction>
@@ -128,7 +125,7 @@ export function SectionCards({
128
125
  <CardHeader>
129
126
  <CardDescription className="flex items-center gap-1.5">
130
127
  <AlertTriangleIcon className="size-3.5" />
131
- Pending Proposals
128
+ Undeployed Proposals
132
129
  <InfoTip text="Evolution proposals that have been generated but not yet validated or deployed. Requires running selftune evolve." />
133
130
  </CardDescription>
134
131
  <CardTitle className="text-2xl font-semibold tabular-nums @[250px]/card:text-3xl">
@@ -140,9 +137,7 @@ export function SectionCards({
140
137
  no evolution runs yet
141
138
  </Badge>
142
139
  ) : pendingCount > 0 ? (
143
- <Badge variant="secondary">
144
- awaiting review
145
- </Badge>
140
+ <Badge variant="secondary">not yet deployed</Badge>
146
141
  ) : null}
147
142
  </CardAction>
148
143
  </CardHeader>
@@ -168,5 +163,5 @@ export function SectionCards({
168
163
  </CardHeader>
169
164
  </Card>
170
165
  </div>
171
- )
166
+ );
172
167
  }