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,241 @@
1
+ /**
2
+ * skill-scaffold.ts
3
+ *
4
+ * Builds draft workflow skills from repeated telemetry-discovered workflows.
5
+ * The draft is preview-first by default so agents can review the scaffold before
6
+ * writing it into a local skill registry.
7
+ */
8
+
9
+ import { join } from "node:path";
10
+
11
+ import type { DiscoveredWorkflow } from "../types.js";
12
+ import { findGitRepositoryRoot } from "../utils/skill-discovery.js";
13
+
14
+ export interface WorkflowSkillDraft {
15
+ title: string;
16
+ skill_name: string;
17
+ description: string;
18
+ output_dir: string;
19
+ skill_dir: string;
20
+ skill_path: string;
21
+ content: string;
22
+ source_workflow: {
23
+ workflow_id: string;
24
+ skills: string[];
25
+ occurrence_count: number;
26
+ synergy_score: number;
27
+ representative_query: string;
28
+ };
29
+ }
30
+
31
+ export interface WorkflowSkillDraftOptions {
32
+ outputDir?: string;
33
+ skillName?: string;
34
+ description?: string;
35
+ cwd?: string;
36
+ }
37
+
38
+ const STOPWORDS = new Set([
39
+ "a",
40
+ "an",
41
+ "and",
42
+ "for",
43
+ "from",
44
+ "in",
45
+ "into",
46
+ "of",
47
+ "on",
48
+ "or",
49
+ "the",
50
+ "to",
51
+ "with",
52
+ ]);
53
+
54
+ function splitWords(value: string): string[] {
55
+ return value
56
+ .replace(/[^A-Za-z0-9]+/g, " ")
57
+ .trim()
58
+ .split(/\s+/)
59
+ .filter(Boolean);
60
+ }
61
+
62
+ function titleCase(value: string): string {
63
+ return splitWords(value)
64
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
65
+ .join(" ");
66
+ }
67
+
68
+ export function slugifyWorkflowSkillName(value: string): string {
69
+ return splitWords(value)
70
+ .map((word) => word.toLowerCase())
71
+ .join("-")
72
+ .replace(/-+/g, "-")
73
+ .replace(/^-|-$/g, "");
74
+ }
75
+
76
+ function deriveBaseLabel(workflow: DiscoveredWorkflow): string {
77
+ const filteredQueryWords = splitWords(workflow.representative_query).filter(
78
+ (word) => !STOPWORDS.has(word.toLowerCase()),
79
+ );
80
+
81
+ if (filteredQueryWords.length >= 2) {
82
+ return filteredQueryWords.slice(0, 5).join(" ");
83
+ }
84
+
85
+ return `${workflow.skills.join(" ")} workflow`;
86
+ }
87
+
88
+ function formatList(items: string[]): string {
89
+ if (items.length === 0) return "";
90
+ if (items.length === 1) return items[0];
91
+ if (items.length === 2) return `${items[0]} and ${items[1]}`;
92
+ return `${items.slice(0, -1).join(", ")}, and ${items[items.length - 1]}`;
93
+ }
94
+
95
+ function wrapFoldedScalar(value: string, width = 78): string[] {
96
+ const words = value.split(/\s+/).filter(Boolean);
97
+ const lines: string[] = [];
98
+ let current = "";
99
+
100
+ for (const word of words) {
101
+ const candidate = current.length === 0 ? word : `${current} ${word}`;
102
+ if (candidate.length > width && current.length > 0) {
103
+ lines.push(` ${current}`);
104
+ current = word;
105
+ } else {
106
+ current = candidate;
107
+ }
108
+ }
109
+
110
+ if (current.length > 0) lines.push(` ${current}`);
111
+ return lines.length > 0 ? lines : [" "];
112
+ }
113
+
114
+ export function getDefaultWorkflowSkillOutputDir(cwd: string = process.cwd()): string {
115
+ const repoRoot = findGitRepositoryRoot(cwd);
116
+ return join(repoRoot ?? cwd, ".agents", "skills");
117
+ }
118
+
119
+ export function buildWorkflowSkillDescription(
120
+ workflow: DiscoveredWorkflow,
121
+ override?: string,
122
+ ): string {
123
+ if (override && override.trim().length > 0) return override.trim();
124
+
125
+ const chain = formatList(workflow.skills);
126
+ const query = workflow.representative_query.trim();
127
+ if (query.length > 0) {
128
+ return `Use when the user wants to ${query}. Coordinates ${chain} in sequence.`;
129
+ }
130
+
131
+ return `Use when the task consistently needs ${chain} in sequence.`;
132
+ }
133
+
134
+ export function buildWorkflowSkillContent(
135
+ workflow: DiscoveredWorkflow,
136
+ title: string,
137
+ skillName: string,
138
+ description: string,
139
+ ): string {
140
+ const workflowName = title.endsWith("Workflow") ? title : `${title} Workflow`;
141
+ const chain = workflow.skills.join(" → ");
142
+ const query = workflow.representative_query.trim();
143
+ const foldedDescription = wrapFoldedScalar(description).join("\n");
144
+
145
+ const whenToUseLines =
146
+ query.length > 0
147
+ ? [
148
+ `- The user asks to "${query}"`,
149
+ `- The request repeatedly needs this skill chain: ${chain}`,
150
+ ]
151
+ : [`- The request repeatedly needs this skill chain: ${chain}`];
152
+
153
+ const executionPlanLines = workflow.skills.map(
154
+ (skill, index) =>
155
+ `${index + 1}. Invoke \`${skill}\` in its established role for this workflow.`,
156
+ );
157
+
158
+ return `---
159
+ name: ${skillName}
160
+ description: >
161
+ ${foldedDescription}
162
+ metadata:
163
+ author: selftune-autogen
164
+ version: 0.1.0
165
+ category: developer-tools
166
+ generated_by: selftune workflows scaffold
167
+ source_workflow_id: ${workflow.workflow_id}
168
+ ---
169
+
170
+ # ${title}
171
+
172
+ This draft skill was scaffolded by selftune from repeated workflow telemetry.
173
+ Review the routing language and execution notes before broad distribution.
174
+
175
+ ## When to Use
176
+
177
+ ${whenToUseLines.join("\n")}
178
+
179
+ ## Execution Plan
180
+
181
+ ${executionPlanLines.join("\n")}
182
+
183
+ ## Workflows
184
+
185
+ ### ${workflowName}
186
+ - **Skills:** ${chain}
187
+ ${query.length > 0 ? `- **Trigger:** ${query}\n` : ""}- **Source:** Discovered from ${workflow.occurrence_count} sessions (synergy: ${workflow.synergy_score.toFixed(2)})
188
+
189
+ ## Notes
190
+
191
+ - This is a proposal scaffold, not a silently published marketplace skill.
192
+ - Add tighter scope boundaries and richer examples before publishing.
193
+ `;
194
+ }
195
+
196
+ export function buildWorkflowSkillDraft(
197
+ workflow: DiscoveredWorkflow,
198
+ options: WorkflowSkillDraftOptions = {},
199
+ ): WorkflowSkillDraft {
200
+ const baseLabel = options.skillName?.trim() || deriveBaseLabel(workflow);
201
+ const skillName = slugifyWorkflowSkillName(baseLabel);
202
+ const title = titleCase(baseLabel) || titleCase(`${workflow.skills.join(" ")} workflow`);
203
+ const description = buildWorkflowSkillDescription(workflow, options.description);
204
+ const outputDir = options.outputDir?.trim() || getDefaultWorkflowSkillOutputDir(options.cwd);
205
+ const skillDir = join(outputDir, skillName);
206
+ const skillPath = join(skillDir, "SKILL.md");
207
+
208
+ return {
209
+ title,
210
+ skill_name: skillName,
211
+ description,
212
+ output_dir: outputDir,
213
+ skill_dir: skillDir,
214
+ skill_path: skillPath,
215
+ content: buildWorkflowSkillContent(workflow, title, skillName, description),
216
+ source_workflow: {
217
+ workflow_id: workflow.workflow_id,
218
+ skills: workflow.skills,
219
+ occurrence_count: workflow.occurrence_count,
220
+ synergy_score: workflow.synergy_score,
221
+ representative_query: workflow.representative_query,
222
+ },
223
+ };
224
+ }
225
+
226
+ export function formatWorkflowSkillDraft(draft: WorkflowSkillDraft): string {
227
+ const lines = [
228
+ `Draft workflow skill: ${draft.title}`,
229
+ `Skill name: ${draft.skill_name}`,
230
+ `Output path: ${draft.skill_path}`,
231
+ `Source workflow: ${draft.source_workflow.workflow_id}`,
232
+ `Occurrences: ${draft.source_workflow.occurrence_count} | Synergy: ${draft.source_workflow.synergy_score.toFixed(2)}`,
233
+ ];
234
+
235
+ if (draft.source_workflow.representative_query.trim().length > 0) {
236
+ lines.push(`Representative query: "${draft.source_workflow.representative_query.trim()}"`);
237
+ }
238
+
239
+ lines.push("", draft.content.trimEnd());
240
+ return lines.join("\n");
241
+ }
@@ -8,13 +8,14 @@
8
8
  * - cliMain() (reads logs, discovers workflows, prints output or saves)
