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.
- package/README.md +35 -35
- package/apps/local-dashboard/dist/assets/index-4_dAY17K.js +16 -0
- package/apps/local-dashboard/dist/assets/index-BxV5WZHc.css +2 -0
- package/apps/local-dashboard/dist/assets/rolldown-runtime-Dw2cE7zH.js +1 -0
- package/apps/local-dashboard/dist/assets/vendor-react-CKkiCskZ.js +11 -0
- package/apps/local-dashboard/dist/assets/vendor-table-pHbDxq36.js +8 -0
- package/apps/local-dashboard/dist/assets/vendor-ui-7xD7fNEU.js +12 -0
- package/apps/local-dashboard/dist/index.html +16 -15
- package/bin/selftune.cjs +1 -1
- package/cli/selftune/activation-rules.ts +1 -0
- package/cli/selftune/alpha-upload/build-payloads.ts +18 -2
- package/cli/selftune/alpha-upload/stage-canonical.ts +94 -0
- package/cli/selftune/auth/device-code.ts +32 -0
- package/cli/selftune/auto-update.ts +12 -0
- package/cli/selftune/badge/badge.ts +1 -0
- package/cli/selftune/canonical-export.ts +5 -0
- package/cli/selftune/claude-agents.ts +154 -0
- package/cli/selftune/contribute/bundle.ts +1 -0
- package/cli/selftune/contribute/contribute.ts +1 -0
- package/cli/selftune/cron/setup.ts +2 -2
- package/cli/selftune/dashboard-server.ts +1 -0
- package/cli/selftune/eval/hooks-to-evals.ts +1 -0
- package/cli/selftune/eval/import-skillsbench.ts +1 -0
- package/cli/selftune/eval/synthetic-evals.ts +2 -3
- package/cli/selftune/eval/unit-test.ts +1 -0
- package/cli/selftune/evolution/deploy-proposal.ts +9 -238
- package/cli/selftune/evolution/evolve-body.ts +93 -6
- package/cli/selftune/evolution/evolve.ts +3 -7
- package/cli/selftune/evolution/propose-body.ts +3 -2
- package/cli/selftune/evolution/propose-routing.ts +3 -2
- package/cli/selftune/evolution/refine-body.ts +3 -2
- package/cli/selftune/evolution/rollback.ts +1 -1
- package/cli/selftune/export.ts +1 -0
- package/cli/selftune/grading/grade-session.ts +8 -0
- package/cli/selftune/hooks/auto-activate.ts +1 -0
- package/cli/selftune/hooks/evolution-guard.ts +1 -1
- package/cli/selftune/hooks/prompt-log.ts +1 -0
- package/cli/selftune/hooks/session-stop.ts +34 -40
- package/cli/selftune/hooks/skill-change-guard.ts +1 -0
- package/cli/selftune/hooks/skill-eval.ts +1 -1
- package/cli/selftune/index.ts +23 -14
- package/cli/selftune/ingestors/claude-replay.ts +1 -0
- package/cli/selftune/ingestors/codex-rollout.ts +1 -0
- package/cli/selftune/ingestors/codex-wrapper.ts +1 -0
- package/cli/selftune/ingestors/openclaw-ingest.ts +1 -0
- package/cli/selftune/ingestors/opencode-ingest.ts +1 -0
- package/cli/selftune/init.ts +121 -29
- package/cli/selftune/localdb/db.ts +1 -0
- package/cli/selftune/localdb/direct-write.ts +39 -0
- package/cli/selftune/localdb/materialize.ts +2 -0
- package/cli/selftune/localdb/queries.ts +53 -0
- package/cli/selftune/localdb/schema.ts +28 -0
- package/cli/selftune/normalization.ts +1 -0
- package/cli/selftune/observability.ts +1 -0
- package/cli/selftune/repair/skill-usage.ts +1 -0
- package/cli/selftune/routes/orchestrate-runs.ts +1 -0
- package/cli/selftune/routes/overview.ts +1 -0
- package/cli/selftune/routes/report.ts +1 -1
- package/cli/selftune/routes/skill-report.ts +2 -1
- package/cli/selftune/status.ts +1 -1
- package/cli/selftune/sync.ts +30 -1
- package/cli/selftune/uninstall.ts +412 -0
- package/cli/selftune/utils/canonical-log.ts +2 -0
- package/cli/selftune/utils/frontmatter.ts +50 -7
- package/cli/selftune/utils/jsonl.ts +1 -0
- package/cli/selftune/utils/llm-call.ts +131 -3
- package/cli/selftune/utils/skill-log.ts +1 -0
- package/cli/selftune/utils/transcript.ts +1 -0
- package/cli/selftune/utils/trigger-check.ts +1 -1
- package/cli/selftune/workflows/skill-md-writer.ts +5 -5
- package/cli/selftune/workflows/workflows.ts +1 -0
- package/package.json +37 -33
- package/packages/telemetry-contract/fixtures/golden.test.ts +1 -0
- package/packages/telemetry-contract/package.json +1 -1
- package/packages/telemetry-contract/src/schemas.ts +1 -0
- package/packages/telemetry-contract/tests/compatibility.test.ts +1 -0
- package/packages/ui/README.md +35 -34
- package/packages/ui/package.json +3 -3
- package/packages/ui/src/components/ActivityTimeline.tsx +50 -43
- package/packages/ui/src/components/EvidenceViewer.tsx +306 -182
- package/packages/ui/src/components/EvolutionTimeline.tsx +83 -72
- package/packages/ui/src/components/InfoTip.tsx +4 -3
- package/packages/ui/src/components/OrchestrateRunsPanel.tsx +60 -53
- package/packages/ui/src/components/section-cards.tsx +20 -25
- package/packages/ui/src/components/skill-health-grid.tsx +213 -193
- package/packages/ui/src/lib/constants.tsx +1 -0
- package/packages/ui/src/primitives/badge.tsx +12 -15
- package/packages/ui/src/primitives/button.tsx +7 -7
- package/packages/ui/src/primitives/card.tsx +15 -26
- package/packages/ui/src/primitives/checkbox.tsx +7 -8
- package/packages/ui/src/primitives/collapsible.tsx +5 -5
- package/packages/ui/src/primitives/dropdown-menu.tsx +45 -55
- package/packages/ui/src/primitives/label.tsx +6 -6
- package/packages/ui/src/primitives/select.tsx +28 -37
- package/packages/ui/src/primitives/table.tsx +17 -44
- package/packages/ui/src/primitives/tabs.tsx +14 -21
- package/packages/ui/src/primitives/tooltip.tsx +10 -22
- package/skill/SKILL.md +70 -57
- package/skill/Workflows/AlphaUpload.md +4 -4
- package/skill/Workflows/AutoActivation.md +11 -6
- package/skill/Workflows/Badge.md +22 -16
- package/skill/Workflows/Baseline.md +34 -36
- package/skill/Workflows/Composability.md +16 -11
- package/skill/Workflows/Contribute.md +26 -21
- package/skill/Workflows/Cron.md +23 -22
- package/skill/Workflows/Dashboard.md +32 -27
- package/skill/Workflows/Doctor.md +33 -27
- package/skill/Workflows/Evals.md +48 -47
- package/skill/Workflows/EvolutionMemory.md +31 -21
- package/skill/Workflows/Evolve.md +84 -82
- package/skill/Workflows/EvolveBody.md +58 -47
- package/skill/Workflows/Grade.md +16 -13
- package/skill/Workflows/ImportSkillsBench.md +9 -6
- package/skill/Workflows/Ingest.md +36 -21
- package/skill/Workflows/Initialize.md +108 -40
- package/skill/Workflows/Orchestrate.md +22 -16
- package/skill/Workflows/Replay.md +12 -7
- package/skill/Workflows/Rollback.md +13 -6
- package/skill/Workflows/Schedule.md +6 -6
- package/skill/Workflows/Sync.md +18 -11
- package/skill/Workflows/UnitTest.md +28 -17
- package/skill/Workflows/Watch.md +28 -21
- package/skill/agents/diagnosis-analyst.md +11 -0
- package/skill/agents/evolution-reviewer.md +15 -1
- package/skill/agents/integration-guide.md +10 -0
- package/skill/agents/pattern-analyst.md +12 -1
- package/skill/references/grading-methodology.md +23 -24
- package/skill/references/interactive-config.md +7 -7
- package/skill/references/invocation-taxonomy.md +22 -20
- package/skill/references/logs.md +14 -6
- package/skill/references/setup-patterns.md +4 -2
- package/.claude/agents/diagnosis-analyst.md +0 -156
- package/.claude/agents/evolution-reviewer.md +0 -180
- package/.claude/agents/integration-guide.md +0 -212
- package/.claude/agents/pattern-analyst.md +0 -160
- package/apps/local-dashboard/dist/assets/index-Bs3Y4ixf.css +0 -1
- package/apps/local-dashboard/dist/assets/index-C4UYGWKr.js +0 -15
- package/apps/local-dashboard/dist/assets/vendor-react-BQH_6WrG.js +0 -60
- package/apps/local-dashboard/dist/assets/vendor-table-dK1QMLq9.js +0 -26
- package/apps/local-dashboard/dist/assets/vendor-ui-CO2mrx6e.js +0 -341
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Line-based YAML frontmatter parser for SKILL.md files.
|
|
5
5
|
* Extracts name, description, and version without a YAML library.
|
|
6
|
+
* Also provides `replaceDescription` — the single public API for replacing
|
|
7
|
+
* a skill's description in SKILL.md (handles both frontmatter and plain markdown).
|
|
6
8
|
*/
|
|
7
9
|
|
|
8
10
|
// ---------------------------------------------------------------------------
|
|
@@ -137,16 +139,57 @@ export function parseFrontmatter(content: string): SkillFrontmatter {
|
|
|
137
139
|
// ---------------------------------------------------------------------------
|
|
138
140
|
|
|
139
141
|
/**
|
|
140
|
-
* Replace the
|
|
141
|
-
*
|
|
142
|
-
*
|
|
142
|
+
* Replace the description between the first `#` heading and the first `##`
|
|
143
|
+
* heading. If no `##` heading exists, the entire body after the heading is
|
|
144
|
+
* replaced. Used as fallback when no YAML frontmatter is present.
|
|
145
|
+
*/
|
|
146
|
+
function replaceMarkdownDescription(currentContent: string, newDescription: string): string {
|
|
147
|
+
const lines = currentContent.split("\n");
|
|
148
|
+
|
|
149
|
+
let headingIndex = -1;
|
|
150
|
+
for (let i = 0; i < lines.length; i++) {
|
|
151
|
+
if (lines[i].startsWith("# ") && !lines[i].startsWith("## ")) {
|
|
152
|
+
headingIndex = i;
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (headingIndex === -1) {
|
|
158
|
+
return `${newDescription}\n${currentContent}`;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
let subHeadingIndex = -1;
|
|
162
|
+
for (let i = headingIndex + 1; i < lines.length; i++) {
|
|
163
|
+
if (lines[i].startsWith("## ")) {
|
|
164
|
+
subHeadingIndex = i;
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const preamble = headingIndex > 0 ? `${lines.slice(0, headingIndex).join("\n")}\n` : "";
|
|
170
|
+
const headingLine = lines[headingIndex];
|
|
171
|
+
const descriptionBlock = newDescription.length > 0 ? `\n${newDescription}\n` : "\n";
|
|
172
|
+
|
|
173
|
+
if (subHeadingIndex === -1) {
|
|
174
|
+
return `${preamble}${headingLine}\n${descriptionBlock}\n`;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const afterSubHeading = lines.slice(subHeadingIndex).join("\n");
|
|
178
|
+
return `${preamble}${headingLine}\n${descriptionBlock}\n${afterSubHeading}`;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Replace a skill's description in SKILL.md.
|
|
143
183
|
*
|
|
144
|
-
*
|
|
184
|
+
* If the file has YAML frontmatter, replaces the `description:` field
|
|
185
|
+
* (using folded scalar for long/special-char descriptions).
|
|
186
|
+
* Otherwise, falls back to markdown heading-based replacement.
|
|
145
187
|
*/
|
|
146
|
-
export function
|
|
188
|
+
export function replaceDescription(content: string, newDescription: string): string {
|
|
147
189
|
const lines = content.split("\n");
|
|
148
190
|
|
|
149
|
-
|
|
191
|
+
// No frontmatter — fall back to markdown heading-based replacement
|
|
192
|
+
if (lines[0]?.trim() !== "---") return replaceMarkdownDescription(content, newDescription);
|
|
150
193
|
|
|
151
194
|
let endIdx = -1;
|
|
152
195
|
for (let i = 1; i < lines.length; i++) {
|
|
@@ -155,7 +198,7 @@ export function replaceFrontmatterDescription(content: string, newDescription: s
|
|
|
155
198
|
break;
|
|
156
199
|
}
|
|
157
200
|
}
|
|
158
|
-
if (endIdx < 0) return content;
|
|
201
|
+
if (endIdx < 0) return replaceMarkdownDescription(content, newDescription);
|
|
159
202
|
|
|
160
203
|
// Find and replace the description within frontmatter lines
|
|
161
204
|
const yamlLines = lines.slice(1, endIdx);
|
|
@@ -123,6 +123,9 @@ function sleep(ms: number): Promise<void> {
|
|
|
123
123
|
// Call LLM via agent subprocess
|
|
124
124
|
// ---------------------------------------------------------------------------
|
|
125
125
|
|
|
126
|
+
/** Effort level for Claude CLI (controls thinking depth). Opus 4.6 only for 'max'. */
|
|
127
|
+
export type EffortLevel = "low" | "medium" | "high" | "max";
|
|
128
|
+
|
|
126
129
|
/** Call LLM via agent subprocess (claude/codex/opencode). Returns raw text. */
|
|
127
130
|
export async function callViaAgent(
|
|
128
131
|
systemPrompt: string,
|
|
@@ -130,6 +133,7 @@ export async function callViaAgent(
|
|
|
130
133
|
agent: string,
|
|
131
134
|
modelFlag?: string,
|
|
132
135
|
retryOpts?: RetryOptions,
|
|
136
|
+
effort?: EffortLevel,
|
|
133
137
|
): Promise<string> {
|
|
134
138
|
// Write prompt to temp file to avoid shell quoting issues
|
|
135
139
|
const promptFile = join(tmpdir(), `selftune-llm-${Date.now()}.txt`);
|
|
@@ -145,6 +149,9 @@ export async function callViaAgent(
|
|
|
145
149
|
const resolved = resolveModelFlag(modelFlag);
|
|
146
150
|
cmd.push("--model", resolved);
|
|
147
151
|
}
|
|
152
|
+
if (effort) {
|
|
153
|
+
cmd.push("--effort", effort);
|
|
154
|
+
}
|
|
148
155
|
} else if (agent === "codex") {
|
|
149
156
|
cmd = ["codex", "exec", "--skip-git-repo-check", promptContent];
|
|
150
157
|
} else if (agent === "opencode") {
|
|
@@ -173,9 +180,10 @@ export async function callViaAgent(
|
|
|
173
180
|
env: { ...process.env, CLAUDECODE: "" },
|
|
174
181
|
});
|
|
175
182
|
|
|
176
|
-
// Longer timeout for heavier models
|
|
183
|
+
// Longer timeout for heavier models and thinking effort levels
|
|
177
184
|
const isLightModel = modelFlag === "haiku" || modelFlag?.includes("haiku");
|
|
178
|
-
const
|
|
185
|
+
const isThinking = effort === "high" || effort === "max";
|
|
186
|
+
const timeoutMs = isThinking ? 600_000 : isLightModel ? 120_000 : 300_000;
|
|
179
187
|
const timeout = setTimeout(() => proc.kill(), timeoutMs);
|
|
180
188
|
const exitCode = await proc.exited;
|
|
181
189
|
clearTimeout(timeout);
|
|
@@ -210,6 +218,125 @@ export async function callViaAgent(
|
|
|
210
218
|
}
|
|
211
219
|
}
|
|
212
220
|
|
|
221
|
+
// ---------------------------------------------------------------------------
|
|
222
|
+
// Call LLM via named subagent (multi-turn, agentic)
|
|
223
|
+
// ---------------------------------------------------------------------------
|
|
224
|
+
|
|
225
|
+
/** Options for calling a named Claude Code subagent. */
|
|
226
|
+
export interface SubagentCallOptions {
|
|
227
|
+
/** Name of the subagent (synced into ~/.claude/agents/ by selftune init/update). */
|
|
228
|
+
agentName: string;
|
|
229
|
+
/** The task prompt for the subagent. */
|
|
230
|
+
prompt: string;
|
|
231
|
+
/** Optional system prompt appended to the agent's built-in instructions. */
|
|
232
|
+
appendSystemPrompt?: string;
|
|
233
|
+
/** Maximum agentic turns (default: 8). */
|
|
234
|
+
maxTurns?: number;
|
|
235
|
+
/** Model override (overrides the agent's frontmatter model). */
|
|
236
|
+
modelFlag?: string;
|
|
237
|
+
/** Effort level for thinking depth. */
|
|
238
|
+
effort?: EffortLevel;
|
|
239
|
+
/** Retry options. */
|
|
240
|
+
retryOpts?: RetryOptions;
|
|
241
|
+
/** Tools the agent is allowed to use without prompting. */
|
|
242
|
+
allowedTools?: string[];
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Call a named Claude Code subagent in print mode. The subagent runs its
|
|
247
|
+
* multi-turn workflow (reading files, running commands, etc.) and returns
|
|
248
|
+
* the final text output.
|
|
249
|
+
*
|
|
250
|
+
* Unlike callViaAgent(), this does NOT use --bare (agents need discovery)
|
|
251
|
+
* and passes --agent + --max-turns for agentic multi-turn behavior.
|
|
252
|
+
* Only supports the claude CLI.
|
|
253
|
+
*/
|
|
254
|
+
export async function callViaSubagent(options: SubagentCallOptions): Promise<string> {
|
|
255
|
+
const {
|
|
256
|
+
agentName,
|
|
257
|
+
prompt,
|
|
258
|
+
appendSystemPrompt,
|
|
259
|
+
maxTurns = 8,
|
|
260
|
+
modelFlag,
|
|
261
|
+
effort,
|
|
262
|
+
retryOpts,
|
|
263
|
+
allowedTools,
|
|
264
|
+
} = options;
|
|
265
|
+
|
|
266
|
+
const cmd: string[] = [
|
|
267
|
+
"claude",
|
|
268
|
+
"-p",
|
|
269
|
+
prompt,
|
|
270
|
+
"--agent",
|
|
271
|
+
agentName,
|
|
272
|
+
"--max-turns",
|
|
273
|
+
String(maxTurns),
|
|
274
|
+
];
|
|
275
|
+
|
|
276
|
+
if (appendSystemPrompt) {
|
|
277
|
+
cmd.push("--append-system-prompt", appendSystemPrompt);
|
|
278
|
+
}
|
|
279
|
+
if (modelFlag) {
|
|
280
|
+
const resolved = resolveModelFlag(modelFlag);
|
|
281
|
+
cmd.push("--model", resolved);
|
|
282
|
+
}
|
|
283
|
+
if (effort) {
|
|
284
|
+
cmd.push("--effort", effort);
|
|
285
|
+
}
|
|
286
|
+
if (allowedTools && allowedTools.length > 0) {
|
|
287
|
+
cmd.push("--allowedTools", ...allowedTools);
|
|
288
|
+
}
|
|
289
|
+
// Skip permissions since this runs non-interactively in a pipeline
|
|
290
|
+
cmd.push("--dangerously-skip-permissions");
|
|
291
|
+
|
|
292
|
+
const maxRetries = retryOpts?.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
293
|
+
const initialBackoffMs = retryOpts?.initialBackoffMs ?? DEFAULT_INITIAL_BACKOFF_MS;
|
|
294
|
+
let lastError: Error | undefined;
|
|
295
|
+
|
|
296
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
297
|
+
if (attempt > 0) {
|
|
298
|
+
const backoffMs = initialBackoffMs * 2 ** (attempt - 1);
|
|
299
|
+
logger.warn(
|
|
300
|
+
`Retry ${attempt}/${maxRetries} for subagent '${agentName}' after ${backoffMs}ms backoff`,
|
|
301
|
+
);
|
|
302
|
+
await sleep(backoffMs);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
try {
|
|
306
|
+
const proc = Bun.spawn(cmd, {
|
|
307
|
+
stdout: "pipe",
|
|
308
|
+
stderr: "pipe",
|
|
309
|
+
env: { ...process.env, CLAUDECODE: "" },
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
// Subagents get a generous timeout — they do multi-turn work
|
|
313
|
+
const isThinking = effort === "high" || effort === "max";
|
|
314
|
+
const timeoutMs = isThinking ? 600_000 : 300_000;
|
|
315
|
+
const timeout = setTimeout(() => proc.kill(), timeoutMs);
|
|
316
|
+
const exitCode = await proc.exited;
|
|
317
|
+
clearTimeout(timeout);
|
|
318
|
+
|
|
319
|
+
if (exitCode !== 0) {
|
|
320
|
+
const stderr = await new Response(proc.stderr).text();
|
|
321
|
+
throw new Error(
|
|
322
|
+
`Subagent '${agentName}' exited with code ${exitCode}.\nstderr: ${stderr.slice(0, 500)}`,
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const raw = await new Response(proc.stdout).text();
|
|
327
|
+
return raw;
|
|
328
|
+
} catch (err) {
|
|
329
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
330
|
+
if (!isTransientError(lastError) || attempt === maxRetries) {
|
|
331
|
+
throw lastError;
|
|
332
|
+
}
|
|
333
|
+
logger.warn(`Transient failure on attempt ${attempt + 1}: ${lastError.message}`);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
throw lastError ?? new Error("callViaSubagent: unexpected retry loop exit");
|
|
338
|
+
}
|
|
339
|
+
|
|
213
340
|
// ---------------------------------------------------------------------------
|
|
214
341
|
// Unified dispatcher
|
|
215
342
|
// ---------------------------------------------------------------------------
|
|
@@ -220,9 +347,10 @@ export async function callLlm(
|
|
|
220
347
|
userPrompt: string,
|
|
221
348
|
agent: string,
|
|
222
349
|
modelFlag?: string,
|
|
350
|
+
effort?: EffortLevel,
|
|
223
351
|
): Promise<string> {
|
|
224
352
|
if (!agent) {
|
|
225
353
|
throw new Error("Agent must be specified for callLlm");
|
|
226
354
|
}
|
|
227
|
-
return callViaAgent(systemPrompt, userPrompt, agent, modelFlag);
|
|
355
|
+
return callViaAgent(systemPrompt, userPrompt, agent, modelFlag, undefined, effort);
|
|
228
356
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
|
+
|
|
3
4
|
import { REPAIRED_SKILL_LOG, REPAIRED_SKILL_SESSIONS_MARKER, SKILL_LOG } from "../constants.js";
|
|
4
5
|
import type { SkillUsageRecord } from "../types.js";
|
|
5
6
|
import { loadMarker, readJsonl, saveMarker } from "./jsonl.js";
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
6
6
|
import { basename, dirname } from "node:path";
|
|
7
|
+
|
|
7
8
|
import { CLAUDE_CODE_PROJECTS_DIR } from "../constants.js";
|
|
8
9
|
import type { SessionTelemetryRecord, TranscriptMetrics } from "../types.js";
|
|
9
10
|
import { isActionableQueryText } from "./query-filter.js";
|
|
@@ -64,7 +64,7 @@ export function buildBatchTriggerCheckPrompt(description: string, queries: strin
|
|
|
64
64
|
* original query order. Defaults to false for unparseable or missing lines.
|
|
65
65
|
*/
|
|
66
66
|
export function parseBatchTriggerResponse(response: string, queryCount: number): boolean[] {
|
|
67
|
-
const results: boolean[] =
|
|
67
|
+
const results: boolean[] = Array.from({ length: queryCount }, () => false);
|
|
68
68
|
const lines = response.trim().split("\n");
|
|
69
69
|
|
|
70
70
|
for (const line of lines) {
|
|
@@ -36,7 +36,7 @@ export function parseWorkflowsSection(content: string): CodifiedWorkflow[] {
|
|
|
36
36
|
// Find the end of the section (next ## heading or EOF)
|
|
37
37
|
let sectionEnd = lines.length;
|
|
38
38
|
for (let i = sectionStart; i < lines.length; i++) {
|
|
39
|
-
if (
|
|
39
|
+
if (lines[i].startsWith("## ") && lines[i].trim() !== "## Workflows") {
|
|
40
40
|
sectionEnd = i;
|
|
41
41
|
break;
|
|
42
42
|
}
|
|
@@ -155,7 +155,7 @@ export function appendWorkflow(content: string, workflow: CodifiedWorkflow): str
|
|
|
155
155
|
// Find the end of the workflows section (next ## heading or EOF)
|
|
156
156
|
let sectionEnd = lines.length;
|
|
157
157
|
for (let i = sectionStart + 1; i < lines.length; i++) {
|
|
158
|
-
if (
|
|
158
|
+
if (lines[i].startsWith("## ")) {
|
|
159
159
|
sectionEnd = i;
|
|
160
160
|
break;
|
|
161
161
|
}
|
|
@@ -210,7 +210,7 @@ export function removeWorkflow(content: string, name: string): string {
|
|
|
210
210
|
// Find the end of the workflows section
|
|
211
211
|
let sectionEnd = lines.length;
|
|
212
212
|
for (let i = sectionStart + 1; i < lines.length; i++) {
|
|
213
|
-
if (
|
|
213
|
+
if (lines[i].startsWith("## ")) {
|
|
214
214
|
sectionEnd = i;
|
|
215
215
|
break;
|
|
216
216
|
}
|
|
@@ -226,7 +226,7 @@ export function removeWorkflow(content: string, name: string): string {
|
|
|
226
226
|
// Find the end of this subsection (next ### or ## or sectionEnd)
|
|
227
227
|
subEnd = sectionEnd;
|
|
228
228
|
for (let j = i + 1; j < sectionEnd; j++) {
|
|
229
|
-
if (
|
|
229
|
+
if (lines[j].startsWith("### ")) {
|
|
230
230
|
subEnd = j;
|
|
231
231
|
break;
|
|
232
232
|
}
|
|
@@ -255,7 +255,7 @@ export function removeWorkflow(content: string, name: string): string {
|
|
|
255
255
|
|
|
256
256
|
// Check if the workflows section is now empty
|
|
257
257
|
const remaining = [...before.slice(sectionStart + 1), ...after.slice(0, sectionEnd - removeTo)];
|
|
258
|
-
const hasRemainingWorkflows = remaining.some((l) =>
|
|
258
|
+
const hasRemainingWorkflows = remaining.some((l) => l.startsWith("### "));
|
|
259
259
|
|
|
260
260
|
if (!hasRemainingWorkflows) {
|
|
261
261
|
// Remove the entire ## Workflows section (heading + any blank lines)
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
12
12
|
import { parseArgs } from "node:util";
|
|
13
|
+
|
|
13
14
|
import { getDb } from "../localdb/db.js";
|
|
14
15
|
import { querySessionTelemetry, querySkillUsageRecords } from "../localdb/queries.js";
|
|
15
16
|
import type {
|
package/package.json
CHANGED
|
@@ -1,41 +1,44 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "selftune",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.12",
|
|
4
4
|
"description": "Self-improving skills CLI for AI agents",
|
|
5
|
-
"
|
|
5
|
+
"keywords": [
|
|
6
|
+
"agent",
|
|
7
|
+
"bun",
|
|
8
|
+
"claude-code",
|
|
9
|
+
"cli",
|
|
10
|
+
"codex",
|
|
11
|
+
"eval",
|
|
12
|
+
"evolution",
|
|
13
|
+
"grading",
|
|
14
|
+
"opencode",
|
|
15
|
+
"self-improving",
|
|
16
|
+
"selftune",
|
|
17
|
+
"skill",
|
|
18
|
+
"telemetry",
|
|
19
|
+
"typescript"
|
|
20
|
+
],
|
|
21
|
+
"homepage": "https://github.com/selftune-dev/selftune#readme",
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/selftune-dev/selftune/issues"
|
|
24
|
+
},
|
|
6
25
|
"license": "MIT",
|
|
7
26
|
"author": "Daniel Petro",
|
|
8
|
-
"homepage": "https://github.com/selftune-dev/selftune#readme",
|
|
9
27
|
"repository": {
|
|
10
28
|
"type": "git",
|
|
11
29
|
"url": "git+https://github.com/selftune-dev/selftune.git"
|
|
12
30
|
},
|
|
13
|
-
"bugs": {
|
|
14
|
-
"url": "https://github.com/selftune-dev/selftune/issues"
|
|
15
|
-
},
|
|
16
31
|
"funding": {
|
|
17
32
|
"type": "github",
|
|
18
33
|
"url": "https://github.com/sponsors/WellDunDun"
|
|
19
34
|
},
|
|
20
|
-
"keywords": [
|
|
21
|
-
"selftune",
|
|
22
|
-
"skill",
|
|
23
|
-
"self-improving",
|
|
24
|
-
"claude-code",
|
|
25
|
-
"codex",
|
|
26
|
-
"opencode",
|
|
27
|
-
"eval",
|
|
28
|
-
"grading",
|
|
29
|
-
"evolution",
|
|
30
|
-
"cli",
|
|
31
|
-
"agent",
|
|
32
|
-
"telemetry",
|
|
33
|
-
"bun",
|
|
34
|
-
"typescript"
|
|
35
|
-
],
|
|
36
35
|
"bin": {
|
|
37
36
|
"selftune": "bin/selftune.cjs"
|
|
38
37
|
},
|
|
38
|
+
"workspaces": [
|
|
39
|
+
"packages/*",
|
|
40
|
+
"apps/*"
|
|
41
|
+
],
|
|
39
42
|
"files": [
|
|
40
43
|
"assets/",
|
|
41
44
|
"bin/",
|
|
@@ -44,39 +47,40 @@
|
|
|
44
47
|
"packages/telemetry-contract/",
|
|
45
48
|
"packages/ui/",
|
|
46
49
|
"templates/",
|
|
47
|
-
".claude/agents/",
|
|
48
50
|
"skill/",
|
|
49
51
|
"README.md",
|
|
50
52
|
"CHANGELOG.md"
|
|
51
53
|
],
|
|
54
|
+
"type": "module",
|
|
52
55
|
"scripts": {
|
|
53
|
-
"dev": "
|
|
56
|
+
"dev": "trap 'kill 0' EXIT; bun --watch run cli/selftune/dashboard-server.ts --port 7888 --runtime-mode dev-server & sleep 1 && cd apps/local-dashboard && bunx vite --strictPort",
|
|
54
57
|
"dev:server": "bun --watch run cli/selftune/dashboard-server.ts --port 7888 --runtime-mode dev-server",
|
|
55
58
|
"dev:dashboard": "bun run cli/selftune/index.ts dashboard --port 7888 --no-open",
|
|
56
|
-
"lint": "bunx
|
|
57
|
-
"lint:fix": "bunx
|
|
59
|
+
"lint": "bunx oxlint",
|
|
60
|
+
"lint:fix": "bunx oxlint --fix",
|
|
61
|
+
"format": "bunx oxfmt",
|
|
62
|
+
"format:check": "bunx oxfmt --check",
|
|
58
63
|
"lint:arch": "bun run lint-architecture.ts",
|
|
59
64
|
"test": "bun test tests/ packages/telemetry-contract/",
|
|
60
65
|
"test:fast": "bun test $(find tests -name '*.test.ts' ! -name 'evolve.test.ts' ! -name 'integration.test.ts' ! -name 'dashboard-server.test.ts' ! -path '*/blog-proof/*')",
|
|
61
66
|
"test:slow": "bun test tests/evolution/evolve.test.ts tests/evolution/integration.test.ts tests/monitoring/integration.test.ts tests/dashboard/dashboard-server.test.ts",
|
|
62
67
|
"build:dashboard": "cd apps/local-dashboard && bunx vite build",
|
|
68
|
+
"prepack": "node scripts/publish-package-json.cjs prepare",
|
|
69
|
+
"postpack": "node scripts/publish-package-json.cjs restore",
|
|
63
70
|
"link:claude-workspace": "bash scripts/link-claude-workspace.sh",
|
|
64
71
|
"sync-version": "bun run scripts/sync-skill-version.ts",
|
|
65
72
|
"validate:subagents": "bun run scripts/validate-subagent-docs.ts",
|
|
66
73
|
"prepublishOnly": "bun run sync-version && bun run build:dashboard",
|
|
67
74
|
"typecheck:dashboard": "cd apps/local-dashboard && bunx tsc --noEmit",
|
|
68
|
-
"check": "bun run lint && bun run lint:arch && bun run typecheck:dashboard && bun run test",
|
|
75
|
+
"check": "bun run lint && bun run format:check && bun run lint:arch && bun run typecheck:dashboard && bun run test",
|
|
69
76
|
"start": "bun run cli/selftune/index.ts --help"
|
|
70
77
|
},
|
|
71
|
-
"workspaces": [
|
|
72
|
-
"packages/*",
|
|
73
|
-
"apps/*"
|
|
74
|
-
],
|
|
75
78
|
"dependencies": {
|
|
76
79
|
"@selftune/telemetry-contract": "file:packages/telemetry-contract"
|
|
77
80
|
},
|
|
78
81
|
"devDependencies": {
|
|
79
|
-
"@
|
|
80
|
-
"
|
|
82
|
+
"@types/bun": "^1.1.0",
|
|
83
|
+
"oxfmt": "^0.41.0",
|
|
84
|
+
"oxlint": "^1.56.0"
|
|
81
85
|
}
|
|
82
86
|
}
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
"version": "1.0.0",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Canonical telemetry schema, types, and validators for selftune",
|
|
6
|
-
"type": "module",
|
|
7
6
|
"license": "MIT",
|
|
8
7
|
"author": "Daniel Petro",
|
|
9
8
|
"repository": {
|
|
@@ -11,6 +10,7 @@
|
|
|
11
10
|
"url": "git+https://github.com/selftune-dev/selftune.git",
|
|
12
11
|
"directory": "packages/telemetry-contract"
|
|
13
12
|
},
|
|
13
|
+
"type": "module",
|
|
14
14
|
"exports": {
|
|
15
15
|
".": "./index.ts",
|
|
16
16
|
"./schemas": "./src/schemas.ts",
|
package/packages/ui/README.md
CHANGED
|
@@ -17,16 +17,16 @@ Add as a workspace dependency:
|
|
|
17
17
|
Import from subpath exports:
|
|
18
18
|
|
|
19
19
|
```tsx
|
|
20
|
-
import { Badge, Button, Card } from "@selftune/ui/primitives"
|
|
21
|
-
import { SkillHealthGrid, EvolutionTimeline } from "@selftune/ui/components"
|
|
22
|
-
import { cn, timeAgo, deriveStatus, STATUS_CONFIG } from "@selftune/ui/lib"
|
|
23
|
-
import type { SkillCard, EvolutionEntry } from "@selftune/ui/types"
|
|
20
|
+
import { Badge, Button, Card } from "@selftune/ui/primitives";
|
|
21
|
+
import { SkillHealthGrid, EvolutionTimeline } from "@selftune/ui/components";
|
|
22
|
+
import { cn, timeAgo, deriveStatus, STATUS_CONFIG } from "@selftune/ui/lib";
|
|
23
|
+
import type { SkillCard, EvolutionEntry } from "@selftune/ui/types";
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
Or import everything from the root:
|
|
27
27
|
|
|
28
28
|
```tsx
|
|
29
|
-
import { Badge, SkillHealthGrid, cn, type SkillCard } from "@selftune/ui"
|
|
29
|
+
import { Badge, SkillHealthGrid, cn, type SkillCard } from "@selftune/ui";
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
## Exports
|
|
@@ -35,44 +35,44 @@ import { Badge, SkillHealthGrid, cn, type SkillCard } from "@selftune/ui"
|
|
|
35
35
|
|
|
36
36
|
shadcn/ui components built on [@base-ui/react](https://base-ui.com/):
|
|
37
37
|
|
|
38
|
-
| Component
|
|
39
|
-
|
|
40
|
-
| `Badge`, `badgeVariants`
|
|
41
|
-
| `Button`, `buttonVariants`
|
|
42
|
-
| `Card`, `CardHeader`, `CardTitle`, `CardDescription`, `CardAction`, `CardContent`, `CardFooter` | card.tsx
|
|
43
|
-
| `Checkbox`
|
|
44
|
-
| `Collapsible`, `CollapsibleTrigger`, `CollapsibleContent`
|
|
45
|
-
| `DropdownMenu`, `DropdownMenuTrigger`, `DropdownMenuContent`, `DropdownMenuItem`, ...
|
|
46
|
-
| `Label`
|
|
47
|
-
| `Select`, `SelectTrigger`, `SelectContent`, `SelectItem`, ...
|
|
48
|
-
| `Table`, `TableHeader`, `TableBody`, `TableRow`, `TableHead`, `TableCell`, ...
|
|
49
|
-
| `Tabs`, `TabsList`, `TabsTrigger`, `TabsContent`
|
|
50
|
-
| `Tooltip`, `TooltipTrigger`, `TooltipContent`, `TooltipProvider`
|
|
38
|
+
| Component | Source |
|
|
39
|
+
| ----------------------------------------------------------------------------------------------- | ----------------- |
|
|
40
|
+
| `Badge`, `badgeVariants` | badge.tsx |
|
|
41
|
+
| `Button`, `buttonVariants` | button.tsx |
|
|
42
|
+
| `Card`, `CardHeader`, `CardTitle`, `CardDescription`, `CardAction`, `CardContent`, `CardFooter` | card.tsx |
|
|
43
|
+
| `Checkbox` | checkbox.tsx |
|
|
44
|
+
| `Collapsible`, `CollapsibleTrigger`, `CollapsibleContent` | collapsible.tsx |
|
|
45
|
+
| `DropdownMenu`, `DropdownMenuTrigger`, `DropdownMenuContent`, `DropdownMenuItem`, ... | dropdown-menu.tsx |
|
|
46
|
+
| `Label` | label.tsx |
|
|
47
|
+
| `Select`, `SelectTrigger`, `SelectContent`, `SelectItem`, ... | select.tsx |
|
|
48
|
+
| `Table`, `TableHeader`, `TableBody`, `TableRow`, `TableHead`, `TableCell`, ... | table.tsx |
|
|
49
|
+
| `Tabs`, `TabsList`, `TabsTrigger`, `TabsContent` | tabs.tsx |
|
|
50
|
+
| `Tooltip`, `TooltipTrigger`, `TooltipContent`, `TooltipProvider` | tooltip.tsx |
|
|
51
51
|
|
|
52
52
|
### Domain Components (`@selftune/ui/components`)
|
|
53
53
|
|
|
54
54
|
Presentational components for selftune dashboard views. No data fetching, no routing — pass data and callbacks as props.
|
|
55
55
|
|
|
56
|
-
| Component
|
|
57
|
-
|
|
58
|
-
| `SkillHealthGrid`
|
|
59
|
-
| `EvolutionTimeline`
|
|
60
|
-
| `ActivityPanel`
|
|
61
|
-
| `EvidenceViewer`
|
|
62
|
-
| `SectionCards`
|
|
63
|
-
| `OrchestrateRunsPanel` | Collapsible orchestrate run reports with per-skill action details.
|
|
64
|
-
| `InfoTip`
|
|
56
|
+
| Component | Description |
|
|
57
|
+
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
|
|
58
|
+
| `SkillHealthGrid` | Sortable/filterable data table with drag-and-drop, pagination, and view tabs. Accepts `renderSkillName` prop for custom routing. |
|
|
59
|
+
| `EvolutionTimeline` | Proposal lifecycle timeline grouped by proposal ID, with pass rate deltas. |
|
|
60
|
+
| `ActivityPanel` | Tabbed activity feed (undeployed proposals, timeline events, unmatched queries). |
|
|
61
|
+
| `EvidenceViewer` | Full evidence trail for a proposal — side-by-side diffs, validation results, iteration rounds. |
|
|
62
|
+
| `SectionCards` | Dashboard metric stat cards (skills count, pass rate, unmatched, sessions, etc.). |
|
|
63
|
+
| `OrchestrateRunsPanel` | Collapsible orchestrate run reports with per-skill action details. |
|
|
64
|
+
| `InfoTip` | Small info icon with tooltip, used to explain metrics. |
|
|
65
65
|
|
|
66
66
|
### Utilities (`@selftune/ui/lib`)
|
|
67
67
|
|
|
68
|
-
| Export
|
|
69
|
-
|
|
70
|
-
| `cn(...inputs)`
|
|
71
|
-
| `timeAgo(timestamp)`
|
|
72
|
-
| `formatRate(rate)`
|
|
73
|
-
| `deriveStatus(passRate, checks)` | Derive `SkillHealthStatus` from pass rate and check count
|
|
68
|
+
| Export | Description |
|
|
69
|
+
| -------------------------------- | --------------------------------------------------------------- |
|
|
70
|
+
| `cn(...inputs)` | Tailwind class merge utility (clsx + tailwind-merge) |
|
|
71
|
+
| `timeAgo(timestamp)` | Relative time string ("3h ago", "2d ago") |
|
|
72
|
+
| `formatRate(rate)` | Format 0-1 rate as percentage string ("85%") |
|
|
73
|
+
| `deriveStatus(passRate, checks)` | Derive `SkillHealthStatus` from pass rate and check count |
|
|
74
74
|
| `sortByPassRateAndChecks(items)` | Sort skill cards by pass rate ascending, then checks descending |
|
|
75
|
-
| `STATUS_CONFIG`
|
|
75
|
+
| `STATUS_CONFIG` | Icon, variant, and label for each `SkillHealthStatus` value |
|
|
76
76
|
|
|
77
77
|
### Types (`@selftune/ui/types`)
|
|
78
78
|
|
|
@@ -107,6 +107,7 @@ This package uses Tailwind v4. The Vite plugin auto-scans imported workspace pac
|
|
|
107
107
|
Required: `react`, `react-dom`
|
|
108
108
|
|
|
109
109
|
Optional (only needed by specific components):
|
|
110
|
+
|
|
110
111
|
- `@dnd-kit/*` — SkillHealthGrid drag-and-drop
|
|
111
112
|
- `@tanstack/react-table` — SkillHealthGrid table
|
|
112
113
|
- `react-markdown` — EvidenceViewer markdown rendering
|
package/packages/ui/package.json
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
"version": "1.0.0",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Shared UI components for selftune dashboards",
|
|
6
|
-
"type": "module",
|
|
7
6
|
"license": "MIT",
|
|
8
7
|
"author": "Daniel Petro",
|
|
9
8
|
"repository": {
|
|
@@ -11,6 +10,7 @@
|
|
|
11
10
|
"url": "git+https://github.com/selftune-dev/selftune.git",
|
|
12
11
|
"directory": "packages/ui"
|
|
13
12
|
},
|
|
13
|
+
"type": "module",
|
|
14
14
|
"exports": {
|
|
15
15
|
".": "./index.ts",
|
|
16
16
|
"./primitives": "./src/primitives/index.ts",
|
|
@@ -30,13 +30,13 @@
|
|
|
30
30
|
"@types/react-dom": "^19.0.0"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
|
-
"react": "^19.0.0",
|
|
34
|
-
"react-dom": "^19.0.0",
|
|
35
33
|
"@dnd-kit/core": "^6.0.0",
|
|
36
34
|
"@dnd-kit/modifiers": "^9.0.0",
|
|
37
35
|
"@dnd-kit/sortable": "^10.0.0",
|
|
38
36
|
"@dnd-kit/utilities": "^3.0.0",
|
|
39
37
|
"@tanstack/react-table": "^8.0.0",
|
|
38
|
+
"react": "^19.0.0",
|
|
39
|
+
"react-dom": "^19.0.0",
|
|
40
40
|
"react-markdown": "^10.0.0",
|
|
41
41
|
"recharts": "^2.0.0"
|
|
42
42
|
},
|