tarsk 0.5.78 → 0.5.80

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/dist/bundled-skills/skill-creator/eval-viewer/generate_review.js +183 -95
  2. package/dist/bundled-skills/skill-creator/scripts/__tests__/improve_description.test.js +77 -0
  3. package/dist/bundled-skills/skill-creator/scripts/__tests__/package_skill.test.js +203 -0
  4. package/dist/bundled-skills/skill-creator/scripts/__tests__/run_eval.test.js +125 -0
  5. package/dist/bundled-skills/skill-creator/scripts/__tests__/utils.test.js +149 -0
  6. package/dist/bundled-skills/skill-creator/scripts/aggregate_benchmark.js +2 -0
  7. package/dist/bundled-skills/skill-creator/scripts/improve_description.js +42 -22
  8. package/dist/bundled-skills/skill-creator/scripts/package_skill.js +68 -40
  9. package/dist/bundled-skills/skill-creator/scripts/run_eval.js +60 -32
  10. package/dist/bundled-skills/skill-creator/scripts/run_loop.js +224 -150
  11. package/dist/bundled-skills/skill-creator/scripts/utils.js +71 -41
  12. package/dist/index.js +267445 -257080
  13. package/dist/node_modules/@libsql/darwin-x64/README.md +3 -0
  14. package/dist/node_modules/@libsql/darwin-x64/index.node +0 -0
  15. package/dist/node_modules/@libsql/darwin-x64/package.json +36 -0
  16. package/dist/public/assets/account-view-DVfacvrH.js +1 -0
  17. package/dist/public/assets/alert-dialog-DTxl_mXc.js +7 -0
  18. package/dist/public/assets/api-B_ZIptgC.js +2 -0
  19. package/dist/public/assets/{async-error-boundary-Dll_zcqr.js → async-error-boundary-DmJy8jgz.js} +1 -1
  20. package/dist/public/assets/browser-tab-BZSxX4fR.js +12 -0
  21. package/dist/public/assets/{button-B5aMz2Rk.js → button-DhxraVUW.js} +1 -1
  22. package/dist/public/assets/{card-DRr14MEQ.js → card-DZy9jCWQ.js} +1 -1
  23. package/dist/public/assets/chat-dock-context-DhO-jYMh.js +1 -0
  24. package/dist/public/assets/{commit-dialog-C-JfaoS6.js → commit-dialog-D0z3emO_.js} +1 -1
  25. package/dist/public/assets/{commit-error-dialog-VXqZLeta.js → commit-error-dialog-CVSOwpqp.js} +1 -1
  26. package/dist/public/assets/{commit-method-fields-DAd1unUF.js → commit-method-fields-C7Du43jy.js} +1 -1
  27. package/dist/public/assets/conversation-history-view-CkI6-Hvt.js +1 -0
  28. package/dist/public/assets/create-project-dialog-Ciiyx0LH.js +2 -0
  29. package/dist/public/assets/{create-repo-dialog-mBGGkX-g.js → create-repo-dialog-lS-Tko1c.js} +1 -1
  30. package/dist/public/assets/delete-confirm-dialog-Ciwqi6ts.js +1 -0
  31. package/dist/public/assets/diff-view-DKEDlvjR.js +4 -0
  32. package/dist/public/assets/{dist-COfI64N5.js → dist-BZmr2Qex.js} +1 -1
  33. package/dist/public/assets/{dist-DwblUvSf.js → dist-Bihbg_hO.js} +1 -1
  34. package/dist/public/assets/{dist-TCNzmc2V.js → dist-BkOEUrQf.js} +1 -1
  35. package/dist/public/assets/{dist-DDYi9MQo.js → dist-CA_zFhwJ.js} +1 -1
  36. package/dist/public/assets/dist-DZtmmQ-F.js +2 -0
  37. package/dist/public/assets/{dist-sThe8WYP.js → dist-Dj1qT-gv.js} +1 -1
  38. package/dist/public/assets/{dist-CWYnBq-n.js → dist-aB1qUwWW.js} +1 -1
  39. package/dist/public/assets/{dist-T8-4ZfS0.js → dist-iDwSbcD5.js} +1 -1
  40. package/dist/public/assets/{es2015-Bu0NIPbn.js → es2015-B0KHrBGH.js} +1 -1
  41. package/dist/public/assets/{explorer-monaco-editor-q8CnSwWA.js → explorer-monaco-editor-YoaTqh0f.js} +1 -1
  42. package/dist/public/assets/explorer-tab-view-DlBIsvNp.js +2 -0
  43. package/dist/public/assets/explorer-tree-BWb4wB5M.js +1 -0
  44. package/dist/public/assets/explorer-view-qOBi4ACi.js +76 -0
  45. package/dist/public/assets/{git-history-dialog-XEr5pugf.js → git-history-dialog-DOcupC-P.js} +1 -1
  46. package/dist/public/assets/index-B3oaBks2.css +1 -0
  47. package/dist/public/assets/index-DdJcjvu2.js +112 -0
  48. package/dist/public/assets/{lucide-react-B-115rul.js → lucide-react-CNNzKD4G.js} +1 -1
  49. package/dist/public/assets/{model-list-controls-DAZM0NBC.js → model-list-controls-DOpHpzG8.js} +1 -1
  50. package/dist/public/assets/onboarding-dialog-CfkWB6ST.js +1 -0
  51. package/dist/public/assets/{pill-tabs-QsjH027B.js → pill-tabs-CiONEGsi.js} +1 -1
  52. package/dist/public/assets/{progress-indicator-C01aPXIe.js → progress-indicator-C7Kj9U3t.js} +1 -1
  53. package/dist/public/assets/project-coding-view-CrOGfL2D.js +1 -0
  54. package/dist/public/assets/{project-danger-zone-view-BaSu793w.js → project-danger-zone-view-BfHTp5LI.js} +1 -1
  55. package/dist/public/assets/project-env-vars-view-QrpeAxIt.js +1 -0
  56. package/dist/public/assets/project-model-aliases-view-D_pamHsA.js +1 -0
  57. package/dist/public/assets/project-settings-view-C04tQu0x.js +1 -0
  58. package/dist/public/assets/project-tools-view-0j2uWvRy.js +1 -0
  59. package/dist/public/assets/providers-list-view-4pcZxBFJ.js +1 -0
  60. package/dist/public/assets/pull-request-dialog-HXBislex.js +1 -0
  61. package/dist/public/assets/{pull-with-changes-dialog-DDp_srHq.js → pull-with-changes-dialog-CFLl2SQP.js} +1 -1
  62. package/dist/public/assets/{push-before-pr-dialog-Bt0BChlV.js → push-before-pr-dialog-CD1rZKan.js} +1 -1
  63. package/dist/public/assets/{scroll-area-DwRwR2nQ.js → scroll-area-BQ9bdsVQ.js} +1 -1
  64. package/dist/public/assets/settings-general-tabs-view-CKaL-A9_.js +2 -0
  65. package/dist/public/assets/settings-general-view-ybttpP37.js +1 -0
  66. package/dist/public/assets/settings-list-n-CrPPNl.js +1 -0
  67. package/dist/public/assets/settings-logs-view-CFD-_MKT.js +5 -0
  68. package/dist/public/assets/settings-mcp-servers-view-kTpnzhrf.js +5 -0
  69. package/dist/public/assets/settings-models-view-RmIvRn-i.js +1 -0
  70. package/dist/public/assets/settings-plugins-view-fSxDPjsF.js +2 -0
  71. package/dist/public/assets/settings-providers-view-Bj4-iemE.js +2 -0
  72. package/dist/public/assets/settings-setup-view-ORNCF3ma.js +2 -0
  73. package/dist/public/assets/settings-skills-view-Dl38QbWw.js +2 -0
  74. package/dist/public/assets/settings-slack-view-Dx4Pdxjq.js +1 -0
  75. package/dist/public/assets/settings-slash-commands-view-CSm-ZqwM.js +1 -0
  76. package/dist/public/assets/settings-statistics-view-B8YLP2Nb.js +1 -0
  77. package/dist/public/assets/settings-storage-view-Cnmt9-0H.js +1 -0
  78. package/dist/public/assets/settings-subagents-view-Be7aa0Va.js +2 -0
  79. package/dist/public/assets/settings-system-prompt-view-KlPFbkat.js +1 -0
  80. package/dist/public/assets/{settings-tab-bar-DAHxT5KE.js → settings-tab-bar-BJtpuIky.js} +1 -1
  81. package/dist/public/assets/settings-view-dchqYhRC.js +2 -0
  82. package/dist/public/assets/{slug-utils-BQZ6c2py.js → slug-utils-Ccbyp4Mu.js} +1 -1
  83. package/dist/public/assets/store-D4D62vkY.js +9 -0
  84. package/dist/public/assets/{table-7hMwxpkF.js → table-DCmeRDBK.js} +1 -1
  85. package/dist/public/assets/tasks-view-CRBklv4O.js +1 -0
  86. package/dist/public/assets/{terminal-panel-BNOIOh8z.js → terminal-panel-DlfAu8U1.js} +2 -2
  87. package/dist/public/assets/{toggle-group-UiksQ0n9.js → toggle-group-Dl5P_F6a.js} +1 -1
  88. package/dist/public/assets/{tooltip-Dvmz78Z_.js → tooltip-Cg_EhO9M.js} +1 -1
  89. package/dist/public/assets/{use-active-dock-panel-instance-2LfvItRH.js → use-active-dock-panel-instance-iaFSG9kX.js} +1 -1
  90. package/dist/public/assets/use-git-check-BfkMHX3g.js +1 -0
  91. package/dist/public/assets/use-installed-ai-files-BtR-wIuu.js +1 -0
  92. package/dist/public/assets/use-settings-page-loading-CpaBd260.js +1 -0
  93. package/dist/public/assets/use-toast-xpb5jNtN.js +1 -0
  94. package/dist/public/assets/{useBaseQuery-D5as4SgS.js → useBaseQuery-ChJtNS_3.js} +1 -1
  95. package/dist/public/assets/utils-BCj_kT9l.js +1 -0
  96. package/dist/public/assets/{web-B9ndApmi.js → web-BC7Gc4bP.js} +1 -1
  97. package/dist/public/assets/{web-DyqJbQ3L.js → web-BGZnt3gU.js} +1 -1
  98. package/dist/public/assets/{web-gEWIDddb.js → web-BdHVYPFA.js} +1 -1
  99. package/dist/public/assets/{web-BkuMY08B.js → web-C5YunZdw.js} +1 -1
  100. package/dist/public/browser-preview-rpc.js +123 -68
  101. package/dist/public/index.html +25 -23
  102. package/dist/scaffold-templates.json +1 -1
  103. package/package.json +6 -5
  104. package/dist/public/assets/account-view-Bzv2EHnc.js +0 -1
  105. package/dist/public/assets/api-OO3KSG8d.js +0 -2
  106. package/dist/public/assets/browser-tab-Cwa1TB3I.js +0 -12
  107. package/dist/public/assets/chat-dock-context-Cnq0BWgm.js +0 -1
  108. package/dist/public/assets/context-menu-BKOotkeD.js +0 -1
  109. package/dist/public/assets/conversation-history-view-CcdQRxoP.js +0 -1
  110. package/dist/public/assets/create-project-dialog-D1VaSgqh.js +0 -2
  111. package/dist/public/assets/delete-confirm-dialog-Cx9nT7Xd.js +0 -1
  112. package/dist/public/assets/diff-view-CyOnLSEA.js +0 -4
  113. package/dist/public/assets/dist-DNkA3vYh.js +0 -2
  114. package/dist/public/assets/explorer-tab-view-G2HmkQyY.js +0 -2
  115. package/dist/public/assets/explorer-tree-DAuc2Npx.js +0 -1
  116. package/dist/public/assets/explorer-view-BDMlnV3f.js +0 -76
  117. package/dist/public/assets/index-C4WzOmf3.css +0 -1
  118. package/dist/public/assets/index-DD_a7tQU.js +0 -3665
  119. package/dist/public/assets/onboarding-dialog-Dd9hiOOf.js +0 -1
  120. package/dist/public/assets/project-coding-view-DbKI7sbW.js +0 -1
  121. package/dist/public/assets/project-env-vars-view-BB66j52E.js +0 -1
  122. package/dist/public/assets/project-model-aliases-view-o1-Z2es5.js +0 -1
  123. package/dist/public/assets/project-settings-view-BKjvtOtk.js +0 -1
  124. package/dist/public/assets/project-tools-view-CZbblznF.js +0 -1
  125. package/dist/public/assets/providers-list-view-ChY2DXYc.js +0 -1
  126. package/dist/public/assets/pull-request-dialog-BZgY4vrw.js +0 -1
  127. package/dist/public/assets/settings-general-tabs-view-BH7AaXyP.js +0 -2
  128. package/dist/public/assets/settings-general-view-B-t99LmD.js +0 -1
  129. package/dist/public/assets/settings-list-Dn4CHP4Z.js +0 -1
  130. package/dist/public/assets/settings-logs-view-BCRjz-Fr.js +0 -5
  131. package/dist/public/assets/settings-mcp-servers-view-xv9D4DPO.js +0 -5
  132. package/dist/public/assets/settings-models-view-rUeqlE4k.js +0 -1
  133. package/dist/public/assets/settings-plugins-view-qnWmII61.js +0 -2
  134. package/dist/public/assets/settings-providers-view-DsxTdBEQ.js +0 -2
  135. package/dist/public/assets/settings-setup-view-ew5KW8gL.js +0 -2
  136. package/dist/public/assets/settings-skills-view-C4NxP4gI.js +0 -3
  137. package/dist/public/assets/settings-slack-view-BNT5ervf.js +0 -1
  138. package/dist/public/assets/settings-slash-commands-view-CE7FT3dB.js +0 -1
  139. package/dist/public/assets/settings-statistics-view-jKPoSWBs.js +0 -1
  140. package/dist/public/assets/settings-storage-view-C_Rs-9Hw.js +0 -1
  141. package/dist/public/assets/settings-subagents-view-DHVSAmCY.js +0 -2
  142. package/dist/public/assets/settings-system-prompt-view-B_nF2HUy.js +0 -1
  143. package/dist/public/assets/settings-view-BY6OO-xy.js +0 -2
  144. package/dist/public/assets/store-B7EgrEO7.js +0 -9
  145. package/dist/public/assets/tasks-view-Dc7sthjm.js +0 -1
  146. package/dist/public/assets/use-installed-ai-files-BkpswqGI.js +0 -1
  147. package/dist/public/assets/use-settings-page-loading-BTySlInF.js +0 -1
  148. package/dist/public/assets/utils-B-qS8qMg.js +0 -1
