selftune 0.2.23 → 0.2.25

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 (219) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +93 -15
  3. package/apps/local-dashboard/dist/assets/index-DgY2KGP-.css +1 -0
  4. package/apps/local-dashboard/dist/assets/index-Dhgv5BQO.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/alpha-upload/build-payloads.ts +3 -3
  12. package/cli/selftune/alpha-upload/stage-canonical.ts +17 -11
  13. package/cli/selftune/auto-update.ts +200 -8
  14. package/cli/selftune/canonical-export.ts +55 -25
  15. package/cli/selftune/command-surface.ts +397 -0
  16. package/cli/selftune/contribute/contribute.ts +64 -13
  17. package/cli/selftune/contribution-config.ts +57 -3
  18. package/cli/selftune/contribution-preferences.ts +117 -0
  19. package/cli/selftune/contribution-signals.ts +8 -4
  20. package/cli/selftune/contribution-staging.ts +13 -2
  21. package/cli/selftune/contributions.ts +55 -121
  22. package/cli/selftune/creator-contributions.ts +29 -10
  23. package/cli/selftune/cron/setup.ts +7 -3
  24. package/cli/selftune/dashboard-contract.ts +73 -0
  25. package/cli/selftune/dashboard-server.ts +168 -17
  26. package/cli/selftune/dashboard.ts +350 -17
  27. package/cli/selftune/eval/baseline.ts +21 -5
  28. package/cli/selftune/eval/execution-eval.ts +170 -0
  29. package/cli/selftune/eval/family-overlap.ts +2 -2
  30. package/cli/selftune/eval/hooks-to-evals.ts +228 -82
  31. package/cli/selftune/eval/import-skillsbench.ts +2 -2
  32. package/cli/selftune/eval/invocation-classifier.ts +56 -0
  33. package/cli/selftune/eval/synthetic-evals.ts +5 -3
  34. package/cli/selftune/eval/unit-test-cli.ts +7 -4
  35. package/cli/selftune/evolution/apply-proposal.ts +295 -0
  36. package/cli/selftune/evolution/engines/replay-engine.ts +79 -57
  37. package/cli/selftune/evolution/evolve-body.ts +100 -39
  38. package/cli/selftune/evolution/evolve.ts +244 -52
  39. package/cli/selftune/evolution/rollback.ts +0 -1
  40. package/cli/selftune/evolution/validate-body.ts +68 -42
  41. package/cli/selftune/evolution/validate-host-replay.ts +510 -60
  42. package/cli/selftune/evolution/validate-proposal.ts +11 -150
  43. package/cli/selftune/evolution/validate-routing.ts +43 -41
  44. package/cli/selftune/evolution/validation-contract.ts +91 -0
  45. package/cli/selftune/grading/auto-grade.ts +11 -7
  46. package/cli/selftune/grading/grade-session.ts +10 -16
  47. package/cli/selftune/index.ts +35 -10
  48. package/cli/selftune/ingestors/claude-replay.ts +15 -10
  49. package/cli/selftune/ingestors/codex-wrapper.ts +3 -3
  50. package/cli/selftune/ingestors/opencode-ingest.ts +2 -2
  51. package/cli/selftune/ingestors/pi-ingest.ts +3 -2
  52. package/cli/selftune/init.ts +27 -3
  53. package/cli/selftune/localdb/direct-write.ts +35 -1
  54. package/cli/selftune/localdb/queries/cron.ts +34 -0
  55. package/cli/selftune/localdb/queries/dashboard.ts +834 -0
  56. package/cli/selftune/localdb/queries/evolution.ts +158 -0
  57. package/cli/selftune/localdb/queries/execution.ts +133 -0
  58. package/cli/selftune/localdb/queries/json.ts +18 -0
  59. package/cli/selftune/localdb/queries/monitoring.ts +263 -0
  60. package/cli/selftune/localdb/queries/raw.ts +95 -0
  61. package/cli/selftune/localdb/queries/staging.ts +270 -0
  62. package/cli/selftune/localdb/queries/trust.ts +392 -0
  63. package/cli/selftune/localdb/queries.ts +60 -2288
  64. package/cli/selftune/localdb/schema.ts +21 -0
  65. package/cli/selftune/monitoring/watch.ts +96 -29
  66. package/cli/selftune/normalization.ts +3 -0
  67. package/cli/selftune/observability.ts +4 -2
  68. package/cli/selftune/orchestrate/cli.ts +161 -0
  69. package/cli/selftune/orchestrate/execute.ts +295 -0
  70. package/cli/selftune/orchestrate/finalize.ts +157 -0
  71. package/cli/selftune/orchestrate/locks.ts +40 -0
  72. package/cli/selftune/orchestrate/plan.ts +131 -0
  73. package/cli/selftune/orchestrate/post-run.ts +59 -0
  74. package/cli/selftune/orchestrate/prepare.ts +334 -0
  75. package/cli/selftune/orchestrate/report.ts +182 -0
  76. package/cli/selftune/orchestrate/runtime.ts +120 -0
  77. package/cli/selftune/orchestrate/signals.ts +48 -0
  78. package/cli/selftune/orchestrate.ts +150 -1173
  79. package/cli/selftune/repair/skill-usage.ts +5 -2
  80. package/cli/selftune/routes/overview.ts +5 -2
  81. package/cli/selftune/routes/skill-report.ts +15 -2
  82. package/cli/selftune/schedule.ts +5 -5
  83. package/cli/selftune/status.ts +39 -2
  84. package/cli/selftune/testing-readiness.ts +597 -0
  85. package/cli/selftune/types.ts +44 -4
  86. package/cli/selftune/uninstall.ts +2 -1
  87. package/cli/selftune/utils/canonical-log.ts +1 -9
  88. package/cli/selftune/utils/cli-error.ts +9 -0
  89. package/cli/selftune/utils/llm-call.ts +126 -6
  90. package/cli/selftune/utils/skill-discovery.ts +2 -0
  91. package/cli/selftune/workflows/proposals.ts +184 -0
  92. package/cli/selftune/workflows/skill-scaffold.ts +241 -0
  93. package/cli/selftune/workflows/workflows.ts +100 -26
  94. package/node_modules/@selftune/telemetry-contract/fixtures/complete-push.ts +1 -1
  95. package/node_modules/@selftune/telemetry-contract/fixtures/evidence-only-push.ts +1 -1
  96. package/node_modules/@selftune/telemetry-contract/fixtures/partial-push-no-sessions.ts +1 -1
  97. package/node_modules/@selftune/telemetry-contract/fixtures/partial-push-unresolved-parents.ts +1 -1
  98. package/node_modules/@selftune/telemetry-contract/src/schemas.ts +41 -1
  99. package/node_modules/@selftune/telemetry-contract/src/types.ts +103 -2
  100. package/package.json +25 -9
  101. package/packages/dashboard-core/AGENTS.md +18 -0
  102. package/packages/dashboard-core/README.md +30 -0
  103. package/packages/dashboard-core/index.ts +3 -0
  104. package/packages/dashboard-core/package.json +39 -0
  105. package/packages/dashboard-core/src/chrome/DashboardChrome.tsx +74 -0
  106. package/packages/dashboard-core/src/chrome/DashboardHeader.tsx +200 -0
  107. package/packages/dashboard-core/src/chrome/DashboardSidebar.tsx +219 -0
  108. package/packages/dashboard-core/src/chrome/RuntimeBadge.tsx +46 -0
  109. package/packages/dashboard-core/src/chrome/index.ts +14 -0
  110. package/packages/dashboard-core/src/chrome/types.ts +81 -0
  111. package/packages/dashboard-core/src/chrome/utils.ts +23 -0
  112. package/packages/dashboard-core/src/gates/FeatureGate.tsx +11 -0
  113. package/packages/dashboard-core/src/gates/LockedRoute.tsx +29 -0
  114. package/packages/dashboard-core/src/gates/UpgradeCard.tsx +89 -0
  115. package/packages/dashboard-core/src/gates/index.ts +3 -0
  116. package/packages/dashboard-core/src/host/DashboardHostProvider.tsx +62 -0
  117. package/packages/dashboard-core/src/host/adapter.ts +47 -0
  118. package/packages/dashboard-core/src/host/capabilities.ts +55 -0
  119. package/packages/dashboard-core/src/host/index.ts +3 -0
  120. package/packages/dashboard-core/src/models/analytics.ts +39 -0
  121. package/packages/dashboard-core/src/models/index.ts +4 -0
  122. package/packages/dashboard-core/src/models/overview.ts +98 -0
  123. package/packages/dashboard-core/src/models/runtime.ts +7 -0
  124. package/packages/dashboard-core/src/models/skills.ts +34 -0
  125. package/packages/dashboard-core/src/routes/index.ts +2 -0
  126. package/packages/dashboard-core/src/routes/manifest.test.ts +70 -0
  127. package/packages/dashboard-core/src/routes/manifest.ts +451 -0
  128. package/packages/dashboard-core/src/routes/types.ts +39 -0
  129. package/packages/dashboard-core/src/screens/analytics/AnalyticsScreen.tsx +278 -0
  130. package/packages/dashboard-core/src/screens/analytics/index.ts +1 -0
  131. package/packages/dashboard-core/src/screens/index.ts +37 -0
  132. package/packages/dashboard-core/src/screens/overview/OverviewComparisonSurface.test.ts +101 -0
  133. package/packages/dashboard-core/src/screens/overview/OverviewComparisonSurface.tsx +393 -0
  134. package/packages/dashboard-core/src/screens/overview/OverviewCompositionSurface.test.tsx +113 -0
  135. package/packages/dashboard-core/src/screens/overview/OverviewCompositionSurface.tsx +72 -0
  136. package/packages/dashboard-core/src/screens/overview/OverviewCoreSurface.tsx +71 -0
  137. package/packages/dashboard-core/src/screens/overview/OverviewOnboardingBanner.tsx +90 -0
  138. package/packages/dashboard-core/src/screens/overview/OverviewRunSummary.tsx +40 -0
  139. package/packages/dashboard-core/src/screens/overview/index.ts +16 -0
  140. package/packages/dashboard-core/src/screens/overview/types.ts +13 -0
  141. package/packages/dashboard-core/src/screens/skill-report/SkillReportDailyBreakdownSection.tsx +99 -0
  142. package/packages/dashboard-core/src/screens/skill-report/SkillReportDataQualityTabContent.tsx +35 -0
  143. package/packages/dashboard-core/src/screens/skill-report/SkillReportEvidenceRail.tsx +71 -0
  144. package/packages/dashboard-core/src/screens/skill-report/SkillReportEvidenceSection.tsx +63 -0
  145. package/packages/dashboard-core/src/screens/skill-report/SkillReportEvidenceTabContent.tsx +25 -0
  146. package/packages/dashboard-core/src/screens/skill-report/SkillReportInvocationsSection.tsx +24 -0
  147. package/packages/dashboard-core/src/screens/skill-report/SkillReportMissedQueriesSection.tsx +79 -0
  148. package/packages/dashboard-core/src/screens/skill-report/SkillReportScaffold.tsx +150 -0
  149. package/packages/dashboard-core/src/screens/skill-report/SkillReportSections.test.tsx +224 -0
  150. package/packages/dashboard-core/src/screens/skill-report/SkillReportTabs.test.tsx +76 -0
  151. package/packages/dashboard-core/src/screens/skill-report/SkillReportTabs.tsx +88 -0
  152. package/packages/dashboard-core/src/screens/skill-report/SkillReportTrendSection.tsx +33 -0
  153. package/packages/dashboard-core/src/screens/skill-report/SkillReportTrustBadge.tsx +67 -0
  154. package/packages/dashboard-core/src/screens/skill-report/index.ts +45 -0
  155. package/packages/dashboard-core/src/screens/skills/SkillsLibraryScreen.tsx +162 -0
  156. package/packages/dashboard-core/src/screens/skills/index.ts +6 -0
  157. package/packages/telemetry-contract/fixtures/complete-push.ts +1 -1
  158. package/packages/telemetry-contract/fixtures/evidence-only-push.ts +1 -1
  159. package/packages/telemetry-contract/fixtures/partial-push-no-sessions.ts +1 -1
  160. package/packages/telemetry-contract/fixtures/partial-push-unresolved-parents.ts +1 -1
  161. package/packages/telemetry-contract/src/schemas.ts +41 -1
  162. package/packages/telemetry-contract/src/types.ts +103 -2
  163. package/packages/ui/src/components/EvidenceViewer.tsx +80 -25
  164. package/packages/ui/src/components/OverviewPanels.tsx +67 -26
  165. package/packages/ui/src/primitives/tabs.tsx +7 -6
  166. package/packages/ui/src/types.ts +10 -0
  167. package/skill/SKILL.md +130 -332
  168. package/skill/agents/diagnosis-analyst.md +3 -3
  169. package/skill/agents/evolution-reviewer.md +3 -3
  170. package/skill/agents/integration-guide.md +3 -3
  171. package/skill/agents/pattern-analyst.md +2 -2
  172. package/skill/references/cli-quick-reference.md +89 -0
  173. package/skill/references/creator-playbook.md +131 -0
  174. package/skill/references/examples.md +48 -0
  175. package/skill/references/troubleshooting.md +47 -0
  176. package/skill/references/version-history.md +1 -1
  177. package/skill/selftune.contribute.json +11 -0
  178. package/skill/{Workflows → workflows}/Baseline.md +20 -1
  179. package/skill/{Workflows → workflows}/Contribute.md +23 -10
  180. package/skill/{Workflows → workflows}/Contributions.md +13 -5
  181. package/skill/workflows/CreateTestDeploy.md +170 -0
  182. package/skill/{Workflows → workflows}/CreatorContributions.md +18 -6
  183. package/skill/{Workflows → workflows}/Cron.md +1 -1
  184. package/skill/{Workflows → workflows}/Dashboard.md +20 -0
  185. package/skill/{Workflows → workflows}/Doctor.md +1 -1
  186. package/skill/{Workflows → workflows}/Evals.md +67 -2
  187. package/skill/{Workflows → workflows}/Evolve.md +119 -30
  188. package/skill/{Workflows → workflows}/EvolveBody.md +41 -1
  189. package/skill/{Workflows → workflows}/Grade.md +1 -1
  190. package/skill/{Workflows → workflows}/Initialize.md +8 -4
  191. package/skill/{Workflows → workflows}/Orchestrate.md +13 -3
  192. package/skill/{Workflows → workflows}/Schedule.md +3 -3
  193. package/skill/workflows/SignalsDashboard.md +87 -0
  194. package/skill/{Workflows → workflows}/UnitTest.md +19 -0
  195. package/skill/{Workflows → workflows}/Watch.md +42 -2
  196. package/skill/{Workflows → workflows}/Workflows.md +39 -2
  197. package/apps/local-dashboard/dist/assets/index-CwOtTrUS.css +0 -1
  198. package/apps/local-dashboard/dist/assets/index-f1HQpbeH.js +0 -59
  199. package/apps/local-dashboard/dist/assets/vendor-react-CKkiCskZ.js +0 -11
  200. package/apps/local-dashboard/dist/assets/vendor-ui-jVSaIZey.js +0 -12
  201. /package/skill/{Workflows → workflows}/AlphaUpload.md +0 -0
  202. /package/skill/{Workflows → workflows}/AutoActivation.md +0 -0
  203. /package/skill/{Workflows → workflows}/Badge.md +0 -0
  204. /package/skill/{Workflows → workflows}/Composability.md +0 -0
  205. /package/skill/{Workflows → workflows}/EvolutionMemory.md +0 -0
  206. /package/skill/{Workflows → workflows}/ExportCanonical.md +0 -0
  207. /package/skill/{Workflows → workflows}/Hook.md +0 -0
  208. /package/skill/{Workflows → workflows}/ImportSkillsBench.md +0 -0
  209. /package/skill/{Workflows → workflows}/Ingest.md +0 -0
  210. /package/skill/{Workflows → workflows}/PlatformHooks.md +0 -0
  211. /package/skill/{Workflows → workflows}/Quickstart.md +0 -0
  212. /package/skill/{Workflows → workflows}/Recover.md +0 -0
  213. /package/skill/{Workflows → workflows}/Registry.md +0 -0
  214. /package/skill/{Workflows → workflows}/RepairSkillUsage.md +0 -0
  215. /package/skill/{Workflows → workflows}/Replay.md +0 -0
  216. /package/skill/{Workflows → workflows}/Rollback.md +0 -0
  217. /package/skill/{Workflows → workflows}/Sync.md +0 -0
  218. /package/skill/{Workflows → workflows}/Telemetry.md +0 -0
  219. /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";