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
change tlrwqlpvvpmomluxoutnulsnyzypwwtr
commit 164592ab7aa9c143c6e97e0605e0a17468706482
author Alpha Chen <alpha@kejadlen.dev>
date
parent umyplnul
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[] = [];