skilld-harness 3.6.3 → 3.6.5

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 CHANGED
@@ -48,11 +48,14 @@ Both `Ok` and `Err` carry a `report`, so a failed run still reports its cost.
48
48
  A token count is `undefined` when the adapter does not report it.
49
49
  If the run fails before the Agent starts, `steps` is 0.
50
50
  `report.warnings` names each source file the Harness left out for size, each `onEvent` failure, and each cleanup problem.
51
- An `InvalidSkill` error lists each failed output check in `issues`.
51
+ If the output fails a check, the Harness sends the issues back to the Agent in the same session, up to two times.
52
+ Each repair turn adds its steps and usage to the report, and a warning names the issues.
53
+ An `InvalidSkill` error lists each check that still fails in `issues`.
52
54
 
53
55
  The Harness checks the rules that the generation Skills state:
54
56
 
55
57
  - The frontmatter contains only `name` and `description`.
58
+ - The description is one plain YAML line without double quotes, backticks, or `%`.
56
59
  - `SKILL.md` links every file under `references/`, by a Markdown link or an inline code path.
57
60
  - A package Skill keeps `SKILL.md` under 500 lines and writes at most eight reference files.
58
61
  - A project Skill points only at project files and gives at least one search command.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/harness.ts"],"mappings":";;UAGiB;WACN;WACA;;KAGC;WAEC;WACA;;WAGA;WACA;WACA;;KAGD;WAEC;WACA,QAAQ;WACR,aAAa;;WAGb;WACA;WACA,aAAa;;WAGb;WACA;;UAGI;WACN;WACA;WACA;WACA;WACA;WACA;;;UAIM;;WAEN;;WAEA;WACA;;;KAIC;WACK;WAA4B;;WAE9B;WACA;WACA;WACA;WACA;;WAGA;WACA;WACA;WACA,OAAO;;UAGL;WACN,SAAS;;;;;WAKT,WAAW,OAAO;;UAGZ;WACN;WACA;;UAGM;WACN;WACA;WACA;;UAGM;WACN;WACA;WACA;WACA,OAAO,cAAc;WACrB,gBAAgB,cAAc;;UAGxB;WACN;WACA;WACA;WACA;;UAGM;WACN;WACA;WACA,UAAU,cAAc;;KAGvB;WACK;WAA+B;;WAEjC;WACA;WACA,UAAU,cAAc;WACxB;;WAEE;WAA8B;WAA0B;;WACxD;WAA+B;WAA0B,QAAQ;;WACjE;WAAmC;WAA0B;;WAC7D;WAA6B;WAA0B;;WACvD;WAAkC;WAA0B;WAAuB;;WACnF;WAA4B;;;UAG5B;WACN,cAAc;WACd,cAAc;WACd,aAAa;;;;;;UAOP;;WAEN,OAAO;;WAEP;;;;;WAKA,UAAU;;KAGT,eAAe,YAAY,mBAAmB;WACzC;WAAqB,OAAO,eAAe;WAAe,QAAQ;;WAClE;WAAsB,OAAO;WAAwB,QAAQ;;UAE7D;WACN,MAAM,YAAY,UAAU,OAAO,KAAK,UAAU,oBAAoB,QAAQ,eAAe;;UAGvF;WACN,SAAS;;WAET,SAAS;;WAET,gBAAgB;WAChB,eAAe,QAAQ;;WAEvB,SAAS,gBAAgB,MAAM,SAAS,OAAO,gBAAgB,QAAQ;;wBC0GlE,mBAAmB,SAAS,4BAA4B"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/harness.ts"],"mappings":";;UAGiB;WACN;WACA;;KAGC;WAEC;WACA;;WAGA;WACA;WACA;;KAGD;WAEC;WACA,QAAQ;WACR,aAAa;;WAGb;WACA;WACA,aAAa;;WAGb;WACA;;UAGI;WACN;WACA;WACA;WACA;WACA;WACA;;;UAIM;;WAEN;;WAEA;WACA;;;KAIC;WACK;WAA4B;;WAE9B;WACA;WACA;WACA;WACA;;WAGA;WACA;WACA;WACA,OAAO;;UAGL;WACN,SAAS;;;;;WAKT,WAAW,OAAO;;UAGZ;WACN;WACA;;UAGM;WACN;WACA;WACA;;UAGM;WACN;WACA;WACA;WACA,OAAO,cAAc;WACrB,gBAAgB,cAAc;;UAGxB;WACN;WACA;WACA;WACA;;UAGM;WACN;WACA;WACA,UAAU,cAAc;;KAGvB;WACK;WAA+B;;WAEjC;WACA;WACA,UAAU,cAAc;WACxB;;WAEE;WAA8B;WAA0B;;WACxD;WAA+B;WAA0B,QAAQ;;WACjE;WAAmC;WAA0B;;WAC7D;WAA6B;WAA0B;;WACvD;WAAkC;WAA0B;WAAuB;;WACnF;WAA4B;;;UAG5B;WACN,cAAc;WACd,cAAc;WACd,aAAa;;;;;;UAOP;;WAEN,OAAO;;WAEP;;;;;WAKA,UAAU;;KAGT,eAAe,YAAY,mBAAmB;WACzC;WAAqB,OAAO,eAAe;WAAe,QAAQ;;WAClE;WAAsB,OAAO;WAAwB,QAAQ;;UAE7D;WACN,MAAM,YAAY,UAAU,OAAO,KAAK,UAAU,oBAAoB,QAAQ,eAAe;;UAGvF;WACN,SAAS;;WAET,SAAS;;WAET,gBAAgB;WAChB,eAAe,QAAQ;;WAEvB,SAAS,gBAAgB,MAAM,SAAS,OAAO,gBAAgB,QAAQ;;wBC6HlE,mBAAmB,SAAS,4BAA4B"}
