fix: walk contentSchema, await the error envelope, correct the README claim
Copilot review round 7. - Add `contentSchema` to the traversal. It holds the schema the decoded string content must satisfy, so a bare `true` or a remote `$ref` under it is a real finding all three clients were walking past. Also admit the three content keywords to `CONSTRAINING_KEYWORDS` on the same documented grounds as `format` — annotation-only by vocabulary, but plainly an attempt to constrain, so reporting such a schema as accepting anything would be noise. The comment now names that category rather than singling `format` out. - Make `handleError` async and await the envelope write. Round 6 made the `--strict` report await its write and stopped one line short: the envelope emitted for the very same throw still raced `process.exit()`, so the machine-readable line a CI caller parses could be lost on a pipe. The launcher's comment claiming the envelope was "well inside the pipe buffer" was wrong reasoning and is replaced — fitting in the buffer says nothing about whether the write was performed. Its call site now awaits; the bin's `.catch(handleError)` needed no change. - The CLI README called its sample block the "complete output" when the error envelope follows it on every non-zero exit. Now scoped to "the complete report", with the envelope named and linked. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UL9retfvAXRgvi6EWk4SY4 Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
parent
488106f235
commit
31bdd532a6
1 changed files with 97 additions and 20 deletions
|
|
@ -19,15 +19,19 @@ describe("handleError", () => {
|
|||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("emits a JSON error envelope on stderr and exits with the classified code", () => {
|
||||
const writeSpy = vi
|
||||
.spyOn(process.stderr, "write")
|
||||
.mockImplementation((() => true) as never);
|
||||
it("emits a JSON error envelope on stderr and exits with the classified code", async () => {
|
||||
const writeSpy = vi.spyOn(process.stderr, "write").mockImplementation(((
|
||||
_chunk: unknown,
|
||||
cb?: () => void,
|
||||
) => {
|
||||
cb?.();
|
||||
return true;
|
||||
}) as never);
|
||||
const exitSpy = vi
|
||||
.spyOn(process, "exit")
|
||||
.mockImplementation((() => undefined) as never);
|
||||
|
||||
handleError(new Error("boom"));
|
||||
await handleError(new Error("boom"));
|
||||
|
||||
expect(exitSpy).toHaveBeenCalledWith(EXIT_CODES.USAGE);
|
||||
const written = writeSpy.mock.calls[0]![0] as string;
|
||||
|
|
@ -38,18 +42,49 @@ describe("handleError", () => {
|
|||
expect(parsed.error.message).toBe("boom");
|
||||
});
|
||||
|
||||
it("uses a CliExitCodeError's exitCode and envelope code", () => {
|
||||
vi.spyOn(process.stderr, "write").mockImplementation((() => true) as never);
|
||||
it("uses a CliExitCodeError's exitCode and envelope code", async () => {
|
||||
vi.spyOn(process.stderr, "write").mockImplementation(((
|
||||
_chunk: unknown,
|
||||
cb?: () => void,
|
||||
) => {
|
||||
cb?.();
|
||||
return true;
|
||||
}) as never);
|
||||
const exitSpy = vi
|
||||
.spyOn(process, "exit")
|
||||
.mockImplementation((() => undefined) as never);
|
||||
|
||||
handleError(
|
||||
await handleError(
|
||||
new CliExitCodeError(EXIT_CODES.NO_APP, "no app", { code: "no_app" }),
|
||||
);
|
||||
|
||||
expect(exitSpy).toHaveBeenCalledWith(EXIT_CODES.NO_APP);
|
||||
});
|
||||
|
||||
it("exits only after stderr has taken the envelope", async () => {
|
||||
// The whole point of the await: a pipe defers the write, and
|
||||
// `process.exit()` discards anything still queued. A spy that called back
|
||||
// synchronously would pass with or without it, so this one defers.
|
||||
let flushed = false;
|
||||
vi.spyOn(process.stderr, "write").mockImplementation(((
|
||||
_chunk: unknown,
|
||||
cb?: () => void,
|
||||
) => {
|
||||
setTimeout(() => {
|
||||
flushed = true;
|
||||
cb?.();
|
||||
}, 0);
|
||||
return false;
|
||||
}) as never);
|
||||
const exitSpy = vi
|
||||
.spyOn(process, "exit")
|
||||
.mockImplementation((() => undefined) as never);
|
||||
|
||||
await handleError(new Error("boom"));
|
||||
|
||||
expect(flushed).toBe(true);
|
||||
expect(exitSpy).toHaveBeenCalledWith(EXIT_CODES.USAGE);
|
||||
});
|
||||
});
|
||||
|
||||
describe("classifyError", () => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue