tarsk 0.5.41 → 0.5.43
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/dist/bundled-skills/skill-creator/LICENSE.txt +202 -0
- package/dist/bundled-skills/skill-creator/SKILL.md +510 -0
- package/dist/bundled-skills/skill-creator/agents/analyzer.md +283 -0
- package/dist/bundled-skills/skill-creator/agents/comparator.md +203 -0
- package/dist/bundled-skills/skill-creator/agents/grader.md +227 -0
- package/dist/bundled-skills/skill-creator/assets/eval_review.html +292 -0
- package/dist/bundled-skills/skill-creator/eval-viewer/generate_review.js +544 -0
- package/dist/bundled-skills/skill-creator/eval-viewer/viewer.html +1478 -0
- package/dist/bundled-skills/skill-creator/package.json +3 -0
- package/dist/bundled-skills/skill-creator/references/schemas.md +423 -0
- package/dist/bundled-skills/skill-creator/scripts/aggregate_benchmark.js +377 -0
- package/dist/bundled-skills/skill-creator/scripts/generate_report.js +345 -0
- package/dist/bundled-skills/skill-creator/scripts/improve_description.js +263 -0
- package/dist/bundled-skills/skill-creator/scripts/package_skill.js +147 -0
- package/dist/bundled-skills/skill-creator/scripts/quick_validate.js +132 -0
- package/dist/bundled-skills/skill-creator/scripts/run_eval.js +345 -0
- package/dist/bundled-skills/skill-creator/scripts/run_loop.js +411 -0
- package/dist/bundled-skills/skill-creator/scripts/utils.js +60 -0
- package/dist/index.js +8817 -6340
- package/dist/public/assets/{account-view-D-dJ0y-D.js → account-view-xKotpUyx.js} +1 -1
- package/dist/public/assets/api-D6uLdHBQ.js +1 -0
- package/dist/public/assets/browser-tab-DxigYzoT.js +1 -0
- package/dist/public/assets/commit-dialog-CLQM9ah3.js +1 -0
- package/dist/public/assets/context-menu-rC7iWcty.js +1 -0
- package/dist/public/assets/create-repo-dialog-C6k5wZPW.js +1 -0
- package/dist/public/assets/{dialogs-config-B-LZ4nOb.js → dialogs-config-CjKh5Rl2.js} +14 -14
- package/dist/public/assets/diff-view-DWDWI5nl.js +3 -0
- package/dist/public/assets/explorer-tab-view-B0kT8Hl6.js +2 -0
- package/dist/public/assets/explorer-tree-BC4fBpxi.js +1 -0
- package/dist/public/assets/explorer-view-DIM08sdy.js +1 -0
- package/dist/public/assets/git-history-dialog-CuxOTngT.js +1 -0
- package/dist/public/assets/git-ops-button-C04zFAnF.js +2 -0
- package/dist/public/assets/history-view-ar7GLZ-R.js +9 -0
- package/dist/public/assets/index--HY4BbcM.js +90 -0
- package/dist/public/assets/index-DKOXV50p.css +1 -0
- package/dist/public/assets/mcp-server-card-Cy4RU2_Q.js +1 -0
- package/dist/public/assets/merged-pr-dialog-Bo07VouF.js +1 -0
- package/dist/public/assets/model-star-rating-BmkpdXfr.js +1 -0
- package/dist/public/assets/onboarding-ClZrOxX7.js +1 -0
- package/dist/public/assets/project-settings-view-Dm9pQAp_.js +1 -0
- package/dist/public/assets/providers-list-view-D5gHsjl_.js +1 -0
- package/dist/public/assets/pull-request-dialog-8AYlOUNX.js +1 -0
- package/dist/public/assets/pull-with-changes-dialog-CSa5OE-d.js +1 -0
- package/dist/public/assets/push-before-pr-dialog-D5W_xsqv.js +1 -0
- package/dist/public/assets/radio-group-CbatNaj1.js +1 -0
- package/dist/public/assets/react-vendor-DwQYi7es.js +16 -0
- package/dist/public/assets/settings-general-view-BP5ULy9A.js +1 -0
- package/dist/public/assets/settings-instructions-view-DMAjbi6E.js +1 -0
- package/dist/public/assets/settings-list-B8hiBkBz.js +1 -0
- package/dist/public/assets/settings-mcp-servers-view-OimQz-Rd.js +5 -0
- package/dist/public/assets/{settings-models-skeleton-ClrbJy_p.js → settings-models-skeleton-DPnYbg69.js} +1 -1
- package/dist/public/assets/settings-models-view-Fq3WtdKG.js +1 -0
- package/dist/public/assets/settings-rules-view-DBk7DzV2.js +8 -0
- package/dist/public/assets/settings-skills-view-CmOw-WMM.js +2 -0
- package/dist/public/assets/settings-slash-commands-view-FsrF5FkK.js +1 -0
- package/dist/public/assets/settings-subagents-view-D98Nxoly.js +2 -0
- package/dist/public/assets/{settings-system-prompt-view-Dl66VFaj.js → settings-system-prompt-view-B6Hy9ZyK.js} +1 -1
- package/dist/public/assets/settings-view-J-rjoRcU.js +2 -0
- package/dist/public/assets/skeleton-BHhGML7J.js +1 -0
- package/dist/public/assets/slug-utils-DyRUJ1NS.js +1 -0
- package/dist/public/assets/terminal-panel-DTOx74_o.js +1 -0
- package/dist/public/assets/{ui-components-C4RrfJEJ.js → ui-components-Jc6oi6bz.js} +1 -1
- package/dist/public/assets/use-deferred-search-B7EdyRbt.js +1 -0
- package/dist/public/assets/{utils-B7FQXlI6.js → utils-tgi5ym_d.js} +1 -1
- package/dist/public/assets/web-C3vJZ_3_.js +1 -0
- package/dist/public/assets/web-CUAWBWPy.js +1 -0
- package/dist/public/assets/{whisper-wasm-EGutPGND.js → whisper-wasm-CWcbC1MB.js} +1 -1
- package/dist/public/browser-preview-rpc.js +484 -0
- package/dist/public/index.html +8 -8
- package/package.json +4 -3
- package/dist/public/assets/api-DJaJqkc6.js +0 -1
- package/dist/public/assets/browser-tab-D7wEj-BD.js +0 -1
- package/dist/public/assets/commit-dialog-DdhGDH4F.js +0 -1
- package/dist/public/assets/context-menu-asf2g-KX.js +0 -1
- package/dist/public/assets/create-repo-dialog-Bltp6PKZ.js +0 -1
- package/dist/public/assets/diff-view-Bi545EPj.js +0 -3
- package/dist/public/assets/explorer-tab-view-B-P555GE.js +0 -2
- package/dist/public/assets/explorer-tree-CyXhVrI7.js +0 -1
- package/dist/public/assets/explorer-view-BAHDhIGN.js +0 -1
- package/dist/public/assets/git-history-dialog-Bci_iQmi.js +0 -1
- package/dist/public/assets/git-ops-button-1lum9QXI.js +0 -2
- package/dist/public/assets/history-view-CAkN8PCo.js +0 -9
- package/dist/public/assets/index-BLO68CQl.js +0 -69
- package/dist/public/assets/index-jIBJk8xl.css +0 -1
- package/dist/public/assets/mcp-server-card-DQUpkDFV.js +0 -1
- package/dist/public/assets/merged-pr-dialog-tHPrJ2CK.js +0 -1
- package/dist/public/assets/onboarding-Bbvb7kO4.js +0 -1
- package/dist/public/assets/project-settings-view-BRkHOq_q.js +0 -1
- package/dist/public/assets/providers-list-view-Dex879vv.js +0 -1
- package/dist/public/assets/pull-request-dialog-WHdmrW83.js +0 -1
- package/dist/public/assets/pull-with-changes-dialog-Ck3OwINV.js +0 -1
- package/dist/public/assets/push-before-pr-dialog-Bvqvz04U.js +0 -1
- package/dist/public/assets/radio-group-B0xvu5B9.js +0 -1
- package/dist/public/assets/react-vendor-D8PTA4EX.js +0 -16
- package/dist/public/assets/settings-general-view-lUxshNA9.js +0 -1
- package/dist/public/assets/settings-instructions-view-C57XGLha.js +0 -1
- package/dist/public/assets/settings-list-CHGKmGl_.js +0 -1
- package/dist/public/assets/settings-mcp-servers-view-DpqkhrgB.js +0 -5
- package/dist/public/assets/settings-models-view-QPEdnibD.js +0 -1
- package/dist/public/assets/settings-rules-view-WJU--cRq.js +0 -8
- package/dist/public/assets/settings-skills-view-mgHy4G_g.js +0 -2
- package/dist/public/assets/settings-slash-commands-view-LB5tVqy1.js +0 -1
- package/dist/public/assets/settings-subagents-view-QR2qlA_y.js +0 -2
- package/dist/public/assets/settings-view-BlVJv4Pz.js +0 -2
- package/dist/public/assets/skeleton-K-fVduHt.js +0 -1
- package/dist/public/assets/terminal-panel-BGQxckfH.js +0 -2
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/** Generate an HTML report from run_loop.js output. */
|
|
3
|
+
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
|
|
7
|
+
function escapeHtml(value) {
|
|
8
|
+
return String(value)
|
|
9
|
+
.replace(/&/g, "&")
|
|
10
|
+
.replace(/</g, "<")
|
|
11
|
+
.replace(/>/g, ">")
|
|
12
|
+
.replace(/"/g, """)
|
|
13
|
+
.replace(/'/g, "'");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function aggregateRuns(results) {
|
|
17
|
+
let correct = 0;
|
|
18
|
+
let total = 0;
|
|
19
|
+
for (const r of results) {
|
|
20
|
+
const runs = r.runs ?? 0;
|
|
21
|
+
const triggers = r.triggers ?? 0;
|
|
22
|
+
total += runs;
|
|
23
|
+
if (r.should_trigger ?? true) {
|
|
24
|
+
correct += triggers;
|
|
25
|
+
} else {
|
|
26
|
+
correct += runs - triggers;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return [correct, total];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function scoreClass(correct, total) {
|
|
33
|
+
if (total > 0) {
|
|
34
|
+
const ratio = correct / total;
|
|
35
|
+
if (ratio >= 0.8) return "score-good";
|
|
36
|
+
if (ratio >= 0.5) return "score-ok";
|
|
37
|
+
}
|
|
38
|
+
return "score-bad";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function generateHtml(data, autoRefresh = false, skillName = "") {
|
|
42
|
+
const history = data.history ?? [];
|
|
43
|
+
const titlePrefix = skillName ? escapeHtml(`${skillName} \u2014 `) : "";
|
|
44
|
+
|
|
45
|
+
const trainQueries = [];
|
|
46
|
+
const testQueries = [];
|
|
47
|
+
if (history.length) {
|
|
48
|
+
for (const r of history[0].train_results ?? history[0].results ?? []) {
|
|
49
|
+
trainQueries.push({ query: r.query, should_trigger: r.should_trigger ?? true });
|
|
50
|
+
}
|
|
51
|
+
if (history[0].test_results) {
|
|
52
|
+
for (const r of history[0].test_results) {
|
|
53
|
+
testQueries.push({ query: r.query, should_trigger: r.should_trigger ?? true });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const refreshTag = autoRefresh ? ' <meta http-equiv="refresh" content="5">\n' : "";
|
|
59
|
+
const htmlParts = [
|
|
60
|
+
`<!DOCTYPE html>
|
|
61
|
+
<html>
|
|
62
|
+
<head>
|
|
63
|
+
<meta charset="utf-8">
|
|
64
|
+
${refreshTag} <title>${titlePrefix}Skill Description Optimization</title>
|
|
65
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
66
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
67
|
+
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@500;600&family=Lora:wght@400;500&display=swap" rel="stylesheet">
|
|
68
|
+
<style>
|
|
69
|
+
body {
|
|
70
|
+
font-family: 'Lora', Georgia, serif;
|
|
71
|
+
max-width: 100%;
|
|
72
|
+
margin: 0 auto;
|
|
73
|
+
padding: 20px;
|
|
74
|
+
background: #faf9f5;
|
|
75
|
+
color: #141413;
|
|
76
|
+
}
|
|
77
|
+
h1 { font-family: 'Poppins', sans-serif; color: #141413; }
|
|
78
|
+
.explainer {
|
|
79
|
+
background: white;
|
|
80
|
+
padding: 15px;
|
|
81
|
+
border-radius: 6px;
|
|
82
|
+
margin-bottom: 20px;
|
|
83
|
+
border: 1px solid #e8e6dc;
|
|
84
|
+
color: #b0aea5;
|
|
85
|
+
font-size: 0.875rem;
|
|
86
|
+
line-height: 1.6;
|
|
87
|
+
}
|
|
88
|
+
.summary {
|
|
89
|
+
background: white;
|
|
90
|
+
padding: 15px;
|
|
91
|
+
border-radius: 6px;
|
|
92
|
+
margin-bottom: 20px;
|
|
93
|
+
border: 1px solid #e8e6dc;
|
|
94
|
+
}
|
|
95
|
+
.summary p { margin: 5px 0; }
|
|
96
|
+
.best { color: #788c5d; font-weight: bold; }
|
|
97
|
+
.table-container {
|
|
98
|
+
overflow-x: auto;
|
|
99
|
+
width: 100%;
|
|
100
|
+
}
|
|
101
|
+
table {
|
|
102
|
+
border-collapse: collapse;
|
|
103
|
+
background: white;
|
|
104
|
+
border: 1px solid #e8e6dc;
|
|
105
|
+
border-radius: 6px;
|
|
106
|
+
font-size: 12px;
|
|
107
|
+
min-width: 100%;
|
|
108
|
+
}
|
|
109
|
+
th, td {
|
|
110
|
+
padding: 8px;
|
|
111
|
+
text-align: left;
|
|
112
|
+
border: 1px solid #e8e6dc;
|
|
113
|
+
white-space: normal;
|
|
114
|
+
word-wrap: break-word;
|
|
115
|
+
}
|
|
116
|
+
th {
|
|
117
|
+
font-family: 'Poppins', sans-serif;
|
|
118
|
+
background: #141413;
|
|
119
|
+
color: #faf9f5;
|
|
120
|
+
font-weight: 500;
|
|
121
|
+
}
|
|
122
|
+
th.test-col {
|
|
123
|
+
background: #6a9bcc;
|
|
124
|
+
}
|
|
125
|
+
th.query-col { min-width: 200px; }
|
|
126
|
+
td.description {
|
|
127
|
+
font-family: monospace;
|
|
128
|
+
font-size: 11px;
|
|
129
|
+
word-wrap: break-word;
|
|
130
|
+
max-width: 400px;
|
|
131
|
+
}
|
|
132
|
+
td.result {
|
|
133
|
+
text-align: center;
|
|
134
|
+
font-size: 16px;
|
|
135
|
+
min-width: 40px;
|
|
136
|
+
}
|
|
137
|
+
td.test-result {
|
|
138
|
+
background: #f0f6fc;
|
|
139
|
+
}
|
|
140
|
+
.pass { color: #788c5d; }
|
|
141
|
+
.fail { color: #c44; }
|
|
142
|
+
.rate {
|
|
143
|
+
font-size: 9px;
|
|
144
|
+
color: #b0aea5;
|
|
145
|
+
display: block;
|
|
146
|
+
}
|
|
147
|
+
tr:hover { background: #faf9f5; }
|
|
148
|
+
.score {
|
|
149
|
+
display: inline-block;
|
|
150
|
+
padding: 2px 6px;
|
|
151
|
+
border-radius: 4px;
|
|
152
|
+
font-weight: bold;
|
|
153
|
+
font-size: 11px;
|
|
154
|
+
}
|
|
155
|
+
.score-good { background: #eef2e8; color: #788c5d; }
|
|
156
|
+
.score-ok { background: #fef3c7; color: #d97706; }
|
|
157
|
+
.score-bad { background: #fceaea; color: #c44; }
|
|
158
|
+
.train-label { color: #b0aea5; font-size: 10px; }
|
|
159
|
+
.test-label { color: #6a9bcc; font-size: 10px; font-weight: bold; }
|
|
160
|
+
.best-row { background: #f5f8f2; }
|
|
161
|
+
th.positive-col { border-bottom: 3px solid #788c5d; }
|
|
162
|
+
th.negative-col { border-bottom: 3px solid #c44; }
|
|
163
|
+
th.test-col.positive-col { border-bottom: 3px solid #788c5d; }
|
|
164
|
+
th.test-col.negative-col { border-bottom: 3px solid #c44; }
|
|
165
|
+
.legend { font-family: 'Poppins', sans-serif; display: flex; gap: 20px; margin-bottom: 10px; font-size: 13px; align-items: center; }
|
|
166
|
+
.legend-item { display: flex; align-items: center; gap: 6px; }
|
|
167
|
+
.legend-swatch { width: 16px; height: 16px; border-radius: 3px; display: inline-block; }
|
|
168
|
+
.swatch-positive { background: #141413; border-bottom: 3px solid #788c5d; }
|
|
169
|
+
.swatch-negative { background: #141413; border-bottom: 3px solid #c44; }
|
|
170
|
+
.swatch-test { background: #6a9bcc; }
|
|
171
|
+
.swatch-train { background: #141413; }
|
|
172
|
+
</style>
|
|
173
|
+
</head>
|
|
174
|
+
<body>
|
|
175
|
+
<h1>${titlePrefix}Skill Description Optimization</h1>
|
|
176
|
+
<div class="explainer">
|
|
177
|
+
<strong>Optimizing your skill's description.</strong> This page updates automatically as Tarsk tests different versions of your skill's description. Each row is an iteration — a new description attempt. The columns show test queries: green checkmarks mean the skill triggered correctly (or correctly didn't trigger), red crosses mean it got it wrong. The "Train" score shows performance on queries used to improve the description; the "Test" score shows performance on held-out queries the optimizer hasn't seen. When it's done, Tarsk will apply the best-performing description to your skill.
|
|
178
|
+
</div>
|
|
179
|
+
`,
|
|
180
|
+
];
|
|
181
|
+
|
|
182
|
+
const bestTestScore = data.best_test_score;
|
|
183
|
+
htmlParts.push(`
|
|
184
|
+
<div class="summary">
|
|
185
|
+
<p><strong>Original:</strong> ${escapeHtml(data.original_description ?? "N/A")}</p>
|
|
186
|
+
<p class="best"><strong>Best:</strong> ${escapeHtml(data.best_description ?? "N/A")}</p>
|
|
187
|
+
<p><strong>Best Score:</strong> ${data.best_score ?? "N/A"} ${bestTestScore ? "(test)" : "(train)"}</p>
|
|
188
|
+
<p><strong>Iterations:</strong> ${data.iterations_run ?? 0} | <strong>Train:</strong> ${data.train_size ?? "?"} | <strong>Test:</strong> ${data.test_size ?? "?"}</p>
|
|
189
|
+
</div>
|
|
190
|
+
`);
|
|
191
|
+
|
|
192
|
+
htmlParts.push(`
|
|
193
|
+
<div class="legend">
|
|
194
|
+
<span style="font-weight:600">Query columns:</span>
|
|
195
|
+
<span class="legend-item"><span class="legend-swatch swatch-positive"></span> Should trigger</span>
|
|
196
|
+
<span class="legend-item"><span class="legend-swatch swatch-negative"></span> Should NOT trigger</span>
|
|
197
|
+
<span class="legend-item"><span class="legend-swatch swatch-train"></span> Train</span>
|
|
198
|
+
<span class="legend-item"><span class="legend-swatch swatch-test"></span> Test</span>
|
|
199
|
+
</div>
|
|
200
|
+
`);
|
|
201
|
+
|
|
202
|
+
htmlParts.push(`
|
|
203
|
+
<div class="table-container">
|
|
204
|
+
<table>
|
|
205
|
+
<thead>
|
|
206
|
+
<tr>
|
|
207
|
+
<th>Iter</th>
|
|
208
|
+
<th>Train</th>
|
|
209
|
+
<th>Test</th>
|
|
210
|
+
<th class="query-col">Description</th>
|
|
211
|
+
`);
|
|
212
|
+
|
|
213
|
+
for (const qinfo of trainQueries) {
|
|
214
|
+
const polarity = qinfo.should_trigger ? "positive-col" : "negative-col";
|
|
215
|
+
htmlParts.push(` <th class="${polarity}">${escapeHtml(qinfo.query)}</th>\n`);
|
|
216
|
+
}
|
|
217
|
+
for (const qinfo of testQueries) {
|
|
218
|
+
const polarity = qinfo.should_trigger ? "positive-col" : "negative-col";
|
|
219
|
+
htmlParts.push(
|
|
220
|
+
` <th class="test-col ${polarity}">${escapeHtml(qinfo.query)}</th>\n`,
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
htmlParts.push(` </tr>
|
|
225
|
+
</thead>
|
|
226
|
+
<tbody>
|
|
227
|
+
`);
|
|
228
|
+
|
|
229
|
+
let bestIter;
|
|
230
|
+
if (testQueries.length) {
|
|
231
|
+
bestIter = history.reduce(
|
|
232
|
+
(best, h) => ((h.test_passed ?? 0) > (best.test_passed ?? 0) ? h : best),
|
|
233
|
+
history[0],
|
|
234
|
+
).iteration;
|
|
235
|
+
} else {
|
|
236
|
+
bestIter = history.reduce(
|
|
237
|
+
(best, h) =>
|
|
238
|
+
(h.train_passed ?? h.passed ?? 0) > (best.train_passed ?? best.passed ?? 0) ? h : best,
|
|
239
|
+
history[0],
|
|
240
|
+
).iteration;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
for (const h of history) {
|
|
244
|
+
const iteration = h.iteration ?? "?";
|
|
245
|
+
const description = h.description ?? "";
|
|
246
|
+
const trainResults = h.train_results ?? h.results ?? [];
|
|
247
|
+
const testResults = h.test_results ?? [];
|
|
248
|
+
const trainByQuery = Object.fromEntries(trainResults.map((r) => [r.query, r]));
|
|
249
|
+
const testByQuery = testResults.length
|
|
250
|
+
? Object.fromEntries(testResults.map((r) => [r.query, r]))
|
|
251
|
+
: {};
|
|
252
|
+
|
|
253
|
+
const [trainCorrect, trainRuns] = aggregateRuns(trainResults);
|
|
254
|
+
const [testCorrect, testRuns] = aggregateRuns(testResults);
|
|
255
|
+
const trainClass = scoreClass(trainCorrect, trainRuns);
|
|
256
|
+
const testClass = scoreClass(testCorrect, testRuns);
|
|
257
|
+
const rowClass = iteration === bestIter ? "best-row" : "";
|
|
258
|
+
|
|
259
|
+
htmlParts.push(` <tr class="${rowClass}">
|
|
260
|
+
<td>${iteration}</td>
|
|
261
|
+
<td><span class="score ${trainClass}">${trainCorrect}/${trainRuns}</span></td>
|
|
262
|
+
<td><span class="score ${testClass}">${testCorrect}/${testRuns}</span></td>
|
|
263
|
+
<td class="description">${escapeHtml(description)}</td>
|
|
264
|
+
`);
|
|
265
|
+
|
|
266
|
+
for (const qinfo of trainQueries) {
|
|
267
|
+
const r = trainByQuery[qinfo.query] ?? {};
|
|
268
|
+
const didPass = r.pass ?? false;
|
|
269
|
+
const triggers = r.triggers ?? 0;
|
|
270
|
+
const runs = r.runs ?? 0;
|
|
271
|
+
const icon = didPass ? "✓" : "✗";
|
|
272
|
+
const cssClass = didPass ? "pass" : "fail";
|
|
273
|
+
htmlParts.push(
|
|
274
|
+
` <td class="result ${cssClass}">${icon}<span class="rate">${triggers}/${runs}</span></td>\n`,
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
for (const qinfo of testQueries) {
|
|
279
|
+
const r = testByQuery[qinfo.query] ?? {};
|
|
280
|
+
const didPass = r.pass ?? false;
|
|
281
|
+
const triggers = r.triggers ?? 0;
|
|
282
|
+
const runs = r.runs ?? 0;
|
|
283
|
+
const icon = didPass ? "✓" : "✗";
|
|
284
|
+
const cssClass = didPass ? "pass" : "fail";
|
|
285
|
+
htmlParts.push(
|
|
286
|
+
` <td class="result test-result ${cssClass}">${icon}<span class="rate">${triggers}/${runs}</span></td>\n`,
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
htmlParts.push(" </tr>\n");
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
htmlParts.push(` </tbody>
|
|
294
|
+
</table>
|
|
295
|
+
</div>
|
|
296
|
+
</body>
|
|
297
|
+
</html>
|
|
298
|
+
`);
|
|
299
|
+
|
|
300
|
+
return htmlParts.join("");
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function parseArgs(argv) {
|
|
304
|
+
const args = { input: null, output: null, skillName: "" };
|
|
305
|
+
const positional = [];
|
|
306
|
+
for (let i = 2; i < argv.length; i++) {
|
|
307
|
+
const arg = argv[i];
|
|
308
|
+
if (arg === "-o" || arg === "--output") args.output = argv[++i];
|
|
309
|
+
else if (arg === "--skill-name") args.skillName = argv[++i] ?? "";
|
|
310
|
+
else if (!arg.startsWith("-")) positional.push(arg);
|
|
311
|
+
}
|
|
312
|
+
args.input = positional[0] ?? null;
|
|
313
|
+
return args;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function main() {
|
|
317
|
+
const args = parseArgs(process.argv);
|
|
318
|
+
if (!args.input) {
|
|
319
|
+
console.error(
|
|
320
|
+
"Usage: node generate_report.js <input.json|-> [-o output.html] [--skill-name NAME]",
|
|
321
|
+
);
|
|
322
|
+
process.exit(1);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
let data;
|
|
326
|
+
if (args.input === "-") {
|
|
327
|
+
data = JSON.parse(fs.readFileSync(0, "utf-8"));
|
|
328
|
+
} else {
|
|
329
|
+
data = JSON.parse(fs.readFileSync(args.input, "utf-8"));
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const htmlOutput = generateHtml(data, false, args.skillName);
|
|
333
|
+
if (args.output) {
|
|
334
|
+
fs.writeFileSync(args.output, htmlOutput);
|
|
335
|
+
console.error(`Report written to ${args.output}`);
|
|
336
|
+
} else {
|
|
337
|
+
process.stdout.write(htmlOutput);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (require.main === module) {
|
|
342
|
+
main();
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
module.exports = { generateHtml };
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/** Improve a skill description based on eval results. */
|
|
3
|
+
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const { spawnSync } = require("child_process");
|
|
7
|
+
const { parseSkillMd } = require("./utils.js");
|
|
8
|
+
|
|
9
|
+
function callTarsk(prompt, model, timeoutMs = 300000) {
|
|
10
|
+
const cmd = ["tarsk", "-p", "--output-format", "text"];
|
|
11
|
+
if (model) {
|
|
12
|
+
cmd.push("--model", model);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const env = { ...process.env };
|
|
16
|
+
delete env.TarskCODE;
|
|
17
|
+
|
|
18
|
+
const result = spawnSync("tarsk", cmd.slice(1), {
|
|
19
|
+
input: prompt,
|
|
20
|
+
encoding: "utf-8",
|
|
21
|
+
env,
|
|
22
|
+
timeout: timeoutMs,
|
|
23
|
+
maxBuffer: 50 * 1024 * 1024,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
if (result.error) {
|
|
27
|
+
throw result.error;
|
|
28
|
+
}
|
|
29
|
+
if (result.status !== 0) {
|
|
30
|
+
throw new Error(`tarsk -p exited ${result.status}\nstderr: ${result.stderr}`);
|
|
31
|
+
}
|
|
32
|
+
return result.stdout;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function improveDescription(
|
|
36
|
+
skillName,
|
|
37
|
+
skillContent,
|
|
38
|
+
currentDescription,
|
|
39
|
+
evalResults,
|
|
40
|
+
history,
|
|
41
|
+
model,
|
|
42
|
+
testResults = null,
|
|
43
|
+
logDir = null,
|
|
44
|
+
iteration = null,
|
|
45
|
+
) {
|
|
46
|
+
const failedTriggers = evalResults.results.filter((r) => r.should_trigger && !r.pass);
|
|
47
|
+
const falseTriggers = evalResults.results.filter((r) => !r.should_trigger && !r.pass);
|
|
48
|
+
|
|
49
|
+
const trainScore = `${evalResults.summary.passed}/${evalResults.summary.total}`;
|
|
50
|
+
let scoresSummary;
|
|
51
|
+
if (testResults) {
|
|
52
|
+
const testScore = `${testResults.summary.passed}/${testResults.summary.total}`;
|
|
53
|
+
scoresSummary = `Train: ${trainScore}, Test: ${testScore}`;
|
|
54
|
+
} else {
|
|
55
|
+
scoresSummary = `Train: ${trainScore}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let prompt = `You are optimizing a skill description for a Tarsk Code skill called "${skillName}". A "skill" is sort of like a prompt, but with progressive disclosure -- there's a title and description that Tarsk sees when deciding whether to use the skill, and then if it does use the skill, it reads the .md file which has lots more details and potentially links to other resources in the skill folder like helper files and scripts and additional documentation or examples.
|
|
59
|
+
|
|
60
|
+
The description appears in Tarsk's "available_skills" list. When a user sends a query, Tarsk decides whether to invoke the skill based solely on the title and on this description. Your goal is to write a description that triggers for relevant queries, and doesn't trigger for irrelevant ones.
|
|
61
|
+
|
|
62
|
+
Here's the current description:
|
|
63
|
+
<current_description>
|
|
64
|
+
"${currentDescription}"
|
|
65
|
+
</current_description>
|
|
66
|
+
|
|
67
|
+
Current scores (${scoresSummary}):
|
|
68
|
+
<scores_summary>
|
|
69
|
+
`;
|
|
70
|
+
|
|
71
|
+
if (failedTriggers.length) {
|
|
72
|
+
prompt += "FAILED TO TRIGGER (should have triggered but didn't):\n";
|
|
73
|
+
for (const r of failedTriggers) {
|
|
74
|
+
prompt += ` - "${r.query}" (triggered ${r.triggers}/${r.runs} times)\n`;
|
|
75
|
+
}
|
|
76
|
+
prompt += "\n";
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (falseTriggers.length) {
|
|
80
|
+
prompt += "FALSE TRIGGERS (triggered but shouldn't have):\n";
|
|
81
|
+
for (const r of falseTriggers) {
|
|
82
|
+
prompt += ` - "${r.query}" (triggered ${r.triggers}/${r.runs} times)\n`;
|
|
83
|
+
}
|
|
84
|
+
prompt += "\n";
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (history.length) {
|
|
88
|
+
prompt += "PREVIOUS ATTEMPTS (do NOT repeat these — try something structurally different):\n\n";
|
|
89
|
+
for (const h of history) {
|
|
90
|
+
const trainS = `${h.train_passed ?? h.passed ?? 0}/${h.train_total ?? h.total ?? 0}`;
|
|
91
|
+
const testS = h.test_passed != null ? `${h.test_passed}/${h.test_total ?? "?"}` : null;
|
|
92
|
+
const scoreStr = `train=${trainS}` + (testS ? `, test=${testS}` : "");
|
|
93
|
+
prompt += `<attempt ${scoreStr}>\n`;
|
|
94
|
+
prompt += `Description: "${h.description}"\n`;
|
|
95
|
+
if (h.results) {
|
|
96
|
+
prompt += "Train results:\n";
|
|
97
|
+
for (const r of h.results) {
|
|
98
|
+
const status = r.pass ? "PASS" : "FAIL";
|
|
99
|
+
prompt += ` [${status}] "${r.query.slice(0, 80)}" (triggered ${r.triggers}/${r.runs})\n`;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (h.note) {
|
|
103
|
+
prompt += `Note: ${h.note}\n`;
|
|
104
|
+
}
|
|
105
|
+
prompt += "</attempt>\n\n";
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
prompt += `</scores_summary>
|
|
110
|
+
|
|
111
|
+
Skill content (for context on what the skill does):
|
|
112
|
+
<skill_content>
|
|
113
|
+
${skillContent}
|
|
114
|
+
</skill_content>
|
|
115
|
+
|
|
116
|
+
Based on the failures, write a new and improved description that is more likely to trigger correctly. When I say "based on the failures", it's a bit of a tricky line to walk because we don't want to overfit to the specific cases you're seeing. So what I DON'T want you to do is produce an ever-expanding list of specific queries that this skill should or shouldn't trigger for. Instead, try to generalize from the failures to broader categories of user intent and situations where this skill would be useful or not useful. The reason for this is twofold:
|
|
117
|
+
|
|
118
|
+
1. Avoid overfitting
|
|
119
|
+
2. The list might get loooong and it's injected into ALL queries and there might be a lot of skills, so we don't want to blow too much space on any given description.
|
|
120
|
+
|
|
121
|
+
Concretely, your description should not be more than about 100-200 words, even if that comes at the cost of accuracy. There is a hard limit of 1024 characters — descriptions over that will be truncated, so stay comfortably under it.
|
|
122
|
+
|
|
123
|
+
Here are some tips that we've found to work well in writing these descriptions:
|
|
124
|
+
- The skill should be phrased in the imperative -- "Use this skill for" rather than "this skill does"
|
|
125
|
+
- The skill description should focus on the user's intent, what they are trying to achieve, vs. the implementation details of how the skill works.
|
|
126
|
+
- The description competes with other skills for Tarsk's attention — make it distinctive and immediately recognizable.
|
|
127
|
+
- If you're getting lots of failures after repeated attempts, change things up. Try different sentence structures or wordings.
|
|
128
|
+
|
|
129
|
+
I'd encourage you to be creative and mix up the style in different iterations since you'll have multiple opportunities to try different approaches and we'll just grab the highest-scoring one at the end.
|
|
130
|
+
|
|
131
|
+
Please respond with only the new description text in <new_description> tags, nothing else.`;
|
|
132
|
+
|
|
133
|
+
let text = callTarsk(prompt, model);
|
|
134
|
+
let match = text.match(/<new_description>([\s\S]*?)<\/new_description>/);
|
|
135
|
+
let description = match
|
|
136
|
+
? match[1].trim().replace(/^["']|["']$/g, "")
|
|
137
|
+
: text.trim().replace(/^["']|["']$/g, "");
|
|
138
|
+
|
|
139
|
+
const transcript = {
|
|
140
|
+
iteration,
|
|
141
|
+
prompt,
|
|
142
|
+
response: text,
|
|
143
|
+
parsed_description: description,
|
|
144
|
+
char_count: description.length,
|
|
145
|
+
over_limit: description.length > 1024,
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
if (description.length > 1024) {
|
|
149
|
+
const shortenPrompt =
|
|
150
|
+
`${prompt}\n\n` +
|
|
151
|
+
`---\n\n` +
|
|
152
|
+
`A previous attempt produced this description, which at ` +
|
|
153
|
+
`${description.length} characters is over the 1024-character hard limit:\n\n` +
|
|
154
|
+
`"${description}"\n\n` +
|
|
155
|
+
`Rewrite it to be under 1024 characters while keeping the most ` +
|
|
156
|
+
`important trigger words and intent coverage. Respond with only ` +
|
|
157
|
+
`the new description in <new_description> tags.`;
|
|
158
|
+
const shortenText = callTarsk(shortenPrompt, model);
|
|
159
|
+
match = shortenText.match(/<new_description>([\s\S]*?)<\/new_description>/);
|
|
160
|
+
const shortened = match
|
|
161
|
+
? match[1].trim().replace(/^["']|["']$/g, "")
|
|
162
|
+
: shortenText.trim().replace(/^["']|["']$/g, "");
|
|
163
|
+
|
|
164
|
+
transcript.rewrite_prompt = shortenPrompt;
|
|
165
|
+
transcript.rewrite_response = shortenText;
|
|
166
|
+
transcript.rewrite_description = shortened;
|
|
167
|
+
transcript.rewrite_char_count = shortened.length;
|
|
168
|
+
description = shortened;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
transcript.final_description = description;
|
|
172
|
+
|
|
173
|
+
if (logDir) {
|
|
174
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
175
|
+
const logFile = path.join(logDir, `improve_iter_${iteration ?? "unknown"}.json`);
|
|
176
|
+
fs.writeFileSync(logFile, JSON.stringify(transcript, null, 2));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return description;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function parseArgs(argv) {
|
|
183
|
+
const args = {
|
|
184
|
+
evalResults: null,
|
|
185
|
+
skillPath: null,
|
|
186
|
+
history: null,
|
|
187
|
+
model: null,
|
|
188
|
+
verbose: false,
|
|
189
|
+
};
|
|
190
|
+
for (let i = 2; i < argv.length; i++) {
|
|
191
|
+
const arg = argv[i];
|
|
192
|
+
if (arg === "--eval-results") args.evalResults = argv[++i];
|
|
193
|
+
else if (arg === "--skill-path") args.skillPath = argv[++i];
|
|
194
|
+
else if (arg === "--history") args.history = argv[++i];
|
|
195
|
+
else if (arg === "--model") args.model = argv[++i];
|
|
196
|
+
else if (arg === "--verbose") args.verbose = true;
|
|
197
|
+
}
|
|
198
|
+
return args;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function main() {
|
|
202
|
+
const args = parseArgs(process.argv);
|
|
203
|
+
if (!args.evalResults || !args.skillPath || !args.model) {
|
|
204
|
+
console.error(
|
|
205
|
+
"Usage: node improve_description.js --eval-results PATH --skill-path PATH --model MODEL [--history PATH] [--verbose]",
|
|
206
|
+
);
|
|
207
|
+
process.exit(1);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const skillPath = path.resolve(args.skillPath);
|
|
211
|
+
if (!fs.existsSync(path.join(skillPath, "SKILL.md"))) {
|
|
212
|
+
console.error(`Error: No SKILL.md found at ${skillPath}`);
|
|
213
|
+
process.exit(1);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const evalResults = JSON.parse(fs.readFileSync(args.evalResults, "utf-8"));
|
|
217
|
+
let history = [];
|
|
218
|
+
if (args.history) {
|
|
219
|
+
history = JSON.parse(fs.readFileSync(args.history, "utf-8"));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const { name, content } = parseSkillMd(skillPath);
|
|
223
|
+
const currentDescription = evalResults.description;
|
|
224
|
+
|
|
225
|
+
if (args.verbose) {
|
|
226
|
+
console.error(`Current: ${currentDescription}`);
|
|
227
|
+
console.error(`Score: ${evalResults.summary.passed}/${evalResults.summary.total}`);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const newDescription = improveDescription(
|
|
231
|
+
name,
|
|
232
|
+
content,
|
|
233
|
+
currentDescription,
|
|
234
|
+
evalResults,
|
|
235
|
+
history,
|
|
236
|
+
args.model,
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
if (args.verbose) {
|
|
240
|
+
console.error(`Improved: ${newDescription}`);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const output = {
|
|
244
|
+
description: newDescription,
|
|
245
|
+
history: [
|
|
246
|
+
...history,
|
|
247
|
+
{
|
|
248
|
+
description: currentDescription,
|
|
249
|
+
passed: evalResults.summary.passed,
|
|
250
|
+
failed: evalResults.summary.failed,
|
|
251
|
+
total: evalResults.summary.total,
|
|
252
|
+
results: evalResults.results,
|
|
253
|
+
},
|
|
254
|
+
],
|
|
255
|
+
};
|
|
256
|
+
console.log(JSON.stringify(output, null, 2));
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (require.main === module) {
|
|
260
|
+
main();
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
module.exports = { improveDescription, callTarsk };
|