package/dist/index.mjs CHANGED
@@ -3,7 +3,7 @@ import { lstat, mkdir, open, opendir, realpath, rename, rm, rmdir, unlink, write
3
3
  import { basename, dirname, join, posix, relative, resolve, sep } from "node:path";
4
4
  import { HarnessAgent } from "@ai-sdk/harness/agent";
5
5
  import { createHash, randomUUID } from "node:crypto";
6
- import { parseDocument } from "yaml";
6
+ import { Scalar, isScalar, parseDocument } from "yaml";
7
7
  import { constants } from "node:fs";
8
8
  import { Readable } from "node:stream";
9
9
  import { createGunzip } from "node:zlib";
@@ -362,6 +362,7 @@ function checkProjectSkill(input) {
362
362
  const allowedFrontmatter = /* @__PURE__ */ new Set(["name", "description"]);
363
363
  const packageSkillLineLimit = 500;
364
364
  const packageSkillReferenceLimit = 8;
365
+ const riskyDescription = /["`%]/;
365
366
  const invalid = (issues) => err({
366
367
  _tag: "InvalidSkill",
367
368
  message: "Skill output failed deterministic checks.",
@@ -428,6 +429,8 @@ const validateGeneratedSkill = (name, files, contract) => {
428
429
  if (frontmatter.name !== name) issues.push("Frontmatter name must match the Skill directory name.");
429
430
  if (typeof frontmatter.name !== "string" || !isSkillName(frontmatter.name)) issues.push("Frontmatter name is invalid.");
430
431
  if (typeof frontmatter.description !== "string" || frontmatter.description.trim().length === 0 || frontmatter.description.length > 1024) issues.push("Frontmatter description must contain 1 to 1024 characters.");
432
+ const description = document.get("description", true);
433
+ if (typeof frontmatter.description === "string" && (!isScalar(description) || description.type !== Scalar.PLAIN || riskyDescription.test(frontmatter.description))) issues.push("Frontmatter description must be one plain line without double quotes, backticks, or %. Name symptoms in plain words.");
431
434
  issues.push(...structureIssues(source, files, contract));
432
435
  return issues.length === 0 ? ok(void 0) : invalid(issues);
433
436
  };
@@ -1146,11 +1149,23 @@ function toUsage(usage) {
1146
1149
  outputTokens: usage.outputTokens
1147
1150
  };
1148
1151
  }
1149
- function cancelled() {
1150
- return err({
1151
- _tag: "Cancelled",
1152
- message: "Skill run was cancelled."
1153
- });
1152
+ function sumUsage(a, b) {
1153
+ const sum = (x, y) => x === void 0 || y === void 0 ? void 0 : x + y;
1154
+ return {
1155
+ inputTokens: sum(a.inputTokens, b.inputTokens),
1156
+ cachedInputTokens: sum(a.cachedInputTokens, b.cachedInputTokens),
1157
+ outputTokens: sum(a.outputTokens, b.outputTokens)
1158
+ };
1159
+ }
1160
+ const OUTPUT_REPAIR_TURNS = 2;
1161
+ function repairRequest(outputPath, issues) {
1162
+ return [
1163
+ `The Harness checked the output at \`${outputPath}\` and found these problems:`,
1164
+ "",
1165
+ ...issues.map((issue) => `- ${issue}`),
1166
+ "",
1167
+ `Fix each problem in place. Write no files outside \`${outputPath}\`. Finish when the output passes these checks.`
1168
+ ].join("\n");
1154
1169
  }
1155
1170
  async function prepareCurrentSkill(destination, policy, signal) {
1156
1171
  const root = resolve(destination.rootDir);
@@ -1301,12 +1316,18 @@ async function writePreparedSource(active, prepared, signal) {
1301
1316
  abortSignal: signal
1302
1317
  });
1303
1318
  }
1304
- function toAgentError(cause, signal) {
1305
- return signal?.aborted ? cancelled() : err({
1319
+ function agentError(cause, signal) {
1320
+ return signal?.aborted ? {
1321
+ _tag: "Cancelled",
1322
+ message: "Skill run was cancelled."
1323
+ } : {
1306
1324
  _tag: "AgentFailed",
1307
1325
  message: "Harness Agent failed during the Skill run.",
1308
1326
  cause
1309
- });
1327
+ };
1328
+ }
1329
+ function toAgentError(cause, signal) {
1330
+ return err(agentError(cause, signal));
1310
1331
  }
1311
1332
  function createSkillHarness(options) {
1312
1333
  const policy = parseOutputPolicy({
@@ -1380,47 +1401,71 @@ function createSkillHarness(options) {
1380
1401
  const currentSkillPath = posix.join(active.workDir, "input/current-skill");
1381
1402
  const outputPath = posix.join(active.workDir, "skilld-output", prepared.outputName);
1382
1403
  const prompt = renderRequest(requestContent(skill), sourcePath, currentSkillPath, outputPath, prepared.outputName);
1404
+ const outputSandbox = active.sandbox;
1383
1405
  let step = 0;
1384
- const generated = await agent.generate({
1385
- session,
1386
- prompt,
1387
- abortSignal: signal,
1388
- onStepStart: (event) => {
1389
- step = event.stepNumber;
1390
- log.emit({
1391
- _tag: "StepStart",
1392
- step
1393
- });
1394
- },
1395
- onToolExecutionStart: ({ toolCall }) => {
1396
- log.emit({
1397
- _tag: "ToolCall",
1398
- step,
1399
- toolName: toolCall.toolName,
1400
- toolCallId: toolCall.toolCallId,
1401
- input: toolCall.input
1402
- });
1403
- },
1404
- onStepEnd: (result) => {
1405
- log.emit({
1406
- _tag: "StepFinish",
1407
- step: result.stepNumber,
1408
- finishReason: result.finishReason,
1409
- usage: toUsage(result.usage)
1410
- });
1411
- }
1412
- }).then(ok, (cause) => err(cause));
1413
- if (generated._tag === "Err") return toAgentError(generated.error, signal);
1414
- log.settle(toUsage(generated.value.totalUsage), generated.value.steps.length);
1415
- if (signal?.aborted) return cancelled();
1416
- const collected = await collectSandboxOutput(active.sandbox, outputPath, policy, signal);
1417
- if (collected._tag === "Err") return collected;
1406
+ let stepOffset = 0;
1407
+ let usage;
1408
+ const turn = async (text) => {
1409
+ const generated = await agent.generate({
1410
+ session,
1411
+ prompt: text,
1412
+ abortSignal: signal,
1413
+ onStepStart: (event) => {
1414
+ step = stepOffset + event.stepNumber;
1415
+ log.emit({
1416
+ _tag: "StepStart",
1417
+ step
1418
+ });
1419
+ },
1420
+ onToolExecutionStart: ({ toolCall }) => {
1421
+ log.emit({
1422
+ _tag: "ToolCall",
1423
+ step,
1424
+ toolName: toolCall.toolName,
1425
+ toolCallId: toolCall.toolCallId,
1426
+ input: toolCall.input
1427
+ });
1428
+ },
1429
+ onStepEnd: (result) => {
1430
+ log.emit({
1431
+ _tag: "StepFinish",
1432
+ step: stepOffset + result.stepNumber,
1433
+ finishReason: result.finishReason,
1434
+ usage: toUsage(result.usage)
1435
+ });
1436
+ }
1437
+ }).then(ok, (cause) => err(cause));
1438
+ if (generated._tag === "Err") return err(agentError(generated.error, signal));
1439
+ const turnUsage = toUsage(generated.value.totalUsage);
1440
+ usage = usage ? sumUsage(usage, turnUsage) : turnUsage;
1441
+ stepOffset += generated.value.steps.length;
1442
+ log.settle(usage, stepOffset);
1443
+ return signal?.aborted ? err({
1444
+ _tag: "Cancelled",
1445
+ message: "Skill run was cancelled."
1446
+ }) : ok(void 0);
1447
+ };
1448
+ const check = async () => {
1449
+ const collected = await collectSandboxOutput(outputSandbox, outputPath, policy, signal);
1450
+ if (collected._tag === "Err") return collected;
1451
+ const validated = prepared.skillName === "review-skill" ? validateSkillReview(collected.value) : validateGeneratedSkill(prepared.outputName, collected.value, prepared.skillName === "generate-project-skill" ? {
1452
+ _tag: "ProjectSkill",
1453
+ projectPaths: prepared.source.files.map((file) => file.path)
1454
+ } : { _tag: "PackageSkill" });
1455
+ return validated._tag === "Err" ? validated : ok(collected.value);
1456
+ };
1457
+ const first = await turn(prompt);
1458
+ if (first._tag === "Err") return first;
1459
+ let checked = await check();
1460
+ for (let repair = 1; repair <= OUTPUT_REPAIR_TURNS && checked._tag === "Err" && checked.error._tag === "InvalidSkill"; repair++) {
1461
+ log.warn(`Output checks failed; repair turn ${repair} of ${OUTPUT_REPAIR_TURNS}: ${checked.error.issues.join(" ")}`);
1462
+ const repaired = await turn(repairRequest(outputPath, checked.error.issues));
1463
+ if (repaired._tag === "Err") return repaired;
1464
+ checked = await check();
1465
+ }
1466
+ if (checked._tag === "Err") return checked;
1467
+ const collected = checked;
1418
1468
  if (prepared.skillName === "review-skill") return validateSkillReview(collected.value);
1419
- const validated = validateGeneratedSkill(prepared.outputName, collected.value, prepared.skillName === "generate-project-skill" ? {
1420
- _tag: "ProjectSkill",
1421
- projectPaths: prepared.source.files.map((file) => file.path)
1422
- } : { _tag: "PackageSkill" });
1423
- if (validated._tag === "Err") return validated;
1424
1469
  if (!prepared.destination) return err({
1425
1470
  _tag: "InvalidInput",
1426
1471
  message: "Skill destination is required."