selftune 0.2.22 → 0.2.24

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 (270) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +95 -15
  3. package/apps/local-dashboard/dist/assets/index-DgY2KGP-.css +1 -0
  4. package/apps/local-dashboard/dist/assets/index-Dmx7LPVX.js +15 -0
  5. package/apps/local-dashboard/dist/assets/vendor-react-C5oyHiV1.js +11 -0
  6. package/apps/local-dashboard/dist/assets/{vendor-table-BIiI3YhS.js → vendor-table-Bc_bbKd8.js} +1 -1
  7. package/apps/local-dashboard/dist/assets/vendor-ui-B3BPIYy7.js +1 -0
  8. package/apps/local-dashboard/dist/index.html +5 -5
  9. package/cli/selftune/adapters/codex/install.ts +310 -78
  10. package/cli/selftune/adapters/opencode/install.ts +3 -4
  11. package/cli/selftune/adapters/pi/hook.ts +273 -0
  12. package/cli/selftune/adapters/pi/install.ts +207 -0
  13. package/cli/selftune/alpha-upload/build-payloads.ts +3 -3
  14. package/cli/selftune/alpha-upload/stage-canonical.ts +17 -11
  15. package/cli/selftune/auto-update.ts +200 -8
  16. package/cli/selftune/canonical-export.ts +55 -25
  17. package/cli/selftune/command-surface.ts +397 -0
  18. package/cli/selftune/constants.ts +10 -1
  19. package/cli/selftune/contribute/contribute.ts +64 -13
  20. package/cli/selftune/contribution-config.ts +57 -3
  21. package/cli/selftune/contribution-preferences.ts +117 -0
  22. package/cli/selftune/contribution-signals.ts +8 -4
  23. package/cli/selftune/contribution-staging.ts +13 -2
  24. package/cli/selftune/contributions.ts +55 -121
  25. package/cli/selftune/creator-contributions.ts +29 -10
  26. package/cli/selftune/cron/setup.ts +7 -3
  27. package/cli/selftune/dashboard-contract.ts +87 -0
  28. package/cli/selftune/dashboard-server.ts +168 -17
  29. package/cli/selftune/dashboard.ts +350 -17
  30. package/cli/selftune/eval/baseline.ts +21 -5
  31. package/cli/selftune/eval/execution-eval.ts +170 -0
  32. package/cli/selftune/eval/family-overlap.ts +2 -2
  33. package/cli/selftune/eval/hooks-to-evals.ts +228 -82
  34. package/cli/selftune/eval/import-skillsbench.ts +2 -2
  35. package/cli/selftune/eval/invocation-classifier.ts +56 -0
  36. package/cli/selftune/eval/synthetic-evals.ts +5 -3
  37. package/cli/selftune/eval/unit-test-cli.ts +7 -4
  38. package/cli/selftune/evolution/apply-proposal.ts +295 -0
  39. package/cli/selftune/evolution/engines/judge-engine.ts +96 -0
  40. package/cli/selftune/evolution/engines/replay-engine.ts +180 -0
  41. package/cli/selftune/evolution/evidence.ts +2 -6
  42. package/cli/selftune/evolution/evolve-body.ts +152 -38
  43. package/cli/selftune/evolution/evolve.ts +244 -52
  44. package/cli/selftune/evolution/rollback.ts +0 -1
  45. package/cli/selftune/evolution/validate-body.ts +111 -49
  46. package/cli/selftune/evolution/validate-host-replay.ts +510 -60
  47. package/cli/selftune/evolution/validate-proposal.ts +11 -150
  48. package/cli/selftune/evolution/validate-routing.ts +51 -108
  49. package/cli/selftune/evolution/validation-contract.ts +91 -0
  50. package/cli/selftune/grading/auto-grade.ts +11 -7
  51. package/cli/selftune/grading/grade-session.ts +10 -16
  52. package/cli/selftune/hooks/skill-eval.ts +2 -1
  53. package/cli/selftune/hooks-shared/types.ts +1 -0
  54. package/cli/selftune/index.ts +58 -15
  55. package/cli/selftune/ingestors/claude-replay.ts +15 -10
  56. package/cli/selftune/ingestors/codex-wrapper.ts +3 -3
  57. package/cli/selftune/ingestors/opencode-ingest.ts +2 -2
  58. package/cli/selftune/ingestors/pi-ingest.ts +727 -0
  59. package/cli/selftune/init.ts +38 -4
  60. package/cli/selftune/localdb/direct-write.ts +120 -1
  61. package/cli/selftune/localdb/materialize.ts +6 -7
  62. package/cli/selftune/localdb/queries/cron.ts +34 -0
  63. package/cli/selftune/localdb/queries/dashboard.ts +834 -0
  64. package/cli/selftune/localdb/queries/evolution.ts +158 -0
  65. package/cli/selftune/localdb/queries/execution.ts +133 -0
  66. package/cli/selftune/localdb/queries/json.ts +18 -0
  67. package/cli/selftune/localdb/queries/monitoring.ts +263 -0
  68. package/cli/selftune/localdb/queries/raw.ts +95 -0
  69. package/cli/selftune/localdb/queries/staging.ts +270 -0
  70. package/cli/selftune/localdb/queries/trust.ts +392 -0
  71. package/cli/selftune/localdb/queries.ts +60 -2162
  72. package/cli/selftune/localdb/schema.ts +59 -0
  73. package/cli/selftune/monitoring/watch.ts +96 -29
  74. package/cli/selftune/normalization.ts +3 -0
  75. package/cli/selftune/observability.ts +12 -3
  76. package/cli/selftune/orchestrate/cli.ts +161 -0
  77. package/cli/selftune/orchestrate/execute.ts +295 -0
  78. package/cli/selftune/orchestrate/finalize.ts +157 -0
  79. package/cli/selftune/orchestrate/locks.ts +40 -0
  80. package/cli/selftune/orchestrate/plan.ts +131 -0
  81. package/cli/selftune/orchestrate/post-run.ts +59 -0
  82. package/cli/selftune/orchestrate/prepare.ts +334 -0
  83. package/cli/selftune/orchestrate/report.ts +182 -0
  84. package/cli/selftune/orchestrate/runtime.ts +120 -0
  85. package/cli/selftune/orchestrate/signals.ts +48 -0
  86. package/cli/selftune/orchestrate.ts +162 -1142
  87. package/cli/selftune/registry/client.ts +74 -0
  88. package/cli/selftune/registry/history.ts +54 -0
  89. package/cli/selftune/registry/index.ts +90 -0
  90. package/cli/selftune/registry/install.ts +141 -0
  91. package/cli/selftune/registry/list.ts +44 -0
  92. package/cli/selftune/registry/push.ts +171 -0
  93. package/cli/selftune/registry/rollback.ts +49 -0
  94. package/cli/selftune/registry/status.ts +62 -0
  95. package/cli/selftune/registry/sync.ts +125 -0
  96. package/cli/selftune/repair/skill-usage.ts +9 -3
  97. package/cli/selftune/routes/overview.ts +5 -2
  98. package/cli/selftune/routes/skill-report.ts +15 -2
  99. package/cli/selftune/schedule.ts +5 -5
  100. package/cli/selftune/status.ts +70 -2
  101. package/cli/selftune/sync.ts +127 -23
  102. package/cli/selftune/testing-readiness.ts +597 -0
  103. package/cli/selftune/types.ts +46 -5
  104. package/cli/selftune/uninstall.ts +2 -1
  105. package/cli/selftune/utils/canonical-log.ts +1 -9
  106. package/cli/selftune/utils/cli-error.ts +9 -0
  107. package/cli/selftune/utils/jsonl.ts +1 -30
  108. package/cli/selftune/utils/llm-call.ts +126 -6
  109. package/cli/selftune/utils/skill-discovery.ts +24 -0
  110. package/cli/selftune/workflows/proposals.ts +184 -0
  111. package/cli/selftune/workflows/skill-scaffold.ts +241 -0
  112. package/cli/selftune/workflows/workflows.ts +100 -26
  113. package/node_modules/@selftune/telemetry-contract/fixtures/complete-push.ts +1 -1
  114. package/node_modules/@selftune/telemetry-contract/fixtures/evidence-only-push.ts +2 -2
  115. package/node_modules/@selftune/telemetry-contract/fixtures/golden.test.ts +0 -1
  116. package/node_modules/@selftune/telemetry-contract/fixtures/partial-push-no-sessions.ts +1 -1
  117. package/node_modules/@selftune/telemetry-contract/fixtures/partial-push-unresolved-parents.ts +2 -2
  118. package/node_modules/@selftune/telemetry-contract/package.json +1 -1
  119. package/node_modules/@selftune/telemetry-contract/src/index.ts +1 -0
  120. package/node_modules/@selftune/telemetry-contract/src/schemas.ts +63 -5
  121. package/node_modules/@selftune/telemetry-contract/src/types.ts +97 -7
  122. package/node_modules/@selftune/telemetry-contract/tests/compatibility.test.ts +0 -1
  123. package/package.json +25 -9
  124. package/packages/dashboard-core/AGENTS.md +18 -0
  125. package/packages/dashboard-core/README.md +30 -0
  126. package/packages/dashboard-core/index.ts +3 -0
  127. package/packages/dashboard-core/package.json +39 -0
  128. package/packages/dashboard-core/src/chrome/DashboardChrome.tsx +74 -0
  129. package/packages/dashboard-core/src/chrome/DashboardHeader.tsx +200 -0
  130. package/packages/dashboard-core/src/chrome/DashboardSidebar.tsx +219 -0
  131. package/packages/dashboard-core/src/chrome/RuntimeBadge.tsx +46 -0
  132. package/packages/dashboard-core/src/chrome/index.ts +14 -0
  133. package/packages/dashboard-core/src/chrome/types.ts +81 -0
  134. package/packages/dashboard-core/src/chrome/utils.ts +23 -0
  135. package/packages/dashboard-core/src/gates/FeatureGate.tsx +11 -0
  136. package/packages/dashboard-core/src/gates/LockedRoute.tsx +29 -0
  137. package/packages/dashboard-core/src/gates/UpgradeCard.tsx +89 -0
  138. package/packages/dashboard-core/src/gates/index.ts +3 -0
  139. package/packages/dashboard-core/src/host/DashboardHostProvider.tsx +62 -0
  140. package/packages/dashboard-core/src/host/adapter.ts +47 -0
  141. package/packages/dashboard-core/src/host/capabilities.ts +55 -0
  142. package/packages/dashboard-core/src/host/index.ts +3 -0
  143. package/packages/dashboard-core/src/models/analytics.ts +39 -0
  144. package/packages/dashboard-core/src/models/index.ts +4 -0
  145. package/packages/dashboard-core/src/models/overview.ts +98 -0
  146. package/packages/dashboard-core/src/models/runtime.ts +7 -0
  147. package/packages/dashboard-core/src/models/skills.ts +34 -0
  148. package/packages/dashboard-core/src/routes/index.ts +2 -0
  149. package/packages/dashboard-core/src/routes/manifest.test.ts +70 -0
  150. package/packages/dashboard-core/src/routes/manifest.ts +451 -0
  151. package/packages/dashboard-core/src/routes/types.ts +39 -0
  152. package/packages/dashboard-core/src/screens/analytics/AnalyticsScreen.tsx +278 -0
  153. package/packages/dashboard-core/src/screens/analytics/index.ts +1 -0
  154. package/packages/dashboard-core/src/screens/index.ts +37 -0
  155. package/packages/dashboard-core/src/screens/overview/OverviewComparisonSurface.test.ts +101 -0
  156. package/packages/dashboard-core/src/screens/overview/OverviewComparisonSurface.tsx +393 -0
  157. package/packages/dashboard-core/src/screens/overview/OverviewCompositionSurface.test.tsx +113 -0
  158. package/packages/dashboard-core/src/screens/overview/OverviewCompositionSurface.tsx +72 -0
  159. package/packages/dashboard-core/src/screens/overview/OverviewCoreSurface.tsx +71 -0
  160. package/packages/dashboard-core/src/screens/overview/OverviewOnboardingBanner.tsx +90 -0
  161. package/packages/dashboard-core/src/screens/overview/OverviewRunSummary.tsx +40 -0
  162. package/packages/dashboard-core/src/screens/overview/index.ts +16 -0
  163. package/packages/dashboard-core/src/screens/overview/types.ts +13 -0
  164. package/packages/dashboard-core/src/screens/skill-report/SkillReportDailyBreakdownSection.tsx +99 -0
  165. package/packages/dashboard-core/src/screens/skill-report/SkillReportDataQualityTabContent.tsx +35 -0
  166. package/packages/dashboard-core/src/screens/skill-report/SkillReportEvidenceRail.tsx +71 -0
  167. package/packages/dashboard-core/src/screens/skill-report/SkillReportEvidenceSection.tsx +63 -0
  168. package/packages/dashboard-core/src/screens/skill-report/SkillReportEvidenceTabContent.tsx +25 -0
  169. package/packages/dashboard-core/src/screens/skill-report/SkillReportInvocationsSection.tsx +24 -0
  170. package/packages/dashboard-core/src/screens/skill-report/SkillReportMissedQueriesSection.tsx +79 -0
  171. package/packages/dashboard-core/src/screens/skill-report/SkillReportScaffold.tsx +150 -0
  172. package/packages/dashboard-core/src/screens/skill-report/SkillReportSections.test.tsx +224 -0
  173. package/packages/dashboard-core/src/screens/skill-report/SkillReportTabs.test.tsx +76 -0
  174. package/packages/dashboard-core/src/screens/skill-report/SkillReportTabs.tsx +88 -0
  175. package/packages/dashboard-core/src/screens/skill-report/SkillReportTrendSection.tsx +33 -0
  176. package/packages/dashboard-core/src/screens/skill-report/SkillReportTrustBadge.tsx +67 -0
  177. package/packages/dashboard-core/src/screens/skill-report/index.ts +45 -0
  178. package/packages/dashboard-core/src/screens/skills/SkillsLibraryScreen.tsx +162 -0
  179. package/packages/dashboard-core/src/screens/skills/index.ts +6 -0
  180. package/packages/telemetry-contract/fixtures/complete-push.ts +1 -1
  181. package/packages/telemetry-contract/fixtures/evidence-only-push.ts +2 -2
  182. package/packages/telemetry-contract/fixtures/golden.test.ts +0 -1
  183. package/packages/telemetry-contract/fixtures/partial-push-no-sessions.ts +1 -1
  184. package/packages/telemetry-contract/fixtures/partial-push-unresolved-parents.ts +2 -2
  185. package/packages/telemetry-contract/package.json +1 -1
  186. package/packages/telemetry-contract/src/index.ts +1 -0
  187. package/packages/telemetry-contract/src/schemas.ts +63 -5
  188. package/packages/telemetry-contract/src/types.ts +97 -7
  189. package/packages/telemetry-contract/tests/compatibility.test.ts +0 -1
  190. package/packages/ui/AGENTS.md +16 -0
  191. package/packages/ui/README.md +1 -1
  192. package/packages/ui/package.json +1 -1
  193. package/packages/ui/src/components/ActivityTimeline.tsx +152 -168
  194. package/packages/ui/src/components/AnalyticsCharts.tsx +344 -0
  195. package/packages/ui/src/components/EvidenceViewer.tsx +229 -464
  196. package/packages/ui/src/components/EvolutionTimeline.tsx +34 -87
  197. package/packages/ui/src/components/InfoTip.tsx +1 -2
  198. package/packages/ui/src/components/InvocationsPanel.tsx +413 -0
  199. package/packages/ui/src/components/JobHistoryTimeline.tsx +156 -0
  200. package/packages/ui/src/components/OrchestrateRunsPanel.tsx +18 -36
  201. package/packages/ui/src/components/OverviewPanels.tsx +693 -0
  202. package/packages/ui/src/components/PipelineStatusBar.tsx +65 -0
  203. package/packages/ui/src/components/SkillReportGuide.tsx +215 -0
  204. package/packages/ui/src/components/SkillReportPanels.tsx +919 -0
  205. package/packages/ui/src/components/SkillsLibrary.tsx +437 -0
  206. package/packages/ui/src/components/index.ts +56 -1
  207. package/packages/ui/src/components/section-cards.tsx +18 -35
  208. package/packages/ui/src/components/skill-health-grid.tsx +47 -37
  209. package/packages/ui/src/lib/constants.tsx +0 -1
  210. package/packages/ui/src/primitives/card.tsx +1 -1
  211. package/packages/ui/src/primitives/checkbox.tsx +1 -1
  212. package/packages/ui/src/primitives/dropdown-menu.tsx +2 -2
  213. package/packages/ui/src/primitives/select.tsx +2 -2
  214. package/packages/ui/src/primitives/tabs.tsx +7 -6
  215. package/packages/ui/src/types.ts +182 -4
  216. package/skill/SKILL.md +130 -318
  217. package/skill/agents/diagnosis-analyst.md +3 -3
  218. package/skill/agents/evolution-reviewer.md +3 -3
  219. package/skill/agents/integration-guide.md +3 -3
  220. package/skill/agents/pattern-analyst.md +2 -2
  221. package/skill/references/cli-quick-reference.md +89 -0
  222. package/skill/references/creator-playbook.md +131 -0
  223. package/skill/references/examples.md +48 -0
  224. package/skill/references/troubleshooting.md +47 -0
  225. package/skill/references/version-history.md +1 -1
  226. package/skill/selftune.contribute.json +11 -0
  227. package/skill/{Workflows → workflows}/Baseline.md +20 -1
  228. package/skill/{Workflows → workflows}/Contribute.md +23 -10
  229. package/skill/{Workflows → workflows}/Contributions.md +13 -5
  230. package/skill/workflows/CreateTestDeploy.md +170 -0
  231. package/skill/{Workflows → workflows}/CreatorContributions.md +18 -6
  232. package/skill/{Workflows → workflows}/Cron.md +1 -1
  233. package/skill/{Workflows → workflows}/Dashboard.md +20 -0
  234. package/skill/{Workflows → workflows}/Doctor.md +1 -1
  235. package/skill/{Workflows → workflows}/Evals.md +67 -2
  236. package/skill/{Workflows → workflows}/Evolve.md +119 -30
  237. package/skill/{Workflows → workflows}/EvolveBody.md +41 -1
  238. package/skill/{Workflows → workflows}/Grade.md +1 -1
  239. package/skill/{Workflows → workflows}/Ingest.md +60 -2
  240. package/skill/{Workflows → workflows}/Initialize.md +16 -9
  241. package/skill/{Workflows → workflows}/Orchestrate.md +13 -3
  242. package/skill/{Workflows → workflows}/PlatformHooks.md +19 -3
  243. package/skill/workflows/Registry.md +99 -0
  244. package/skill/{Workflows → workflows}/Schedule.md +3 -3
  245. package/skill/workflows/SignalsDashboard.md +87 -0
  246. package/skill/{Workflows → workflows}/Sync.md +3 -1
  247. package/skill/{Workflows → workflows}/UnitTest.md +19 -0
  248. package/skill/{Workflows → workflows}/Watch.md +42 -2
  249. package/skill/{Workflows → workflows}/Workflows.md +39 -2
  250. package/apps/local-dashboard/dist/assets/index-D8O-RG1I.js +0 -60
  251. package/apps/local-dashboard/dist/assets/index-_EcLywDg.css +0 -1
  252. package/apps/local-dashboard/dist/assets/vendor-react-CKkiCskZ.js +0 -11
  253. package/apps/local-dashboard/dist/assets/vendor-ui-CGEmUayx.js +0 -12
  254. package/cli/selftune/utils/html.ts +0 -27
  255. package/packages/ui/src/components/RecentActivityFeed.tsx +0 -117
  256. /package/skill/{Workflows → workflows}/AlphaUpload.md +0 -0
  257. /package/skill/{Workflows → workflows}/AutoActivation.md +0 -0
  258. /package/skill/{Workflows → workflows}/Badge.md +0 -0
  259. /package/skill/{Workflows → workflows}/Composability.md +0 -0
  260. /package/skill/{Workflows → workflows}/EvolutionMemory.md +0 -0
  261. /package/skill/{Workflows → workflows}/ExportCanonical.md +0 -0
  262. /package/skill/{Workflows → workflows}/Hook.md +0 -0
  263. /package/skill/{Workflows → workflows}/ImportSkillsBench.md +0 -0
  264. /package/skill/{Workflows → workflows}/Quickstart.md +0 -0
  265. /package/skill/{Workflows → workflows}/Recover.md +0 -0
  266. /package/skill/{Workflows → workflows}/RepairSkillUsage.md +0 -0
  267. /package/skill/{Workflows → workflows}/Replay.md +0 -0
  268. /package/skill/{Workflows → workflows}/Rollback.md +0 -0
  269. /package/skill/{Workflows → workflows}/Telemetry.md +0 -0
  270. /package/skill/{Workflows → workflows}/Uninstall.md +0 -0
