typescript-doc-quickstart-tsc-strict-fix
The quickstart code fence had four strict-mode errors from two root causes, both fixed without weakening types:
Fix 1 — history: [] on both Decision returns (resolves 2× TS2741)
decide(): Decision {
if (Math.random() > 0.5) {
return { outcome: "proceed", history: [] }; // was: { outcome: "proceed" }
}
return { outcome: "standby", history: [] }; // was: { outcome: "standby" }
}
Fix 2 — health(): HealthResponse return annotation (resolves TS2416 + TS2345)
health(): HealthResponse { // was: health() { ... }
return {
status: "ok",
capabilities: ["health", "decide"],
};
}
Why the annotation is the single fix for the whole health() chain, and as const is not:
| Attempt | Result |
|---|---|
status: "ok" as const alone |
Error just moves: status narrows to "ok", but capabilities is still widened to string[] → not assignable to DecisionType[] (Type 'string' is not assignable to type 'DecisionType') |
capabilities: ["health","decide"] as const |
Array becomes readonly ["health","decide"] → not assignable to mutable DecisionType[] |
health(): HealthResponse (the fix) |
The literal is contextually typed by the annotation: status is checked against "ok" \| "degraded" and the array is checked element-wise against DecisionType[] while staying mutable → 0 errors |
The fix mirrors the in-repo MinimalHarness pattern: annotate the override return as HealthResponse and default history to [] on every Decision return.
Verified with the repo's compiler and config — TypeScript **5.9.3**, `npx tsc --noEmit -p scratch` against the strict base config (`strict: true`, `ES2022`, `Node16`), with the code fence extracted into `packages/coding-agent/scratch/quickstart.ts` inside the package so the `@earendil-works/pi-coding-agent` import resolves via **package self-reference** (`name` + `exports` → real `dist/index.d.ts`; no node_modules present in the mirror).
| Check | Command | Result |
|---|---|---|
| Buggy reconstruction | `tsc --noEmit -p scratch` | Exactly the 4 reported errors: `TS2416` (`health()` widened: `status: string` not assignable to `"ok" \| "degraded"`), `TS2741` ×2 (`history` missing on both `Decision` returns), `TS2345` (`report(harness.health())` widening) |
| Fixed (annotation + `history: []`) | `tsc --noEmit -p scratch` | **exit 0, 0 errors** |
| Dead end 1 (`status: "ok" as const`) | `tsc --noEmit` on variant | TS2416 moves to `capabilities` (`string[]` vs `DecisionType[]`) — confirms the chain described |
| Dead end 2 (array `as const`) | `tsc --noEmit` on variant | TS2416 `readonly ["health","decide"]` not assignable to mutable `DecisionType[]` |
| Negative control (fix is live-checking) | `tsc --noEmit` | `capabilities: ["read","decide"]` → TS2322; `{ outcome: "proceed" }` → TS2741. The annotation still enforces the union and the required `history` — it did not degrade to `any` |
| Self-reference import | same run | `typeof createAgentSession` bound to the real factory signature with zero node_modules in the mirror → resolution via package `exports` works |
**Edge cases tested:** two-branch `decide()` (both returns annotated), both union members of `status`, mutable-array contextual typing, and the negative controls above proving the fix keeps the types strict.{"model": "deepseek-v4-flash", "problem_class": "typescript-doc-quickstart-tsc-strict-fix", "result": "passed", "tests": 4}