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,11 +1,10 @@
1
- import { ClockIcon, GitPullRequestArrowIcon, SearchXIcon, ActivityIcon } from "lucide-react";
2
-
3
- import { timeAgo } from "../lib/format";
4
1
  import { Badge } from "../primitives/badge";
5
2
  import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../primitives/card";
6
3
  import { Tabs, TabsContent, TabsList, TabsTrigger } from "../primitives/tabs";
7
4
  import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../primitives/tooltip";
8
5
  import type { EvolutionEntry, PendingProposal, UnmatchedQuery } from "../types";
6
+ import { timeAgo } from "../lib/format";
7
+ import { ClockIcon, GitPullRequestArrowIcon, SearchXIcon, ActivityIcon } from "lucide-react";
9
8
 
10
9
  const ACTION_VARIANT: Record<string, "default" | "secondary" | "destructive" | "outline"> = {
11
10
  created: "outline",
@@ -21,179 +20,15 @@ export function ActivityPanel({
21
20
  pendingProposals,
22
21
  unmatchedQueries,
23
22
  onSelectProposal,
24
- embedded = false,
25
23
  }: {
26
24
  evolution: EvolutionEntry[];
27
25
  pendingProposals: PendingProposal[];
28
26
  unmatchedQueries: UnmatchedQuery[];
29
27
  onSelectProposal?: (skillName: string, proposalId: string) => void;
30
- embedded?: boolean;
31
28
  }) {
32
29
  const hasActivity =
33
30
  evolution.length > 0 || pendingProposals.length > 0 || unmatchedQueries.length > 0;
34
31
 
35
- const content = hasActivity ? (
36
- <Tabs
37
- defaultValue={
38
- pendingProposals.length > 0 ? "pending" : evolution.length > 0 ? "timeline" : "unmatched"
39
- }
40
- >
41
- <TooltipProvider>
42
- <TabsList className="w-full">
43
- {pendingProposals.length > 0 && (
44
- <Tooltip>
45
- <TooltipTrigger
46
- render={
47
- <TabsTrigger
48
- value="pending"
49
- className="flex-1 gap-1.5"
50
- aria-label={`Pending proposals (${pendingProposals.length})`}
51
- />
52
- }
53
- >
54
- <GitPullRequestArrowIcon className="size-3.5" />
55
- <Badge variant="secondary" className="h-4 px-1 text-[10px]">
56
- {pendingProposals.length}
57
- </Badge>
58
- </TooltipTrigger>
59
- <TooltipContent>Undeployed proposals</TooltipContent>
60
- </Tooltip>
61
- )}
62
- <Tooltip>
63
- <TooltipTrigger
64
- render={<TabsTrigger value="timeline" className="flex-1" aria-label="Timeline" />}
65
- >
66
- <ClockIcon className="size-3.5" />
67
- </TooltipTrigger>
68
- <TooltipContent>Timeline</TooltipContent>
69
- </Tooltip>
70
- {unmatchedQueries.length > 0 && (
71
- <Tooltip>
72
- <TooltipTrigger
73
- render={
74
- <TabsTrigger
75
- value="unmatched"
76
- className="flex-1 gap-1.5"
77
- aria-label={`Unmatched queries (${unmatchedQueries.length})`}
78
- />
79
- }
80
- >
81
- <SearchXIcon className="size-3.5" />
82
- <Badge variant="destructive" className="h-4 px-1 text-[10px]">
83
- {unmatchedQueries.length}
84
- </Badge>
85
- </TooltipTrigger>
86
- <TooltipContent>Unmatched queries</TooltipContent>
87
- </Tooltip>
88
- )}
89
- </TabsList>
90
- </TooltipProvider>
91
-
92
- {pendingProposals.length > 0 && (
93
- <TabsContent value="pending" className="mt-4 space-y-3">
94
- {pendingProposals.slice(0, 10).map((p) => (
95
- <button
96
- key={p.proposal_id}
97
- type="button"
98
- onClick={() => {
99
- if (p.skill_name && onSelectProposal) onSelectProposal(p.skill_name, p.proposal_id);
100
- }}
101
- disabled={!p.skill_name || !onSelectProposal}
102
- className="flex w-full gap-3 rounded-md p-1.5 text-left transition-colors enabled:hover:bg-accent/40 disabled:cursor-default"
103
- >
104
- <div className="mt-1 size-2 shrink-0 rounded-full bg-primary-accent" />
105
- <div className="flex-1 min-w-0 space-y-1">
106
- <div className="flex items-center gap-2">
107
- <Badge variant={ACTION_VARIANT[p.action] ?? "secondary"} className="text-[10px]">
108
- {p.action}
109
- </Badge>
110
- <span className="text-[10px] text-slate-500 font-mono">
111
- {timeAgo(p.timestamp)}
112
- </span>
113
- </div>
114
- <p className="text-xs text-muted-foreground line-clamp-2">{p.details}</p>
115
- {p.skill_name && (
116
- <span className="text-[10px] text-muted-foreground/60 font-mono">
117
- {p.skill_name} · #{p.proposal_id.slice(0, 8)}
118
- </span>
119
- )}
120
- </div>
121
- </button>
122
- ))}
123
- </TabsContent>
124
- )}
125
-
126
- <TabsContent value="timeline" className="mt-4 space-y-3">
127
- {evolution.slice(0, 30).map((entry, i) => (
128
- <button
129
- key={`${entry.proposal_id}-${i}`}
130
- type="button"
131
- onClick={() => {
132
- if (entry.skill_name && onSelectProposal)
133
- onSelectProposal(entry.skill_name, entry.proposal_id);
134
- }}
135
- disabled={!entry.skill_name || !onSelectProposal}
136
- className="flex w-full gap-3 rounded-md p-1.5 text-left transition-colors enabled:hover:bg-accent/40 disabled:cursor-default"
137
- >
138
- <div
139
- className={`mt-1 size-2 shrink-0 rounded-full ${
140
- entry.action === "deployed"
141
- ? "bg-primary"
142
- : entry.action === "rejected" || entry.action === "rolled_back"
143
- ? "bg-destructive"
144
- : entry.action === "validated"
145
- ? "bg-primary-accent"
146
- : "bg-primary-accent"
147
- }`}
148
- />
149
- <div className="flex-1 min-w-0 space-y-1">
150
- <div className="flex items-center gap-2">
151
- <Badge
152
- variant={ACTION_VARIANT[entry.action] ?? "secondary"}
153
- className="text-[10px]"
154
- >
155
- {entry.action}
156
- </Badge>
157
- <span className="text-xs text-muted-foreground font-mono">
158
- {timeAgo(entry.timestamp)}
159
- </span>
160
- </div>
161
- <p className="text-xs text-muted-foreground line-clamp-2">{entry.details}</p>
162
- <span className="text-[10px] text-muted-foreground/60 font-mono">
163
- {entry.skill_name ? `${entry.skill_name} · ` : ""}#{entry.proposal_id.slice(0, 8)}
164
- </span>
165
- </div>
166
- </button>
167
- ))}
168
- {evolution.length === 0 && (
169
- <p className="text-sm text-muted-foreground py-4 text-center">No timeline events</p>
170
- )}
171
- </TabsContent>
172
-
173
- {unmatchedQueries.length > 0 && (
174
- <TabsContent value="unmatched" className="mt-4 space-y-2">
175
- {unmatchedQueries.slice(0, 15).map((q, i) => (
176
- <div key={`${q.session_id}-${i}`} className="flex gap-3">
177
- <div className="mt-1 size-2 shrink-0 rounded-full bg-muted-foreground/40" />
178
- <div className="flex-1 min-w-0 space-y-0.5">
179
- <span className="font-mono text-xs text-muted-foreground">
180
- {timeAgo(q.timestamp)}
181
- </span>
182
- <p className="line-clamp-2 font-mono text-xs text-foreground/80">{q.query}</p>
183
- </div>
184
- </div>
185
- ))}
186
- </TabsContent>
187
- )}
188
- </Tabs>
189
- ) : (
190
- <p className="py-6 text-center text-sm text-muted-foreground">No recent activity</p>
191
- );
192
-
193
- if (embedded) {
194
- return <div>{content}</div>;
195
- }
196
-
197
32
  if (!hasActivity) {
198
33
  return (
199
34
  <Card>
@@ -219,7 +54,156 @@ export function ActivityPanel({
219
54
  </CardTitle>
220
55
  <CardDescription>Recent evolution events and queries</CardDescription>
221
56
  </CardHeader>
222
- <CardContent>{content}</CardContent>
57
+ <CardContent>
58
+ <Tabs
59
+ defaultValue={
60
+ pendingProposals.length > 0
61
+ ? "pending"
62
+ : evolution.length > 0
63
+ ? "timeline"
64
+ : "unmatched"
65
+ }
66
+ >
67
+ <TooltipProvider>
68
+ <TabsList className="w-full">
69
+ {pendingProposals.length > 0 && (
70
+ <Tooltip>
71
+ <TooltipTrigger
72
+ render={<TabsTrigger value="pending" className="flex-1 gap-1.5" />}
73
+ >
74
+ <GitPullRequestArrowIcon className="size-3.5" />
75
+ <Badge variant="secondary" className="h-4 px-1 text-[10px]">
76
+ {pendingProposals.length}
77
+ </Badge>
78
+ </TooltipTrigger>
79
+ <TooltipContent>Pending proposals</TooltipContent>
80
+ </Tooltip>
81
+ )}
82
+ <Tooltip>
83
+ <TooltipTrigger render={<TabsTrigger value="timeline" className="flex-1" />}>
84
+ <ClockIcon className="size-3.5" />
85
+ </TooltipTrigger>
86
+ <TooltipContent>Timeline</TooltipContent>
87
+ </Tooltip>
88
+ {unmatchedQueries.length > 0 && (
89
+ <Tooltip>
90
+ <TooltipTrigger
91
+ render={<TabsTrigger value="unmatched" className="flex-1 gap-1.5" />}
92
+ >
93
+ <SearchXIcon className="size-3.5" />
94
+ <Badge variant="destructive" className="h-4 px-1 text-[10px]">
95
+ {unmatchedQueries.length}
96
+ </Badge>
97
+ </TooltipTrigger>
98
+ <TooltipContent>Unmatched queries</TooltipContent>
99
+ </Tooltip>
100
+ )}
101
+ </TabsList>
102
+ </TooltipProvider>
103
+
104
+ {pendingProposals.length > 0 && (
105
+ <TabsContent value="pending" className="mt-4 space-y-3">
106
+ {pendingProposals.slice(0, 10).map((p) => (
107
+ <button
108
+ key={p.proposal_id}
109
+ type="button"
110
+ onClick={() => {
111
+ if (p.skill_name && onSelectProposal)
112
+ onSelectProposal(p.skill_name, p.proposal_id);
113
+ }}
114
+ disabled={!p.skill_name || !onSelectProposal}
115
+ className="flex w-full gap-3 rounded-md p-1.5 text-left transition-colors enabled:hover:bg-accent/40 disabled:cursor-default"
116
+ >
117
+ <div className="mt-1 size-2 shrink-0 rounded-full bg-amber-400" />
118
+ <div className="flex-1 min-w-0 space-y-1">
119
+ <div className="flex items-center gap-2">
120
+ <Badge
121
+ variant={ACTION_VARIANT[p.action] ?? "secondary"}
122
+ className="text-[10px]"
123
+ >
124
+ {p.action}
125
+ </Badge>
126
+ <span className="text-xs text-muted-foreground font-mono">
127
+ {timeAgo(p.timestamp)}
128
+ </span>
129
+ </div>
130
+ <p className="text-xs text-muted-foreground line-clamp-2">{p.details}</p>
131
+ {p.skill_name && (
132
+ <span className="text-[10px] text-muted-foreground/60 font-mono">
133
+ {p.skill_name} · #{p.proposal_id.slice(0, 8)}
134
+ </span>
135
+ )}
136
+ </div>
137
+ </button>
138
+ ))}
139
+ </TabsContent>
140
+ )}
141
+
142
+ <TabsContent value="timeline" className="mt-4 space-y-3">
143
+ {evolution.slice(0, 30).map((entry, i) => (
144
+ <button
145
+ key={`${entry.proposal_id}-${i}`}
146
+ type="button"
147
+ onClick={() => {
148
+ if (entry.skill_name && onSelectProposal)
149
+ onSelectProposal(entry.skill_name, entry.proposal_id);
150
+ }}
151
+ disabled={!entry.skill_name || !onSelectProposal}
152
+ className="flex w-full gap-3 rounded-md p-1.5 text-left transition-colors enabled:hover:bg-accent/40 disabled:cursor-default"
153
+ >
154
+ <div
155
+ className={`mt-1 size-2 shrink-0 rounded-full ${
156
+ entry.action === "deployed"
157
+ ? "bg-emerald-500"
158
+ : entry.action === "rejected" || entry.action === "rolled_back"
159
+ ? "bg-red-500"
160
+ : entry.action === "validated"
161
+ ? "bg-amber-400"
162
+ : "bg-primary-accent"
163
+ }`}
164
+ />
165
+ <div className="flex-1 min-w-0 space-y-1">
166
+ <div className="flex items-center gap-2">
167
+ <Badge
168
+ variant={ACTION_VARIANT[entry.action] ?? "secondary"}
169
+ className="text-[10px]"
170
+ >
171
+ {entry.action}
172
+ </Badge>
173
+ <span className="text-xs text-muted-foreground font-mono">
174
+ {timeAgo(entry.timestamp)}
175
+ </span>
176
+ </div>
177
+ <p className="text-xs text-muted-foreground line-clamp-2">{entry.details}</p>
178
+ <span className="text-[10px] text-muted-foreground/60 font-mono">
179
+ {entry.skill_name ? `${entry.skill_name} · ` : ""}#
180
+ {entry.proposal_id.slice(0, 8)}
181
+ </span>
182
+ </div>
183
+ </button>
184
+ ))}
185
+ {evolution.length === 0 && (
186
+ <p className="text-sm text-muted-foreground text-center py-4">No timeline events</p>
187
+ )}
188
+ </TabsContent>
189
+
190
+ {unmatchedQueries.length > 0 && (
191
+ <TabsContent value="unmatched" className="mt-4 space-y-2">
192
+ {unmatchedQueries.slice(0, 15).map((q, i) => (
193
+ <div key={`${q.session_id}-${i}`} className="flex gap-3">
194
+ <div className="mt-1 size-2 shrink-0 rounded-full bg-muted-foreground/40" />
195
+ <div className="flex-1 min-w-0 space-y-0.5">
196
+ <span className="text-xs text-muted-foreground font-mono">
197
+ {timeAgo(q.timestamp)}
198
+ </span>
199
+ <p className="text-xs font-mono text-foreground/80 line-clamp-2">{q.query}</p>
200
+ </div>
201
+ </div>
202
+ ))}
203
+ </TabsContent>
204
+ )}
205
+ </Tabs>
206
+ </CardContent>
223
207
  </Card>
224
208
  );
225
209
  }
@@ -0,0 +1,344 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+
5
+ /* ── Types ──────────────────────────────────────────────── */
6
+
7
+ export interface PassRateTrendPoint {
8
+ date: string;
9
+ pass_rate: number;
10
+ total_checks: number;
11
+ }
12
+
13
+ export interface SkillRanking {
14
+ skill_name: string;
15
+ pass_rate: number;
16
+ total_checks: number;
17
+ triggered_count: number;
18
+ }
19
+
20
+ export interface DailyActivity {
21
+ date: string;
22
+ checks: number;
23
+ }
24
+
25
+ export interface EvolutionImpact {
26
+ skill_name: string;
27
+ proposal_id: string;
28
+ deployed_at: string;
29
+ pass_rate_before: number;
30
+ pass_rate_after: number;
31
+ }
32
+
33
+ export interface AnalyticsSummary {
34
+ total_evolutions: number;
35
+ avg_improvement: number;
36
+ total_checks_30d: number;
37
+ active_skills: number;
38
+ }
39
+
40
+ export interface AnalyticsResponse {
41
+ pass_rate_trend: PassRateTrendPoint[];
42
+ skill_rankings: SkillRanking[];
43
+ daily_activity: DailyActivity[];
44
+ evolution_impact: EvolutionImpact[];
45
+ summary: AnalyticsSummary;
46
+ }
47
+
48
+ /* ── Helpers ────────────────────────────────────────────── */
49
+
50
+ function formatDayBucketLabel(day: string): string {
51
+ const parts = day.split("-");
52
+ const month = parts[1];
53
+ const date = parts[2];
54
+ if (!month || !date) return day;
55
+ return `${Number(month)}/${Number(date)}`;
56
+ }
57
+
58
+ /* ── SVG Line Chart ─────────────────────────────────────── */
59
+
60
+ export function PassRateTrendChart({
61
+ data,
62
+ mode,
63
+ }: {
64
+ data: PassRateTrendPoint[];
65
+ mode: "pass_rate" | "volume";
66
+ }) {
67
+ const width = 720;
68
+ const height = 260;
69
+ const padX = 48;
70
+ const padY = 32;
71
+ const padBottom = 28;
72
+
73
+ const values = data.map((d) => (mode === "pass_rate" ? d.pass_rate * 100 : d.total_checks));
74
+ const maxVal = Math.max(...values, mode === "pass_rate" ? 100 : 1);
75
+ const minVal = 0;
76
+
77
+ const chartW = width - padX * 2;
78
+ const chartH = height - padY - padBottom;
79
+
80
+ const points = values.map((v, i) => {
81
+ const x = padX + (i / Math.max(1, values.length - 1)) * chartW;
82
+ const y = padY + chartH - ((v - minVal) / Math.max(1, maxVal - minVal)) * chartH;
83
+ return { x, y };
84
+ });
85
+
86
+ const pathD = points.map((p, i) => `${i === 0 ? "M" : "L"}${p.x},${p.y}`).join(" ");
87
+ const areaD = `${pathD} L${points[points.length - 1]?.x ?? padX},${padY + chartH} L${padX},${padY + chartH} Z`;
88
+
89
+ const yTicks =
90
+ mode === "pass_rate"
91
+ ? [0, 25, 50, 75, 100]
92
+ : Array.from({ length: 5 }, (_, i) => Math.round((maxVal / 4) * i));
93
+
94
+ const xLabels: Array<{ label: string; x: number }> = [];
95
+ const step = Math.max(1, Math.floor(data.length / 6));
96
+ for (let i = 0; i < data.length; i += step) {
97
+ const d = data[i];
98
+ const pt = points[i];
99
+ if (d && pt) {
100
+ xLabels.push({ label: formatDayBucketLabel(d.date), x: pt.x });
101
+ }
102
+ }
103
+
104
+ if (data.length === 0) {
105
+ return (
106
+ <div className="flex items-center justify-center h-[260px] text-muted-foreground text-sm">
107
+ No trend data available yet
108
+ </div>
109
+ );
110
+ }
111
+
112
+ return (
113
+ <svg
114
+ viewBox={`0 0 ${width} ${height}`}
115
+ className="w-full h-auto"
116
+ preserveAspectRatio="xMidYMid meet"
117
+ >
118
+ <defs>
119
+ <linearGradient id="analytics-chart-fill" x1="0" y1="0" x2="0" y2="1">
120
+ <stop offset="0%" stopColor="var(--primary)" stopOpacity="0.3" />
121
+ <stop offset="100%" stopColor="var(--primary)" stopOpacity="0.02" />
122
+ </linearGradient>
123
+ </defs>
124
+
125
+ {yTicks.map((tick) => {
126
+ const y = padY + chartH - ((tick - minVal) / Math.max(1, maxVal - minVal)) * chartH;
127
+ return (
128
+ <g key={tick}>
129
+ <line
130
+ x1={padX}
131
+ y1={y}
132
+ x2={width - padX}
133
+ y2={y}
134
+ stroke="var(--border)"
135
+ strokeWidth="0.5"
136
+ strokeDasharray="4 4"
137
+ />
138
+ <text
139
+ x={padX - 8}
140
+ y={y + 3}
141
+ textAnchor="end"
142
+ fill="var(--muted-foreground)"
143
+ fontSize="9"
144
+ fontFamily="var(--font-headline)"
145
+ >
146
+ {mode === "pass_rate" ? `${tick}%` : tick}
147
+ </text>
148
+ </g>
149
+ );
150
+ })}
151
+
152
+ {xLabels.map((label) => (
153
+ <text
154
+ key={label.label}
155
+ x={label.x}
156
+ y={height - 4}
157
+ textAnchor="middle"
158
+ fill="var(--muted-foreground)"
159
+ fontSize="9"
160
+ fontFamily="var(--font-headline)"
161
+ >
162
+ {label.label}
163
+ </text>
164
+ ))}
165
+
166
+ {points.length > 1 && <path d={areaD} fill="url(#analytics-chart-fill)" />}
167
+
168
+ {points.length > 1 && (
169
+ <path
170
+ d={pathD}
171
+ fill="none"
172
+ stroke="var(--primary)"
173
+ strokeWidth="2"
174
+ strokeLinecap="round"
175
+ strokeLinejoin="round"
176
+ style={{ filter: "drop-shadow(0 0 4px rgba(79,242,255,0.5))" }}
177
+ />
178
+ )}
179
+
180
+ {points.map((p, i) => (
181
+ <circle
182
+ key={i}
183
+ cx={p.x}
184
+ cy={p.y}
185
+ r="3"
186
+ fill="var(--primary)"
187
+ stroke="var(--muted)"
188
+ strokeWidth="1.5"
189
+ />
190
+ ))}
191
+ </svg>
192
+ );
193
+ }
194
+
195
+ /* ── Skill Rankings List ────────────────────────────────── */
196
+
197
+ export function SkillRankingsList({ skills }: { skills: SkillRanking[] }) {
198
+ if (skills.length === 0) {
199
+ return (
200
+ <div className="flex-1 flex items-center justify-center">
201
+ <p className="text-sm text-muted-foreground">No skills graded yet</p>
202
+ </div>
203
+ );
204
+ }
205
+
206
+ return (
207
+ <div className="flex-1 flex flex-col gap-4">
208
+ {skills.map((skill) => (
209
+ <div key={skill.skill_name}>
210
+ <div className="flex items-center justify-between mb-1.5">
211
+ <span className="font-headline text-[11px] uppercase tracking-wider text-foreground truncate max-w-[65%]">
212
+ {skill.skill_name}
213
+ </span>
214
+ <span className="font-headline text-xs font-semibold text-primary">
215
+ {Math.round(skill.pass_rate * 100)}%
216
+ </span>
217
+ </div>
218
+ <div className="h-[1.5px] rounded-full bg-border/30 overflow-hidden">
219
+ <div
220
+ className="h-full rounded-full bg-primary transition-all duration-500"
221
+ style={{
222
+ width: `${Math.round(skill.pass_rate * 100)}%`,
223
+ boxShadow: "0 0 6px rgba(79,242,255,0.4)",
224
+ }}
225
+ />
226
+ </div>
227
+ <p className="text-[10px] text-muted-foreground mt-1">
228
+ {skill.total_checks} checks &middot; {skill.triggered_count} triggered
229
+ </p>
230
+ </div>
231
+ ))}
232
+ </div>
233
+ );
234
+ }
235
+
236
+ /* ── Activity Heatmap ───────────────────────────────────── */
237
+
238
+ export function ActivityHeatmap({ data }: { data: DailyActivity[] }) {
239
+ const cells = data.slice(-84);
240
+ const maxChecks = Math.max(...cells.map((d) => d.checks), 1);
241
+
242
+ if (cells.length === 0) {
243
+ return (
244
+ <div className="flex items-center justify-center h-32 text-muted-foreground text-sm">
245
+ No grading activity recorded yet
246
+ </div>
247
+ );
248
+ }
249
+
250
+ return (
251
+ <div className="flex flex-col h-full">
252
+ <div className="flex flex-wrap gap-1.5 flex-1 content-start">
253
+ {cells.map((day) => {
254
+ const intensity = day.checks / maxChecks;
255
+ const opacity = Math.max(0.08, intensity);
256
+ return (
257
+ <div
258
+ key={day.date}
259
+ className="size-5 rounded-sm transition-colors"
260
+ style={{
261
+ backgroundColor: `color-mix(in srgb, var(--primary) ${Math.round(opacity * 100)}%, transparent)`,
262
+ }}
263
+ title={`${day.date}: ${day.checks} checks`}
264
+ />
265
+ );
266
+ })}
267
+ </div>
268
+ <div className="flex items-center justify-end gap-2 mt-auto pt-3">
269
+ <span className="text-[10px] font-headline uppercase tracking-widest text-muted-foreground">
270
+ Quiet
271
+ </span>
272
+ {[8, 25, 50, 75, 100].map((pct) => (
273
+ <div
274
+ key={pct}
275
+ className="size-3 rounded-sm"
276
+ style={{
277
+ backgroundColor: `color-mix(in srgb, var(--primary) ${pct}%, transparent)`,
278
+ }}
279
+ />
280
+ ))}
281
+ <span className="text-[10px] font-headline uppercase tracking-widest text-muted-foreground">
282
+ Active
283
+ </span>
284
+ </div>
285
+ </div>
286
+ );
287
+ }
288
+
289
+ /* ── Evolution ROI List ─────────────────────────────────── */
290
+
291
+ export function EvolutionROIList({ impacts }: { impacts: EvolutionImpact[] }) {
292
+ if (impacts.length === 0) {
293
+ return (
294
+ <div className="flex items-center justify-center h-32">
295
+ <p className="text-sm text-muted-foreground">No evolution deployments yet</p>
296
+ </div>
297
+ );
298
+ }
299
+
300
+ return (
301
+ <div className="flex flex-col gap-3 max-h-[260px] overflow-y-auto">
302
+ {impacts.map((evo) => {
303
+ const delta = (evo.pass_rate_after - evo.pass_rate_before) * 100;
304
+ const improved = delta > 0;
305
+ return (
306
+ <div
307
+ key={evo.proposal_id}
308
+ className="flex items-center justify-between bg-muted/50 rounded-lg px-4 py-3"
309
+ >
310
+ <div className="min-w-0">
311
+ <p className="font-headline text-[11px] uppercase tracking-wider text-foreground truncate">
312
+ {evo.skill_name}
313
+ </p>
314
+ <p className="text-[10px] text-muted-foreground mt-0.5">
315
+ {Math.round(evo.pass_rate_before * 100)}% &rarr;{" "}
316
+ {Math.round(evo.pass_rate_after * 100)}%
317
+ </p>
318
+ </div>
319
+ <div className="flex items-center gap-1.5 shrink-0">
320
+ <svg
321
+ className={`size-3.5 ${improved ? "text-primary" : "text-destructive rotate-90"}`}
322
+ viewBox="0 0 24 24"
323
+ fill="none"
324
+ stroke="currentColor"
325
+ strokeWidth="2"
326
+ strokeLinecap="round"
327
+ strokeLinejoin="round"
328
+ >
329
+ <line x1="7" y1="17" x2="17" y2="7" />
330
+ <polyline points="7 7 17 7 17 17" />
331
+ </svg>
332
+ <span
333
+ className={`font-headline text-sm font-semibold ${improved ? "text-primary" : "text-destructive"}`}
334
+ >
335
+ {improved ? "+" : ""}
336
+ {Math.round(delta)}%
337
+ </span>
338
+ </div>
339
+ </div>
340
+ );
341
+ })}
342
+ </div>
343
+ );
344
+ }