skill-eval: name the colon-space trap in eval expectations
An expectation quoting a jj message ("`lib: add a and b`") parses as a
mapping, and "expected a non-empty string, got object" gave no way to
see why.
Assisted-by: Claude Opus 5 via pi
diff --git a/ai/pi/extensions/skill-eval/evals.test.ts b/ai/pi/extensions/skill-eval/evals.test.ts
index f9d89332..06049490 100644
--- a/ai/pi/extensions/skill-eval/evals.test.ts
+++ b/ai/pi/extensions/skill-eval/evals.test.ts
@@ -131,3 +131,13 @@ test("loadEvalSuite surfaces YAML decoding failures", async () => {
await rm(dir, { recursive: true, force: true });
}
});
+
+test("parseEvalSuite explains the colon-space trap when prose parses as a mapping", () => {
+ const result = parseEvalSuite({
+ adherence: [{ prompt: "split a file out", expect: [{ "the log shows `lib": "add a and b` afterwards" }] }],
+ });
+ assert.equal(result.ok, false);
+ if (result.ok) return;
+ assert.match(result.errors[0], /got object\./);
+ assert.match(result.errors[0], /colon followed by a space \("the log shows `lib:"\) makes YAML read the line as a mapping/);
+});
diff --git a/ai/pi/extensions/skill-eval/evals.ts b/ai/pi/extensions/skill-eval/evals.ts
index a2245ba6..2616afaa 100644
--- a/ai/pi/extensions/skill-eval/evals.ts
+++ b/ai/pi/extensions/skill-eval/evals.ts
@@ -138,7 +138,7 @@ function parseAdherenceCases(raw: unknown, path: string, errors: string[]): Adhe
} else {
entry.expect.forEach((item, i) => {
if (typeof item !== "string" || !item.trim()) {
- errors.push(`${entryPath}.expect[${i}]: expected a non-empty string, got ${describe(item)}.`);
+ errors.push(`${entryPath}.expect[${i}]: expected a non-empty string, got ${describe(item)}.${hintForYamlShape(item)}`);
return;
}
expect.push(item.trim());
@@ -176,6 +176,14 @@ function describe(value: unknown): string {
return typeof value;
}
+/** A colon followed by a space turns a plain scalar into a mapping, which trips up prose. */
+function hintForYamlShape(value: unknown): string {
+ if (!isRecord(value)) return "";
+ const key = Object.keys(value)[0];
+ const colon = key ? ` ("${key}:")` : "";
+ return ` A colon followed by a space${colon} makes YAML read the line as a mapping. Quote the whole entry.`;
+}
+
/** Validate decoded evals.yml data. Collects every problem rather than failing on the first. */
export function parseEvalSuite(data: unknown, source = EVALS_FILENAME): ParseResult {
const errors: string[] = [];