selftune 0.1.2 → 0.2.0
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.
- package/.claude/agents/diagnosis-analyst.md +146 -0
- package/.claude/agents/evolution-reviewer.md +167 -0
- package/.claude/agents/integration-guide.md +200 -0
- package/.claude/agents/pattern-analyst.md +147 -0
- package/CHANGELOG.md +38 -1
- package/README.md +96 -256
- package/assets/BeforeAfter.gif +0 -0
- package/assets/FeedbackLoop.gif +0 -0
- package/assets/logo.svg +9 -0
- package/assets/skill-health-badge.svg +20 -0
- package/cli/selftune/activation-rules.ts +171 -0
- package/cli/selftune/badge/badge-data.ts +108 -0
- package/cli/selftune/badge/badge-svg.ts +212 -0
- package/cli/selftune/badge/badge.ts +103 -0
- package/cli/selftune/constants.ts +75 -1
- package/cli/selftune/contribute/bundle.ts +314 -0
- package/cli/selftune/contribute/contribute.ts +214 -0
- package/cli/selftune/contribute/sanitize.ts +162 -0
- package/cli/selftune/cron/setup.ts +266 -0
- package/cli/selftune/dashboard-server.ts +582 -0
- package/cli/selftune/dashboard.ts +31 -12
- package/cli/selftune/eval/baseline.ts +247 -0
- package/cli/selftune/eval/composability.ts +117 -0
- package/cli/selftune/eval/generate-unit-tests.ts +143 -0
- package/cli/selftune/eval/hooks-to-evals.ts +68 -2
- package/cli/selftune/eval/import-skillsbench.ts +221 -0
- package/cli/selftune/eval/synthetic-evals.ts +172 -0
- package/cli/selftune/eval/unit-test-cli.ts +152 -0
- package/cli/selftune/eval/unit-test.ts +196 -0
- package/cli/selftune/evolution/deploy-proposal.ts +142 -1
- package/cli/selftune/evolution/evolve-body.ts +492 -0
- package/cli/selftune/evolution/evolve.ts +479 -104
- package/cli/selftune/evolution/extract-patterns.ts +32 -1
- package/cli/selftune/evolution/pareto.ts +314 -0
- package/cli/selftune/evolution/propose-body.ts +171 -0
- package/cli/selftune/evolution/propose-description.ts +100 -2
- package/cli/selftune/evolution/propose-routing.ts +166 -0
- package/cli/selftune/evolution/refine-body.ts +141 -0
- package/cli/selftune/evolution/rollback.ts +20 -3
- package/cli/selftune/evolution/validate-body.ts +254 -0
- package/cli/selftune/evolution/validate-proposal.ts +257 -35
- package/cli/selftune/evolution/validate-routing.ts +177 -0
- package/cli/selftune/grading/grade-session.ts +145 -19
- package/cli/selftune/grading/pre-gates.ts +104 -0
- package/cli/selftune/hooks/auto-activate.ts +185 -0
- package/cli/selftune/hooks/evolution-guard.ts +165 -0
- package/cli/selftune/hooks/skill-change-guard.ts +112 -0
- package/cli/selftune/index.ts +88 -0
- package/cli/selftune/ingestors/claude-replay.ts +351 -0
- package/cli/selftune/ingestors/codex-rollout.ts +1 -1
- package/cli/selftune/ingestors/openclaw-ingest.ts +440 -0
- package/cli/selftune/ingestors/opencode-ingest.ts +2 -2
- package/cli/selftune/init.ts +168 -5
- package/cli/selftune/last.ts +2 -2
- package/cli/selftune/memory/writer.ts +447 -0
- package/cli/selftune/monitoring/watch.ts +25 -2
- package/cli/selftune/status.ts +18 -15
- package/cli/selftune/types.ts +377 -5
- package/cli/selftune/utils/frontmatter.ts +217 -0
- package/cli/selftune/utils/llm-call.ts +29 -3
- package/cli/selftune/utils/transcript.ts +35 -0
- package/cli/selftune/utils/trigger-check.ts +89 -0
- package/cli/selftune/utils/tui.ts +156 -0
- package/dashboard/index.html +585 -19
- package/package.json +17 -6
- package/skill/SKILL.md +127 -10
- package/skill/Workflows/AutoActivation.md +144 -0
- package/skill/Workflows/Badge.md +118 -0
- package/skill/Workflows/Baseline.md +121 -0
- package/skill/Workflows/Composability.md +100 -0
- package/skill/Workflows/Contribute.md +91 -0
- package/skill/Workflows/Cron.md +155 -0
- package/skill/Workflows/Dashboard.md +203 -0
- package/skill/Workflows/Doctor.md +37 -1
- package/skill/Workflows/Evals.md +73 -5
- package/skill/Workflows/EvolutionMemory.md +152 -0
- package/skill/Workflows/Evolve.md +111 -6
- package/skill/Workflows/EvolveBody.md +159 -0
- package/skill/Workflows/ImportSkillsBench.md +111 -0
- package/skill/Workflows/Ingest.md +129 -15
- package/skill/Workflows/Initialize.md +58 -3
- package/skill/Workflows/Replay.md +70 -0
- package/skill/Workflows/Rollback.md +20 -1
- package/skill/Workflows/UnitTest.md +138 -0
- package/skill/Workflows/Watch.md +22 -0
- package/skill/settings_snippet.json +23 -0
- package/templates/activation-rules-default.json +27 -0
- package/templates/multi-skill-settings.json +64 -0
- package/templates/single-skill-settings.json +58 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* selftune cron — Manage OpenClaw cron jobs for selftune automation.
|
|
4
|
+
*
|
|
5
|
+
* Subcommands:
|
|
6
|
+
* setup Register default selftune cron jobs with OpenClaw
|
|
7
|
+
* list Show registered selftune cron jobs
|
|
8
|
+
* remove Remove all selftune cron jobs
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* selftune cron setup [--dry-run] [--tz <timezone>]
|
|
12
|
+
* selftune cron list
|
|
13
|
+
* selftune cron remove [--dry-run]
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
17
|
+
import { homedir } from "node:os";
|
|
18
|
+
import { join } from "node:path";
|
|
19
|
+
import { parseArgs } from "node:util";
|
|
20
|
+
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// Types & constants
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
export interface CronJobConfig {
|
|
26
|
+
name: string;
|
|
27
|
+
cron: string;
|
|
28
|
+
message: string;
|
|
29
|
+
description: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const DEFAULT_CRON_JOBS: CronJobConfig[] = [
|
|
33
|
+
{
|
|
34
|
+
name: "selftune-ingest",
|
|
35
|
+
cron: "*/30 * * * *",
|
|
36
|
+
message: "Run selftune ingest-openclaw to capture any new sessions.",
|
|
37
|
+
description: "Ingest new sessions every 30 minutes",
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: "selftune-status",
|
|
41
|
+
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
|
+
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",
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: "selftune-watch",
|
|
54
|
+
cron: "0 */6 * * *",
|
|
55
|
+
message: "Run selftune watch on all recently evolved skills to detect regressions.",
|
|
56
|
+
description: "Monitor regressions every 6 hours",
|
|
57
|
+
},
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// Helpers (exported for testability)
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
/** Build the argument array for `openclaw cron add`. */
|
|
65
|
+
export function buildCronAddArgs(job: CronJobConfig, tz: string): string[] {
|
|
66
|
+
return [
|
|
67
|
+
"cron",
|
|
68
|
+
"add",
|
|
69
|
+
"--name",
|
|
70
|
+
job.name,
|
|
71
|
+
"--cron",
|
|
72
|
+
job.cron,
|
|
73
|
+
"--tz",
|
|
74
|
+
tz,
|
|
75
|
+
"--session",
|
|
76
|
+
"isolated",
|
|
77
|
+
"--message",
|
|
78
|
+
job.message,
|
|
79
|
+
];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Return the default path to OpenClaw's cron jobs file. */
|
|
83
|
+
export function getOpenClawJobsPath(): string {
|
|
84
|
+
return join(homedir(), ".openclaw", "cron", "jobs.json");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Type guard that validates all required CronJobConfig fields. */
|
|
88
|
+
function isCronJobConfig(value: unknown): value is CronJobConfig {
|
|
89
|
+
if (typeof value !== "object" || value === null) return false;
|
|
90
|
+
const obj = value as Record<string, unknown>;
|
|
91
|
+
return (
|
|
92
|
+
typeof obj.name === "string" &&
|
|
93
|
+
typeof obj.cron === "string" &&
|
|
94
|
+
typeof obj.message === "string" &&
|
|
95
|
+
typeof obj.description === "string"
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Load cron jobs from a JSON file, filtering for selftune entries. */
|
|
100
|
+
export function loadCronJobs(jobsPath: string): CronJobConfig[] {
|
|
101
|
+
if (!existsSync(jobsPath)) {
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
const raw = readFileSync(jobsPath, "utf-8");
|
|
106
|
+
const data = JSON.parse(raw);
|
|
107
|
+
if (!Array.isArray(data)) {
|
|
108
|
+
return [];
|
|
109
|
+
}
|
|
110
|
+
return data.filter((j: unknown) => isCronJobConfig(j) && j.name.startsWith("selftune-"));
|
|
111
|
+
} catch {
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
// Subcommands
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
|
|
120
|
+
/** Register default cron jobs with OpenClaw. */
|
|
121
|
+
export async function setupCronJobs(tz: string, dryRun: boolean): Promise<void> {
|
|
122
|
+
const openclawPath = Bun.which("openclaw");
|
|
123
|
+
if (!openclawPath) {
|
|
124
|
+
console.error("Error: openclaw is not installed or not in PATH.");
|
|
125
|
+
console.error("");
|
|
126
|
+
console.error("Install OpenClaw:");
|
|
127
|
+
console.error(" https://openclaw.dev/install");
|
|
128
|
+
console.error("");
|
|
129
|
+
console.error("Or ensure the openclaw binary is in your PATH.");
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
console.log(`Registering ${DEFAULT_CRON_JOBS.length} cron jobs (tz=${tz})...\n`);
|
|
134
|
+
|
|
135
|
+
for (const job of DEFAULT_CRON_JOBS) {
|
|
136
|
+
const args = buildCronAddArgs(job, tz);
|
|
137
|
+
|
|
138
|
+
if (dryRun) {
|
|
139
|
+
console.log(`[DRY RUN] openclaw ${args.join(" ")}`);
|
|
140
|
+
} else {
|
|
141
|
+
const proc = Bun.spawn(["openclaw", ...args], {
|
|
142
|
+
stdout: "inherit",
|
|
143
|
+
stderr: "inherit",
|
|
144
|
+
});
|
|
145
|
+
const exitCode = await proc.exited;
|
|
146
|
+
if (exitCode !== 0) {
|
|
147
|
+
console.error(
|
|
148
|
+
`Error: openclaw cron add failed for "${job.name}" with exit code ${exitCode}`,
|
|
149
|
+
);
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
152
|
+
console.log(` Registered: ${job.name} — ${job.description}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
console.log("\nDone.");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Show registered selftune cron jobs. */
|
|
160
|
+
export function listCronJobs(): void {
|
|
161
|
+
const jobsPath = getOpenClawJobsPath();
|
|
162
|
+
const jobs = loadCronJobs(jobsPath);
|
|
163
|
+
|
|
164
|
+
if (jobs.length === 0) {
|
|
165
|
+
if (!existsSync(jobsPath)) {
|
|
166
|
+
console.log("No cron jobs file found at:", jobsPath);
|
|
167
|
+
} else {
|
|
168
|
+
console.log("No selftune cron jobs registered.");
|
|
169
|
+
}
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Print as formatted table
|
|
174
|
+
const nameWidth = Math.max(20, ...jobs.map((j) => j.name.length));
|
|
175
|
+
const cronWidth = Math.max(16, ...jobs.map((j) => j.cron.length));
|
|
176
|
+
|
|
177
|
+
console.log(`${"NAME".padEnd(nameWidth)} ${"SCHEDULE".padEnd(cronWidth)} DESCRIPTION`);
|
|
178
|
+
console.log(`${"─".repeat(nameWidth)} ${"─".repeat(cronWidth)} ${"─".repeat(40)}`);
|
|
179
|
+
|
|
180
|
+
for (const job of jobs) {
|
|
181
|
+
console.log(`${job.name.padEnd(nameWidth)} ${job.cron.padEnd(cronWidth)} ${job.description}`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Remove all selftune cron jobs from OpenClaw. */
|
|
186
|
+
export async function removeCronJobs(dryRun: boolean): Promise<void> {
|
|
187
|
+
const jobsPath = getOpenClawJobsPath();
|
|
188
|
+
const jobs = loadCronJobs(jobsPath);
|
|
189
|
+
|
|
190
|
+
if (jobs.length === 0) {
|
|
191
|
+
console.log("No selftune cron jobs to remove.");
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
console.log(`Removing ${jobs.length} selftune cron jobs...\n`);
|
|
196
|
+
|
|
197
|
+
for (const job of jobs) {
|
|
198
|
+
if (dryRun) {
|
|
199
|
+
console.log(`[DRY RUN] openclaw cron remove --name ${job.name}`);
|
|
200
|
+
} else {
|
|
201
|
+
const proc = Bun.spawn(["openclaw", "cron", "remove", "--name", job.name], {
|
|
202
|
+
stdout: "inherit",
|
|
203
|
+
stderr: "inherit",
|
|
204
|
+
});
|
|
205
|
+
const exitCode = await proc.exited;
|
|
206
|
+
if (exitCode !== 0) {
|
|
207
|
+
console.error(
|
|
208
|
+
`Error: openclaw cron remove failed for "${job.name}" with exit code ${exitCode}`,
|
|
209
|
+
);
|
|
210
|
+
process.exit(1);
|
|
211
|
+
}
|
|
212
|
+
console.log(` Removed: ${job.name}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
console.log("\nDone.");
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// ---------------------------------------------------------------------------
|
|
220
|
+
// CLI entry point
|
|
221
|
+
// ---------------------------------------------------------------------------
|
|
222
|
+
|
|
223
|
+
export async function cliMain(): Promise<void> {
|
|
224
|
+
const subcommand = process.argv[2];
|
|
225
|
+
|
|
226
|
+
const { values } = parseArgs({
|
|
227
|
+
options: {
|
|
228
|
+
"dry-run": { type: "boolean", default: false },
|
|
229
|
+
tz: { type: "string" },
|
|
230
|
+
},
|
|
231
|
+
strict: false,
|
|
232
|
+
allowPositionals: true,
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// Get timezone: flag > env > system default
|
|
236
|
+
const tz = values.tz ?? process.env.TZ ?? Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
237
|
+
|
|
238
|
+
switch (subcommand) {
|
|
239
|
+
case "setup":
|
|
240
|
+
await setupCronJobs(tz, values["dry-run"] ?? false);
|
|
241
|
+
break;
|
|
242
|
+
case "list":
|
|
243
|
+
listCronJobs();
|
|
244
|
+
break;
|
|
245
|
+
case "remove":
|
|
246
|
+
await removeCronJobs(values["dry-run"] ?? false);
|
|
247
|
+
break;
|
|
248
|
+
default:
|
|
249
|
+
console.log(`selftune cron — Manage OpenClaw cron jobs
|
|
250
|
+
|
|
251
|
+
Usage:
|
|
252
|
+
selftune cron setup [--dry-run] [--tz <timezone>]
|
|
253
|
+
selftune cron list
|
|
254
|
+
selftune cron remove [--dry-run]
|
|
255
|
+
|
|
256
|
+
Subcommands:
|
|
257
|
+
setup Register default selftune cron jobs with OpenClaw
|
|
258
|
+
list Show registered selftune cron jobs
|
|
259
|
+
remove Remove all selftune cron jobs`);
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (import.meta.main) {
|
|
265
|
+
await cliMain();
|
|
266
|
+
}
|