selftune 0.2.0 → 0.2.2

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 (122) hide show
  1. package/.claude/agents/diagnosis-analyst.md +20 -10
  2. package/.claude/agents/evolution-reviewer.md +14 -1
  3. package/.claude/agents/integration-guide.md +18 -6
  4. package/.claude/agents/pattern-analyst.md +18 -5
  5. package/CHANGELOG.md +12 -4
  6. package/README.md +43 -35
  7. package/apps/local-dashboard/dist/assets/geist-cyrillic-wght-normal-CHSlOQsW.woff2 +0 -0
  8. package/apps/local-dashboard/dist/assets/geist-latin-ext-wght-normal-DMtmJ5ZE.woff2 +0 -0
  9. package/apps/local-dashboard/dist/assets/geist-latin-wght-normal-Dm3htQBi.woff2 +0 -0
  10. package/apps/local-dashboard/dist/assets/index-C4EOTFZ2.js +15 -0
  11. package/apps/local-dashboard/dist/assets/index-bl-Webyd.css +1 -0
  12. package/apps/local-dashboard/dist/assets/vendor-react-U7zYD9Rg.js +60 -0
  13. package/apps/local-dashboard/dist/assets/vendor-table-B7VF2Ipl.js +26 -0
  14. package/apps/local-dashboard/dist/assets/vendor-ui-D7_zX_qy.js +346 -0
  15. package/apps/local-dashboard/dist/favicon.png +0 -0
  16. package/apps/local-dashboard/dist/index.html +17 -0
  17. package/apps/local-dashboard/dist/logo.png +0 -0
  18. package/apps/local-dashboard/dist/logo.svg +9 -0
  19. package/cli/selftune/badge/badge-data.ts +1 -1
  20. package/cli/selftune/badge/badge.ts +4 -8
  21. package/cli/selftune/canonical-export.ts +183 -0
  22. package/cli/selftune/constants.ts +28 -0
  23. package/cli/selftune/contribute/contribute.ts +1 -1
  24. package/cli/selftune/cron/setup.ts +17 -17
  25. package/cli/selftune/dashboard-contract.ts +202 -0
  26. package/cli/selftune/dashboard-server.ts +653 -186
  27. package/cli/selftune/dashboard.ts +41 -176
  28. package/cli/selftune/eval/baseline.ts +5 -4
  29. package/cli/selftune/eval/composability-v2.ts +273 -0
  30. package/cli/selftune/eval/hooks-to-evals.ts +34 -15
  31. package/cli/selftune/eval/unit-test-cli.ts +1 -1
  32. package/cli/selftune/evolution/evidence.ts +26 -0
  33. package/cli/selftune/evolution/evolve-body.ts +105 -11
  34. package/cli/selftune/evolution/evolve.ts +371 -25
  35. package/cli/selftune/evolution/extract-patterns.ts +87 -29
  36. package/cli/selftune/evolution/rollback.ts +2 -2
  37. package/cli/selftune/grading/auto-grade.ts +200 -0
  38. package/cli/selftune/grading/grade-session.ts +448 -97
  39. package/cli/selftune/grading/results.ts +42 -0
  40. package/cli/selftune/hooks/prompt-log.ts +172 -2
  41. package/cli/selftune/hooks/session-stop.ts +123 -3
  42. package/cli/selftune/hooks/skill-eval.ts +119 -3
  43. package/cli/selftune/index.ts +395 -116
  44. package/cli/selftune/ingestors/claude-replay.ts +140 -114
  45. package/cli/selftune/ingestors/codex-rollout.ts +345 -46
  46. package/cli/selftune/ingestors/codex-wrapper.ts +207 -39
  47. package/cli/selftune/ingestors/openclaw-ingest.ts +141 -8
  48. package/cli/selftune/ingestors/opencode-ingest.ts +193 -17
  49. package/cli/selftune/init.ts +227 -14
  50. package/cli/selftune/last.ts +14 -5
  51. package/cli/selftune/localdb/db.ts +63 -0
  52. package/cli/selftune/localdb/materialize.ts +428 -0
  53. package/cli/selftune/localdb/queries.ts +376 -0
  54. package/cli/selftune/localdb/schema.ts +204 -0
  55. package/cli/selftune/monitoring/watch.ts +66 -15
  56. package/cli/selftune/normalization.ts +682 -0
  57. package/cli/selftune/observability.ts +19 -44
  58. package/cli/selftune/orchestrate.ts +1073 -0
  59. package/cli/selftune/quickstart.ts +203 -0
  60. package/cli/selftune/repair/skill-usage.ts +576 -0
  61. package/cli/selftune/schedule.ts +561 -0
  62. package/cli/selftune/status.ts +48 -26
  63. package/cli/selftune/sync.ts +627 -0
  64. package/cli/selftune/types.ts +148 -0
  65. package/cli/selftune/utils/canonical-log.ts +45 -0
  66. package/cli/selftune/utils/hooks.ts +41 -0
  67. package/cli/selftune/utils/html.ts +27 -0
  68. package/cli/selftune/utils/llm-call.ts +78 -20
  69. package/cli/selftune/utils/math.ts +10 -0
  70. package/cli/selftune/utils/query-filter.ts +139 -0
  71. package/cli/selftune/utils/skill-discovery.ts +340 -0
  72. package/cli/selftune/utils/skill-log.ts +68 -0
  73. package/cli/selftune/utils/skill-usage-confidence.ts +18 -0
  74. package/cli/selftune/utils/transcript.ts +272 -26
  75. package/cli/selftune/workflows/discover.ts +254 -0
  76. package/cli/selftune/workflows/skill-md-writer.ts +288 -0
  77. package/cli/selftune/workflows/workflows.ts +188 -0
  78. package/package.json +21 -8
  79. package/packages/telemetry-contract/README.md +11 -0
  80. package/packages/telemetry-contract/fixtures/golden.json +87 -0
  81. package/packages/telemetry-contract/fixtures/golden.test.ts +42 -0
  82. package/packages/telemetry-contract/index.ts +1 -0
  83. package/packages/telemetry-contract/package.json +19 -0
  84. package/packages/telemetry-contract/src/index.ts +2 -0
  85. package/packages/telemetry-contract/src/types.ts +163 -0
  86. package/packages/telemetry-contract/src/validators.ts +109 -0
  87. package/skill/SKILL.md +84 -53
  88. package/skill/Workflows/AutoActivation.md +17 -16
  89. package/skill/Workflows/Badge.md +6 -0
  90. package/skill/Workflows/Baseline.md +46 -23
  91. package/skill/Workflows/Composability.md +12 -5
  92. package/skill/Workflows/Contribute.md +17 -14
  93. package/skill/Workflows/Cron.md +56 -79
  94. package/skill/Workflows/Dashboard.md +45 -34
  95. package/skill/Workflows/Doctor.md +30 -17
  96. package/skill/Workflows/Evals.md +64 -40
  97. package/skill/Workflows/EvolutionMemory.md +2 -0
  98. package/skill/Workflows/Evolve.md +102 -47
  99. package/skill/Workflows/EvolveBody.md +6 -6
  100. package/skill/Workflows/Grade.md +36 -31
  101. package/skill/Workflows/ImportSkillsBench.md +11 -5
  102. package/skill/Workflows/Ingest.md +43 -36
  103. package/skill/Workflows/Initialize.md +44 -30
  104. package/skill/Workflows/Orchestrate.md +139 -0
  105. package/skill/Workflows/Replay.md +39 -18
  106. package/skill/Workflows/Rollback.md +3 -3
  107. package/skill/Workflows/Schedule.md +61 -0
  108. package/skill/Workflows/Sync.md +88 -0
  109. package/skill/Workflows/UnitTest.md +34 -22
  110. package/skill/Workflows/Watch.md +14 -4
  111. package/skill/Workflows/Workflows.md +129 -0
  112. package/skill/assets/activation-rules-default.json +26 -0
  113. package/skill/assets/multi-skill-settings.json +63 -0
  114. package/skill/assets/single-skill-settings.json +57 -0
  115. package/skill/references/invocation-taxonomy.md +2 -2
  116. package/skill/references/logs.md +164 -2
  117. package/skill/references/setup-patterns.md +65 -0
  118. package/skill/references/version-history.md +40 -0
  119. package/skill/settings_snippet.json +1 -1
  120. package/templates/multi-skill-settings.json +7 -7
  121. package/templates/single-skill-settings.json +6 -6
  122. package/dashboard/index.html +0 -1680
