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
@@ -2,19 +2,25 @@
2
2
  * Auto-update check for selftune CLI.
3
3
  *
4
4
  * Runs before command dispatch (skipped for hooks and --help).
5
+ * Set SELFTUNE_SKIP_AUTO_UPDATE=1 or SELFTUNE_SKIP_UPDATE_CHECK=1 to disable
6
+ * it for source-tree smoke tests and hermetic automation.
5
7
  * Checks npm registry at most once per hour (cached in ~/.selftune/update-check.json).
6
- * If outdated, auto-updates via `npm install -g selftune@latest` and notifies the user.
8
+ * If outdated, auto-updates the active global install (npm or Bun) and syncs
9
+ * bundled skill files into common global skill registries.
7
10
  */
8
11
 
9
12
  import { spawnSync } from "node:child_process";
10
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
13
+ import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
11
14
  import { homedir } from "node:os";
12
- import { join } from "node:path";
15
+ import { join, resolve } from "node:path";
13
16
 
14
17
  import { SELFTUNE_CONFIG_DIR } from "./constants.js";
15
18
 
16
19
  const UPDATE_CHECK_PATH = join(SELFTUNE_CONFIG_DIR, "update-check.json");
17
20
  const CHECK_INTERVAL_MS = 60 * 60 * 1000; // 1 hour
21
+ const PACKAGE_NAME = "selftune";
22
+ const PACKAGE_ROOT = resolve(import.meta.dir, "..", "..");
23
+ const BUNDLED_SKILL_DIR = join(PACKAGE_ROOT, "skill");
18
24
 
