Skip to content

Commit 4889ecf

Browse files
dmealingclaude
andcommitted
fix(cli): address review findings on prompt-snapshot [FR-004]
- correct stale header comment (--check is implemented, not "a later task") - narrow @Format against the render escaper registry (fmtAttr in ESCAPERS) so the RenderFormat cast is checked, not an unsafe assertion; keep the conditional spread (exactOptionalPropertyTypes forbids `format: undefined`) - add a --check render-error coverage test (unresolvable @textRef -> exit 1) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent f11ea19 commit 4889ecf

2 files changed

Lines changed: 24 additions & 5 deletions

File tree

server/typescript/packages/cli/src/commands/prompt-snapshot.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33
// For each template.* node with a committed fixture payload, render its @textRef
44
// text against that payload (same engine, provider, and @format escaping prod
55
// uses) and snapshot the byte-exact output under .metaobjects/snapshots/<name>/.
6-
// Write mode (default) overwrites output.snap; --check (a later task) diffs and
7-
// fails on drift. Closes the gap the template's own git history misses: a shared
8-
// partial or payload-shape change that silently alters the rendered prompt.
6+
// Write mode (default) overwrites output.snap; --check compares against the
7+
// committed golden and exits 1 on drift (never writes). Closes the gap the
8+
// template's own git history misses: a shared partial or payload-shape change
9+
// that silently alters the rendered prompt.
910

1011
import { join } from "node:path";
1112
import { existsSync, readFileSync, mkdirSync, writeFileSync } from "node:fs";
@@ -15,7 +16,7 @@ import { FileProvider } from "../lib/file-provider.js";
1516
import { snapshotPaths, unifiedDiff } from "../lib/snapshot.js";
1617
import { loadMemory } from "@metaobjectsdev/sdk";
1718
import { TYPE_TEMPLATE, TEMPLATE_ATTR_TEXT_REF, TEMPLATE_ATTR_FORMAT } from "@metaobjectsdev/metadata";
18-
import { render, type RenderFormat } from "@metaobjectsdev/render";
19+
import { render, ESCAPERS, type RenderFormat } from "@metaobjectsdev/render";
1920

2021
const DEFAULT_PROMPTS_DIR = "prompts";
2122

@@ -77,8 +78,13 @@ export async function promptSnapshotCommand(args: string[], cwd: string): Promis
7778
continue;
7879
}
7980

81+
// @format is loader-validated against the template format vocabulary; narrow
82+
// against the render engine's own escaper registry so the RenderFormat cast is
83+
// a checked narrowing (never a TypeError on an unknown format), and omit the
84+
// key entirely when absent (exactOptionalPropertyTypes forbids `format: undefined`).
8085
const fmtAttr = tmpl.ownAttr(TEMPLATE_ATTR_FORMAT);
81-
const format = typeof fmtAttr === "string" ? (fmtAttr as RenderFormat) : undefined;
86+
const format =
87+
typeof fmtAttr === "string" && fmtAttr in ESCAPERS ? (fmtAttr as RenderFormat) : undefined;
8288

8389
let rendered: string;
8490
try {

server/typescript/packages/cli/test/integration/prompt-snapshot.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,4 +159,17 @@ describe("meta prompt-snapshot --check", () => {
159159
rmSync(tmp, { recursive: true, force: true });
160160
}
161161
});
162+
163+
test("exit 1 when a template's @textRef does not resolve (render error path)", async () => {
164+
const tmp = scaffold();
165+
writePayload(tmp, "farewell", { name: "Ada" });
166+
await run(["prompt-snapshot", "--cwd", tmp]); // write the golden first
167+
rmSync(join(tmp, "prompts", "prompt", "farewell.mustache"), { force: true }); // now unresolvable
168+
try {
169+
expect(await run(["prompt-snapshot", "--check", "--cwd", tmp])).toBe(1);
170+
expect([...out, ...err].join("\n")).toContain("farewell");
171+
} finally {
172+
rmSync(tmp, { recursive: true, force: true });
173+
}
174+
});
162175
});

0 commit comments

Comments
 (0)