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
@@ -0,0 +1,150 @@
1
+ "use client";
2
+
3
+ import type { ReactNode } from "react";
4
+ import { useState } from "react";
5
+
6
+ import {
7
+ SkillReportGuideSheet,
8
+ SkillReportOnboardingBanner,
9
+ SkillReportTopRow,
10
+ SkillTrustNarrativePanel,
11
+ TrustSignalsGrid,
12
+ } from "@selftune/ui/components";
13
+ import { Button } from "@selftune/ui/primitives";
14
+ import type { TrustFields, TrustState } from "@selftune/ui/types";
15
+
16
+ export interface SkillReportNextAction {
17
+ icon: ReactNode;
18
+ text: string;
19
+ actionLabel: string;
20
+ variant: "default" | "secondary" | "destructive" | "outline";
21
+ }
22
+
23
+ export interface SkillReportScaffoldProps {
24
+ backLink: ReactNode;
25
+ title: string;
26
+ statusBadge?: ReactNode;
27
+ toolbarMeta?: ReactNode;
28
+ summary?: ReactNode;
29
+ showOnboardingBanner?: boolean;
30
+ guideButtonLabel?: string;
31
+ nextAction: SkillReportNextAction;
32
+ trustState: TrustState;
33
+ coverage?: TrustFields["coverage"];
34
+ evidenceQuality?: TrustFields["evidence_quality"];
35
+ routingQuality?: TrustFields["routing_quality"];
36
+ evolutionState?: TrustFields["evolution_state"];
37
+ dataHygiene?: TrustFields["data_hygiene"];
38
+ fallbackChecks: number;
39
+ fallbackSessions: number;
40
+ fallbackEvidenceRows: number;
41
+ fallbackEvolutionRows: number;
42
+ fallbackLatestAction?: string;
43
+ nextActionText: string;
44
+ children?: ReactNode;
45
+ }
46
+
47
+ export function SkillReportScaffold({
48
+ backLink,
49
+ title,
50
+ statusBadge,
51
+ toolbarMeta,
52
+ summary,
53
+ showOnboardingBanner = false,
54
+ guideButtonLabel = "How to read this page",
55
+ nextAction,
56
+ trustState,
57
+ coverage,
58
+ evidenceQuality,
59
+ routingQuality,
60
+ evolutionState,
61
+ dataHygiene,
62
+ fallbackChecks,
63
+ fallbackSessions,
64
+ fallbackEvidenceRows,
65
+ fallbackEvolutionRows,
66
+ fallbackLatestAction,
67
+ nextActionText,
68
+ children,
69
+ }: SkillReportScaffoldProps) {
70
+ const [isGuideOpen, setIsGuideOpen] = useState(false);
71
+
72
+ return (
73
+ <>
74
+ <SkillReportGuideSheet open={isGuideOpen} onOpenChange={setIsGuideOpen} />
75
+
76
+ <div className="@container/main flex flex-1 flex-col gap-5 p-4 lg:px-6 lg:pb-6 lg:pt-0">
77
+ <div className="sticky top-0 z-30 space-y-2 border-b border-border/15 bg-background/95 py-2.5 backdrop-blur supports-[backdrop-filter]:bg-background/85">
78
+ <div className="flex flex-wrap items-center gap-3">
79
+ {backLink}
80
+ <h1 className="shrink-0 font-headline text-base font-semibold tracking-tight lg:text-lg">
81
+ {title}
82
+ </h1>
83
+ {statusBadge}
84
+ <div className="ml-auto flex shrink-0 items-center gap-4">
85
+ <Button variant="outline" size="sm" onClick={() => setIsGuideOpen(true)}>
86
+ {guideButtonLabel}
87
+ </Button>
88
+ {toolbarMeta}
89
+ </div>
90
+ </div>
91
+
92
+ {summary ? (
93
+ <div className="space-y-1.5 text-sm leading-relaxed text-muted-foreground">
94
+ {summary}
95
+ </div>
96
+ ) : null}
97
+ </div>
98
+
99
+ {showOnboardingBanner ? (
100
+ <SkillReportOnboardingBanner onOpenGuide={() => setIsGuideOpen(true)} />
101
+ ) : null}
102
+
103
+ <div className="space-y-4">
104
+ <SkillReportTopRow
105
+ nextAction={nextAction}
106
+ latestDecision={
107
+ (evolutionState?.evolution_rows ?? fallbackEvolutionRows) > 0 &&
108
+ (evolutionState?.latest_action ?? fallbackLatestAction)
109
+ ? {
110
+ action: evolutionState?.latest_action ?? fallbackLatestAction ?? "No data",
111
+ timestamp: evolutionState?.latest_timestamp ?? null,
112
+ evolutionCount: evolutionState?.evolution_rows ?? fallbackEvolutionRows,
113
+ }
114
+ : undefined
115
+ }
116
+ />
117
+
118
+ <SkillTrustNarrativePanel
119
+ trustState={trustState}
120
+ coverage={coverage}
121
+ evidenceQuality={evidenceQuality}
122
+ routingQuality={routingQuality}
123
+ evolutionState={evolutionState}
124
+ dataHygiene={dataHygiene}
125
+ fallbackChecks={fallbackChecks}
126
+ fallbackSessions={fallbackSessions}
127
+ nextActionText={nextActionText}
128
+ onOpenGuide={() => setIsGuideOpen(true)}
129
+ />
130
+
131
+ <TrustSignalsGrid
132
+ coverage={coverage}
133
+ evidenceQuality={evidenceQuality}
134
+ routingQuality={routingQuality}
135
+ evolutionState={evolutionState}
136
+ fallbackChecks={fallbackChecks}
137
+ fallbackSessions={fallbackSessions}
138
+ fallbackEvidenceRows={fallbackEvidenceRows}
139
+ fallbackEvolutionRows={fallbackEvolutionRows}
140
+ fallbackLatestAction={fallbackLatestAction}
141
+ />
142
+ </div>
143
+
144
+ {children ? (
145
+ <div className="space-y-4 border-t border-border/10 pt-4">{children}</div>
146
+ ) : null}
147
+ </div>
148
+ </>
149
+ );
150
+ }
@@ -0,0 +1,224 @@
1
+ import type { ReactNode } from "react";
2
+ import { renderToStaticMarkup } from "react-dom/server";
3
+ import { describe, expect, it, vi } from "vitest";
4
+
5
+ vi.mock("@selftune/ui/components", () => ({
6
+ DataQualityPanel: ({
7
+ evidenceQuality,
8
+ dataHygiene,
9
+ }: {
10
+ evidenceQuality?: { prompt_link_rate: number };
11
+ dataHygiene?: { raw_checks: number };
12
+ }) => (
13
+ <div>
14
+ Data Quality
15
+ {evidenceQuality ? ` / prompt ${evidenceQuality.prompt_link_rate}` : ""}
16
+ {dataHygiene ? ` / rows ${dataHygiene.raw_checks}` : ""}
17
+ </div>
18
+ ),
19
+ EvidenceViewer: ({ proposalId }: { proposalId: string }) => (
20
+ <div>Evidence Viewer {proposalId}</div>
21
+ ),
22
+ InvocationsPanel: ({
23
+ invocations,
24
+ sessionMetadata,
25
+ }: {
26
+ invocations: Array<unknown>;
27
+ sessionMetadata?: Array<unknown>;
28
+ }) => (
29
+ <div>
30
+ Invocations {invocations.length}
31
+ {sessionMetadata ? ` / sessions ${sessionMetadata.length}` : ""}
32
+ </div>
33
+ ),
34
+ PromptEvidencePanel: ({
35
+ examples,
36
+ }: {
37
+ examples: { good: Array<unknown>; missed: Array<unknown>; noisy: Array<unknown> };
38
+ }) => (
39
+ <div>
40
+ Prompt Evidence / good {examples.good.length} / missed {examples.missed.length} / noisy{" "}
41
+ {examples.noisy.length}
42
+ </div>
43
+ ),
44
+ }));
45
+
46
+ vi.mock("@selftune/ui/primitives", () => ({
47
+ Card: ({ children }: { children?: ReactNode }) => <section>{children}</section>,
48
+ CardContent: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
49
+ }));
50
+
51
+ vi.mock("./SkillReportEvidenceRail", () => ({
52
+ SkillReportEvidenceRail: ({ activeProposal }: { activeProposal: string | null }) => (
53
+ <div>Evidence Rail {activeProposal ?? "none"}</div>
54
+ ),
55
+ }));
56
+
57
+ import { SkillReportEvidenceSection } from "./SkillReportEvidenceSection";
58
+ import { SkillReportEvidenceTabContent } from "./SkillReportEvidenceTabContent";
59
+ import { SkillReportInvocationsSection } from "./SkillReportInvocationsSection";
60
+ import { SkillReportDataQualityTabContent } from "./SkillReportDataQualityTabContent";
61
+
62
+ describe("Skill report shared sections", () => {
63
+ it("renders the shared evidence viewer layout", () => {
64
+ const html = renderToStaticMarkup(
65
+ <SkillReportEvidenceSection
66
+ evolution={[
67
+ {
68
+ proposal_id: "p1",
69
+ action: "validated",
70
+ timestamp: "2026-04-11T00:00:00Z",
71
+ details: "Validated",
72
+ },
73
+ ]}
74
+ activeProposal="p1"
75
+ onSelect={() => {}}
76
+ evidence={[
77
+ {
78
+ proposal_id: "p1",
79
+ target: "description",
80
+ stage: "validated",
81
+ timestamp: "2026-04-11T00:00:00Z",
82
+ rationale: null,
83
+ confidence: null,
84
+ original_text: null,
85
+ proposed_text: null,
86
+ validation: null,
87
+ details: null,
88
+ eval_set: [],
89
+ },
90
+ ]}
91
+ viewerProposalId="p1"
92
+ showViewer
93
+ />,
94
+ );
95
+
96
+ expect(html).toContain("Evidence Rail p1");
97
+ expect(html).toContain("Evidence Viewer p1");
98
+ });
99
+
100
+ it("renders the empty state when the viewer is disabled", () => {
101
+ const html = renderToStaticMarkup(
102
+ <SkillReportEvidenceSection
103
+ evolution={[]}
104
+ activeProposal={null}
105
+ onSelect={() => {}}
106
+ evidence={[]}
107
+ viewerProposalId=""
108
+ showViewer={false}
109
+ emptyState={<div>No shared evidence yet</div>}
110
+ />,
111
+ );
112
+
113
+ expect(html).toContain("No shared evidence yet");
114
+ });
115
+
116
+ it("renders the shared invocations wrapper", () => {
117
+ const html = renderToStaticMarkup(
118
+ <SkillReportInvocationsSection
119
+ invocations={[
120
+ {
121
+ timestamp: "2026-04-11T00:00:00Z",
122
+ session_id: "sess-1",
123
+ triggered: true,
124
+ query: "test query",
125
+ invocation_mode: "implicit",
126
+ confidence: 0.7,
127
+ tool_name: null,
128
+ agent_type: "main",
129
+ },
130
+ ]}
131
+ sessionMetadata={[
132
+ {
133
+ session_id: "sess-1",
134
+ agent_cli: "codex",
135
+ },
136
+ ]}
137
+ callout={<div>Operational invocations only</div>}
138
+ />,
139
+ );
140
+
141
+ expect(html).toContain("Operational invocations only");
142
+ expect(html).toContain("Invocations 1 / sessions 1");
143
+ });
144
+
145
+ it("renders prompt evidence ahead of the shared evidence viewer", () => {
146
+ const html = renderToStaticMarkup(
147
+ <SkillReportEvidenceTabContent
148
+ examples={{
149
+ good: [
150
+ {
151
+ timestamp: "2026-04-12T00:00:00Z",
152
+ session_id: "sess-1",
153
+ query_text: "good query",
154
+ triggered: true,
155
+ confidence: 0.91,
156
+ invocation_mode: "explicit",
157
+ prompt_kind: null,
158
+ source: "codex",
159
+ platform: "codex",
160
+ workspace_path: null,
161
+ query_origin: "matched_prompt",
162
+ is_system_like: false,
163
+ observation_kind: "canonical",
164
+ },
165
+ ],
166
+ missed: [],
167
+ noisy: [],
168
+ }}
169
+ evolution={[]}
170
+ activeProposal={null}
171
+ onSelect={() => {}}
172
+ evidence={[]}
173
+ viewerProposalId=""
174
+ showViewer={false}
175
+ emptyState={<div>No shared evidence yet</div>}
176
+ />,
177
+ );
178
+
179
+ expect(html).toContain("Prompt Evidence / good 1 / missed 0 / noisy 0");
180
+ expect(html).toContain("No shared evidence yet");
181
+ });
182
+
183
+ it("renders the data-quality panel when metrics are available", () => {
184
+ const html = renderToStaticMarkup(
185
+ <SkillReportDataQualityTabContent
186
+ evidenceQuality={{
187
+ prompt_link_rate: 0.85,
188
+ inline_query_rate: 0.6,
189
+ user_prompt_rate: 0.7,
190
+ meta_prompt_rate: 0.05,
191
+ internal_prompt_rate: 0.03,
192
+ no_prompt_rate: 0.22,
193
+ system_like_rate: 0.04,
194
+ invocation_mode_coverage: 1,
195
+ confidence_coverage: 0.9,
196
+ source_coverage: 1,
197
+ scope_coverage: 0.95,
198
+ }}
199
+ dataHygiene={{
200
+ naming_variants: [],
201
+ source_breakdown: [],
202
+ prompt_kind_breakdown: [],
203
+ observation_breakdown: [],
204
+ raw_checks: 42,
205
+ operational_checks: 40,
206
+ internal_prompt_rows: 1,
207
+ internal_prompt_rate: 0.02,
208
+ legacy_rows: 1,
209
+ legacy_rate: 0.02,
210
+ repaired_rows: 0,
211
+ repaired_rate: 0,
212
+ }}
213
+ />,
214
+ );
215
+
216
+ expect(html).toContain("Data Quality / prompt 0.85 / rows 42");
217
+ });
218
+
219
+ it("renders the empty data-quality state when metrics are unavailable", () => {
220
+ const html = renderToStaticMarkup(<SkillReportDataQualityTabContent />);
221
+
222
+ expect(html).toContain("Detailed data-quality metrics are not available for this skill yet.");
223
+ });
224
+ });
@@ -0,0 +1,76 @@
1
+ import type { ReactNode } from "react";
2
+ import { renderToStaticMarkup } from "react-dom/server";
3
+ import { describe, expect, it, vi } from "vitest";
4
+
5
+ vi.mock("@selftune/ui/primitives", () => ({
6
+ Tabs: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
7
+ TabsList: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
8
+ TabsTrigger: ({ children }: { children?: ReactNode }) => <button>{children}</button>,
9
+ TabsContent: ({ children }: { children?: ReactNode }) => <section>{children}</section>,
10
+ Tooltip: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
11
+ TooltipTrigger: ({ children, render }: { children?: ReactNode; render?: ReactNode }) => (
12
+ <div>
13
+ {render}
14
+ {children}
15
+ </div>
16
+ ),
17
+ TooltipContent: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
18
+ }));
19
+
20
+ import { SkillReportTabs } from "./SkillReportTabs";
21
+
22
+ describe("SkillReportTabs", () => {
23
+ it("renders only visible tabs and their content", () => {
24
+ const html = renderToStaticMarkup(
25
+ <SkillReportTabs
26
+ defaultValue="evidence"
27
+ tabs={[
28
+ {
29
+ value: "evidence",
30
+ label: "Evidence",
31
+ content: <div>Evidence body</div>,
32
+ },
33
+ {
34
+ value: "invocations",
35
+ label: "Invocations",
36
+ content: <div>Invocations body</div>,
37
+ },
38
+ {
39
+ value: "hidden",
40
+ label: "Hidden",
41
+ hidden: true,
42
+ content: <div>Hidden body</div>,
43
+ },
44
+ ]}
45
+ />,
46
+ );
47
+
48
+ expect(html).toContain("Evidence");
49
+ expect(html).toContain("Invocations");
50
+ expect(html).toContain("Evidence body");
51
+ expect(html).toContain("Invocations body");
52
+ expect(html).not.toContain("Hidden");
53
+ expect(html).not.toContain("Hidden body");
54
+ });
55
+
56
+ it("renders tooltip and badge content when configured", () => {
57
+ const html = renderToStaticMarkup(
58
+ <SkillReportTabs
59
+ defaultValue="invocations"
60
+ tabs={[
61
+ {
62
+ value: "invocations",
63
+ label: "Invocations",
64
+ badge: <span>12</span>,
65
+ tooltip: "Operational invocations only",
66
+ content: <div>Invocations body</div>,
67
+ },
68
+ ]}
69
+ />,
70
+ );
71
+
72
+ expect(html).toContain("Invocations");
73
+ expect(html).toContain("12");
74
+ expect(html).toContain("Operational invocations only");
75
+ });
76
+ });
@@ -0,0 +1,88 @@
1
+ "use client";
2
+
3
+ import type { ReactNode } from "react";
4
+
5
+ import {
6
+ Tabs,
7
+ TabsContent,
8
+ TabsList,
9
+ TabsTrigger,
10
+ Tooltip,
11
+ TooltipContent,
12
+ TooltipTrigger,
13
+ } from "@selftune/ui/primitives";
14
+
15
+ export interface SkillReportTabDefinition {
16
+ value: string;
17
+ label: ReactNode;
18
+ badge?: ReactNode;
19
+ tooltip?: ReactNode;
20
+ content: ReactNode;
21
+ hidden?: boolean;
22
+ contentClassName?: string;
23
+ }
24
+
25
+ export interface SkillReportTabsProps {
26
+ tabs: SkillReportTabDefinition[];
27
+ value?: string;
28
+ defaultValue?: string;
29
+ onValueChange?: (value: string) => void;
30
+ }
31
+
32
+ const TRIGGER_CLASS_NAME =
33
+ "rounded-lg px-3 font-headline text-xs uppercase tracking-wider data-active:bg-background/70 data-active:text-foreground";
34
+
35
+ export function SkillReportTabs({
36
+ tabs,
37
+ value,
38
+ defaultValue,
39
+ onValueChange,
40
+ }: SkillReportTabsProps) {
41
+ const visibleTabs = tabs.filter((tab) => !tab.hidden);
42
+ if (visibleTabs.length === 0) return null;
43
+
44
+ const firstValue = visibleTabs[0]?.value;
45
+ const tabsProps =
46
+ value !== undefined
47
+ ? { value, onValueChange }
48
+ : { defaultValue: defaultValue ?? firstValue, onValueChange };
49
+
50
+ return (
51
+ <Tabs {...tabsProps}>
52
+ <TabsList
53
+ variant="line"
54
+ className="rounded-xl border border-border/10 bg-muted/20 px-1.5 py-1"
55
+ >
56
+ {visibleTabs.map((tab) => {
57
+ const triggerChildren = (
58
+ <>
59
+ {tab.label}
60
+ {tab.badge}
61
+ </>
62
+ );
63
+
64
+ return tab.tooltip ? (
65
+ <Tooltip key={tab.value}>
66
+ <TooltipTrigger
67
+ render={<TabsTrigger value={tab.value} className={TRIGGER_CLASS_NAME} />}
68
+ >
69
+ {triggerChildren}
70
+ </TooltipTrigger>
71
+ <TooltipContent>{tab.tooltip}</TooltipContent>
72
+ </Tooltip>
73
+ ) : (
74
+ <TabsTrigger key={tab.value} value={tab.value} className={TRIGGER_CLASS_NAME}>
75
+ {triggerChildren}
76
+ </TabsTrigger>
77
+ );
78
+ })}
79
+ </TabsList>
80
+
81
+ {visibleTabs.map((tab) => (
82
+ <TabsContent key={tab.value} value={tab.value} className={tab.contentClassName}>
83
+ {tab.content}
84
+ </TabsContent>
85
+ ))}
86
+ </Tabs>
87
+ );
88
+ }
@@ -0,0 +1,33 @@
1
+ "use client";
2
+
3
+ import type { ReactNode } from "react";
4
+
5
+ import { PassRateTrendChart, type PassRateTrendPoint } from "@selftune/ui/components";
6
+ import { Card, CardContent, CardHeader, CardTitle } from "@selftune/ui/primitives";
7
+
8
+ export interface SkillReportTrendSectionProps {
9
+ data: PassRateTrendPoint[];
10
+ title?: ReactNode;
11
+ mode?: "pass_rate" | "volume";
12
+ isLoading?: boolean;
13
+ loadingState?: ReactNode;
14
+ }
15
+
16
+ export function SkillReportTrendSection({
17
+ data,
18
+ title = "Pass Rate Trend",
19
+ mode = "pass_rate",
20
+ isLoading = false,
21
+ loadingState,
22
+ }: SkillReportTrendSectionProps) {
23
+ return (
24
+ <Card className="bg-muted border-none shadow-none ring-0">
25
+ <CardHeader>
26
+ <CardTitle className="font-headline text-lg tracking-tight">{title}</CardTitle>
27
+ </CardHeader>
28
+ <CardContent>
29
+ {isLoading && loadingState ? loadingState : <PassRateTrendChart data={data} mode={mode} />}
30
+ </CardContent>
31
+ </Card>
32
+ );
33
+ }
@@ -0,0 +1,67 @@
1
+ import type { ReactNode } from "react";
2
+ import {
3
+ EyeIcon,
4
+ ShieldAlertIcon,
5
+ ShieldCheckIcon,
6
+ ShieldIcon,
7
+ ShieldQuestionIcon,
8
+ } from "lucide-react";
9
+
10
+ import { Badge } from "@selftune/ui/primitives";
11
+ import type { TrustState } from "@selftune/ui/types";
12
+
13
+ export function SkillReportTrustBadge({ state }: { state: TrustState }) {
14
+ const config = getSkillReportTrustBadgeConfig(state);
15
+
16
+ return (
17
+ <Badge variant={config.variant} className="gap-1 shrink-0 text-[10px]">
18
+ {config.icon}
19
+ {config.label}
20
+ </Badge>
21
+ );
22
+ }
23
+
24
+ export function getSkillReportTrustBadgeConfig(state: TrustState): {
25
+ label: string;
26
+ variant: "default" | "secondary" | "destructive" | "outline";
27
+ icon: ReactNode;
28
+ } {
29
+ switch (state) {
30
+ case "low_sample":
31
+ return {
32
+ label: "Low Sample",
33
+ variant: "secondary",
34
+ icon: <ShieldQuestionIcon className="size-3" />,
35
+ };
36
+ case "observed":
37
+ return {
38
+ label: "Observed",
39
+ variant: "outline",
40
+ icon: <EyeIcon className="size-3" />,
41
+ };
42
+ case "watch":
43
+ return {
44
+ label: "Watch",
45
+ variant: "secondary",
46
+ icon: <ShieldAlertIcon className="size-3" />,
47
+ };
48
+ case "validated":
49
+ return {
50
+ label: "Validated",
51
+ variant: "default",
52
+ icon: <ShieldCheckIcon className="size-3" />,
53
+ };
54
+ case "deployed":
55
+ return {
56
+ label: "Deployed",
57
+ variant: "default",
58
+ icon: <ShieldCheckIcon className="size-3" />,
59
+ };
60
+ case "rolled_back":
61
+ return {
62
+ label: "Rolled Back",
63
+ variant: "destructive",
64
+ icon: <ShieldIcon className="size-3" />,
65
+ };
66
+ }
67
+ }
@@ -0,0 +1,45 @@
1
+ export {
2
+ SkillReportEvidenceSection,
3
+ type SkillReportEvidenceSectionProps,
4
+ } from "./SkillReportEvidenceSection";
5
+ export {
6
+ SkillReportEvidenceRail,
7
+ type SkillReportEvidenceRailProps,
8
+ } from "./SkillReportEvidenceRail";
9
+ export {
10
+ SkillReportInvocationsSection,
11
+ type SkillReportInvocationsSectionProps,
12
+ } from "./SkillReportInvocationsSection";
13
+ export {
14
+ SkillReportEvidenceTabContent,
15
+ type SkillReportEvidenceTabContentProps,
16
+ } from "./SkillReportEvidenceTabContent";
17
+ export {
18
+ SkillReportDataQualityTabContent,
19
+ type SkillReportDataQualityTabContentProps,
20
+ } from "./SkillReportDataQualityTabContent";
21
+ export {
22
+ SkillReportTrendSection,
23
+ type SkillReportTrendSectionProps,
24
+ } from "./SkillReportTrendSection";
25
+ export {
26
+ SkillReportDailyBreakdownSection,
27
+ type SkillReportDailyBreakdownRow,
28
+ type SkillReportDailyBreakdownSectionProps,
29
+ } from "./SkillReportDailyBreakdownSection";
30
+ export {
31
+ SkillReportMissedQueriesSection,
32
+ type SkillReportMissedQueryRow,
33
+ type SkillReportMissedQueriesSectionProps,
34
+ } from "./SkillReportMissedQueriesSection";
35
+ export {
36
+ SkillReportTabs,
37
+ type SkillReportTabDefinition,
38
+ type SkillReportTabsProps,
39
+ } from "./SkillReportTabs";
40
+ export {
41
+ SkillReportScaffold,
42
+ type SkillReportNextAction,
43
+ type SkillReportScaffoldProps,
44
+ } from "./SkillReportScaffold";
45
+ export { SkillReportTrustBadge, getSkillReportTrustBadgeConfig } from "./SkillReportTrustBadge";