19
25
  interface UpdateCheckCache {
20
26
  lastCheck: number;
@@ -22,6 +28,33 @@ interface UpdateCheckCache {
22
28
  latestVersion: string;
23
29
  }
24
30
 
31
+ type InstallSource = "bun-global" | "npm-global";
32
+
33
+ interface UpdateCommand {
34
+ source: InstallSource;
35
+ command: string;
36
+ args: string[];
37
+ manualCommand: string;
38
+ }
39
+
40
+ interface UpdateCommandOptions {
41
+ homeDir?: string;
42
+ moduleDir?: string;
43
+ npmGlobalRoot?: string | null;
44
+ }
45
+
46
+ function isTruthyEnv(value: string | undefined): boolean {
47
+ if (!value) return false;
48
+ return !["0", "false", "no", "off"].includes(value.trim().toLowerCase());
49
+ }
50
+
51
+ export function isAutoUpdateSkipped(): boolean {
52
+ return (
53
+ isTruthyEnv(process.env.SELFTUNE_SKIP_AUTO_UPDATE) ||
54
+ isTruthyEnv(process.env.SELFTUNE_SKIP_UPDATE_CHECK)
55
+ );
56
+ }
57
+
25
58
  function readCache(): UpdateCheckCache | null {
26
59
  try {
27
60
  if (!existsSync(UPDATE_CHECK_PATH)) return null;
@@ -47,6 +80,139 @@ function getCurrentVersion(): string {
47
80
  return JSON.parse(readFileSync(pkgPath, "utf-8")).version;
48
81
  }
49
82
 
83
+ function normalizePath(path: string): string {
84
+ return resolve(path).replaceAll("\\", "/");
85
+ }
86
+
87
+ function getActivePackageRoot(moduleDir = import.meta.dir): string {
88
+ return resolve(moduleDir, "..", "..");
89
+ }
90
+
91
+ function getNpmGlobalRoot(): string | null {
92
+ const result = spawnSync("npm", ["root", "-g"], {
93
+ stdio: ["ignore", "pipe", "ignore"],
94
+ timeout: 5000,
95
+ });
96
+ if (result.status !== 0) return null;
97
+ const root = result.stdout?.toString().trim();
98
+ return root ? root : null;
99
+ }
100
+
101
+ function buildManualUpdateCommand(source: InstallSource, version: string): string {
102
+ const packageSpec = `${PACKAGE_NAME}@${version}`;
103
+ if (source === "bun-global") {
104
+ return `bun add -g ${packageSpec}`;
105
+ }
106
+ return `npm install -g ${packageSpec}`;
107
+ }
108
+
109
+ export function resolveSelftuneUpdateCommand(
110
+ version: string,
111
+ options?: UpdateCommandOptions,
112
+ ): UpdateCommand | null {
113
+ const homeDir = options?.homeDir ?? homedir();
114
+ const activePackageRoot = normalizePath(getActivePackageRoot(options?.moduleDir));
115
+
116
+ const bunPackageRoot = normalizePath(
117
+ join(homeDir, ".bun", "install", "global", "node_modules", PACKAGE_NAME),
118
+ );
119
+ if (
120
+ activePackageRoot === bunPackageRoot ||
121
+ activePackageRoot.includes("/.bun/install/global/node_modules/selftune")
122
+ ) {
123
+ return {
124
+ source: "bun-global",
125
+ command: "bun",
126
+ args: ["add", "-g", `${PACKAGE_NAME}@${version}`],
127
+ manualCommand: buildManualUpdateCommand("bun-global", version),
128
+ };
129
+ }
130
+
131
+ const npmGlobalRoot = options?.npmGlobalRoot ?? getNpmGlobalRoot();
132
+ if (npmGlobalRoot) {
133
+ const npmPackageRoot = normalizePath(join(npmGlobalRoot, PACKAGE_NAME));
134
+ if (activePackageRoot === npmPackageRoot) {
135
+ return {
136
+ source: "npm-global",
137
+ command: "npm",
138
+ args: ["install", "-g", `${PACKAGE_NAME}@${version}`],
139
+ manualCommand: buildManualUpdateCommand("npm-global", version),
140
+ };
141
+ }
142
+ }
143
+
144
+ if (activePackageRoot.includes("/lib/node_modules/selftune")) {
145
+ return {
146
+ source: "npm-global",
147
+ command: "npm",
148
+ args: ["install", "-g", `${PACKAGE_NAME}@${version}`],
149
+ manualCommand: buildManualUpdateCommand("npm-global", version),
150
+ };
151
+ }
152
+
153
+ return null;
154
+ }
155
+
156
+ export function getSelftuneUpdateHint(version = "latest", options?: UpdateCommandOptions): string {
157
+ return (
158
+ resolveSelftuneUpdateCommand(version, options)?.manualCommand ??
159
+ "npx skills add selftune-dev/selftune"
160
+ );
161
+ }
162
+
163
+ function readSkillVersion(skillDir: string): string | null {
164
+ try {
165
+ const skillPath = join(skillDir, "SKILL.md");
166
+ if (!existsSync(skillPath)) return null;
167
+ const skillContent = readFileSync(skillPath, "utf-8");
168
+ const match = skillContent.match(/^\s*version:\s*(.+)$/m);
169
+ return match?.[1]?.trim() ?? null;
170
+ } catch {
171
+ return null;
172
+ }
173
+ }
174
+
175
+ export function getInstalledSkillDirs(homeDir = homedir()): string[] {
176
+ return [
177
+ join(homeDir, ".claude", "skills", PACKAGE_NAME),
178
+ join(homeDir, ".agents", "skills", PACKAGE_NAME),
179
+ ].filter((dir) => existsSync(dir));
180
+ }
181
+
182
+ export function syncInstalledSkillFiles(options?: {
183
+ force?: boolean;
184
+ homeDir?: string;
185
+ packageSkillDir?: string;
186
+ }): string[] {
187
+ const homeDir = options?.homeDir ?? homedir();
188
+ const packageSkillDir = options?.packageSkillDir ?? BUNDLED_SKILL_DIR;
189
+ if (!existsSync(packageSkillDir)) return [];
190
+
191
+ const sourceVersion = readSkillVersion(packageSkillDir);
192
+ const syncedDirs: string[] = [];
193
+
194
+ for (const targetDir of getInstalledSkillDirs(homeDir)) {
195
+ const targetVersion = readSkillVersion(targetDir);
196
+ const shouldSync =
197
+ options?.force ||
198
+ sourceVersion === null ||
199
+ targetVersion === null ||
200
+ sourceVersion !== targetVersion;
201
+ if (!shouldSync) continue;
202
+
203
+ mkdirSync(targetDir, { recursive: true });
204
+ for (const entry of readdirSync(packageSkillDir)) {
205
+ cpSync(join(packageSkillDir, entry), join(targetDir, entry), {
206
+ recursive: true,
207
+ force: true,
208
+ });
209
+ }
210
+ syncedDirs.push(targetDir);
211
+ }
212
+
213
+ return syncedDirs;
214
+ }
215
+
50
216
  function compareSemver(a: string, b: string): -1 | 0 | 1 {
51
217
  const pa = a.split(".").map(Number);
52
218
  const pb = b.split(".").map(Number);
@@ -66,6 +232,8 @@ function compareSemver(a: string, b: string): -1 | 0 | 1 {
66
232
  */
67
233
  export async function autoUpdate(): Promise<void> {
68
234
  try {
235
+ if (isAutoUpdateSkipped()) return;
236
+
69
237
  const currentVersion = getCurrentVersion();
70
238
  const cache = readCache();
71
239
 
@@ -74,6 +242,8 @@ export async function autoUpdate(): Promise<void> {
74
242
  // Even with a recent check, if we know we're outdated, try updating
75
243
  if (cache.latestVersion && compareSemver(currentVersion, cache.latestVersion) < 0) {
76
244
  await performUpdate(currentVersion, cache.latestVersion);
245
+ } else if (cache.latestVersion && compareSemver(currentVersion, cache.latestVersion) >= 0) {
246
+ syncInstalledSkillFiles();
77
247
  }
78
248
  return;
79
249
  }
@@ -87,7 +257,7 @@ export async function autoUpdate(): Promise<void> {
87
257
  signal: controller.signal,
88
258
  });
89
259
  if (!res.ok) {
90
- writeCache({ lastCheck: Date.now(), currentVersion, latestVersion: currentVersion });
260
+ writeCache({ lastCheck: Date.now(), currentVersion, latestVersion: "" });
91
261
  return;
92
262
  }
93
263
  const data = (await res.json()) as { version: string };
@@ -102,7 +272,10 @@ export async function autoUpdate(): Promise<void> {
102
272
  // Auto-update if outdated
103
273
  if (compareSemver(currentVersion, latestVersion) < 0) {
104
274
  await performUpdate(currentVersion, latestVersion);
275
+ return;
105
276
  }
277
+
278
+ syncInstalledSkillFiles();
106
279
  } catch {
107
280
  // Non-critical — silently skip
108
281
  }
@@ -111,7 +284,16 @@ export async function autoUpdate(): Promise<void> {
111
284
  async function performUpdate(currentVersion: string, latestVersion: string): Promise<void> {
112
285
  console.error(`[selftune] Update available: v${currentVersion} → v${latestVersion}. Updating...`);
113
286
 
114
- const result = spawnSync("npm", ["install", "-g", `selftune@${latestVersion}`], {
287
+ const updateCommand = resolveSelftuneUpdateCommand(latestVersion);
288
+ if (!updateCommand) {
289
+ console.error(
290
+ "[selftune] Auto-update skipped. Current install path is not a supported global package install.",
291
+ );
292
+ console.error(`[selftune] Refresh manually: ${getSelftuneUpdateHint(latestVersion)}`);
293
+ return;
294
+ }
295
+
296
+ const result = spawnSync(updateCommand.command, updateCommand.args, {
115
297
  stdio: ["ignore", "pipe", "pipe"],
116
298
  timeout: 30000,
117
299
  });
@@ -130,11 +312,21 @@ async function performUpdate(currentVersion: string, latestVersion: string): Pro
130
312
  } catch {
131
313
  // Non-critical — updated CLI is usable even if agent sync fails
132
314
  }
315
+
316
+ // Refresh installed selftune skill registries after a successful package update.
317
+ try {
318
+ const syncedSkillDirs = syncInstalledSkillFiles({ force: true });
319
+ if (getInstalledSkillDirs().length > 0 && syncedSkillDirs.length === 0) {
320
+ console.error(
321
+ `[selftune] Skill file sync failed — run: ${getSelftuneUpdateHint(latestVersion)}`,
322
+ );
323
+ }
324
+ } catch {
325
+ // Non-critical — skill files can be updated manually
326
+ }
133
327
  } else {
134
328
  const stderr = result.stderr?.toString().trim();
135
- console.error(
136
- `[selftune] Auto-update failed. Run manually: npm install -g selftune@${latestVersion}`,
137
- );
329
+ console.error(`[selftune] Auto-update failed. Run manually: ${updateCommand.manualCommand}`);
138
330
  if (stderr) {
139
331
  console.error(` ${stderr.split("\n")[0]}`);
140
332
  }
@@ -5,6 +5,8 @@ import { readFileSync, writeFileSync } from "node:fs";
5
5
  import { join } from "node:path";
6
6
  import { parseArgs } from "node:util";
7
7
 
8
+ import type { PushPayloadV2 } from "@selftune/telemetry-contract/types";
9
+
8
10
  import { CANONICAL_LOG, CLAUDE_CODE_PROJECTS_DIR } from "./constants.js";
9
11
  import {
10
12
  buildCanonicalRecordsFromReplay,
@@ -61,6 +63,12 @@ function getClientVersion(): string {
61
63
  }
62
64
  }
63
65
 
66
+ function addOptional(record: Record<string, unknown>, key: string, value: unknown): void {
67
+ if (value !== undefined && value !== null) {
68
+ record[key] = value;
69
+ }
70
+ }
71
+
64
72
  export function loadCanonicalRecordsForExport(
65
73
  logPath: string = CANONICAL_LOG,
66
74
  projectsDir: string = CLAUDE_CODE_PROJECTS_DIR,
@@ -88,13 +96,47 @@ export function buildPushPayloadV2(
88
96
  orchestrateRuns: Record<string, unknown>[] = [],
89
97
  gradingResults: Record<string, unknown>[] = [],
90
98
  improvementSignals: Record<string, unknown>[] = [],
91
- ): Record<string, unknown> {
92
- const sessions = records.filter((record) => record.record_kind === "session");
93
- const prompts = records.filter((record) => record.record_kind === "prompt");
94
- const skillInvocations = records.filter((record) => record.record_kind === "skill_invocation");
95
- const executionFacts = records.filter((record) => record.record_kind === "execution_fact");
96
- const normalizationRuns = records.filter((record) => record.record_kind === "normalization_run");
99
+ ): PushPayloadV2 {
100
+ const sessions = records.filter(
101
+ (record): record is Extract<CanonicalRecord, { record_kind: "session" }> =>
102
+ record.record_kind === "session",
103
+ );
104
+ const prompts = records.filter(
105
+ (record): record is Extract<CanonicalRecord, { record_kind: "prompt" }> =>
106
+ record.record_kind === "prompt",
107
+ );
108
+ const skillInvocations = records.filter(
109
+ (record): record is Extract<CanonicalRecord, { record_kind: "skill_invocation" }> =>
110
+ record.record_kind === "skill_invocation",
111
+ );
112
+ const executionFacts = records.filter(
113
+ (record): record is Extract<CanonicalRecord, { record_kind: "execution_fact" }> =>
114
+ record.record_kind === "execution_fact",
115
+ );
116
+ const normalizationRuns = records.filter(
117
+ (record): record is Extract<CanonicalRecord, { record_kind: "normalization_run" }> =>
118
+ record.record_kind === "normalization_run",
119
+ );
97
120
  const normalizerVersion = records[0]?.normalizer_version ?? "1.0.0";
121
+ const evolutionEvidence = evidenceEntries.map((entry) => {
122
+ const record: Record<string, unknown> = {
123
+ evidence_id: entry.evidence_id,
124
+ skill_name: entry.skill_name,
125
+ target: entry.target,
126
+ stage: entry.stage,
127
+ };
128
+ addOptional(record, "timestamp", entry.timestamp);
129
+ addOptional(record, "skill_path", entry.skill_path);
130
+ addOptional(record, "proposal_id", entry.proposal_id);
131
+ addOptional(record, "rationale", entry.rationale);
132
+ addOptional(record, "confidence", entry.confidence);
133
+ addOptional(record, "details", entry.details);
134
+ addOptional(record, "original_text", entry.original_text);
135
+ addOptional(record, "proposed_text", entry.proposed_text);
136
+ addOptional(record, "eval_set_json", entry.eval_set);
137
+ addOptional(record, "validation_json", entry.validation);
138
+ return record;
139
+ });
98
140
 
99
141
  return {
100
142
  schema_version: "2.0",
@@ -107,25 +149,13 @@ export function buildPushPayloadV2(
107
149
  skill_invocations: skillInvocations,
108
150
  execution_facts: executionFacts,
109
151
  normalization_runs: normalizationRuns,
110
- evolution_evidence: evidenceEntries.map((entry) => ({
111
- evidence_id: entry.evidence_id,
112
- timestamp: entry.timestamp,
113
- skill_name: entry.skill_name,
114
- skill_path: entry.skill_path,
115
- proposal_id: entry.proposal_id,
116
- target: entry.target,
117
- stage: entry.stage,
118
- rationale: entry.rationale,
119
- confidence: entry.confidence,
120
- details: entry.details,
121
- original_text: entry.original_text,
122
- proposed_text: entry.proposed_text,
123
- eval_set_json: entry.eval_set,
124
- validation_json: entry.validation,
125
- })),
126
- orchestrate_runs: orchestrateRuns,
127
- grading_results: gradingResults,
128
- improvement_signals: improvementSignals,
152
+ evolution_evidence:
153
+ evolutionEvidence as unknown as PushPayloadV2["canonical"]["evolution_evidence"],
154
+ orchestrate_runs:
155
+ orchestrateRuns as unknown as PushPayloadV2["canonical"]["orchestrate_runs"],
156
+ grading_results: gradingResults as unknown as PushPayloadV2["canonical"]["grading_results"],
157
+ improvement_signals:
158
+ improvementSignals as unknown as PushPayloadV2["canonical"]["improvement_signals"],
129
159
  },
130
160
  };
131
161
  }