9
9
  */
10
10
 
11
- import { existsSync, readFileSync, writeFileSync } from "node:fs";
11
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
12
12
  import { parseArgs } from "node:util";
13
13
 
14
14
  import { getDb } from "../localdb/db.js";
15
15
  import { querySessionTelemetry, querySkillUsageRecords } from "../localdb/queries.js";
16
16
  import type {
17
17
  CodifiedWorkflow,
18
+ DiscoveredWorkflow,
18
19
  SessionTelemetryRecord,
19
20
  SkillUsageRecord,
20
21
  WorkflowDiscoveryReport,
@@ -22,6 +23,38 @@ import type {
22
23
  import { CLIError } from "../utils/cli-error.js";
23
24
  import { discoverWorkflows } from "./discover.js";
24
25
  import { appendWorkflow } from "./skill-md-writer.js";
26
+ import { buildWorkflowSkillDraft, formatWorkflowSkillDraft } from "./skill-scaffold.js";
27
+
28
+ function resolveWorkflowSelection(
29
+ report: WorkflowDiscoveryReport,
30
+ selection: string | undefined,
31
+ ): DiscoveredWorkflow {
32
+ if (!selection) {
33
+ throw new CLIError(
34
+ "Usage: selftune workflows <save|scaffold> <name-or-index>",
35
+ "MISSING_FLAG",
36
+ "Provide a workflow name or index (e.g., selftune workflows scaffold 1).",
37
+ );
38
+ }
39
+
40
+ let workflow = report.workflows.find((w) => w.workflow_id === selection);
41
+ if (!workflow) {
42
+ const idx = Number.parseInt(selection, 10);
43
+ if (!Number.isNaN(idx) && idx >= 1 && idx <= report.workflows.length) {
44
+ workflow = report.workflows[idx - 1];
45
+ }
46
+ }
47
+
48
+ if (!workflow) {
49
+ throw new CLIError(
50
+ `No workflow found matching "${selection}".`,
51
+ "INVALID_FLAG",
52
+ "Run 'selftune workflows' to see discovered workflows and their indices.",
53
+ );
54
+ }
55
+
56
+ return workflow;
57
+ }
25
58
 
26
59
  // ---------------------------------------------------------------------------
27
60
  // formatWorkflows — pure formatter
@@ -69,12 +102,41 @@ export async function cliMain(): Promise<void> {
69
102
  window: { type: "string" },
70
103
  skill: { type: "string" },
71
104
  "skill-path": { type: "string" },
105
+ "output-dir": { type: "string" },
106
+ "skill-name": { type: "string" },
107
+ description: { type: "string" },
108
+ write: { type: "boolean" },
109
+ force: { type: "boolean" },
72
110
  json: { type: "boolean" },
111
+ help: { type: "boolean", short: "h" },
73
112
  },
74
113
  strict: true,
75
114
  allowPositionals: true,
76
115
  });