@@ -0,0 +1,17 @@
1
+ <!DOCTYPE html>
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-C4EOTFZ2.js"></script>
9
+ <link rel="modulepreload" crossorigin href="/assets/vendor-react-U7zYD9Rg.js">
10
+ <link rel="modulepreload" crossorigin href="/assets/vendor-ui-D7_zX_qy.js">
11
+ <link rel="modulepreload" crossorigin href="/assets/vendor-table-B7VF2Ipl.js">
12
+ <link rel="stylesheet" crossorigin href="/assets/index-bl-Webyd.css">
13
+ </head>
14
+ <body>
15
+ <div id="root"></div>
16
+ </body>
17
+ </html>
@@ -0,0 +1,9 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="250" height="250" viewBox="0 0 250 250" fill="none">
2
+ <path d="M 190.16,31.49 C 187.91,29.88 184.51,32.19 185.88,35.16 C 186.31,36.11 187.08,36.54 187.71,37.01 C 218.75,59.86 237.63,92.71 237.63,128.82 C 237.63,175.99 205.12,218.56 153.82,234.69 C 149.89,235.93 150.91,241.71 154.91,240.66 C 205.98,226.96 243.01,181.94 243,128.45 C 242.99,90.87 223.47,56.18 190.16,31.49 Z" fill="currentColor"/>
3
+ <path d="M 125.19,243.91 C 138.08,243.91 147.18,236.44 151.21,225.01 C 193.72,217.79 226.98,184.02 226.98,140.81 C 226.98,121.17 219.82,103.78 209.93,87.04 C 191.42,55.45 165.15,34.72 117.71,28.65 C 112.91,28.04 113.77,34.35 117.19,34.82 C 161.67,39.33 185.84,56.71 203.76,86.42 C 213.87,103.68 220.68,119.61 220.68,140.81 C 220.68,179.96 190.81,211.95 148.71,219.16 C 147.11,219.47 146.27,220.32 145.92,221.8 C 142.95,231.11 135.72,238.02 125.19,237.66 C 64.48,237.66 11.67,191.61 11.67,127.51 C 11.67,79.61 44.82,36.38 93.89,27.77 L 94.11,27.73 L 94.38,26.64 C 97.04,16.61 104.57,11.82 114.19,11.82 C 134.12,13.36 152.91,18.15 170.48,26.08 C 171.92,26.78 173.81,27.09 174.76,25.59 C 176.05,23.72 175.31,21.07 173.01,20.34 C 154.78,11.96 137.21,7.17 114.47,6 H 113.52 C 101.91,6 93.46,12.16 89.49,21.78 C 42.36,31.26 6.17,74.76 6.17,128.08 C 6.17,190.05 57.92,243.91 125.19,243.91 Z" fill="currentColor"/>
4
+ <path d="M 93.67,40.64 C 100.51,52.07 109.54,51.33 114.05,52.17 C 128.72,53.91 141.48,55.78 157.38,62.16 C 162.72,64.47 162.29,58.19 159.18,57.01 C 145.11,51.33 132.48,49.79 111.31,47.48 C 101.83,46.29 95.45,41.18 93.75,32.81 C 55.21,39.46 22.06,72.17 22.06,112.48 C 22.06,131.98 30.36,149.82 43.26,164.49 C 46.23,167.59 50.19,164.13 48.32,161.02 C 36.21,145.54 28.42,129.78 28.42,112.4 C 28.42,79.11 54.91,48.36 89.91,40.36 C 90.76,40.15 91.04,39.87 91.62,40.01 C 92.62,40.01 93.04,39.65 93.67,40.64 Z" fill="currentColor"/>
5
+ <path d="M 152.72,82.77 C 126.61,82.77 113.07,99.44 103.01,119.33 C 100.56,123.36 103.74,125.03 105.61,123.92 C 107.15,123.22 107.89,121.05 108.73,119.61 C 118.22,102.16 130.33,88.56 152.72,88.56 C 181.62,88.56 201.91,116.01 201.91,147.31 C 201.91,175.12 183.47,199.96 152.51,205.75 C 151.84,205.96 151.63,206.03 151.56,205.54 C 147.74,195.37 139.36,188.15 128.07,186.48 C 113.2,184.24 101.23,182.36 83.8,176.81 C 79.3,175.48 77.91,182.36 82.41,183.09 C 97.21,187.46 108.09,189.47 126.25,192.65 C 136.78,194.31 145.41,201.71 147.11,210.95 C 147.74,213.05 149.13,213.41 150.15,213.26 C 183.75,208.61 208.26,180.93 208.26,147.24 C 208.26,115.06 186.94,82.77 152.72,82.77 Z" fill="currentColor"/>
6
+ <path d="M 129.77,105.21 C 122.93,112.05 118.97,122.73 113.77,130.41 C 111.31,133.45 114.56,136.63 117.46,134.46 C 123.75,126.23 127.43,115.62 135.15,108.71 C 138.22,105.81 134.73,101.09 129.77,105.21 Z" fill="currentColor"/>
7
+ <path d="M 136.78,120.31 C 127.71,136.71 120.12,154.91 93.74,154.91 C 66.07,154.91 47.76,128.53 47.76,104.78 C 47.76,84.47 58.57,66.08 77.66,56.25 C 82.23,54.21 79.85,47.76 75.34,49.93 C 54.77,59.72 42.01,80.11 42.01,104.71 C 42.01,131.77 61.86,161.31 93.67,161.31 C 114.77,161.31 128.91,147.24 139.86,124.06 C 142.76,120.45 139.15,117.73 136.78,120.31 Z" fill="currentColor"/>
8
+ <path d="M 30.73,154.7 C 27.76,152.97 23.87,155.93 25.41,158.76 C 41.73,188.36 68.94,199.79 105.75,206.41 C 112.25,207.66 122.07,208.75 123.46,209.03 C 128.07,209.95 128.07,220.18 121.78,220.18 C 107.64,218.94 92.06,215.98 76.23,211.33 C 72.13,210.24 71.04,216.69 75.27,217.64 C 90.41,222.22 103.95,224.74 120.47,226.54 C 133.73,226.54 136.56,209.03 126.03,203.38 C 123.75,202.13 122.73,202.56 112.04,200.76 C 78.09,195.04 54.06,188.98 32.12,155.65 C 31.77,155.23 31.28,154.91 30.73,154.7 Z" fill="currentColor"/>
9
+ </svg>
@@ -15,7 +15,7 @@ export interface BadgeData {
15
15
  label: string;
16
16
  passRate: number | null;
17
17
  trend: "up" | "down" | "stable" | "unknown";
18
- status: "HEALTHY" | "WARNING" | "CRITICAL" | "UNKNOWN";
18
+ status: "HEALTHY" | "WARNING" | "CRITICAL" | "UNGRADED" | "UNKNOWN";
19
19
  color: string;
20
20
  message: string;
21
21
  }
