selftune 0.2.10 → 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/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 +6 -5
- package/cli/selftune/evolution/deploy-proposal.ts +9 -239
- package/cli/selftune/evolution/evolve.ts +3 -6
- package/cli/selftune/evolution/rollback.ts +1 -1
- package/cli/selftune/routes/report.ts +1 -1
- package/cli/selftune/routes/skill-report.ts +1 -1
- package/cli/selftune/status.ts +1 -1
- package/cli/selftune/utils/frontmatter.ts +50 -7
- package/package.json +2 -2
- package/packages/ui/README.md +1 -1
- package/packages/ui/src/components/ActivityTimeline.tsx +1 -1
- package/packages/ui/src/components/section-cards.tsx +2 -2
- package/skill/SKILL.md +1 -1
- package/apps/local-dashboard/dist/assets/index-BZVLv70T.js +0 -16
- package/apps/local-dashboard/dist/assets/index-Bs3Y4ixf.css +0 -1
- package/apps/local-dashboard/dist/assets/vendor-react-BXP54cYo.js +0 -60
- package/apps/local-dashboard/dist/assets/vendor-table-DTF_SXoy.js +0 -26
- package/apps/local-dashboard/dist/assets/vendor-ui-CWU0d1wd.js +0 -341
|
@@ -5,11 +5,12 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>selftune — Dashboard</title>
|
|
7
7
|
<link rel="icon" type="image/png" href="/favicon.png" />
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
10
|
-
<link rel="modulepreload" crossorigin href="/assets/vendor-
|
|
11
|
-
<link rel="modulepreload" crossorigin href="/assets/vendor-
|
|
12
|
-
<link rel="
|
|
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">
|
|
13
14
|
</head>
|
|
14
15
|
<body>
|
|
15
16
|
<div id="root"></div>
|
|
@@ -1,98 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* deploy-proposal.ts
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { copyFileSync, existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
10
|
-
|
|
11
|
-
import type { EvolutionProposal, SkillSections } from "../types.js";
|
|
12
|
-
import type { ValidationResult } from "./validate-proposal.js";
|
|
13
|
-
|
|
14
|
-
// ---------------------------------------------------------------------------
|
|
15
|
-
// Types
|
|
16
|
-
// ---------------------------------------------------------------------------
|
|
17
|
-
|
|
18
|
-
export interface DeployOptions {
|
|
19
|
-
proposal: EvolutionProposal;
|
|
20
|
-
validation: ValidationResult;
|
|
21
|
-
skillPath: string;
|
|
22
|
-
createPr: boolean;
|
|
23
|
-
branchPrefix?: string; // default "selftune/evolve"
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface DeployResult {
|
|
27
|
-
skillMdUpdated: boolean;
|
|
28
|
-
backupPath: string | null;
|
|
29
|
-
branchName: string | null;
|
|
30
|
-
commitMessage: string;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// ---------------------------------------------------------------------------
|
|
34
|
-
// SKILL.md reading
|
|
35
|
-
// ---------------------------------------------------------------------------
|
|
36
|
-
|
|
37
|
-
/** Read the contents of a SKILL.md file. Throws if the file does not exist. */
|
|
38
|
-
export function readSkillMd(skillPath: string): string {
|
|
39
|
-
if (!existsSync(skillPath)) {
|
|
40
|
-
throw new Error(`SKILL.md not found at ${skillPath}`);
|
|
41
|
-
}
|
|
42
|
-
return readFileSync(skillPath, "utf-8");
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// ---------------------------------------------------------------------------
|
|
46
|
-
// Description replacement
|
|
47
|
-
// ---------------------------------------------------------------------------
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Replace the description section of a SKILL.md file.
|
|
4
|
+
* SKILL.md manipulation utilities for the evolution pipeline: description
|
|
5
|
+
* replacement, structured section parsing, section replacement, and full
|
|
6
|
+
* body replacement.
|
|
51
7
|
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
8
|
+
* Evolution is a local personalization — the evolved description reflects how
|
|
9
|
+
* *this user* works, not a change the skill creator should adopt. A future
|
|
10
|
+
* upstream feedback channel (anonymized patterns, not raw descriptions) may
|
|
11
|
+
* let end-users send useful signal back to skill creators, but that's a
|
|
12
|
+
* separate concern from deploy. See TD-019 in tech-debt-tracker.md.
|
|
55
13
|
*/
|
|
56
|
-
export function replaceDescription(currentContent: string, newDescription: string): string {
|
|
57
|
-
const lines = currentContent.split("\n");
|
|
58
14
|
|
|
59
|
-
|
|
60
|
-
let headingIndex = -1;
|
|
61
|
-
for (let i = 0; i < lines.length; i++) {
|
|
62
|
-
if (lines[i].startsWith("# ") && !lines[i].startsWith("## ")) {
|
|
63
|
-
headingIndex = i;
|
|
64
|
-
break;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// If no heading found, just prepend the description
|
|
69
|
-
if (headingIndex === -1) {
|
|
70
|
-
return `${newDescription}\n${currentContent}`;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Find the first ## heading after the main heading
|
|
74
|
-
let subHeadingIndex = -1;
|
|
75
|
-
for (let i = headingIndex + 1; i < lines.length; i++) {
|
|
76
|
-
if (lines[i].startsWith("## ")) {
|
|
77
|
-
subHeadingIndex = i;
|
|
78
|
-
break;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Build the new content, preserving any preamble before the first heading
|
|
83
|
-
const preamble = headingIndex > 0 ? `${lines.slice(0, headingIndex).join("\n")}\n` : "";
|
|
84
|
-
const headingLine = lines[headingIndex];
|
|
85
|
-
const descriptionBlock = newDescription.length > 0 ? `\n${newDescription}\n` : "\n";
|
|
86
|
-
|
|
87
|
-
if (subHeadingIndex === -1) {
|
|
88
|
-
// No sub-heading: preamble + heading + new description + trailing newline
|
|
89
|
-
return `${preamble}${headingLine}\n${descriptionBlock}\n`;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Preamble + heading + description + everything from the first ## onward
|
|
93
|
-
const afterSubHeading = lines.slice(subHeadingIndex).join("\n");
|
|
94
|
-
return `${preamble}${headingLine}\n${descriptionBlock}\n${afterSubHeading}`;
|
|
95
|
-
}
|
|
15
|
+
import type { SkillSections } from "../types.js";
|
|
96
16
|
|
|
97
17
|
// ---------------------------------------------------------------------------
|
|
98
18
|
// Structured SKILL.md parsing
|
|
@@ -234,153 +154,3 @@ export function replaceBody(currentContent: string, proposedBody: string): strin
|
|
|
234
154
|
|
|
235
155
|
return `${parts.join("\n").trimEnd()}\n`;
|
|
236
156
|
}
|
|
237
|
-
|
|
238
|
-
// ---------------------------------------------------------------------------
|
|
239
|
-
// Commit message builder
|
|
240
|
-
// ---------------------------------------------------------------------------
|
|
241
|
-
|
|
242
|
-
/** Build a commit message that includes the skill name and pass rate change. */
|
|
243
|
-
export function buildCommitMessage(
|
|
244
|
-
proposal: EvolutionProposal,
|
|
245
|
-
validation: ValidationResult,
|
|
246
|
-
): string {
|
|
247
|
-
const changePercent = Math.round(validation.net_change * 100);
|
|
248
|
-
const sign = changePercent >= 0 ? "+" : "";
|
|
249
|
-
const passRateStr = `${sign}${changePercent}% pass rate`;
|
|
250
|
-
|
|
251
|
-
return `evolve(${proposal.skill_name}): ${passRateStr}`;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// ---------------------------------------------------------------------------
|
|
255
|
-
// Git/GH operations (PR creation)
|
|
256
|
-
// ---------------------------------------------------------------------------
|
|
257
|
-
|
|
258
|
-
/** Sanitize a string for use in a git branch name. */
|
|
259
|
-
function sanitizeForGitRef(name: string): string {
|
|
260
|
-
return name
|
|
261
|
-
.replace(/[^a-zA-Z0-9._-]/g, "-")
|
|
262
|
-
.replace(/\.{2,}/g, ".")
|
|
263
|
-
.replace(/^[.-]|[.-]$/g, "")
|
|
264
|
-
.replace(/-{2,}/g, "-");
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
/** Generate a branch name from the prefix and skill name. */
|
|
268
|
-
function makeBranchName(prefix: string, skillName: string): string {
|
|
269
|
-
const timestamp = Date.now();
|
|
270
|
-
const safeName = sanitizeForGitRef(skillName) || "untitled";
|
|
271
|
-
return `${prefix}/${safeName}-${timestamp}`;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
/**
|
|
275
|
-
* Run a git/gh command via Bun.spawn. Returns stdout on success.
|
|
276
|
-
* Throws on non-zero exit code or if the command exceeds timeoutMs.
|
|
277
|
-
*/
|
|
278
|
-
async function runCommand(args: string[], cwd?: string, timeoutMs = 30_000): Promise<string> {
|
|
279
|
-
const proc = Bun.spawn(args, {
|
|
280
|
-
cwd,
|
|
281
|
-
stdout: "pipe",
|
|
282
|
-
stderr: "pipe",
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
let timedOut = false;
|
|
286
|
-
const timer = setTimeout(() => {
|
|
287
|
-
timedOut = true;
|
|
288
|
-
proc.kill();
|
|
289
|
-
}, timeoutMs);
|
|
290
|
-
|
|
291
|
-
try {
|
|
292
|
-
// Read stdout and stderr concurrently to avoid deadlock when both pipes fill.
|
|
293
|
-
const [stdout, stderr] = await Promise.all([
|
|
294
|
-
new Response(proc.stdout).text(),
|
|
295
|
-
new Response(proc.stderr).text(),
|
|
296
|
-
]);
|
|
297
|
-
const exitCode = await proc.exited;
|
|
298
|
-
|
|
299
|
-
if (timedOut) {
|
|
300
|
-
throw new Error(`Command timed out after ${timeoutMs}ms: ${args.join(" ")}`);
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
if (exitCode !== 0) {
|
|
304
|
-
throw new Error(`Command failed (exit ${exitCode}): ${args.join(" ")}\n${stderr}`);
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
return stdout.trim();
|
|
308
|
-
} finally {
|
|
309
|
-
clearTimeout(timer);
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
// ---------------------------------------------------------------------------
|
|
314
|
-
// Main deploy function
|
|
315
|
-
// ---------------------------------------------------------------------------
|
|
316
|
-
|
|
317
|
-
/** Deploy a validated evolution proposal to SKILL.md and optionally create a PR. */
|
|
318
|
-
export async function deployProposal(options: DeployOptions): Promise<DeployResult> {
|
|
319
|
-
const { proposal, validation, skillPath, createPr, branchPrefix = "selftune/evolve" } = options;
|
|
320
|
-
|
|
321
|
-
// Step 1: Read current SKILL.md
|
|
322
|
-
const currentContent = readSkillMd(skillPath);
|
|
323
|
-
|
|
324
|
-
// Step 2: Create backup (unique per deploy to avoid overwriting previous backups)
|
|
325
|
-
const backupTimestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
326
|
-
const backupPath = `${skillPath}.${backupTimestamp}.bak`;
|
|
327
|
-
copyFileSync(skillPath, backupPath);
|
|
328
|
-
|
|
329
|
-
// Step 3: Replace description and write
|
|
330
|
-
const updatedContent = replaceDescription(currentContent, proposal.proposed_description);
|
|
331
|
-
writeFileSync(skillPath, updatedContent, "utf-8");
|
|
332
|
-
|
|
333
|
-
// Step 4: Build commit message
|
|
334
|
-
const commitMessage = buildCommitMessage(proposal, validation);
|
|
335
|
-
|
|
336
|
-
// Step 5: Optionally create branch and PR
|
|
337
|
-
let branchName: string | null = null;
|
|
338
|
-
|
|
339
|
-
if (createPr) {
|
|
340
|
-
branchName = makeBranchName(branchPrefix, proposal.skill_name);
|
|
341
|
-
|
|
342
|
-
try {
|
|
343
|
-
// Create and checkout branch
|
|
344
|
-
await runCommand(["git", "checkout", "-b", branchName]);
|
|
345
|
-
|
|
346
|
-
// Stage the SKILL.md
|
|
347
|
-
await runCommand(["git", "add", skillPath]);
|
|
348
|
-
|
|
349
|
-
// Commit
|
|
350
|
-
await runCommand(["git", "commit", "-m", commitMessage]);
|
|
351
|
-
|
|
352
|
-
// Push
|
|
353
|
-
await runCommand(["git", "push", "-u", "origin", branchName]);
|
|
354
|
-
|
|
355
|
-
// Create PR
|
|
356
|
-
await runCommand([
|
|
357
|
-
"gh",
|
|
358
|
-
"pr",
|
|
359
|
-
"create",
|
|
360
|
-
"--title",
|
|
361
|
-
commitMessage,
|
|
362
|
-
"--body",
|
|
363
|
-
`Proposal: ${proposal.proposal_id}\nRationale: ${proposal.rationale}\nNet change: ${validation.net_change > 0 ? "+" : ""}${Math.round(validation.net_change * 100)}%`,
|
|
364
|
-
]);
|
|
365
|
-
} catch (err) {
|
|
366
|
-
// Git/GH operations are best-effort in test environments.
|
|
367
|
-
// The branch name is still returned for tracking.
|
|
368
|
-
console.error(`[WARN] Git/GH operation failed: ${err instanceof Error ? err.message : err}`);
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
return {
|
|
373
|
-
skillMdUpdated: true,
|
|
374
|
-
backupPath,
|
|
375
|
-
branchName,
|
|
376
|
-
commitMessage,
|
|
377
|
-
};
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
// ---------------------------------------------------------------------------
|
|
381
|
-
// CLI entry guard
|
|
382
|
-
// ---------------------------------------------------------------------------
|
|
383
|
-
|
|
384
|
-
if (import.meta.main) {
|
|
385
|
-
console.log("deploy-proposal: use deployProposal() programmatically or via evolve CLI");
|
|
386
|
-
}
|
|
@@ -36,7 +36,7 @@ import type {
|
|
|
36
36
|
SessionTelemetryRecord,
|
|
37
37
|
SkillUsageRecord,
|
|
38
38
|
} from "../types.js";
|
|
39
|
-
import { parseFrontmatter,
|
|
39
|
+
import { parseFrontmatter, replaceDescription } from "../utils/frontmatter.js";
|
|
40
40
|
import { createEvolveTUI } from "../utils/tui.js";
|
|
41
41
|
import { appendAuditEntry } from "./audit.js";
|
|
42
42
|
import { checkConstitution } from "./constitutional.js";
|
|
@@ -958,11 +958,8 @@ export async function evolve(
|
|
|
958
958
|
copyFileSync(skillPath, backupPath);
|
|
959
959
|
tui.done(`Backup created at ${backupPath}`);
|
|
960
960
|
|
|
961
|
-
// Replace the frontmatter
|
|
962
|
-
const updatedContent =
|
|
963
|
-
rawContent,
|
|
964
|
-
lastProposal.proposed_description,
|
|
965
|
-
);
|
|
961
|
+
// Replace the description (handles both frontmatter and plain markdown)
|
|
962
|
+
const updatedContent = replaceDescription(rawContent, lastProposal.proposed_description);
|
|
966
963
|
writeFileSync(skillPath, updatedContent, "utf-8");
|
|
967
964
|
tui.done(`Deployed updated description to ${skillPath}`);
|
|
968
965
|
|
|
@@ -13,8 +13,8 @@ import { parseArgs } from "node:util";
|
|
|
13
13
|
|
|
14
14
|
import { updateContextAfterRollback } from "../memory/writer.js";
|
|
15
15
|
import type { EvolutionAuditEntry } from "../types.js";
|
|
16
|
+
import { replaceDescription } from "../utils/frontmatter.js";
|
|
16
17
|
import { appendAuditEntry, getLastDeployedProposal, readAuditTrail } from "./audit.js";
|
|
17
|
-
import { replaceDescription } from "./deploy-proposal.js";
|
|
18
18
|
|
|
19
19
|
// ---------------------------------------------------------------------------
|
|
20
20
|
// Types
|
|
@@ -180,7 +180,7 @@ function buildReportHTML(
|
|
|
180
180
|
<tr><th>Metric</th><th>Value</th></tr>
|
|
181
181
|
<tr><td>Total Skills</td><td>${statusResult.skills.length}</td></tr>
|
|
182
182
|
<tr><td>Unmatched Queries</td><td>${statusResult.unmatchedQueries}</td></tr>
|
|
183
|
-
<tr><td>
|
|
183
|
+
<tr><td>Undeployed Proposals</td><td>${statusResult.pendingProposals}</td></tr>
|
|
184
184
|
<tr><td>Last Session</td><td>${escapeHtml(statusResult.lastSession ?? "\u2014")}</td></tr>
|
|
185
185
|
</table>
|
|
186
186
|
</div>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Route handler: GET /api/v2/skills/:name
|
|
3
3
|
*
|
|
4
|
-
* Returns SQLite-backed per-skill report with evolution audit,
|
|
4
|
+
* Returns SQLite-backed per-skill report with evolution audit, undeployed proposals,
|
|
5
5
|
* invocation details, duration stats, selftune resource usage, prompt samples,
|
|
6
6
|
* and session metadata.
|
|
7
7
|
*/
|
package/cli/selftune/status.ts
CHANGED
|
@@ -324,7 +324,7 @@ export function formatStatus(result: StatusResult): string {
|
|
|
324
324
|
|
|
325
325
|
// Summary stats
|
|
326
326
|
lines.push(`Unmatched queries: ${result.unmatchedQueries}`);
|
|
327
|
-
lines.push(`
|
|
327
|
+
lines.push(`Undeployed proposals: ${result.pendingProposals}`);
|
|
328
328
|
|
|
329
329
|
// Last session
|
|
330
330
|
if (result.lastSession) {
|
|
@@ -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);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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
6
|
"agent",
|
|
@@ -65,9 +65,9 @@
|
|
|
65
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/*')",
|
|
66
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",
|
|
67
67
|
"build:dashboard": "cd apps/local-dashboard && bunx vite build",
|
|
68
|
-
"link:claude-workspace": "bash scripts/link-claude-workspace.sh",
|
|
69
68
|
"prepack": "node scripts/publish-package-json.cjs prepare",
|
|
70
69
|
"postpack": "node scripts/publish-package-json.cjs restore",
|
|
70
|
+
"link:claude-workspace": "bash scripts/link-claude-workspace.sh",
|
|
71
71
|
"sync-version": "bun run scripts/sync-skill-version.ts",
|
|
72
72
|
"validate:subagents": "bun run scripts/validate-subagent-docs.ts",
|
|
73
73
|
"prepublishOnly": "bun run sync-version && bun run build:dashboard",
|
package/packages/ui/README.md
CHANGED
|
@@ -57,7 +57,7 @@ Presentational components for selftune dashboard views. No data fetching, no rou
|
|
|
57
57
|
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
|
|
58
58
|
| `SkillHealthGrid` | Sortable/filterable data table with drag-and-drop, pagination, and view tabs. Accepts `renderSkillName` prop for custom routing. |
|
|
59
59
|
| `EvolutionTimeline` | Proposal lifecycle timeline grouped by proposal ID, with pass rate deltas. |
|
|
60
|
-
| `ActivityPanel` | Tabbed activity feed (
|
|
60
|
+
| `ActivityPanel` | Tabbed activity feed (undeployed proposals, timeline events, unmatched queries). |
|
|
61
61
|
| `EvidenceViewer` | Full evidence trail for a proposal — side-by-side diffs, validation results, iteration rounds. |
|
|
62
62
|
| `SectionCards` | Dashboard metric stat cards (skills count, pass rate, unmatched, sessions, etc.). |
|
|
63
63
|
| `OrchestrateRunsPanel` | Collapsible orchestrate run reports with per-skill action details. |
|
|
@@ -125,7 +125,7 @@ export function SectionCards({
|
|
|
125
125
|
<CardHeader>
|
|
126
126
|
<CardDescription className="flex items-center gap-1.5">
|
|
127
127
|
<AlertTriangleIcon className="size-3.5" />
|
|
128
|
-
|
|
128
|
+
Undeployed Proposals
|
|
129
129
|
<InfoTip text="Evolution proposals that have been generated but not yet validated or deployed. Requires running selftune evolve." />
|
|
130
130
|
</CardDescription>
|
|
131
131
|
<CardTitle className="text-2xl font-semibold tabular-nums @[250px]/card:text-3xl">
|
|
@@ -137,7 +137,7 @@ export function SectionCards({
|
|
|
137
137
|
no evolution runs yet
|
|
138
138
|
</Badge>
|
|
139
139
|
) : pendingCount > 0 ? (
|
|
140
|
-
<Badge variant="secondary">
|
|
140
|
+
<Badge variant="secondary">not yet deployed</Badge>
|
|
141
141
|
) : null}
|
|
142
142
|
</CardAction>
|
|
143
143
|
</CardHeader>
|