77
116
 
117
+ if (values.help) {
118
+ console.log(`selftune workflows — Discover repeated multi-skill patterns
119
+
120
+ Usage:
121
+ selftune workflows [options]
122
+ selftune workflows save <name-or-index> [--skill-path <path>]
123
+ selftune workflows scaffold <name-or-index> [--output-dir <path>] [--skill-name <name>] [--description <text>] [--write] [--force] [--json]
124
+
125
+ Options:
126
+ --min-occurrences <n> Minimum workflow frequency to show (default: 3)
127
+ --window <n> Only analyze the most recent N sessions
128
+ --skill <name> Only show workflows containing the named skill
129
+ --skill-path <path> Target SKILL.md for the save subcommand
130
+ --output-dir <path> Target skill registry dir for scaffold previews/writes
131
+ --skill-name <name> Override the generated draft skill name
132
+ --description <text> Override the generated draft skill description
133
+ --write Persist the scaffolded draft skill to disk
134
+ --force Overwrite an existing scaffold path when combined with --write
135
+ --json Emit machine-readable JSON
136
+ -h, --help Show this help message`);
137
+ process.exit(0);
138
+ }
139
+
78
140
  const subcommand = positionals[0];
79
141
  const minOccurrences = values["min-occurrences"]
80
142
  ? Number.parseInt(values["min-occurrences"], 10)