@@ -247,37 +247,47 @@ function embedFile(filePath) {
247
247
  }
248
248
  }
249
249
 
250
- function loadPreviousIteration(workspace) {
251
- const result = {};
250
+ function readFeedbackMap(workspace) {
252
251
  const feedbackMap = {};
253
252
  const feedbackPath = path.join(workspace, "feedback.json");
254
- if (fs.existsSync(feedbackPath)) {
255
- try {
256
- const data = JSON.parse(fs.readFileSync(feedbackPath, "utf-8"));
257
- for (const review of data.reviews ?? []) {
258
- if ((review.feedback ?? "").trim()) {
259
- feedbackMap[review.run_id] = review.feedback;
260
- }
253
+ if (!fs.existsSync(feedbackPath)) return feedbackMap;
254
+
255
+ try {
256
+ const data = JSON.parse(fs.readFileSync(feedbackPath, "utf-8"));
257
+ for (const review of data.reviews ?? []) {
258
+ if ((review.feedback ?? "").trim()) {
259
+ feedbackMap[review.run_id] = review.feedback;
261
260
  }
262
- } catch {
263
- // ignore
264
261
  }
262
+ } catch {
263
+ // Ignore missing or malformed previous feedback.
265
264
  }
265
+ return feedbackMap;
266
+ }
266
267
 
267
- const prevRuns = findRuns(workspace);
268
- for (const run of prevRuns) {
268
+ function mapRunsToPreviousIteration(runs, feedbackMap) {
269
+ const result = {};
270
+ for (const run of runs) {
269
271
  result[run.id] = {
270
272
  feedback: feedbackMap[run.id] ?? "",
271
273
  outputs: run.outputs ?? [],
272
274
  };
273
275
  }
276
+ return result;
277
+ }
274
278
 
279
+ function addOrphanedFeedback(result, feedbackMap) {
275
280
  for (const [runId, feedback] of Object.entries(feedbackMap)) {
276
281
  if (!result[runId]) {
277
282
  result[runId] = { feedback, outputs: [] };
278
283
  }
279
284
  }
285
+ }
280
286
 
287
+ function loadPreviousIteration(workspace) {
288
+ const feedbackMap = readFeedbackMap(workspace);
289
+ const result = mapRunsToPreviousIteration(findRuns(workspace), feedbackMap);
290
+ addOrphanedFeedback(result, feedbackMap);
281
291
  return result;
282
292
  }
283
293
 
@@ -306,32 +316,80 @@ function generateHtml(runs, skillName, previous = null, benchmark = null) {
306
316
  return template.replace("/*__EMBEDDED_DATA__*/", `const EMBEDDED_DATA = ${dataJson};`);
307
317
  }
308
318
 
309
- function killPort(port) {
310
- try {
311
- const result = spawnSync("lsof", ["-ti", `:${port}`], {
312
- encoding: "utf-8",
313
- timeout: 5000,
314
- });
315
- for (const pidStr of (result.stdout ?? "").trim().split("\n")) {
316
- if (!pidStr.trim()) continue;
317
- try {
318
- process.kill(parseInt(pidStr.trim(), 10), "SIGTERM");
319
- } catch {
320
- // ignore
321
- }
319
+ function parseProcessIds(output) {
320
+ const processIds = new Set();
321
+ for (const line of (output ?? "").split("\n")) {
322
+ const candidate = line.trim();
323
+ if (!/^\d+$/.test(candidate)) continue;
324
+
325
+ const processId = Number.parseInt(candidate, 10);
326
+ if (Number.isSafeInteger(processId) && processId > 0) {
327
+ processIds.add(processId);
322
328
  }
323
- if ((result.stdout ?? "").trim()) {
324
- const sleep = spawnSync("sleep", ["0.5"], { stdio: "ignore" });
325
- if (sleep.error) {
326
- const deadline = Date.now() + 500;
327
- while (Date.now() < deadline) {
328
- // brief pause after killing port holder
329
- }
330
- }
329
+ }
330
+ return [...processIds];
331
+ }
332
+
333
+ function discoverPortProcesses(port, runCommand) {
334
+ const result = runCommand("lsof", ["-ti", `:${port}`], {
335
+ encoding: "utf-8",
336
+ timeout: 5000,
337
+ });
338
+ if (result.error) throw result.error;
339
+ return parseProcessIds(result.stdout);
340
+ }
341
+
342
+ function terminateProcesses(processIds, terminateProcess) {
343
+ for (const processId of processIds) {
344
+ try {
345
+ terminateProcess(processId, "SIGTERM");
346
+ } catch {
347
+ // Ignore processes which have already exited or cannot be terminated.
331
348
  }
349
+ }
350
+ }
351
+
352
+ function busyWait(duration, getTime) {
353
+ const deadline = getTime() + duration;
354
+ while (getTime() < deadline) {
355
+ // Preserve the fallback when the sleep utility is unavailable.
356
+ }
357
+ }
358
+
359
+ function pauseAfterTermination(runCommand, getTime) {
360
+ try {
361
+ const sleep = runCommand("sleep", ["0.5"], { stdio: "ignore" });
362
+ if (!sleep.error) return;
363
+ } catch {
364
+ // Fall back to a brief busy wait below.
365
+ }
366
+ busyWait(500, getTime);
367
+ }
368
+
369
+ function killProcess(processId, signal) {
370
+ process.kill(processId, signal);
371
+ }
372
+
373
+ function writeStandardError(message) {
374
+ process.stderr.write(message);
375
+ }
376
+
377
+ function killPort(
378
+ port,
379
+ {
380
+ runCommand = spawnSync,
381
+ terminateProcess = killProcess,
382
+ getTime = Date.now,
383
+ writeError = writeStandardError,
384
+ } = {},
385
+ ) {
386
+ try {
387
+ const processIds = discoverPortProcesses(port, runCommand);
388
+ terminateProcesses(processIds, terminateProcess);
389
+ if (processIds.length > 0) pauseAfterTermination(runCommand, getTime);
332
390
  } catch (error) {
333
391
  if (error.message?.includes("ENOENT")) {
334
- process.stderr.write("Note: lsof not found, cannot check if port is in use\n");
392
+ writeError("Note: lsof not found, cannot check if port is in use\n");
335
393
  }
336
394
  }
337
395
  }
@@ -346,70 +404,98 @@ function openUrl(url) {
346
404
  }
347
405
  }
348
406
 
349
- function createReviewServer({ workspace, skillName, feedbackPath, previous, benchmarkPath }) {
350
- return http.createServer((req, res) => {
351
- const url = new URL(req.url ?? "/", "http://127.0.0.1");
352
-
353
- if (req.method === "GET" && (url.pathname === "/" || url.pathname === "/index.html")) {
354
- const runs = findRuns(workspace);
355
- let benchmark = null;
356
- if (benchmarkPath && fs.existsSync(benchmarkPath)) {
357
- try {
358
- benchmark = JSON.parse(fs.readFileSync(benchmarkPath, "utf-8"));
359
- } catch {
360
- // ignore
361
- }
362
- }
363
- const html = generateHtml(runs, skillName, previous, benchmark);
364
- const content = Buffer.from(html, "utf-8");
365
- res.writeHead(200, {
366
- "Content-Type": "text/html; charset=utf-8",
367
- "Content-Length": content.length,
368
- });
369
- res.end(content);
370
- return;
371
- }
407
+ function loadBenchmark(benchmarkPath) {
408
+ if (!benchmarkPath || !fs.existsSync(benchmarkPath)) return null;
372
409
 
373
- if (req.method === "GET" && url.pathname === "/api/feedback") {
374
- const data = fs.existsSync(feedbackPath) ? fs.readFileSync(feedbackPath) : Buffer.from("{}");
375
- res.writeHead(200, {
376
- "Content-Type": "application/json",
377
- "Content-Length": data.length,
378
- });
379
- res.end(data);
380
- return;
381
- }
410
+ try {
411
+ return JSON.parse(fs.readFileSync(benchmarkPath, "utf-8"));
412
+ } catch {
413
+ return null;
414
+ }
415
+ }
382
416
 
383
- if (req.method === "POST" && url.pathname === "/api/feedback") {
384
- const chunks = [];
385
- req.on("data", (chunk) => chunks.push(chunk));
386
- req.on("end", () => {
387
- let resp;
388
- let status = 200;
389
- try {
390
- const body = Buffer.concat(chunks).toString("utf-8");
391
- const data = JSON.parse(body);
392
- if (!data || typeof data !== "object" || !("reviews" in data)) {
393
- throw new Error("Expected JSON object with 'reviews' key");
394
- }
395
- fs.writeFileSync(feedbackPath, `${JSON.stringify(data, null, 2)}\n`);
396
- resp = Buffer.from('{"ok":true}');
397
- } catch (error) {
398
- resp = Buffer.from(JSON.stringify({ error: error.message }));
399
- status = 500;
400
- }
401
- res.writeHead(status, {
402
- "Content-Type": "application/json",
403
- "Content-Length": resp.length,
404
- });
405
- res.end(resp);
406
- });
407
- return;
417
+ function sendResponse(res, status, contentType, content) {
418
+ res.writeHead(status, {
419
+ "Content-Type": contentType,
420
+ "Content-Length": content.length,
421
+ });
422
+ res.end(content);
423
+ }
424
+
425
+ function renderReviewPage(res, { workspace, skillName, previous, benchmarkPath }) {
426
+ const runs = findRuns(workspace);
427
+ const benchmark = loadBenchmark(benchmarkPath);
428
+ const html = generateHtml(runs, skillName, previous, benchmark);
429
+ sendResponse(res, 200, "text/html; charset=utf-8", Buffer.from(html, "utf-8"));
430
+ }
431
+
432
+ function sendFeedback(res, feedbackPath) {
433
+ const data = fs.existsSync(feedbackPath) ? fs.readFileSync(feedbackPath) : Buffer.from("{}");
434
+ sendResponse(res, 200, "application/json", data);
435
+ }
436
+
437
+ function persistFeedback(res, feedbackPath, chunks) {
438
+ let content;
439
+ let status = 200;
440
+ try {
441
+ const body = Buffer.concat(chunks).toString("utf-8");
442
+ const data = JSON.parse(body);
443
+ if (!data || typeof data !== "object" || !("reviews" in data)) {
444
+ throw new Error("Expected JSON object with 'reviews' key");
408
445
  }
446
+ fs.writeFileSync(feedbackPath, `${JSON.stringify(data, null, 2)}\n`);
447
+ content = Buffer.from('{"ok":true}');
448
+ } catch (error) {
449
+ content = Buffer.from(JSON.stringify({ error: error.message }));
450
+ status = 500;
451
+ }
452
+ sendResponse(res, status, "application/json", content);
453
+ }
409
454
 
410
- res.writeHead(404);
411
- res.end();
412
- });
455
+ function receiveFeedback(req, res, feedbackPath) {
456
+ const chunks = [];
457
+
458
+ function collectChunk(chunk) {
459
+ chunks.push(chunk);
460
+ }
461
+
462
+ function finishFeedback() {
463
+ persistFeedback(res, feedbackPath, chunks);
464
+ }
465
+
466
+ req.on("data", collectChunk);
467
+ req.on("end", finishFeedback);
468
+ }
469
+
470
+ function routeReviewRequest(req, res, options) {
471
+ const url = new URL(req.url ?? "/", "http://127.0.0.1");
472
+ const isFeedbackRequest = url.pathname === "/api/feedback";
473
+
474
+ if (req.method === "GET" && (url.pathname === "/" || url.pathname === "/index.html")) {
475
+ renderReviewPage(res, options);
476
+ return;
477
+ }
478
+ if (req.method === "GET" && isFeedbackRequest) {
479
+ sendFeedback(res, options.feedbackPath);
480
+ return;
481
+ }
482
+ if (req.method === "POST" && isFeedbackRequest) {
483
+ receiveFeedback(req, res, options.feedbackPath);
484
+ return;
485
+ }
486
+
487
+ res.writeHead(404);
488
+ res.end();
489
+ }
490
+
491
+ function createReviewServer({ workspace, skillName, feedbackPath, previous, benchmarkPath }) {
492
+ const options = { workspace, skillName, feedbackPath, previous, benchmarkPath };
493
+
494
+ function handleReviewRequest(req, res) {
495
+ routeReviewRequest(req, res, options);
496
+ }
497
+
498
+ return http.createServer(handleReviewRequest);
413
499
  }
414
500
 
415
501
  function parseArgs(argv) {
@@ -540,6 +626,8 @@ function main() {
540
626
  });
541
627
  }
542
628
 
629
+ module.exports = { createReviewServer, killPort, loadPreviousIteration };
630
+
543
631
  if (require.main === module) {
544
632
  main();
545
633
  }
@@ -0,0 +1,77 @@
1
+ const assert = require("node:assert/strict");
2
+ const test = require("node:test");
3
+
4
+ const { buildHistoryText } = require("../improve_description.js");
5
+
6
+ const header =
7
+ "PREVIOUS ATTEMPTS (do NOT repeat these — try something structurally different):\n\n";
8
+ const longQuery = "q".repeat(81);
9
+
10
+ const cases = [
11
+ {
12
+ name: "returns an empty string for empty history",
13
+ history: [],
14
+ expected: "",
15
+ },
16
+ {
17
+ name: "renders legacy score fields",
18
+ history: [{ description: "Legacy description", passed: 2, total: 3 }],
19
+ expected: header + '<attempt train=2/3>\nDescription: "Legacy description"\n</attempt>\n\n',
20
+ },
21
+ {
22
+ name: "renders current train and test score fields",
23
+ history: [
24
+ {
25
+ description: "Current description",
26
+ train_passed: 4,
27
+ train_total: 5,
28
+ test_passed: 0,
29
+ test_total: 2,
30
+ },
31
+ ],
32
+ expected:
33
+ header + '<attempt train=4/5, test=0/2>\nDescription: "Current description"\n</attempt>\n\n',
34
+ },
35
+ {
36
+ name: "renders pass and fail results with truncated queries",
37
+ history: [
38
+ {
39
+ description: "Results description",
40
+ train_passed: 1,
41
+ train_total: 2,
42
+ results: [
43
+ { pass: true, query: "short query", triggers: 2, runs: 2 },
44
+ { pass: false, query: longQuery, triggers: 0, runs: 3 },
45
+ ],
46
+ },
47
+ ],
48
+ expected:
49
+ header +
50
+ '<attempt train=1/2>\nDescription: "Results description"\nTrain results:\n' +
51
+ ' [PASS] "short query" (triggered 2/2)\n' +
52
+ ` [FAIL] "${"q".repeat(80)}" (triggered 0/3)\n` +
53
+ "</attempt>\n\n",
54
+ },
55
+ {
56
+ name: "renders notes and unknown test totals",
57
+ history: [
58
+ {
59
+ description: "Noted description",
60
+ train_passed: 3,
61
+ train_total: 3,
62
+ test_passed: 1,
63
+ note: "Try narrower wording.",
64
+ },
65
+ ],
66
+ expected:
67
+ header +
68
+ '<attempt train=3/3, test=1/?>\nDescription: "Noted description"\n' +
69
+ "Note: Try narrower wording.\n</attempt>\n\n",
70
+ },
71
+ ];
72
+
73
+ for (const testCase of cases) {
74
+ test(testCase.name, () => {
75
+ assert.equal(buildHistoryText(testCase.history), testCase.expected);
76
+ });
77
+ }
@@ -0,0 +1,203 @@
1
+ const assert = require("node:assert/strict");
2
+ const fs = require("node:fs");
3
+ const os = require("node:os");
4
+ const path = require("node:path");
5
+ const test = require("node:test");
6
+
7
+ const { packageSkill } = require("../package_skill.js");
8
+
9
+ function withTemporaryDirectory(callback) {
10
+ const temporaryDirectory = fs.mkdtempSync(path.join(os.tmpdir(), "tarsk-package-skill-"));
11
+ try {
12
+ return callback(temporaryDirectory);
13
+ } finally {
14
+ fs.rmSync(temporaryDirectory, { recursive: true, force: true });
15
+ }
16
+ }
17
+
18
+ function createSkill(rootDirectory, name = "example-skill") {
19
+ const skillPath = path.join(rootDirectory, name);
20
+ fs.mkdirSync(skillPath, { recursive: true });
21
+ fs.writeFileSync(
22
+ path.join(skillPath, "SKILL.md"),
23
+ "---\nname: example-skill\ndescription: Test\n---\n",
24
+ );
25
+ return skillPath;
26
+ }
27
+
28
+ function successfulValidation() {
29
+ return [true, "Skill is valid!"];
30
+ }
31
+
32
+ function unexpectedArchiveExecution() {
33
+ assert.fail("archive execution should not be reached");
34
+ }
35
+
36
+ function dependencies(overrides = {}) {
37
+ return {
38
+ validateSkill: successfulValidation,
39
+ executeArchive: unexpectedArchiveExecution,
40
+ ...overrides,
41
+ };
42
+ }
43
+
44
+ test("rejects a missing skill path before validation", function () {
45
+ withTemporaryDirectory(function (temporaryDirectory) {
46
+ const missingPath = path.join(temporaryDirectory, "missing");
47
+ assert.equal(packageSkill(missingPath, null, dependencies()), null);
48
+ });
49
+ });
50
+
51
+ test("rejects a skill path that is not a directory", function () {
52
+ withTemporaryDirectory(function (temporaryDirectory) {
53
+ const filePath = path.join(temporaryDirectory, "skill.txt");
54
+ fs.writeFileSync(filePath, "not a directory");
55
+ assert.equal(packageSkill(filePath, null, dependencies()), null);
56
+ });
57
+ });
58
+
59
+ test("rejects a directory without SKILL.md", function () {
60
+ withTemporaryDirectory(function (temporaryDirectory) {
61
+ assert.equal(packageSkill(temporaryDirectory, null, dependencies()), null);
62
+ });
63
+ });
64
+
65
+ test("returns null when skill validation fails", function () {
66
+ withTemporaryDirectory(function (temporaryDirectory) {
67
+ const skillPath = createSkill(temporaryDirectory);
68
+ const result = packageSkill(
69
+ skillPath,
70
+ null,
71
+ dependencies({
72
+ validateSkill: function validateInvalidSkill() {
73
+ return [false, "Invalid frontmatter"];
74
+ },
75
+ }),
76
+ );
77
+ assert.equal(result, null);
78
+ });
79
+ });
80
+
81
+ test("creates an archive in the current directory by default", function () {
82
+ withTemporaryDirectory(function (temporaryDirectory) {
83
+ const skillPath = createSkill(temporaryDirectory);
84
+ const archivePath = path.join(temporaryDirectory, "example-skill.skill");
85
+ const previousWorkingDirectory = process.cwd();
86
+ fs.writeFileSync(archivePath, "old archive");
87
+
88
+ try {
89
+ process.chdir(temporaryDirectory);
90
+ const resolvedArchivePath = path.join(process.cwd(), "example-skill.skill");
91
+ const result = packageSkill(
92
+ skillPath,
93
+ null,
94
+ dependencies({
95
+ executeArchive: function createTestArchive(stagingDir, filename, skillName) {
96
+ assert.equal(filename, resolvedArchivePath);
97
+ assert.equal(skillName, "example-skill");
98
+ assert.equal(fs.existsSync(filename), false);
99
+ assert.equal(fs.existsSync(path.join(stagingDir, skillName, "SKILL.md")), true);
100
+ fs.writeFileSync(filename, "new archive");
101
+ },
102
+ }),
103
+ );
104
+
105
+ assert.equal(result, resolvedArchivePath);
106
+ assert.equal(fs.readFileSync(archivePath, "utf8"), "new archive");
107
+ } finally {
108
+ process.chdir(previousWorkingDirectory);
109
+ }
110
+ });
111
+ });
112
+
113
+ test("creates a missing custom output directory", function () {
114
+ withTemporaryDirectory(function (temporaryDirectory) {
115
+ const skillPath = createSkill(temporaryDirectory);
116
+ const outputDirectory = path.join(temporaryDirectory, "nested", "output");
117
+ const expectedArchive = path.join(outputDirectory, "example-skill.skill");
118
+
119
+ const result = packageSkill(
120
+ skillPath,
121
+ outputDirectory,
122
+ dependencies({
123
+ executeArchive: function createTestArchive(_stagingDir, filename) {
124
+ fs.writeFileSync(filename, "archive");
125
+ },
126
+ }),
127
+ );
128
+
129
+ assert.equal(result, expectedArchive);
130
+ assert.equal(fs.existsSync(expectedArchive), true);
131
+ });
132
+ });
133
+
134
+ test("excludes generated, dependency, metadata, and root eval files", function () {
135
+ withTemporaryDirectory(function (temporaryDirectory) {
136
+ const skillPath = createSkill(temporaryDirectory);
137
+ const files = {
138
+ "keep.txt": "keep",
139
+ "compiled.pyc": "exclude",
140
+ ".DS_Store": "exclude",
141
+ "node_modules/package/index.js": "exclude",
142
+ "__pycache__/module.pyc": "exclude",
143
+ "evals/result.json": "exclude",
144
+ "nested/evals/keep.json": "keep",
145
+ };
146
+ for (const [relativePath, content] of Object.entries(files)) {
147
+ const filename = path.join(skillPath, relativePath);
148
+ fs.mkdirSync(path.dirname(filename), { recursive: true });
149
+ fs.writeFileSync(filename, content);
150
+ }
151
+
152
+ let stagedFiles = [];
153
+ const result = packageSkill(
154
+ skillPath,
155
+ temporaryDirectory,
156
+ dependencies({
157
+ executeArchive: function inspectStagedFiles(stagingDir, filename) {
158
+ stagedFiles = fs
159
+ .readdirSync(path.join(stagingDir, "example-skill"), {
160
+ recursive: true,
161
+ withFileTypes: true,
162
+ })
163
+ .filter(function selectFiles(entry) {
164
+ return entry.isFile();
165
+ })
166
+ .map(function relativeFilename(entry) {
167
+ return path.relative(stagingDir, path.join(entry.parentPath, entry.name));
168
+ })
169
+ .sort();
170
+ fs.writeFileSync(filename, "archive");
171
+ },
172
+ }),
173
+ );
174
+
175
+ assert.equal(result, path.join(temporaryDirectory, "example-skill.skill"));
176
+ assert.deepEqual(stagedFiles, [
177
+ "example-skill/SKILL.md",
178
+ "example-skill/keep.txt",
179
+ "example-skill/nested/evals/keep.json",
180
+ ]);
181
+ });
182
+ });
183
+
184
+ test("returns null and removes staging files when archive execution fails", function () {
185
+ withTemporaryDirectory(function (temporaryDirectory) {
186
+ const skillPath = createSkill(temporaryDirectory);
187
+ let stagingDirectory;
188
+
189
+ const result = packageSkill(
190
+ skillPath,
191
+ temporaryDirectory,
192
+ dependencies({
193
+ executeArchive: function failArchive(stagingDir) {
194
+ stagingDirectory = stagingDir;
195
+ throw new Error("zip failed");
196
+ },
197
+ }),
198
+ );
199
+
200
+ assert.equal(result, null);
201
+ assert.equal(fs.existsSync(stagingDirectory), false);
202
+ });
203
+ });