selftune 0.2.9 → 0.2.12

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 (140) hide show
  1. package/README.md +35 -35
  2. package/apps/local-dashboard/dist/assets/index-4_dAY17K.js +16 -0
  3. package/apps/local-dashboard/dist/assets/index-BxV5WZHc.css +2 -0
  4. package/apps/local-dashboard/dist/assets/rolldown-runtime-Dw2cE7zH.js +1 -0
  5. package/apps/local-dashboard/dist/assets/vendor-react-CKkiCskZ.js +11 -0
  6. package/apps/local-dashboard/dist/assets/vendor-table-pHbDxq36.js +8 -0
  7. package/apps/local-dashboard/dist/assets/vendor-ui-7xD7fNEU.js +12 -0
  8. package/apps/local-dashboard/dist/index.html +16 -15
  9. package/bin/selftune.cjs +1 -1
  10. package/cli/selftune/activation-rules.ts +1 -0
  11. package/cli/selftune/alpha-upload/build-payloads.ts +18 -2
  12. package/cli/selftune/alpha-upload/stage-canonical.ts +94 -0
  13. package/cli/selftune/auth/device-code.ts +32 -0
  14. package/cli/selftune/auto-update.ts +12 -0
  15. package/cli/selftune/badge/badge.ts +1 -0
  16. package/cli/selftune/canonical-export.ts +5 -0
  17. package/cli/selftune/claude-agents.ts +154 -0
  18. package/cli/selftune/contribute/bundle.ts +1 -0
  19. package/cli/selftune/contribute/contribute.ts +1 -0
  20. package/cli/selftune/cron/setup.ts +2 -2
  21. package/cli/selftune/dashboard-server.ts +1 -0
  22. package/cli/selftune/eval/hooks-to-evals.ts +1 -0
  23. package/cli/selftune/eval/import-skillsbench.ts +1 -0
  24. package/cli/selftune/eval/synthetic-evals.ts +2 -3
  25. package/cli/selftune/eval/unit-test.ts +1 -0
  26. package/cli/selftune/evolution/deploy-proposal.ts +9 -238
  27. package/cli/selftune/evolution/evolve-body.ts +93 -6
  28. package/cli/selftune/evolution/evolve.ts +3 -7
  29. package/cli/selftune/evolution/propose-body.ts +3 -2
  30. package/cli/selftune/evolution/propose-routing.ts +3 -2
  31. package/cli/selftune/evolution/refine-body.ts +3 -2
  32. package/cli/selftune/evolution/rollback.ts +1 -1
  33. package/cli/selftune/export.ts +1 -0
  34. package/cli/selftune/grading/grade-session.ts +8 -0
  35. package/cli/selftune/hooks/auto-activate.ts +1 -0
  36. package/cli/selftune/hooks/evolution-guard.ts +1 -1
  37. package/cli/selftune/hooks/prompt-log.ts +1 -0
  38. package/cli/selftune/hooks/session-stop.ts +34 -40
  39. package/cli/selftune/hooks/skill-change-guard.ts +1 -0
  40. package/cli/selftune/hooks/skill-eval.ts +1 -1
  41. package/cli/selftune/index.ts +23 -14
  42. package/cli/selftune/ingestors/claude-replay.ts +1 -0
  43. package/cli/selftune/ingestors/codex-rollout.ts +1 -0
  44. package/cli/selftune/ingestors/codex-wrapper.ts +1 -0
  45. package/cli/selftune/ingestors/openclaw-ingest.ts +1 -0
  46. package/cli/selftune/ingestors/opencode-ingest.ts +1 -0
  47. package/cli/selftune/init.ts +121 -29
  48. package/cli/selftune/localdb/db.ts +1 -0
  49. package/cli/selftune/localdb/direct-write.ts +39 -0
  50. package/cli/selftune/localdb/materialize.ts +2 -0
  51. package/cli/selftune/localdb/queries.ts +53 -0
  52. package/cli/selftune/localdb/schema.ts +28 -0
  53. package/cli/selftune/normalization.ts +1 -0
  54. package/cli/selftune/observability.ts +1 -0
  55. package/cli/selftune/repair/skill-usage.ts +1 -0
  56. package/cli/selftune/routes/orchestrate-runs.ts +1 -0
  57. package/cli/selftune/routes/overview.ts +1 -0
  58. package/cli/selftune/routes/report.ts +1 -1
  59. package/cli/selftune/routes/skill-report.ts +2 -1
  60. package/cli/selftune/status.ts +1 -1
  61. package/cli/selftune/sync.ts +30 -1
  62. package/cli/selftune/uninstall.ts +412 -0
  63. package/cli/selftune/utils/canonical-log.ts +2 -0
  64. package/cli/selftune/utils/frontmatter.ts +50 -7
  65. package/cli/selftune/utils/jsonl.ts +1 -0
  66. package/cli/selftune/utils/llm-call.ts +131 -3
  67. package/cli/selftune/utils/skill-log.ts +1 -0
  68. package/cli/selftune/utils/transcript.ts +1 -0
  69. package/cli/selftune/utils/trigger-check.ts +1 -1
  70. package/cli/selftune/workflows/skill-md-writer.ts +5 -5
  71. package/cli/selftune/workflows/workflows.ts +1 -0
  72. package/package.json +37 -33
  73. package/packages/telemetry-contract/fixtures/golden.test.ts +1 -0
  74. package/packages/telemetry-contract/package.json +1 -1
  75. package/packages/telemetry-contract/src/schemas.ts +1 -0
  76. package/packages/telemetry-contract/tests/compatibility.test.ts +1 -0
  77. package/packages/ui/README.md +35 -34
  78. package/packages/ui/package.json +3 -3
  79. package/packages/ui/src/components/ActivityTimeline.tsx +50 -43
  80. package/packages/ui/src/components/EvidenceViewer.tsx +306 -182
  81. package/packages/ui/src/components/EvolutionTimeline.tsx +83 -72
  82. package/packages/ui/src/components/InfoTip.tsx +4 -3
  83. package/packages/ui/src/components/OrchestrateRunsPanel.tsx +60 -53
  84. package/packages/ui/src/components/section-cards.tsx +20 -25
  85. package/packages/ui/src/components/skill-health-grid.tsx +213 -193
  86. package/packages/ui/src/lib/constants.tsx +1 -0
  87. package/packages/ui/src/primitives/badge.tsx +12 -15
  88. package/packages/ui/src/primitives/button.tsx +7 -7
  89. package/packages/ui/src/primitives/card.tsx +15 -26
  90. package/packages/ui/src/primitives/checkbox.tsx +7 -8
  91. package/packages/ui/src/primitives/collapsible.tsx +5 -5
  92. package/packages/ui/src/primitives/dropdown-menu.tsx +45 -55
  93. package/packages/ui/src/primitives/label.tsx +6 -6
  94. package/packages/ui/src/primitives/select.tsx +28 -37
  95. package/packages/ui/src/primitives/table.tsx +17 -44
  96. package/packages/ui/src/primitives/tabs.tsx +14 -21
  97. package/packages/ui/src/primitives/tooltip.tsx +10 -22
  98. package/skill/SKILL.md +70 -57
  99. package/skill/Workflows/AlphaUpload.md +4 -4
  100. package/skill/Workflows/AutoActivation.md +11 -6
  101. package/skill/Workflows/Badge.md +22 -16
  102. package/skill/Workflows/Baseline.md +34 -36
  103. package/skill/Workflows/Composability.md +16 -11
  104. package/skill/Workflows/Contribute.md +26 -21
  105. package/skill/Workflows/Cron.md +23 -22
  106. package/skill/Workflows/Dashboard.md +32 -27
  107. package/skill/Workflows/Doctor.md +33 -27
  108. package/skill/Workflows/Evals.md +48 -47
  109. package/skill/Workflows/EvolutionMemory.md +31 -21
  110. package/skill/Workflows/Evolve.md +84 -82
  111. package/skill/Workflows/EvolveBody.md +58 -47
  112. package/skill/Workflows/Grade.md +16 -13
  113. package/skill/Workflows/ImportSkillsBench.md +9 -6
  114. package/skill/Workflows/Ingest.md +36 -21
  115. package/skill/Workflows/Initialize.md +108 -40
  116. package/skill/Workflows/Orchestrate.md +22 -16
  117. package/skill/Workflows/Replay.md +12 -7
  118. package/skill/Workflows/Rollback.md +13 -6
  119. package/skill/Workflows/Schedule.md +6 -6
  120. package/skill/Workflows/Sync.md +18 -11
  121. package/skill/Workflows/UnitTest.md +28 -17
  122. package/skill/Workflows/Watch.md +28 -21
  123. package/skill/agents/diagnosis-analyst.md +11 -0
  124. package/skill/agents/evolution-reviewer.md +15 -1
  125. package/skill/agents/integration-guide.md +10 -0
  126. package/skill/agents/pattern-analyst.md +12 -1
  127. package/skill/references/grading-methodology.md +23 -24
  128. package/skill/references/interactive-config.md +7 -7
  129. package/skill/references/invocation-taxonomy.md +22 -20
  130. package/skill/references/logs.md +14 -6
  131. package/skill/references/setup-patterns.md +4 -2
  132. package/.claude/agents/diagnosis-analyst.md +0 -156
  133. package/.claude/agents/evolution-reviewer.md +0 -180
  134. package/.claude/agents/integration-guide.md +0 -212
  135. package/.claude/agents/pattern-analyst.md +0 -160
  136. package/apps/local-dashboard/dist/assets/index-Bs3Y4ixf.css +0 -1
  137. package/apps/local-dashboard/dist/assets/index-C4UYGWKr.js +0 -15
  138. package/apps/local-dashboard/dist/assets/vendor-react-BQH_6WrG.js +0 -60
  139. package/apps/local-dashboard/dist/assets/vendor-table-dK1QMLq9.js +0 -26
  140. package/apps/local-dashboard/dist/assets/vendor-ui-CO2mrx6e.js +0 -341