@@ -1,3 +1,8 @@
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";
1
6
  import {
2
7
  CircleDotIcon,
3
8
  RocketIcon,
@@ -9,15 +14,8 @@ import {
9
14
  ChevronDownIcon,
10
15
  ChevronRightIcon,
11
16
  } from "lucide-react";
12
- import { useState } from "react";
13
- import type { ReactNode } from "react";
14
-
15
- import { timeAgo } from "../lib/format";
16
- import { cn } from "../lib/utils";
17
- import { Badge } from "../primitives/badge";
18
- import type { EvalSnapshot, EvolutionEntry } from "../types";
19
17
 
20
- const ACTION_ICON: Record<string, ReactNode> = {
18
+ const ACTION_ICON: Record<string, React.ReactNode> = {
21
19
  created: <CircleDotIcon className="size-3.5" />,
22
20
  validated: <ShieldCheckIcon className="size-3.5" />,
23
21
  deployed: <RocketIcon className="size-3.5" />,
@@ -26,43 +24,27 @@ const ACTION_ICON: Record<string, ReactNode> = {
26
24
  };
27
25
 
28
26
  const ACTION_COLOR: Record<string, string> = {
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",
27
+ created: "bg-blue-500",
28
+ validated: "bg-amber-500",
29
+ deployed: "bg-emerald-500",
30
+ rejected: "bg-red-500",
31
+ rolled_back: "bg-red-400",
42
32
  };
43
33
 
44
34
  const ACTION_RING: Record<string, string> = {
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",
35
+ created: "ring-blue-500/30",
36
+ validated: "ring-amber-500/30",
37
+ deployed: "ring-emerald-500/30",
38
+ rejected: "ring-red-500/30",
39
+ rolled_back: "ring-red-400/30",
58
40
  };
59
41
 
60
42
  const ACTION_LINE: Record<string, string> = {
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",
43
+ created: "bg-blue-500/30",
44
+ validated: "bg-amber-500/30",
45
+ deployed: "bg-emerald-500/30",
46
+ rejected: "bg-red-500/30",
47
+ rolled_back: "bg-red-400/30",
66
48
  };
67
49
 
68
50
  interface Props {
@@ -71,21 +53,6 @@ interface Props {
71
53
  onSelect: (proposalId: string) => void;
72
54
  }
73
55
 
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
-
89
56
  /** Group evolution entries by proposal_id, ordered newest-first. */
90
57
  function groupByProposal(entries: EvolutionEntry[]) {
91
58
  const map = new Map<string, EvolutionEntry[]>();
@@ -125,7 +92,7 @@ function PassRateDelta({ snapshot }: { snapshot: EvalSnapshot }) {
125
92
  <span
126
93
  className={cn(
127
94
  "inline-flex items-center gap-0.5 text-[10px] font-mono font-medium",
128
- isPositive ? "text-primary" : "text-destructive",
95
+ isPositive ? "text-emerald-600 dark:text-emerald-400" : "text-red-500",
129
96
  )}
130
97
  >
131
98
  {isPositive ? (
@@ -151,35 +118,23 @@ function LifecycleLegend() {
151
118
  const [open, setOpen] = useState(false);
152
119
 
153
120
  return (
154
- <div className="px-2 pb-3">
121
+ <div className="px-2 pb-2">
155
122
  <button
156
123
  type="button"
157
124
  onClick={() => setOpen(!open)}
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"
125
+ className="flex items-center gap-1 text-[10px] text-muted-foreground/70 hover:text-muted-foreground transition-colors w-full"
161
126
  >
162
127
  {open ? <ChevronDownIcon className="size-3" /> : <ChevronRightIcon className="size-3" />}
163
128
  Lifecycle stages
164
129
  </button>
165
130
  {open && (
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
- >
131
+ <div className="mt-1.5 space-y-1.5 rounded-md border bg-muted/30 p-2">
170
132
  {LIFECYCLE_STEPS.map((step) => (
171
133
  <div key={step.action} className="flex items-start gap-2">
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>
134
+ <div className={cn("size-2 rounded-full mt-1 shrink-0", ACTION_COLOR[step.action])} />
135
+ <div className="min-w-0">
136
+ <span className="text-[10px] font-medium">{step.label}</span>
137
+ <p className="text-[10px] text-muted-foreground/70 leading-tight">{step.desc}</p>
183
138
  </div>
184
139
  </div>
185
140
  ))}
@@ -202,7 +157,7 @@ export function EvolutionTimeline({ entries, selectedProposalId, onSelect }: Pro
202
157
 
203
158
  return (
204
159
  <div className="flex flex-col gap-0">
205
- <h2 className="px-2 pb-2 text-[10px] font-semibold uppercase tracking-[0.18em] text-muted-foreground/80">
160
+ <h2 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider px-2 pb-2 sticky top-0 z-10 bg-background">
206
161
  Evolution
207
162
  </h2>
208
163
  <LifecycleLegend />
@@ -211,13 +166,11 @@ export function EvolutionTimeline({ entries, selectedProposalId, onSelect }: Pro
211
166
  const terminal = terminalAction(steps);
212
167
  const isSelected = selectedProposalId === proposalId;
213
168
  const lastStep = steps[steps.length - 1];
214
- const dotColor = ACTION_COLOR[terminal] ?? "bg-muted-foreground/20";
215
- const iconColor = ACTION_ICON_COLOR[terminal] ?? "text-muted-foreground";
169
+ const dotColor = ACTION_COLOR[terminal] ?? "bg-muted-foreground";
216
170
  const ringColor = ACTION_RING[terminal] ?? "ring-muted-foreground/30";
217
171
  const lineColor = ACTION_LINE[terminal] ?? "bg-border";
218
172
  const isLast = groupIdx === groups.length - 1;
219
173
  const snapshot = findEvalSnapshot(steps);
220
- const validationBadge = validationModeBadge(lastStep.validation_mode);
221
174
 
222
175
  return (
223
176
  <div key={proposalId} className="relative flex gap-3">
@@ -225,15 +178,14 @@ export function EvolutionTimeline({ entries, selectedProposalId, onSelect }: Pro
225
178
  <div className="flex flex-col items-center">
226
179
  <div
227
180
  className={cn(
228
- "flex items-center justify-center size-7 rounded-full ring-2 shrink-0 z-10",
181
+ "flex items-center justify-center size-7 rounded-full ring-2 text-white shrink-0 z-10",
229
182
  dotColor,
230
183
  ringColor,
231
- iconColor,
232
184
  )}
233
185
  >
234
186
  {ACTION_ICON[terminal] ?? <CircleDotIcon className="size-3.5" />}
235
187
  </div>
236
- {!isLast && <div className={cn("w-0.5 flex-1 min-h-[8px] my-1", lineColor)} />}
188
+ {!isLast && <div className={cn("w-0.5 flex-1 min-h-[16px]", lineColor)} />}
237
189
  </div>
238
190
 
239
191
  {/* Content */}
@@ -262,11 +214,6 @@ export function EvolutionTimeline({ entries, selectedProposalId, onSelect }: Pro
262
214
  <span className="text-[10px] text-muted-foreground">
263
215
  {timeAgo(lastStep.timestamp)}
264
216
  </span>
265
- {validationBadge && (
266
- <Badge variant={validationBadge.variant} className="text-[9px] uppercase">
267
- {validationBadge.label}
268
- </Badge>
269
- )}
270
217
  </div>
271
218
  {/* Pass rate delta from eval snapshot */}
272
219
  {snapshot && (
@@ -293,7 +240,7 @@ export function EvolutionTimeline({ entries, selectedProposalId, onSelect }: Pro
293
240
  key={`${s.action}-${i}`}
294
241
  className={cn(
295
242
  "size-1.5 rounded-full",
296
- ACTION_DOT[s.action] ?? "bg-muted-foreground/40",
243
+ ACTION_COLOR[s.action] ?? "bg-muted-foreground",
297
244
  )}
298
245
  />
299
246
  ))}
@@ -1,6 +1,5 @@
1
- import { InfoIcon } from "lucide-react";
2
-
3
1
  import { Tooltip, TooltipContent, TooltipTrigger } from "../primitives/tooltip";
2
+ import { InfoIcon } from "lucide-react";
4
3
 
5
4
  /** Small info icon that shows a tooltip on hover. Used to explain metrics and concepts. */
6
5
  export function InfoTip({ text }: { text: string }) {
@@ -0,0 +1,413 @@
1
+ import { useMemo, useState } from "react";
2
+ import { ChevronRightIcon, FilterIcon } from "lucide-react";
3
+ import {
4
+ Badge,
5
+ Table,
6
+ TableBody,
7
+ TableCell,
8
+ TableHead,
9
+ TableHeader,
10
+ TableRow,
11
+ } from "../primitives";
12
+ import { InfoTip } from "./InfoTip";
13
+ import { observationBadge, historicalContextBadge } from "./SkillReportPanels";
14
+ import { timeAgo } from "../lib/format";
15
+
16
+ import type { ObservationKind, HistoricalContext } from "../types";
17
+
18
+ /* ─── Public types ────────────────────────────────────── */
19
+
20
+ export interface InvocationRow {
21
+ timestamp: string | null;
22
+ session_id: string | null;
23
+ triggered: boolean;
24
+ query: string;
25
+ invocation_mode: string | null;
26
+ confidence: number | null;
27
+ tool_name: string | null;
28
+ agent_type: string | null;
29
+ observation_kind?: ObservationKind | null;
30
+ historical_context?: HistoricalContext | null;
31
+ }
32
+
33
+ export interface SessionMeta {
34
+ session_id: string;
35
+ started_at?: string | null;
36
+ model?: string | null;
37
+ workspace_path?: string | null;
38
+ platform?: string | null;
39
+ agent_cli?: string | null;
40
+ }
41
+
42
+ export type InvocationFilter = "all" | "misses" | "low_confidence";
43
+
44
+ /* ─── Session group ───────────────────────────────────── */
45
+
46
+ function SessionGroup({
47
+ sessionId,
48
+ meta,
49
+ invocations,
50
+ defaultExpanded,
51
+ }: {
52
+ sessionId: string;
53
+ meta?: SessionMeta;
54
+ invocations: InvocationRow[];
55
+ defaultExpanded: boolean;
56
+ }) {
57
+ const [expanded, setExpanded] = useState(defaultExpanded);
58
+ const ts = meta?.started_at ?? invocations[0]?.timestamp;
59
+
60
+ const modeBreakdown = invocations.reduce(
61
+ (acc, inv) => {
62
+ const mode = inv.invocation_mode ?? "unknown";
63
+ acc[mode] = (acc[mode] ?? 0) + 1;
64
+ return acc;
65
+ },
66
+ {} as Record<string, number>,
67
+ );
68
+
69
+ const formatInvoker = (inv: InvocationRow): { label: string; hint: string } => {
70
+ const cli = meta?.agent_cli?.replace(/_/g, " ");
71
+ const platform = meta?.platform?.replace(/_/g, " ");
72
+
73
+ if (inv.agent_type && inv.agent_type !== "main") {
74
+ return {
75
+ label: inv.agent_type,
76
+ hint: cli ? `${cli} subagent` : "subagent invocation",
77
+ };
78
+ }
79
+ if (cli) {
80
+ return {
81
+ label: cli,
82
+ hint:
83
+ inv.agent_type === "main"
84
+ ? "main agent invocation"
85
+ : "session agent that invoked the skill",
86
+ };
87
+ }
88
+ if (platform) {
89
+ return {
90
+ label: platform,
91
+ hint: inv.agent_type === "main" ? "main agent invocation" : "session platform",
92
+ };
93
+ }
94
+ if (inv.agent_type) {
95
+ return { label: inv.agent_type, hint: "recorded subagent type" };
96
+ }
97
+ return {
98
+ label: "No data",
99
+ hint: "invoker was not captured in this record",
100
+ };
101
+ };
102
+
103
+ return (
104
+ <div className="overflow-hidden rounded-lg border border-slate-200 transition-colors dark:border-slate-800">
105
+ {/* Session header */}
106
+ <button
107
+ type="button"
108
+ className="flex w-full items-center gap-3 px-4 py-3 text-left transition-colors hover:bg-slate-50 active:bg-slate-100 dark:hover:bg-slate-800/40 dark:active:bg-slate-800/60"
109
+ onClick={() => setExpanded(!expanded)}
110
+ >
111
+ <ChevronRightIcon
112
+ className={`size-3.5 shrink-0 text-slate-400 transition-transform duration-150 dark:text-slate-500 ${expanded ? "rotate-90" : ""}`}
113
+ />
114
+ <div className="flex min-w-0 flex-1 flex-col gap-1">
115
+ <div className="flex items-center gap-2">
116
+ <span className="text-sm font-medium text-slate-900 dark:text-white">
117
+ {invocations.length} invocation
118
+ {invocations.length !== 1 ? "s" : ""}
119
+ </span>
120
+ <span className="text-xs text-slate-500 dark:text-slate-400">
121
+ {ts ? timeAgo(ts) : ""}
122
+ </span>
123
+ </div>
124
+ <div className="flex flex-wrap items-center gap-1.5">
125
+ {meta?.model && (
126
+ <Badge variant="secondary" className="text-[10px] font-normal">
127
+ {meta.model}
128
+ </Badge>
129
+ )}
130
+ {meta?.workspace_path && (
131
+ <span
132
+ className="font-mono text-[11px] text-slate-500 dark:text-slate-400"
133
+ title={meta.workspace_path}
134
+ >
135
+ {meta.workspace_path.split("/").slice(-2).join("/")}
136
+ </span>
137
+ )}
138
+ </div>
139
+ </div>
140
+
141
+ {/* Compact mode summary when collapsed */}
142
+ {!expanded && (
143
+ <div className="flex shrink-0 items-center gap-1">
144
+ {Object.entries(modeBreakdown).map(([mode, count]) => (
145
+ <Badge key={mode} variant="outline" className="gap-1 text-[10px] font-normal">
146
+ {mode} <span className="text-slate-400 dark:text-slate-500">{count}</span>
147
+ </Badge>
148
+ ))}
149
+ </div>
150
+ )}
151
+ <span className="shrink-0 font-mono text-[10px] text-slate-300 dark:text-slate-600">
152
+ {sessionId.substring(0, 8)}
153
+ </span>
154
+ </button>
155
+
156
+ {/* Invocation table */}
157
+ {expanded && (
158
+ <div className="overflow-x-auto border-t border-slate-200 dark:border-slate-800">
159
+ <Table>
160
+ <TableHeader>
161
+ <TableRow className="bg-slate-50 hover:bg-slate-50 dark:bg-slate-800/40 dark:hover:bg-slate-800/40">
162
+ <TableHead className="h-8 text-[10px] font-semibold uppercase tracking-[0.15em]">
163
+ Prompt <InfoTip text="The user prompt that led to this skill being invoked" />
164
+ </TableHead>
165
+ <TableHead className="h-8 w-[90px] text-[10px] font-semibold uppercase tracking-[0.15em]">
166
+ Mode{" "}
167
+ <InfoTip text="explicit = user typed /skillname; implicit = user mentioned skill by name; inferred = agent chose skill autonomously" />
168
+ </TableHead>
169
+ <TableHead className="h-8 w-[70px] text-[10px] font-semibold uppercase tracking-[0.15em]">
170
+ Confidence{" "}
171
+ <InfoTip text="Model's confidence score (0-100%) when routing this prompt to the skill" />
172
+ </TableHead>
173
+ <TableHead className="h-8 w-[110px] text-[10px] font-semibold uppercase tracking-[0.15em]">
174
+ Invoker{" "}
175
+ <InfoTip text="Who invoked the skill. Prefers subagent type when present, otherwise falls back to the session agent or platform." />
176
+ </TableHead>
177
+ <TableHead className="h-8 w-[120px] text-[10px] font-semibold uppercase tracking-[0.15em]">
178
+ Evidence
179
+ </TableHead>
180
+ <TableHead className="h-8 w-[70px] text-right text-[10px] font-semibold uppercase tracking-[0.15em]">
181
+ Time
182
+ </TableHead>
183
+ </TableRow>
184
+ </TableHeader>
185
+ <TableBody>
186
+ {invocations.map((inv, i) => (
187
+ <TableRow
188
+ key={i}
189
+ className={!inv.triggered ? "bg-red-50/50 dark:bg-red-950/10" : ""}
190
+ >
191
+ <TableCell
192
+ className="max-w-[500px] truncate py-2 text-sm"
193
+ title={inv.query || undefined}
194
+ >
195
+ {inv.query || (
196
+ <span className="italic text-slate-300 dark:text-slate-600">
197
+ No prompt recorded
198
+ </span>
199
+ )}
200
+ {!inv.triggered && (
201
+ <Badge variant="destructive" className="ml-2 text-[10px] font-normal">
202
+ missed
203
+ </Badge>
204
+ )}
205
+ </TableCell>
206
+ <TableCell className="py-2">
207
+ {inv.invocation_mode ? (
208
+ <Badge variant="secondary" className="text-[10px] font-normal">
209
+ {inv.invocation_mode}
210
+ </Badge>
211
+ ) : (
212
+ <span className="text-[11px] text-slate-400 dark:text-slate-500">
213
+ Unknown mode
214
+ </span>
215
+ )}
216
+ </TableCell>
217
+ <TableCell className="py-2 font-mono text-xs tabular-nums text-slate-600 dark:text-slate-300">
218
+ {inv.confidence !== null
219
+ ? `${Math.round(inv.confidence * 100)}%`
220
+ : "Not recorded"}
221
+ </TableCell>
222
+ <TableCell className="py-2">
223
+ {(() => {
224
+ const invoker = formatInvoker(inv);
225
+ return invoker.label === "No data" ? (
226
+ <span
227
+ className="text-[11px] text-slate-400 dark:text-slate-500"
228
+ title={invoker.hint}
229
+ >
230
+ {invoker.label}
231
+ </span>
232
+ ) : (
233
+ <Badge
234
+ variant={
235
+ inv.agent_type && inv.agent_type !== "main" ? "outline" : "secondary"
236
+ }
237
+ className="text-[10px] font-normal capitalize"
238
+ title={invoker.hint}
239
+ >
240
+ {invoker.label}
241
+ </Badge>
242
+ );
243
+ })()}
244
+ </TableCell>
245
+ <TableCell className="py-2">
246
+ {(() => {
247
+ const observation = observationBadge(inv.observation_kind);
248
+ const historicalCtx = historicalContextBadge(inv.historical_context);
249
+ return observation ? (
250
+ <div className="flex flex-wrap items-center gap-1.5">
251
+ <Badge variant={observation.variant} className="text-[10px] font-normal">
252
+ {observation.label}
253
+ </Badge>
254
+ {historicalCtx && (
255
+ <Badge
256
+ variant={historicalCtx.variant}
257
+ className="text-[10px] font-normal"
258
+ >
259
+ {historicalCtx.label}
260
+ </Badge>
261
+ )}
262
+ </div>
263
+ ) : (
264
+ <div className="flex flex-wrap items-center gap-1.5">
265
+ <span className="text-[11px] text-slate-400 dark:text-slate-500">
266
+ canonical
267
+ </span>
268
+ {historicalCtx && (
269
+ <Badge
270
+ variant={historicalCtx.variant}
271
+ className="text-[10px] font-normal"
272
+ >
273
+ {historicalCtx.label}
274
+ </Badge>
275
+ )}
276
+ </div>
277
+ );
278
+ })()}
279
+ </TableCell>
280
+ <TableCell className="whitespace-nowrap py-2 text-right font-mono text-[11px] text-slate-400 dark:text-slate-500">
281
+ {inv.timestamp ? timeAgo(inv.timestamp) : ""}
282
+ </TableCell>
283
+ </TableRow>
284
+ ))}
285
+ </TableBody>
286
+ </Table>
287
+ </div>
288
+ )}
289
+ </div>
290
+ );
291
+ }
292
+
293
+ /* ─── InvocationsPanel ────────────────────────────────── */
294
+
295
+ export function InvocationsPanel({
296
+ invocations,
297
+ sessionMetadata = [],
298
+ }: {
299
+ invocations: InvocationRow[];
300
+ sessionMetadata?: SessionMeta[];
301
+ }) {
302
+ const [filter, setFilter] = useState<InvocationFilter>("all");
303
+
304
+ const sessionMetaMap = useMemo(
305
+ () => new Map(sessionMetadata.map((s) => [s.session_id, s])),
306
+ [sessionMetadata],
307
+ );
308
+
309
+ const filtered = useMemo(() => {
310
+ switch (filter) {
311
+ case "misses":
312
+ return invocations.filter((i) => !i.triggered);
313
+ case "low_confidence":
314
+ return invocations.filter((i) => i.confidence !== null && i.confidence < 0.5);
315
+ default:
316
+ return invocations;
317
+ }
318
+ }, [invocations, filter]);
319
+
320
+ const groupedSessions = useMemo(() => {
321
+ const sessionMap = new Map<string, InvocationRow[]>();
322
+ for (const inv of filtered) {
323
+ const sid = inv.session_id ?? "unknown";
324
+ const arr = sessionMap.get(sid);
325
+ if (arr) arr.push(inv);
326
+ else sessionMap.set(sid, [inv]);
327
+ }
328
+ return [...sessionMap.entries()].sort(([, a], [, b]) =>
329
+ (b[0]?.timestamp ?? "").localeCompare(a[0]?.timestamp ?? ""),
330
+ );
331
+ }, [filtered]);
332
+
333
+ if (invocations.length === 0) {
334
+ return (
335
+ <div className="flex items-center justify-center rounded-lg border border-dashed border-slate-300 py-12 dark:border-slate-700">
336
+ <p className="text-sm text-slate-500 dark:text-slate-400">
337
+ No invocation records yet. Invocations appear when skills are triggered during real
338
+ sessions.
339
+ </p>
340
+ </div>
341
+ );
342
+ }
343
+
344
+ return (
345
+ <div className="space-y-3">
346
+ {/* Filters */}
347
+ <div className="flex flex-wrap items-center justify-between gap-2">
348
+ <div className="flex flex-wrap items-center gap-2">
349
+ <FilterIcon className="size-3.5 text-slate-400 dark:text-slate-500" />
350
+ {(
351
+ [
352
+ ["all", "All"],
353
+ ["misses", "Misses"],
354
+ ["low_confidence", "Low confidence"],
355
+ ] as const
356
+ ).map(([key, label]) => (
357
+ <button key={key} type="button" onClick={() => setFilter(key)} className="inline-block">
358
+ <Badge
359
+ variant={filter === key ? "default" : "outline"}
360
+ className="cursor-pointer text-[10px]"
361
+ >
362
+ {label}
363
+ </Badge>
364
+ </button>
365
+ ))}
366
+ </div>
367
+ <span className="text-xs text-slate-500 dark:text-slate-400">
368
+ {filtered.length} invocations across {groupedSessions.length} sessions
369
+ </span>
370
+ </div>
371
+
372
+ {/* Legend */}
373
+ <div className="flex flex-wrap items-center gap-x-4 gap-y-1 text-[11px] text-slate-500 dark:text-slate-400">
374
+ <span className="inline-flex items-center gap-1.5">
375
+ <span className="size-1.5 rounded-full bg-slate-400" />
376
+ explicit = user typed /skill
377
+ </span>
378
+ <span className="inline-flex items-center gap-1.5">
379
+ <span className="size-1.5 rounded-full bg-slate-400" />
380
+ implicit = mentioned by name
381
+ </span>
382
+ <span className="inline-flex items-center gap-1.5">
383
+ <span className="size-1.5 rounded-full bg-slate-400" />
384
+ inferred = agent chose autonomously
385
+ </span>
386
+ <span className="inline-flex items-center gap-1.5">
387
+ <span className="size-1.5 rounded-full bg-red-400" />
388
+ missed = skill should have triggered
389
+ </span>
390
+ </div>
391
+
392
+ {/* Session groups */}
393
+ {groupedSessions.length === 0 ? (
394
+ <div className="flex items-center justify-center py-8 text-sm text-slate-500 dark:text-slate-400">
395
+ No invocations match this filter.
396
+ </div>
397
+ ) : (
398
+ groupedSessions.map(([sessionId, sessionInvocations], idx) => {
399
+ const meta = sessionMetaMap.get(sessionId);
400
+ return (
401
+ <SessionGroup
402
+ key={sessionId}
403
+ sessionId={sessionId}
404
+ meta={meta}
405
+ invocations={sessionInvocations}
406
+ defaultExpanded={idx < 3}
407
+ />
408
+ );
409
+ })
410
+ )}
411
+ </div>
412
+ );
413
+ }