@@ -101,31 +163,7 @@ export async function cliMain(): Promise<void> {
101
163
 
102
164
  if (subcommand === "save") {
103
165
  // Save subcommand: find workflow, append to SKILL.md
104
- const nameArg = positionals[1];
105
- if (!nameArg) {
106
- throw new CLIError(
107
- "Usage: selftune workflows save <name-or-index>",
108
- "MISSING_FLAG",
109
- "Provide a workflow name or index (e.g., selftune workflows save 1).",
110
- );
111
- }
112
-
113
- // Match by numeric index (1-based) or workflow_id
114
- let workflow = report.workflows.find((w) => w.workflow_id === nameArg);
115
- if (!workflow) {
116
- const idx = Number.parseInt(nameArg, 10);
117
- if (!Number.isNaN(idx) && idx >= 1 && idx <= report.workflows.length) {
118
- workflow = report.workflows[idx - 1];
119
- }
120
- }
121
-
122
- if (!workflow) {
123
- throw new CLIError(
124
- `No workflow found matching "${nameArg}".`,
125
- "INVALID_FLAG",
126
- "Run 'selftune workflows' to see discovered workflows and their indices.",
127
- );
128
- }
166
+ const workflow = resolveWorkflowSelection(report, positionals[1]);
129
167
 
130
168
  // Determine SKILL.md path
131
169
  let skillPath = values["skill-path"];
@@ -187,6 +225,42 @@ export async function cliMain(): Promise<void> {
187
225
  return;
188
226
  }
189
227
 
228
+ if (subcommand === "scaffold") {
229
+ const workflow = resolveWorkflowSelection(report, positionals[1]);
230
+ const draft = buildWorkflowSkillDraft(workflow, {
231
+ outputDir: values["output-dir"],
232
+ skillName: values["skill-name"],
233
+ description: values.description,
234
+ });
235
+
236
+ if (values.write) {
237
+ if (existsSync(draft.skill_path) && !values.force) {
238
+ throw new CLIError(
239
+ `Refusing to overwrite existing draft at ${draft.skill_path}.`,
240
+ "FILE_EXISTS",
241
+ "Re-run with --force to overwrite the existing draft skill.",
242
+ );
243
+ }
244
+
245
+ mkdirSync(draft.skill_dir, { recursive: true });
246
+ writeFileSync(draft.skill_path, draft.content, "utf-8");
247
+
248
+ if (values.json || !process.stdout.isTTY) {
249
+ console.log(JSON.stringify({ ...draft, written: true }, null, 2));
250
+ } else {
251
+ console.log(`Scaffolded skill "${draft.skill_name}" to ${draft.skill_path}`);
252
+ }
253
+ return;
254
+ }
255
+
256
+ if (values.json || !process.stdout.isTTY) {
257
+ console.log(JSON.stringify({ ...draft, written: false }, null, 2));
258
+ } else {
259
+ console.log(formatWorkflowSkillDraft(draft));
260
+ }
261
+ return;
262
+ }
263
+
190
264
  // Default: discover and display
191
265
  if (values.json || !process.stdout.isTTY) {
192
266
  console.log(JSON.stringify(report, null, 2));
@@ -1,4 +1,4 @@
1
- import type { PushPayloadV2 } from "../src/schemas.js";
1
+ import type { PushPayloadV2 } from "../src/types.js";
2
2
 
3
3
  /**
4
4
  * A valid PushPayloadV2 with at least one of every record type.
@@ -1,4 +1,4 @@
1
- import type { PushPayloadV2 } from "../src/schemas.js";
1
+ import type { PushPayloadV2 } from "../src/types.js";
2
2
 
3
3
  /**
4
4
  * A valid PushPayloadV2 with only evolution_evidence entries and
@@ -7,7 +7,7 @@ import type { PushPayloadV2 } from "../src/schemas.js";
7
7
  export const evidenceOnlyPush: PushPayloadV2 = {
8
8
  schema_version: "2.0",
9
9
  client_version: "0.9.0",
10
- push_id: "d4e5f6a7-b8c9-0123-defa-234567890123",
10
+ push_id: "d4e5f6a7-b8c9-8123-9efa-234567890123",
11
11
  normalizer_version: "0.2.1",
12
12
  canonical: {
13
13
  sessions: [],
@@ -1,7 +1,6 @@
1
1
  import { describe, expect, test } from "bun:test";
2
2
  import { readFileSync } from "node:fs";
3
3
  import { join } from "node:path";
4
-
5
4
  import { CANONICAL_SCHEMA_VERSION } from "../src/types.js";
6
5
  import { isCanonicalRecord } from "../src/validators.js";
7
6
 
@@ -1,4 +1,4 @@
1
- import type { PushPayloadV2 } from "../src/schemas.js";
1
+ import type { PushPayloadV2 } from "../src/types.js";
2
2
 
3
3
  /**
4
4
  * A valid PushPayloadV2 with zero sessions but non-empty evolution_evidence.
@@ -1,4 +1,4 @@
1
- import type { PushPayloadV2 } from "../src/schemas.js";
1
+ import type { PushPayloadV2 } from "../src/types.js";
2
2
 
3
3
  /**
4
4
  * A valid PushPayloadV2 with invocations and prompts that reference a
@@ -10,7 +10,7 @@ import type { PushPayloadV2 } from "../src/schemas.js";
10
10
  export const partialPushUnresolvedParents: PushPayloadV2 = {
11
11
  schema_version: "2.0",
12
12
  client_version: "0.9.0",
13
- push_id: "c3d4e5f6-a7b8-9012-cdef-123456789012",
13
+ push_id: "c3d4e5f6-a7b8-8012-8def-123456789012",
14
14
  normalizer_version: "0.2.1",
15
15
  canonical: {
16
16
  sessions: [],
@@ -13,9 +13,9 @@
13
13
  "type": "module",
14
14
  "exports": {
15
15
  ".": "./index.ts",
16
- "./schemas": "./src/schemas.ts",
17
16
  "./types": "./src/types.ts",
18
17
  "./validators": "./src/validators.ts",
18
+ "./schemas": "./src/schemas.ts",
19
19
  "./fixtures": "./fixtures/index.ts"
20
20
  },
21
21
  "dependencies": {
@@ -1,2 +1,3 @@
1
+ export * from "./schemas.js";
1
2
  export * from "./types.js";
2
3
  export * from "./validators.js";
@@ -1,5 +1,13 @@
1
- import { z } from "zod";
1
+ /**
2
+ * Zod validation schemas for all canonical telemetry record types
3
+ * and the PushPayloadV2 envelope.
4
+ *
5
+ * This is the single source of truth -- cloud consumers should import
6
+ * from @selftune/telemetry-contract/schemas instead of maintaining
7
+ * their own copies.
8
+ */
2
9
 
10
+ import { z } from "zod";
3
11
  import {
4
12
  CANONICAL_CAPTURE_MODES,
5
13
  CANONICAL_COMPLETION_STATUSES,
@@ -11,6 +19,8 @@ import {
11
19
  CANONICAL_SOURCE_SESSION_KINDS,
12
20
  } from "./types.js";
13
21
 
22
+ // ---------- Shared enum schemas ----------
23
+
14
24
  export const canonicalPlatformSchema = z.enum(CANONICAL_PLATFORMS);
15
25
  export const captureModeSchema = z.enum(CANONICAL_CAPTURE_MODES);
16
26
  export const sourceSessionKindSchema = z.enum(CANONICAL_SOURCE_SESSION_KINDS);
@@ -19,6 +29,8 @@ export const invocationModeSchema = z.enum(CANONICAL_INVOCATION_MODES);
19
29
  export const completionStatusSchema = z.enum(CANONICAL_COMPLETION_STATUSES);
20
30
  export const recordKindSchema = z.enum(CANONICAL_RECORD_KINDS);
21
31
 
32
+ // ---------- Shared structural schemas ----------
33
+
22
34
  export const rawSourceRefSchema = z.object({
23
35
  path: z.string().optional(),
24
36
  line: z.number().int().nonnegative().optional(),
@@ -42,6 +54,8 @@ export const canonicalSessionRecordBaseSchema = canonicalRecordBaseSchema.extend
42
54
  session_id: z.string().min(1),
43
55
  });
44
56
 
57
+ // ---------- Canonical record schemas ----------
58
+
45
59
  export const CanonicalSessionRecordSchema = canonicalSessionRecordBaseSchema.extend({
46
60
  record_kind: z.literal("session"),
47
61
  external_session_id: z.string().optional(),
@@ -126,10 +140,13 @@ export const CanonicalEvolutionEvidenceRecordSchema = z.object({
126
140
  evidence_id: z.string().min(1),
127
141
  skill_name: z.string().min(1),
128
142
  proposal_id: z.string().optional(),
143
+ timestamp: z.string().datetime().optional(),
144
+ skill_path: z.string().optional(),
129
145
  target: z.string().min(1),
130
146
  stage: z.string().min(1),
131
147
  rationale: z.string().optional(),
132
148
  confidence: z.number().min(0).max(1).optional(),
149
+ details: z.string().optional(),
133
150
  original_text: z.string().optional(),
134
151
  proposed_text: z.string().optional(),
135
152
  eval_set_json: z.unknown().optional(),
@@ -137,6 +154,39 @@ export const CanonicalEvolutionEvidenceRecordSchema = z.object({
137
154
  raw_source_ref: rawSourceRefSchema.optional(),
138
155
  });
139
156
 
157
+ export const CanonicalGradingResultRecordSchema = z.object({
158
+ grading_id: z.string().min(1),
159
+ session_id: z.string().min(1),
160
+ skill_name: z.string().min(1),
161
+ transcript_path: z.string().nullable().optional(),
162
+ graded_at: z.string().min(1),
163
+ pass_rate: z.number().min(0).max(1).nullable().optional(),
164
+ mean_score: z.number().min(0).max(1).nullable().optional(),
165
+ score_std_dev: z.number().nullable().optional(),
166
+ passed_count: z.number().int().nonnegative().nullable().optional(),
167
+ failed_count: z.number().int().nonnegative().nullable().optional(),
168
+ total_count: z.number().int().nonnegative().nullable().optional(),
169
+ expectations_json: z.string().nullable().optional(),
170
+ claims_json: z.string().nullable().optional(),
171
+ eval_feedback_json: z.string().nullable().optional(),
172
+ failure_feedback_json: z.string().nullable().optional(),
173
+ execution_metrics_json: z.string().nullable().optional(),
174
+ });
175
+
176
+ export const CanonicalImprovementSignalRecordSchema = z.object({
177
+ signal_id: z.string().min(1),
178
+ timestamp: z.string().min(1),
179
+ session_id: z.string().min(1),
180
+ query: z.string().min(1),
181
+ signal_type: z.string().min(1),
182
+ mentioned_skill: z.string().nullable().optional(),
183
+ consumed: z.boolean(),
184
+ consumed_at: z.string().nullable().optional(),
185
+ consumed_by_run: z.string().nullable().optional(),
186
+ });
187
+
188
+ // ---------- Orchestrate run schemas ----------
189
+
140
190
  export const OrchestrateRunSkillActionSchema = z.object({
141
191
  skill: z.string().min(1),
142
192
  action: z.enum(["evolve", "watch", "skip"]),
@@ -163,12 +213,12 @@ export const PushOrchestrateRunRecordSchema = z.object({
163
213
  skill_actions: z.array(OrchestrateRunSkillActionSchema),
164
214
  });
165
215
 
216
+ // ---------- Push V2 envelope ----------
217
+
166
218
  export const PushPayloadV2Schema = z.object({
167
219
  schema_version: z.literal("2.0"),
168
220
  client_version: z.string().min(1),
169
- // Queue-generated push IDs are typically UUIDs, but the wire contract only
170
- // requires a stable non-empty idempotency key.
171
- push_id: z.string().min(1),
221
+ push_id: z.string().uuid(),
172
222
  normalizer_version: z.string().min(1),
173
223
  canonical: z.object({
174
224
  sessions: z.array(CanonicalSessionRecordSchema).min(0),
@@ -178,10 +228,14 @@ export const PushPayloadV2Schema = z.object({
178
228
  normalization_runs: z.array(CanonicalNormalizationRunRecordSchema).min(0),
179
229
  evolution_evidence: z.array(CanonicalEvolutionEvidenceRecordSchema).optional(),
180
230
  orchestrate_runs: z.array(PushOrchestrateRunRecordSchema).optional(),
231
+ grading_results: z.array(CanonicalGradingResultRecordSchema).optional(),
232
+ improvement_signals: z.array(CanonicalImprovementSignalRecordSchema).optional(),
181
233
  }),
182
234
  });
183
235
 
184
- export type PushPayloadV2 = z.infer<typeof PushPayloadV2Schema>;
236
+ // ---------- Inferred types from Zod schemas ----------
237
+
238
+ export type ZodPushPayloadV2 = z.infer<typeof PushPayloadV2Schema>;
185
239
  export type ZodCanonicalSessionRecord = z.infer<typeof CanonicalSessionRecordSchema>;
186
240
  export type ZodCanonicalPromptRecord = z.infer<typeof CanonicalPromptRecordSchema>;
187
241
  export type ZodCanonicalSkillInvocationRecord = z.infer<
@@ -194,4 +248,8 @@ export type ZodCanonicalNormalizationRunRecord = z.infer<
194
248
  export type ZodCanonicalEvolutionEvidenceRecord = z.infer<
195
249
  typeof CanonicalEvolutionEvidenceRecordSchema
196
250
  >;
251
+ export type ZodCanonicalGradingResultRecord = z.infer<typeof CanonicalGradingResultRecordSchema>;
252
+ export type ZodCanonicalImprovementSignalRecord = z.infer<
253
+ typeof CanonicalImprovementSignalRecordSchema
254
+ >;
197
255
  export type ZodPushOrchestrateRunRecord = z.infer<typeof PushOrchestrateRunRecordSchema>;