@@ -8,16 +8,12 @@
8
8
 
9
9
  import { writeFileSync } from "node:fs";
10
10
  import { parseArgs } from "node:util";
11
- import { EVOLUTION_AUDIT_LOG, QUERY_LOG, SKILL_LOG, TELEMETRY_LOG } from "../constants.js";
11
+ import { EVOLUTION_AUDIT_LOG, QUERY_LOG, TELEMETRY_LOG } from "../constants.js";
12
12
  import { doctor } from "../observability.js";
13
13
  import { computeStatus } from "../status.js";
14
- import type {
15
- EvolutionAuditEntry,
16
- QueryLogRecord,
17
- SessionTelemetryRecord,
18
- SkillUsageRecord,
19
- } from "../types.js";
14
+ import type { EvolutionAuditEntry, QueryLogRecord, SessionTelemetryRecord } from "../types.js";
20
15
  import { readJsonl } from "../utils/jsonl.js";
16
+ import { readEffectiveSkillUsageRecords } from "../utils/skill-log.js";
21
17
  import type { BadgeFormat } from "./badge-data.js";
22
18
  import { findSkillBadgeData } from "./badge-data.js";
23
19
  import { formatBadgeOutput } from "./badge-svg.js";
@@ -70,7 +66,7 @@ export function cliMain(): void {
70
66
 
71
67
  // Read log files
72
68
  const telemetry = readJsonl<SessionTelemetryRecord>(TELEMETRY_LOG);
73
- const skillRecords = readJsonl<SkillUsageRecord>(SKILL_LOG);
69
+ const skillRecords = readEffectiveSkillUsageRecords();
74
70
  const queryRecords = readJsonl<QueryLogRecord>(QUERY_LOG);
75
71
  const auditEntries = readJsonl<EvolutionAuditEntry>(EVOLUTION_AUDIT_LOG);
76
72
 
@@ -0,0 +1,183 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { randomUUID } from "node:crypto";
4
+ import { readFileSync, writeFileSync } from "node:fs";
5
+ import { join } from "node:path";
6
+ import { parseArgs } from "node:util";
7
+ import { CANONICAL_LOG, CLAUDE_CODE_PROJECTS_DIR, EVOLUTION_EVIDENCE_LOG } from "./constants.js";
8
+ import {
9
+ buildCanonicalRecordsFromReplay,
10
+ findTranscriptFiles,
11
+ parseSession,
12
+ } from "./ingestors/claude-replay.js";
13
+ import {
14
+ CANONICAL_PLATFORMS,
15
+ CANONICAL_RECORD_KINDS,
16
+ type CanonicalPlatform,
17
+ type CanonicalRecord,
18
+ type CanonicalRecordKind,
19
+ type EvolutionEvidenceEntry,
20
+ } from "./types.js";
21
+ import {
22
+ filterCanonicalRecords,
23
+ readCanonicalRecords,
24
+ serializeCanonicalRecords,
25
+ } from "./utils/canonical-log.js";
26
+ import { readJsonl } from "./utils/jsonl.js";
27
+
28
+ function exitWithUsage(message?: string): never {
29
+ if (message) console.error(`[ERROR] ${message}`);
30
+ console.error(
31
+ `Usage: selftune export-canonical [--out FILE] [--platform NAME] [--record-kind KIND] [--pretty] [--log FILE] [--projects-dir PATH] [--push-payload]`,
32
+ );
33
+ process.exit(1);
34
+ }
35
+
36
+ function validatePlatform(value: string | undefined): CanonicalPlatform | undefined {
37
+ if (!value) return undefined;
38
+ if (!CANONICAL_PLATFORMS.includes(value as CanonicalPlatform)) {
39
+ exitWithUsage(`Unknown platform: ${value}`);
40
+ }
41
+ return value as CanonicalPlatform;
42
+ }
43
+
44
+ function validateRecordKind(value: string | undefined): CanonicalRecordKind | undefined {
45
+ if (!value) return undefined;
46
+ if (!CANONICAL_RECORD_KINDS.includes(value as CanonicalRecordKind)) {
47
+ exitWithUsage(`Unknown record kind: ${value}`);
48
+ }
49
+ return value as CanonicalRecordKind;
50
+ }
51
+
52
+ function getClientVersion(): string {
53
+ try {
54
+ const pkg = JSON.parse(readFileSync(join(import.meta.dir, "../../package.json"), "utf-8"));
55
+ return pkg.version ?? "unknown";
56
+ } catch {
57
+ return "unknown";
58
+ }
59
+ }
60
+
61
+ export function loadCanonicalRecordsForExport(
62
+ logPath: string = CANONICAL_LOG,
63
+ projectsDir: string = CLAUDE_CODE_PROJECTS_DIR,
64
+ platform?: CanonicalPlatform,
65
+ ): CanonicalRecord[] {
66
+ const canonical = readCanonicalRecords(logPath);
67
+ if (canonical.length > 0) return canonical;
68
+
69
+ // Existing installs may have rich Claude Code transcripts but no canonical log yet.
70
+ // Fall back to synthesizing exportable records directly from transcripts.
71
+ if (platform && platform !== "claude_code") return [];
72
+
73
+ const records: CanonicalRecord[] = [];
74
+ for (const transcriptPath of findTranscriptFiles(projectsDir)) {
75
+ const session = parseSession(transcriptPath);
76
+ if (!session) continue;
77
+ records.push(...buildCanonicalRecordsFromReplay(session));
78
+ }
79
+ return records;
80
+ }
81
+
82
+ export function buildPushPayloadV2(
83
+ records: CanonicalRecord[],
84
+ evidenceEntries: EvolutionEvidenceEntry[] = [],
85
+ ): Record<string, unknown> {
86
+ const sessions = records.filter((record) => record.record_kind === "session");
87
+ const prompts = records.filter((record) => record.record_kind === "prompt");
88
+ const skillInvocations = records.filter((record) => record.record_kind === "skill_invocation");
89
+ const executionFacts = records.filter((record) => record.record_kind === "execution_fact");
90
+ const normalizationRuns = records.filter((record) => record.record_kind === "normalization_run");
91
+ const normalizerVersion = records[0]?.normalizer_version ?? "1.0.0";
92
+
93
+ return {
94
+ schema_version: "2.0",
95
+ client_version: getClientVersion(),
96
+ push_id: randomUUID(),
97
+ normalizer_version: normalizerVersion,
98
+ canonical: {
99
+ sessions,
100
+ prompts,
101
+ skill_invocations: skillInvocations,
102
+ execution_facts: executionFacts,
103
+ normalization_runs: normalizationRuns,
104
+ evolution_evidence: evidenceEntries.map((entry) => ({
105
+ skill_name: entry.skill_name,
106
+ proposal_id: entry.proposal_id,
107
+ target: entry.target,
108
+ stage: entry.stage,
109
+ rationale: entry.rationale,
110
+ confidence: entry.confidence,
111
+ original_text: entry.original_text,
112
+ proposed_text: entry.proposed_text,
113
+ eval_set_json: entry.eval_set,
114
+ validation_json: entry.validation,
115
+ })),
116
+ },
117
+ };
118
+ }
119
+
120
+ export function cliMain(): void {
121
+ const { values } = parseArgs({
122
+ args: process.argv.slice(2),
123
+ options: {
124
+ out: { type: "string" },
125
+ platform: { type: "string" },
126
+ "record-kind": { type: "string" },
127
+ pretty: { type: "boolean", default: false },
128
+ log: { type: "string", default: CANONICAL_LOG },
129
+ "projects-dir": { type: "string", default: CLAUDE_CODE_PROJECTS_DIR },
130
+ "push-payload": { type: "boolean", default: false },
131
+ },
132
+ strict: true,
133
+ });
134
+
135
+ const platform = validatePlatform(values.platform);
136
+ const recordKind = validateRecordKind(values["record-kind"]);
137
+ const records = filterCanonicalRecords(
138
+ loadCanonicalRecordsForExport(values.log, values["projects-dir"], platform),
139
+ {
140
+ platform,
141
+ record_kind: recordKind,
142
+ },
143
+ );
144
+
145
+ const output = values["push-payload"]
146
+ ? `${JSON.stringify(
147
+ buildPushPayloadV2(records, readJsonl<EvolutionEvidenceEntry>(EVOLUTION_EVIDENCE_LOG)),
148
+ null,
149
+ values.pretty ? 2 : undefined,
150
+ )}\n`
151
+ : serializeCanonicalRecords(records, values.pretty);
152
+
153
+ if (values.out) {
154
+ writeFileSync(values.out, output, "utf-8");
155
+ console.log(
156
+ JSON.stringify(
157
+ {
158
+ ok: true,
159
+ out: values.out,
160
+ count: records.length,
161
+ format: values["push-payload"] ? "push-payload-v2" : "jsonl",
162
+ pretty: values.pretty,
163
+ platform: platform ?? null,
164
+ record_kind: recordKind ?? null,
165
+ },
166
+ null,
167
+ values.pretty ? 2 : undefined,
168
+ ),
169
+ );
170
+ return;
171
+ }
172
+
173
+ process.stdout.write(output);
174
+ }
175
+
176
+ if (import.meta.main) {
177
+ try {
178
+ cliMain();
179
+ } catch (error) {
180
+ const message = error instanceof Error ? error.message : String(error);
181
+ exitWithUsage(message);
182
+ }
183
+ }
@@ -12,8 +12,15 @@ export const LOG_DIR = join(homedir(), ".claude");
12
12
 
13
13
  export const TELEMETRY_LOG = join(LOG_DIR, "session_telemetry_log.jsonl");
14
14
  export const SKILL_LOG = join(LOG_DIR, "skill_usage_log.jsonl");
15
+ export const REPAIRED_SKILL_LOG = join(LOG_DIR, "skill_usage_repaired.jsonl");
16
+ export const CANONICAL_LOG = join(LOG_DIR, "canonical_telemetry_log.jsonl");
17
+ export const REPAIRED_SKILL_SESSIONS_MARKER = join(LOG_DIR, "skill_usage_repaired_sessions.json");
15
18
  export const QUERY_LOG = join(LOG_DIR, "all_queries_log.jsonl");
16
19
  export const EVOLUTION_AUDIT_LOG = join(LOG_DIR, "evolution_audit_log.jsonl");
20
+ export const EVOLUTION_EVIDENCE_LOG = join(LOG_DIR, "evolution_evidence_log.jsonl");
21
+ export const ORCHESTRATE_RUN_LOG = join(LOG_DIR, "orchestrate_runs.jsonl");
22
+ export const SIGNAL_LOG = join(LOG_DIR, "improvement_signals.jsonl");
23
+ export const ORCHESTRATE_LOCK = join(LOG_DIR, ".orchestrate.lock");
17
24
 
18
25
  /** Evolution memory directory — human-readable session context that survives resets. */
19
26
  export const MEMORY_DIR = join(SELFTUNE_CONFIG_DIR, "memory");
@@ -65,11 +72,20 @@ export const REQUIRED_FIELDS: Record<string, Set<string>> = {
65
72
  skill_usage: new Set(["timestamp", "session_id", "skill_name"]),
66
73
  all_queries: new Set(["timestamp", "session_id", "query"]),
67
74
  evolution_audit: new Set(["timestamp", "proposal_id", "action"]),
75
+ evolution_evidence: new Set(["timestamp", "proposal_id", "skill_name", "stage"]),
68
76
  };
69
77
 
70
78
  /** Agent CLI candidates in detection order. */
71
79
  export const AGENT_CANDIDATES = ["claude", "codex", "opencode", "openclaw"] as const;
72
80
 
81
+ /** Required Claude Code hook keys in settings.json. */
82
+ export const CLAUDE_CODE_HOOK_KEYS = [
83
+ "UserPromptSubmit",
84
+ "PreToolUse",
85
+ "PostToolUse",
86
+ "Stop",
87
+ ] as const;
88
+
73
89
  /** Path for user-defined activation rule overrides. */
74
90
  export const ACTIVATION_RULES_PATH = join(SELFTUNE_CONFIG_DIR, "activation-rules.json");
75
91
 
@@ -83,6 +99,12 @@ export function sessionStatePath(sessionId: string): string {
83
99
  return join(SESSION_STATE_DIR, `session-state-${safe}.json`);
84
100
  }
85
101
 
102
+ /** Build a canonical prompt state file path from a session ID. */
103
+ export function canonicalSessionStatePath(sessionId: string): string {
104
+ const safe = sessionId.replace(/[^a-zA-Z0-9_-]/g, "_");
105
+ return join(SESSION_STATE_DIR, `canonical-session-state-${safe}.json`);
106
+ }
107
+
86
108
  /** Claude Code settings file path. */
87
109
  export const CLAUDE_SETTINGS_PATH = join(homedir(), ".claude", "settings.json");
88
110
 
@@ -92,6 +114,12 @@ export const CLAUDE_CODE_PROJECTS_DIR = join(homedir(), ".claude", "projects");
92
114
  /** Marker file tracking which Claude Code sessions have been ingested. */
93
115
  export const CLAUDE_CODE_MARKER = join(homedir(), ".claude", "claude_code_ingested_sessions.json");
94
116
 
117
+ /** Marker file tracking which Codex rollout files have been ingested. */
118
+ export const CODEX_INGEST_MARKER = join(homedir(), ".claude", "codex_ingested_rollouts.json");
119
+
120
+ /** Marker file tracking which OpenCode sessions have been ingested. */
121
+ export const OPENCODE_INGEST_MARKER = join(homedir(), ".claude", "opencode_ingested_sessions.json");
122
+
95
123
  /** OpenClaw agents directory containing session data. */
96
124
  export const OPENCLAW_AGENTS_DIR = join(homedir(), ".openclaw", "agents");
97
125
 
@@ -152,7 +152,7 @@ async function submitToService(
152
152
  // ---------------------------------------------------------------------------
153
153
 
154
154
  function submitToGitHub(json: string, outputPath: string): boolean {
155
- const repo = "WellDunDun/selftune";
155
+ const repo = "selftune-dev/selftune";
156
156
  const sizeKB = Buffer.byteLength(json, "utf-8") / 1024;
157
157
 
158
158
  let body: string;
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env bun
2
2
  /**
3
- * selftune cron — Manage OpenClaw cron jobs for selftune automation.
3
+ * selftune cron — OpenClaw cron integration for selftune automation.
4
4
  *
5
5
  * Subcommands:
6
6
  * setup Register default selftune cron jobs with OpenClaw
@@ -31,29 +31,25 @@ export interface CronJobConfig {
31
31
 
32
32
  export const DEFAULT_CRON_JOBS: CronJobConfig[] = [
33
33
  {
34
- name: "selftune-ingest",
34
+ name: "selftune-sync",
35
35
  cron: "*/30 * * * *",
36
- message: "Run selftune ingest-openclaw to capture any new sessions.",
37
- description: "Ingest new sessions every 30 minutes",
36
+ message:
37
+ "Run selftune sync to replay and ingest new Claude Code, Codex, OpenCode, and OpenClaw source data, then rebuild the repaired skill-usage overlay.",
38
+ description: "Sync source-truth telemetry every 30 minutes",
38
39
  },
39
40
  {
40
41
  name: "selftune-status",
41
42
  cron: "0 8 * * *",
42
- message: "Run selftune status --json and report any skills with pass rate below 80%.",
43
- description: "Daily health check at 8am",
44
- },
45
- {
46
- name: "selftune-evolve",
47
- cron: "0 3 * * 0",
48
43
  message:
49
- "Run the full selftune evolution pipeline: ingest new sessions, check status, evolve any undertriggering skills, and report results.",
50
- description: "Weekly evolution at 3am Sunday",
44
+ "Run selftune sync first, then run selftune status --json and report any skills with pass rate below 80% or still ungraded due to sparse recent checks.",
45
+ description: "Daily health check after source sync",
51
46
  },
52
47
  {
53
- name: "selftune-watch",
48
+ name: "selftune-orchestrate",
54
49
  cron: "0 */6 * * *",
55
- message: "Run selftune watch on all recently evolved skills to detect regressions.",
56
- description: "Monitor regressions every 6 hours",
50
+ message:
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",
57
53
  },
58
54
  ];
59
55
 
@@ -120,7 +116,7 @@ export function loadCronJobs(jobsPath: string): CronJobConfig[] {
120
116
  /** Register default cron jobs with OpenClaw. */
121
117
  export async function setupCronJobs(tz: string, dryRun: boolean): Promise<void> {
122
118
  const openclawPath = Bun.which("openclaw");
123
- if (!openclawPath) {
119
+ if (!dryRun && !openclawPath) {
124
120
  console.error("Error: openclaw is not installed or not in PATH.");
125
121
  console.error("");
126
122
  console.error("Install OpenClaw:");
@@ -246,7 +242,11 @@ export async function cliMain(): Promise<void> {
246
242
  await removeCronJobs(values["dry-run"] ?? false);
247
243
  break;
248
244
  default:
249
- console.log(`selftune cron — Manage OpenClaw cron jobs
245
+ console.log(`selftune cron — OpenClaw cron integration
246
+
247
+ Registers selftune automation jobs with OpenClaw's Gateway Scheduler.
248
+ This is an optional convenience for OpenClaw users. For generic scheduling
249
+ with system cron, launchd, or systemd, see: selftune schedule
250
250
 
251
251
  Usage:
252
252
  selftune cron setup [--dry-run] [--tz <timezone>]
@@ -0,0 +1,202 @@
1
+ export interface TelemetryRecord {
2
+ timestamp: string;
3
+ session_id: string;
4
+ skills_triggered: string[];
5
+ errors_encountered: number;
6
+ total_tool_calls: number;
7
+ }
8
+
9
+ export interface SkillUsageRecord {
10
+ timestamp: string;
11
+ session_id: string;
12
+ skill_name: string;
13
+ skill_path: string;
14
+ query: string;
15
+ triggered: boolean;
16
+ source: string | null;
17
+ }
18
+
19
+ export interface EvalSnapshot {
20
+ before_pass_rate?: number;
21
+ after_pass_rate?: number;
22
+ net_change?: number;
23
+ improved?: boolean;
24
+ regressions?: Array<Record<string, unknown>>;
25
+ new_passes?: Array<Record<string, unknown>>;
26
+ }
27
+
28
+ export interface EvolutionEntry {
29
+ timestamp: string;
30
+ proposal_id: string;
31
+ action: string;
32
+ details: string;
33
+ eval_snapshot?: EvalSnapshot | null;
34
+ }
35
+
36
+ export interface UnmatchedQuery {
37
+ timestamp: string;
38
+ session_id: string;
39
+ query: string;
40
+ }
41
+
42
+ export interface PendingProposal {
43
+ proposal_id: string;
44
+ action: string;
45
+ timestamp: string;
46
+ details: string;
47
+ skill_name?: string;
48
+ }
49
+
50
+ export interface SkillSummary {
51
+ skill_name: string;
52
+ skill_scope: string | null;
53
+ total_checks: number;
54
+ triggered_count: number;
55
+ pass_rate: number;
56
+ unique_sessions: number;
57
+ last_seen: string | null;
58
+ has_evidence: boolean;
59
+ }
60
+
61
+ export interface OverviewPayload {
62
+ telemetry: TelemetryRecord[];
63
+ skills: SkillUsageRecord[];
64
+ evolution: EvolutionEntry[];
65
+ counts: {
66
+ telemetry: number;
67
+ skills: number;
68
+ evolution: number;
69
+ evidence: number;
70
+ sessions: number;
71
+ prompts: number;
72
+ };
73
+ unmatched_queries: UnmatchedQuery[];
74
+ pending_proposals: PendingProposal[];
75
+ }
76
+
77
+ export interface OverviewResponse {
78
+ overview: OverviewPayload;
79
+ skills: SkillSummary[];
80
+ version?: string;
81
+ }
82
+
83
+ export interface EvidenceEntry {
84
+ proposal_id: string;
85
+ target: string;
86
+ stage: string;
87
+ timestamp: string;
88
+ rationale: string | null;
89
+ confidence: number | null;
90
+ original_text: string | null;
91
+ proposed_text: string | null;
92
+ validation: Record<string, unknown> | null;
93
+ details: string | null;
94
+ eval_set: Array<Record<string, unknown>>;
95
+ }
96
+
97
+ export interface CanonicalInvocation {
98
+ timestamp: string;
99
+ session_id: string;
100
+ skill_name: string;
101
+ invocation_mode: string | null;
102
+ triggered: boolean;
103
+ confidence: number | null;
104
+ tool_name: string | null;
105
+ }
106
+
107
+ export interface PromptSample {
108
+ prompt_text: string;
109
+ prompt_kind: string | null;
110
+ is_actionable: boolean;
111
+ occurred_at: string;
112
+ session_id: string;
113
+ }
114
+
115
+ export interface SessionMeta {
116
+ session_id: string;
117
+ platform: string | null;
118
+ model: string | null;
119
+ agent_cli: string | null;
120
+ branch: string | null;
121
+ workspace_path: string | null;
122
+ started_at: string | null;
123
+ ended_at: string | null;
124
+ completion_status: string | null;
125
+ }
126
+
127
+ export interface SkillReportPayload {
128
+ skill_name: string;
129
+ usage: {
130
+ total_checks: number;
131
+ triggered_count: number;
132
+ pass_rate: number;
133
+ };
134
+ recent_invocations: Array<{
135
+ timestamp: string;
136
+ session_id: string;
137
+ query: string;
138
+ triggered: boolean;
139
+ source: string | null;
140
+ }>;
141
+ evidence: EvidenceEntry[];
142
+ sessions_with_skill: number;
143
+ }
144
+
145
+ // -- Orchestrate run report types --------------------------------------------
146
+
147
+ export interface OrchestrateRunSkillAction {
148
+ skill: string;
149
+ action: "evolve" | "watch" | "skip";
150
+ reason: string;
151
+ deployed?: boolean;
152
+ rolledBack?: boolean;
153
+ alert?: string | null;
154
+ elapsed_ms?: number;
155
+ llm_calls?: number;
156
+ }
157
+
158
+ export interface OrchestrateRunReport {
159
+ run_id: string;
160
+ timestamp: string;
161
+ elapsed_ms: number;
162
+ dry_run: boolean;
163
+ approval_mode: "auto" | "review";
164
+ total_skills: number;
165
+ evaluated: number;
166
+ evolved: number;
167
+ deployed: number;
168
+ watched: number;
169
+ skipped: number;
170
+ skill_actions: OrchestrateRunSkillAction[];
171
+ }
172
+
173
+ export interface OrchestrateRunsResponse {
174
+ runs: OrchestrateRunReport[];
175
+ }
176
+
177
+ // -- Doctor / health check types ----------------------------------------------
178
+ export type { DoctorResult, HealthCheck, HealthStatus } from "./types.js";
179
+
180
+ export interface SkillReportResponse extends SkillReportPayload {
181
+ evolution: EvolutionEntry[];
182
+ pending_proposals: PendingProposal[];
183
+ token_usage: {
184
+ total_input_tokens: number;
185
+ total_output_tokens: number;
186
+ };
187
+ canonical_invocations: CanonicalInvocation[];
188
+ duration_stats: {
189
+ avg_duration_ms: number;
190
+ total_duration_ms: number;
191
+ execution_count: number;
192
+ total_errors: number;
193
+ };
194
+ selftune_stats: {
195
+ total_llm_calls: number;
196
+ total_elapsed_ms: number;
197
+ avg_elapsed_ms: number;
198
+ run_count: number;
199
+ };
200
+ prompt_samples: PromptSample[];
201
+ session_metadata: SessionMeta[];
202
+ }