@@ -1,17 +1,18 @@
1
- <!DOCTYPE html>
1
+ <!doctype html>
2
2
  <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>selftune — Dashboard</title>
7
- <link rel="icon" type="image/png" href="/favicon.png" />
8
- <script type="module" crossorigin src="/assets/index-C4UYGWKr.js"></script>
9
- <link rel="modulepreload" crossorigin href="/assets/vendor-react-BQH_6WrG.js">
10
- <link rel="modulepreload" crossorigin href="/assets/vendor-ui-CO2mrx6e.js">
11
- <link rel="modulepreload" crossorigin href="/assets/vendor-table-dK1QMLq9.js">
12
- <link rel="stylesheet" crossorigin href="/assets/index-Bs3Y4ixf.css">
13
- </head>
14
- <body>
15
- <div id="root"></div>
16
- </body>
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>selftune — Dashboard</title>
7
+ <link rel="icon" type="image/png" href="/favicon.png" />
8
+ <script type="module" crossorigin src="/assets/index-4_dAY17K.js"></script>
9
+ <link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-Dw2cE7zH.js">
10
+ <link rel="modulepreload" crossorigin href="/assets/vendor-react-CKkiCskZ.js">
11
+ <link rel="modulepreload" crossorigin href="/assets/vendor-ui-7xD7fNEU.js">
12
+ <link rel="modulepreload" crossorigin href="/assets/vendor-table-pHbDxq36.js">
13
+ <link rel="stylesheet" crossorigin href="/assets/index-BxV5WZHc.css">
14
+ </head>
15
+ <body>
16
+ <div id="root"></div>
17
+ </body>
17
18
  </html>
package/bin/selftune.cjs CHANGED
@@ -26,6 +26,6 @@ for (const [cmd, args] of runners) {
26
26
  console.error(
27
27
  JSON.stringify({
28
28
  error: "No TypeScript runtime found. Install bun (https://bun.sh) or tsx (npx tsx).",
29
- })
29
+ }),
30
30
  );
31
31
  process.exit(1);
@@ -12,6 +12,7 @@
12
12
 
13
13
  import { existsSync, readdirSync, readFileSync } from "node:fs";
14
14
  import { dirname, join } from "node:path";
15
+
15
16
  import { EVOLUTION_AUDIT_LOG, QUERY_LOG } from "./constants.js";
16
17
  import { getDb } from "./localdb/db.js";
17
18
  import { queryEvolutionAudit, queryQueryLog, querySkillUsageRecords } from "./localdb/queries.js";
@@ -10,7 +10,9 @@
10
10
  */
11
11
 
12
12
  import type { Database } from "bun:sqlite";
13
+
13
14
  import type { CanonicalRecord } from "@selftune/telemetry-contract";
15
+
14
16
  import { buildPushPayloadV2 } from "../canonical-export.js";
15
17
  import type { EvolutionEvidenceEntry } from "../types.js";
16
18
 
@@ -74,6 +76,8 @@ export function buildV2PushPayload(
74
76
  const canonicalRecords: CanonicalRecord[] = [];
75
77
  const evidenceEntries: EvolutionEvidenceEntry[] = [];
76
78
  const orchestrateRuns: Record<string, unknown>[] = [];
79
+ const gradingResults: Record<string, unknown>[] = [];
80
+ const improvementSignals: Record<string, unknown>[] = [];
77
81
  let lastParsedSeq: number | null = null;
78
82
  let hitMalformedRow = false;
79
83
 
@@ -118,6 +122,10 @@ export function buildV2PushPayload(
118
122
  } else if (row.record_kind === "orchestrate_run") {
119
123
  // Orchestrate run records -- pass through as-is
120
124
  orchestrateRuns.push(parsed);
125
+ } else if (row.record_kind === "grading_result") {
126
+ gradingResults.push(parsed);
127
+ } else if (row.record_kind === "improvement_signal") {
128
+ improvementSignals.push(parsed);
121
129
  } else {
122
130
  // Canonical telemetry records -- pass through as-is
123
131
  canonicalRecords.push(parsed as unknown as CanonicalRecord);
@@ -130,12 +138,20 @@ export function buildV2PushPayload(
130
138
  if (
131
139
  canonicalRecords.length === 0 &&
132
140
  evidenceEntries.length === 0 &&
133
- orchestrateRuns.length === 0
141
+ orchestrateRuns.length === 0 &&
142
+ gradingResults.length === 0 &&
143
+ improvementSignals.length === 0
134
144
  ) {
135
145
  return null;
136
146
  }
137
147
 
138
- const payload = buildPushPayloadV2(canonicalRecords, evidenceEntries, orchestrateRuns);
148
+ const payload = buildPushPayloadV2(
149
+ canonicalRecords,
150
+ evidenceEntries,
151
+ orchestrateRuns,
152
+ gradingResults,
153
+ improvementSignals,
154
+ );
139
155
  if (lastParsedSeq === null) {
140
156
  return null;
141
157
  }
@@ -11,13 +11,17 @@
11
11
 
12
12
  import type { Database } from "bun:sqlite";
13
13
  import { createHash } from "node:crypto";
14
+
14
15
  import type { CanonicalRecord } from "@selftune/telemetry-contract";
15
16
  import { isCanonicalRecord } from "@selftune/telemetry-contract";
17
+
16
18
  import { CANONICAL_LOG } from "../constants.js";
17
19
  import {
18
20
  getOrchestrateRuns,
19
21
  queryCanonicalRecordsForStaging,
20
22
  queryEvolutionEvidence,
23
+ queryGradingResults,
24
+ queryImprovementSignals,
21
25
  } from "../localdb/queries.js";
22
26
  import { readJsonl } from "../utils/jsonl.js";
23
27
 
@@ -47,6 +51,22 @@ export function generateEvidenceId(record: Record<string, unknown>): string {
47
51
  return `ev_${createHash("sha256").update(key).digest("hex").slice(0, 16)}`;
48
52
  }
49
53
 
54
+ /**
55
+ * Generate a deterministic grading_id from the result's natural key.
56
+ */
57
+ export function generateGradingId(record: Record<string, unknown>): string {
58
+ const key = `${record.session_id}:${record.skill_name}:${record.graded_at}`;
59
+ return `gr_${createHash("sha256").update(key).digest("hex").slice(0, 16)}`;
60
+ }
61
+
62
+ /**
63
+ * Generate a deterministic signal_id from an improvement signal's natural key.
64
+ */
65
+ export function generateSignalId(record: Record<string, unknown>): string {
66
+ const key = `${record.session_id}:${record.query}:${record.signal_type}:${record.timestamp}`;
67
+ return `sig_${createHash("sha256").update(key).digest("hex").slice(0, 16)}`;
68
+ }
69
+
50
70
  /**
51
71
  * Enrich a raw parsed record: if it is an execution_fact missing
52
72
  * execution_fact_id, inject a deterministic one.
@@ -247,5 +267,79 @@ export function stageCanonicalRecords(db: Database, logPath: string = CANONICAL_
247
267
  }
248
268
  }
249
269
 
270
+ // 4. Stage grading results from SQLite
271
+ try {
272
+ const gradingResults = queryGradingResults(db);
273
+ for (const gr of gradingResults) {
274
+ const recordJson = JSON.stringify({
275
+ grading_id: gr.grading_id,
276
+ session_id: gr.session_id,
277
+ skill_name: gr.skill_name,
278
+ transcript_path: gr.transcript_path,
279
+ graded_at: gr.graded_at,
280
+ pass_rate: gr.pass_rate,
281
+ mean_score: gr.mean_score,
282
+ score_std_dev: gr.score_std_dev,
283
+ passed_count: gr.passed_count,
284
+ failed_count: gr.failed_count,
285
+ total_count: gr.total_count,
286
+ expectations_json: gr.expectations_json,
287
+ claims_json: gr.claims_json,
288
+ eval_feedback_json: gr.eval_feedback_json,
289
+ failure_feedback_json: gr.failure_feedback_json,
290
+ execution_metrics_json: gr.execution_metrics_json,
291
+ });
292
+
293
+ const result = stmt.run(
294
+ "grading_result",
295
+ gr.grading_id,
296
+ recordJson,
297
+ gr.session_id,
298
+ null, // no prompt_id
299
+ gr.graded_at,
300
+ now,
301
+ );
302
+ if (result.changes > 0) staged++;
303
+ }
304
+ } catch (err) {
305
+ if (process.env.DEBUG || process.env.NODE_ENV === "development") {
306
+ console.error("[stage-canonical] failed to stage grading results:", err);
307
+ }
308
+ }
309
+
310
+ // 5. Stage improvement signals from SQLite
311
+ try {
312
+ const signals = queryImprovementSignals(db);
313
+ for (const sig of signals) {
314
+ const signalId = generateSignalId(sig);
315
+ const recordJson = JSON.stringify({
316
+ signal_id: signalId,
317
+ timestamp: sig.timestamp,
318
+ session_id: sig.session_id,
319
+ query: sig.query,
320
+ signal_type: sig.signal_type,
321
+ mentioned_skill: sig.mentioned_skill,
322
+ consumed: sig.consumed,
323
+ consumed_at: sig.consumed_at,
324
+ consumed_by_run: sig.consumed_by_run,
325
+ });
326
+
327
+ const result = stmt.run(
328
+ "improvement_signal",
329
+ signalId,
330
+ recordJson,
331
+ sig.session_id,
332
+ null, // no prompt_id
333
+ sig.timestamp,
334
+ now,
335
+ );
336
+ if (result.changes > 0) staged++;
337
+ }
338
+ } catch (err) {
339
+ if (process.env.DEBUG || process.env.NODE_ENV === "development") {
340
+ console.error("[stage-canonical] failed to stage improvement signals:", err);
341
+ }
342
+ }
343
+
250
344
  return staged;
251
345
  }
@@ -22,6 +22,38 @@ export interface DeviceCodeResult {
22
22
  org_id: string;
23
23
  }
24
24
 
25
+ export function tryOpenUrl(url: string): boolean {
26
+ const command =
27
+ process.platform === "darwin"
28
+ ? ["open", url]
29
+ : process.platform === "linux"
30
+ ? ["xdg-open", url]
31
+ : process.platform === "win32"
32
+ ? ["cmd", "/c", "start", "", url]
33
+ : null;
34
+
35
+ if (!command) return false;
36
+ if (process.platform !== "win32" && !Bun.which(command[0])) return false;
37
+
38
+ try {
39
+ Bun.spawn(command, { stdout: "ignore", stderr: "ignore" });
40
+ return true;
41
+ } catch {
42
+ return false;
43
+ }
44
+ }
45
+
46
+ export function buildVerificationUrl(verificationUrl: string, userCode: string): string {
47
+ try {
48
+ const url = new URL(verificationUrl);
49
+ url.searchParams.set("code", userCode);
50
+ return url.toString();
51
+ } catch {
52
+ const separator = verificationUrl.includes("?") ? "&" : "?";
53
+ return `${verificationUrl}${separator}code=${encodeURIComponent(userCode)}`;
54
+ }
55
+ }
56
+
25
57
  /**
26
58
  * Derive the cloud API base URL from SELFTUNE_ALPHA_ENDPOINT.
27
59
  * The endpoint is the push URL (e.g., https://api.selftune.dev/api/v1/push).
@@ -8,7 +8,9 @@
8
8
 
9
9
  import { spawnSync } from "node:child_process";
10
10
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
11
+ import { homedir } from "node:os";
11
12
  import { join } from "node:path";
13
+
12
14
  import { SELFTUNE_CONFIG_DIR } from "./constants.js";
13
15
 
14
16
  const UPDATE_CHECK_PATH = join(SELFTUNE_CONFIG_DIR, "update-check.json");
@@ -118,6 +120,16 @@ async function performUpdate(currentVersion: string, latestVersion: string): Pro
118
120
  console.error(`[selftune] Updated to v${latestVersion}.`);
119
121
  // Update cache to reflect new version
120
122
  writeCache({ lastCheck: Date.now(), currentVersion: latestVersion, latestVersion });
123
+
124
+ try {
125
+ const claudeDir = join(homedir(), ".claude");
126
+ if (existsSync(claudeDir)) {
127
+ const { installAgentFiles } = await import("./claude-agents.js");
128
+ installAgentFiles({ force: true });
129
+ }
130
+ } catch {
131
+ // Non-critical — updated CLI is usable even if agent sync fails
132
+ }
121
133
  } else {
122
134
  const stderr = result.stderr?.toString().trim();
123
135
  console.error(
@@ -8,6 +8,7 @@
8
8
 
9
9
  import { writeFileSync } from "node:fs";
10
10
  import { parseArgs } from "node:util";
11
+
11
12
  import { getDb } from "../localdb/db.js";
12
13
  import {
13
14
  queryEvolutionAudit,
@@ -4,6 +4,7 @@ import { randomUUID } from "node:crypto";
4
4
  import { readFileSync, writeFileSync } from "node:fs";
5
5
  import { join } from "node:path";
6
6
  import { parseArgs } from "node:util";
7
+
7
8
  import { CANONICAL_LOG, CLAUDE_CODE_PROJECTS_DIR } from "./constants.js";
8
9
  import {
9
10
  buildCanonicalRecordsFromReplay,
@@ -84,6 +85,8 @@ export function buildPushPayloadV2(
84
85
  records: CanonicalRecord[],
85
86
  evidenceEntries: EvolutionEvidenceEntry[] = [],
86
87
  orchestrateRuns: Record<string, unknown>[] = [],
88
+ gradingResults: Record<string, unknown>[] = [],
89
+ improvementSignals: Record<string, unknown>[] = [],
87
90
  ): Record<string, unknown> {
88
91
  const sessions = records.filter((record) => record.record_kind === "session");
89
92
  const prompts = records.filter((record) => record.record_kind === "prompt");
@@ -120,6 +123,8 @@ export function buildPushPayloadV2(
120
123
  validation_json: entry.validation,
121
124
  })),
122
125
  orchestrate_runs: orchestrateRuns,
126
+ grading_results: gradingResults,
127
+ improvement_signals: improvementSignals,
123
128
  },
124
129
  };
125
130
  }
@@ -0,0 +1,154 @@
1
+ import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { dirname, join, resolve } from "node:path";
4
+
5
+ const MANIFEST_FILENAME = ".selftune-manifest.json";
6
+
7
+ const LEGACY_SELFTUNE_AGENT_FILES = [
8
+ "diagnosis-analyst.md",
9
+ "evolution-reviewer.md",
10
+ "integration-guide.md",
11
+ "pattern-analyst.md",
12
+ ] as const;
13
+
14
+ const BUNDLED_AGENT_DIR = resolve(dirname(import.meta.path), "..", "..", "skill", "agents");
15
+
16
+ interface AgentManifest {
17
+ version: 1;
18
+ files: string[];
19
+ synced_at: string;
20
+ }
21
+
22
+ function readManifest(path: string): AgentManifest | null {
23
+ try {
24
+ if (!existsSync(path)) return null;
25
+ const parsed = JSON.parse(readFileSync(path, "utf-8")) as Partial<AgentManifest>;
26
+ if (!Array.isArray(parsed.files)) return null;
27
+ return {
28
+ version: 1,
29
+ files: parsed.files.filter((name): name is string => typeof name === "string"),
30
+ synced_at: typeof parsed.synced_at === "string" ? parsed.synced_at : "",
31
+ };
32
+ } catch {
33
+ return null;
34
+ }
35
+ }
36
+
37
+ function writeManifest(path: string, files: string[]): void {
38
+ const manifest: AgentManifest = {
39
+ version: 1,
40
+ files: [...files].sort(),
41
+ synced_at: new Date().toISOString(),
42
+ };
43
+ writeFileSync(path, JSON.stringify(manifest, null, 2), "utf-8");
44
+ }
45
+
46
+ function readTextIfExists(path: string): string | null {
47
+ try {
48
+ if (!existsSync(path)) return null;
49
+ return readFileSync(path, "utf-8");
50
+ } catch {
51
+ return null;
52
+ }
53
+ }
54
+
55
+ export function getClaudeAgentsDir(homeDir = homedir()): string {
56
+ return join(homeDir, ".claude", "agents");
57
+ }
58
+
59
+ export function getClaudeAgentManifestPath(homeDir = homedir()): string {
60
+ return join(getClaudeAgentsDir(homeDir), MANIFEST_FILENAME);
61
+ }
62
+
63
+ export function listBundledAgentFiles(sourceDir = BUNDLED_AGENT_DIR): string[] {
64
+ try {
65
+ if (!existsSync(sourceDir)) return [];
66
+ return readdirSync(sourceDir)
67
+ .filter((name) => name.endsWith(".md"))
68
+ .sort();
69
+ } catch {
70
+ return [];
71
+ }
72
+ }
73
+
74
+ export function installAgentFiles(options?: {
75
+ homeDir?: string;
76
+ force?: boolean;
77
+ sourceDir?: string;
78
+ }): string[] {
79
+ const homeDir = options?.homeDir ?? homedir();
80
+ const targetDir = getClaudeAgentsDir(homeDir);
81
+ const manifestPath = getClaudeAgentManifestPath(homeDir);
82
+ const sourceDir = options?.sourceDir ?? BUNDLED_AGENT_DIR;
83
+ const sourceFiles = listBundledAgentFiles(sourceDir);
84
+ if (sourceFiles.length === 0) return [];
85
+
86
+ mkdirSync(targetDir, { recursive: true });
87
+
88
+ const manifest = readManifest(manifestPath);
89
+ const managedFiles = new Set<string>([
90
+ ...LEGACY_SELFTUNE_AGENT_FILES,
91
+ ...(manifest?.files ?? []),
92
+ ]);
93
+ const sourceSet = new Set(sourceFiles);
94
+ const changed = new Set<string>();
95
+
96
+ for (const staleFile of managedFiles) {
97
+ if (sourceSet.has(staleFile)) continue;
98
+ const stalePath = join(targetDir, staleFile);
99
+ if (existsSync(stalePath)) {
100
+ rmSync(stalePath, { force: true });
101
+ changed.add(staleFile);
102
+ }
103
+ }
104
+
105
+ for (const fileName of sourceFiles) {
106
+ const sourcePath = join(sourceDir, fileName);
107
+ const targetPath = join(targetDir, fileName);
108
+ const sourceContent = readTextIfExists(sourcePath);
109
+ if (sourceContent === null) continue;
110
+ const existingContent = readTextIfExists(targetPath);
111
+
112
+ if (options?.force || existingContent !== sourceContent) {
113
+ writeFileSync(targetPath, sourceContent, "utf-8");
114
+ changed.add(fileName);
115
+ }
116
+ }
117
+
118
+ writeManifest(manifestPath, sourceFiles);
119
+ return [...changed].sort();
120
+ }
121
+
122
+ export function removeInstalledAgentFiles(options?: { homeDir?: string; dryRun?: boolean }): {
123
+ removed: number;
124
+ files: string[];
125
+ } {
126
+ const homeDir = options?.homeDir ?? homedir();
127
+ const targetDir = getClaudeAgentsDir(homeDir);
128
+ const manifestPath = getClaudeAgentManifestPath(homeDir);
129
+ const manifest = readManifest(manifestPath);
130
+ const managedFiles = new Set<string>([
131
+ ...LEGACY_SELFTUNE_AGENT_FILES,
132
+ ...listBundledAgentFiles(),
133
+ ...(manifest?.files ?? []),
134
+ ]);
135
+ const removed: string[] = [];
136
+
137
+ for (const fileName of managedFiles) {
138
+ const targetPath = join(targetDir, fileName);
139
+ if (!existsSync(targetPath)) continue;
140
+ if (!options?.dryRun) {
141
+ rmSync(targetPath, { force: true });
142
+ }
143
+ removed.push(targetPath);
144
+ }
145
+
146
+ if (existsSync(manifestPath)) {
147
+ if (!options?.dryRun) {
148
+ rmSync(manifestPath, { force: true });
149
+ }
150
+ removed.push(manifestPath);
151
+ }
152
+
153
+ return { removed: removed.length, files: removed };
154
+ }
@@ -8,6 +8,7 @@ import { randomUUID } from "node:crypto";
8
8
  import { existsSync, readdirSync, readFileSync } from "node:fs";
9
9
  import { homedir } from "node:os";
10
10
  import { join } from "node:path";
11
+
11
12
  import {
12
13
  EVOLUTION_AUDIT_LOG,
13
14
  QUERY_LOG,
@@ -10,6 +10,7 @@
10
10
  import { spawnSync } from "node:child_process";
11
11
  import { existsSync, mkdirSync, writeFileSync } from "node:fs";
12
12
  import { parseArgs } from "node:util";
13
+
13
14
  import { CONTRIBUTIONS_DIR } from "../constants.js";
14
15
  import { assembleBundle } from "./bundle.js";
15
16
  import { sanitizeBundle } from "./sanitize.js";
@@ -46,10 +46,10 @@ export const DEFAULT_CRON_JOBS: CronJobConfig[] = [
46
46
  },
47
47
  {
48
48
  name: "selftune-orchestrate",
49
- cron: "0 */6 * * *",
49
+ cron: "0 */2 * * *",
50
50
  message:
51
51
  "Run selftune orchestrate --max-skills 3. This performs source-truth sync, selects candidate skills, evolves validated low-risk descriptions autonomously, and watches recent deployments for regressions.",
52
- description: "Autonomous improvement loop every 6 hours",
52
+ description: "Autonomous improvement loop every 2 hours",
53
53
  },
54
54
  ];
55
55
 
@@ -19,6 +19,7 @@
19
19
  import type { Database } from "bun:sqlite";
20
20
  import { existsSync, readFileSync, unwatchFile, watchFile } from "node:fs";
21
21
  import { dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
22
+
22
23
  import type { BadgeFormat } from "./badge/badge-svg.js";
23
24
  import { LOG_DIR, SELFTUNE_CONFIG_DIR } from "./constants.js";
24
25
  import type {
@@ -21,6 +21,7 @@
21
21
 
22
22
  import { writeFileSync } from "node:fs";
23
23
  import { parseArgs } from "node:util";
24
+
24
25
  import { GENERIC_NEGATIVES, QUERY_LOG, SKILL_LOG, TELEMETRY_LOG } from "../constants.js";
25
26
  import { getDb } from "../localdb/db.js";
26
27
  import {
@@ -13,6 +13,7 @@
13
13
  import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
14
14
  import { join } from "node:path";
15
15
  import { parseArgs } from "node:util";
16
+
16
17
  import type { EvalEntry, SkillsBenchTask } from "../types.js";
17
18
 
18
19
  // ---------------------------------------------------------------------------
@@ -181,9 +181,8 @@ export async function generateSyntheticEvals(
181
181
  try {
182
182
  const { getDb } = await import("../localdb/db.js");
183
183
  const { querySkillUsageRecords, queryQueryLog } = await import("../localdb/queries.js");
184
- const { isHighConfidencePositiveSkillRecord } = await import(
185
- "../utils/skill-usage-confidence.js"
186
- );
184
+ const { isHighConfidencePositiveSkillRecord } =
185
+ await import("../utils/skill-usage-confidence.js");
187
186
 
188
187
  const db = getDb();
189
188
 
@@ -12,6 +12,7 @@
12
12
  */
13
13
 
14
14
  import { existsSync, readFileSync } from "node:fs";
15
+
15
16
  import type {
16
17
  SkillAssertion,
17
18
  SkillUnitTest,