fix(test-harness): restore promisify(exec) + unblock CLI introspection probes

The vitest child-process guard wrapped exec/execFile without preserving the
`[util.promisify.custom]` symbol, so awaited `execAsync` resolved to a raw
stdout string instead of `{stdout, stderr}`. That single regression cascaded
through ~60 "failing" tests across cli, core, engine, and dashboard whose
production code was actually correct. Also relax the AI-CLI blocklist for
cheap introspection (--version/--help/which …), give SIGTERM'd subprocesses a
brief grace period before being flagged as "left running", fix a few real
test-side bugs uncovered along the way (executor mock step transitions, iOS
last-resort keyboard path, mission SSE replay tests racing with the real AI
agent), and convert dashboard route tests' dynamic `await import("../server.js")`
to static imports so first-test timings drop from 2–5s to <200ms.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-04-30 17:40:14 -07:00
parent 8e969bd72e
commit 62f54dab46
18 changed files with 238 additions and 60 deletions

View File

@@ -4406,12 +4406,21 @@ const mockedReviewStep = vi.mocked(mockedReviewStepFn);
*/
async function captureTools(): Promise<Record<string, (id: string, params: any) => Promise<any>>> {
const store = createMockStore();
store.updateStep.mockResolvedValue({
steps: [
{ name: "Preflight", status: "done" },
{ name: "Implement", status: "in-progress" },
{ name: "Testing", status: "pending" },
],
// Simulate the real TaskStore: forward transitions persist, but in-progress
// regressions on done/skipped steps are rejected so executor.ts can surface
// the "already <status>" diagnostic.
const stepStates: Array<{ name: string; status: string }> = [
{ name: "Preflight", status: "done" },
{ name: "Implement", status: "in-progress" },
{ name: "Testing", status: "pending" },
];
store.updateStep.mockImplementation(async (_taskId: string, stepIndex: number, status: string) => {
const current = stepStates[stepIndex];
const isRegression = status === "in-progress" && (current.status === "done" || current.status === "skipped");
if (!isRegression) {
current.status = status;
}
return { steps: stepStates.map((s) => ({ ...s })) };
});
mockedExistsSync.mockReturnValue(true);
@@ -4675,10 +4684,13 @@ describe("Code review verdict enforcement - fn_task_update blocking", () => {
mockedReviewStep.mockResolvedValue({ verdict: "REVISE", review: "Fix", summary: "Bad" });
const tools = await captureTools();
await tools.fn_review_step("c1", { step: 1, type: "code", step_name: "Step1", baseline: "a" });
// Target step 3 (Testing, currently pending) so the in-progress transition is
// a valid forward move — the assertion below only verifies that a REVISE on
// the same step does not produce the "Cannot mark … as done" block.
await tools.fn_review_step("c1", { step: 3, type: "code", step_name: "Testing", baseline: "a" });
// "in-progress" should still work even with REVISE
const result = await tools.fn_task_update("c2", { step: 1, status: "in-progress" });
const result = await tools.fn_task_update("c2", { step: 3, status: "in-progress" });
expect(result.content[0].text).not.toContain("Cannot mark");
expect(result.content[0].text).toContain("→ in-progress");
});
});