Skip to content

Implement the inline @type cast cleanup: 273 of 357 removed or converted - #1589

Merged
sergey-shandar merged 17 commits into
mainfrom
claude/cast-type-annotations-refactor-hq8l8o
Aug 15, 2026
Merged

Implement the inline @type cast cleanup: 273 of 357 removed or converted#1589
sergey-shandar merged 17 commits into
mainfrom
claude/cast-type-annotations-refactor-hq8l8o

Conversation

@sergey-shandar

@sergey-shandar sergey-shandar commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Implements todo/inline-type-casts.md, merged in #1586. That PR was the audit; this one is the cleanup.

273 of the 357 casts are gone. 84 of the audited set remain, each with a recorded reason. The per-site table also lists 11 casts that arrived on main after the audit (mcp/cas/proof, types/patricia_trie via #1582/#1583) and are untouched here — 95 rows in total.

npx tsc exit 0, fjs t → 2838 pass / 0 fail, and zero type differences in the emitted fjs/ declarations against main.

What landed

Step Casts
Delete the redundant ones 182 fjs/js/tokenizer alone lost 37
Array.isArrayinstanceof Array 8 the eight Dir guards
@satisfies + annotated declarations 23 6 hoisted to the declaration form, 17 @satisfies
Cast → runtime check 17 assertNotNullish, discriminant and literal-range asserts
Checked accessors in proofs 53 protocol/mcp/proof 33→0, mcp/proof 11→1, cas/proof 8→0
Reverted in review −10 see below

tsc --noEmit is not a sufficient check

Review found ten removals that were wrong, and npx tsc was green for every one.

Four changed the published API, found by emitting .d.mts before and after:

Export Was Became
effects/node createServer <O extends Operation>(listener: RequestListener<O>) => Effect<O | CreateServer, Server> (...payload: never) => Effect<Operation, never>
effects/node log, error Console the structural type
cas/evo emptyCache Cache { bySubject: {} }
types/range_map fromRange RangeMapArray<T> [T, number][]readonly lost

package.json ships **/*.d.mts and prepack runs tsc --emitDeclarationOnly, so declaration emit is a check the repo already performs and plain npx tsc never sees.

Six deleted the assertion itself. types/nominal/proof.f.mjs demonstrates, per branding strategy, whether < compiles between two branded values — the casts are the demonstration. A brand is unconstructible by design, so the declaration form rejects what the inline cast accepted; the removal left const a = {} compared against const b = {}, proving nothing. In a proof about types the annotation is the test, and "does it still compile" cannot see it being deleted.

Both checks are now recorded in the audit's method, along with watching --noUnusedLocals — an orphaned @typedef is how the nominal case surfaced. The 36 @import entries the cleanup orphaned are cleared, so --noUnusedLocals reports exactly what it does on main.

Other notes

Two changes are not cast removals but were the actual defect: protocol/mcp/proof's step1/step2/step3 declared msg: unknown when every caller passes a JSON-RPC object literal, and fjs run now asserts a module's main is callable before invoking it.

The 37 remaining TS2322/TS2345 sites are deliberately untouched — each overrides a real mismatch and needs an issue against the API it papers over, not a different cast syntax. Those issues are in #1590.

Changelog: changelog/unreleased/1589.md

🤖 Generated with Claude Code

https://claude.ai/code/session_01TQcZKuWSt2rEZrCj1jVZih

claude added 9 commits August 15, 2026 18:12
Step 1 of todo/inline-type-casts.md. 176 of the 181 casts the audit measured as
redundant are gone; `npx tsc` and `fjs t` (2797 tests) pass unchanged.

172 came out in one sweep. The 9 in the two rtti visitors are each removable on
their own but compete for the same instantiation-depth budget, so they were
taken one at a time against a full type-check: 4 more came out, 5 stay. Their
rows in the audit table already predicted this.

Where the cast's parentheses existed only to carry it, they go too — except
after an arrow, where `x => ({ … })` needs them to stay an object literal
rather than a block.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQcZKuWSt2rEZrCj1jVZih
Step 2 of todo/inline-type-casts.md. Eight `/** @type {Dir} */` casts sat
directly after a guard that looked like it should already have narrowed.
`Array.isArray` narrows to `any[]`, which `readonly Vec[]` is not assignable
to, so its negative branch never removes a `readonly` array from a union —
`instanceof Array` does. Swapping the eight guards that the casts depended on
makes the surrounding `assert` and `if` narrow on their own, and all eight
casts delete.

Only those guards are changed. The other `Array.isArray` calls in these files
narrow positively, where both spellings work, and `Array.isArray` is the
cross-realm-correct one — so they stay as they are, with a comment at each
swapped site explaining why it differs.

`npx tsc` and `fjs t` (2797 tests) pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQcZKuWSt2rEZrCj1jVZih
Step 3 of todo/inline-type-casts.md.

23 casts whose expression is genuinely assignable to the target type — so the
cast was pinning a type, never overriding one. Six sit at a `const`
initializer and become the declaration form AGENTS.md prefers; `fjs/effects/node`
already spelled the identical `do_('stat')` case that way two lines above the
inline one. The other 17 become `@satisfies`, which checks assignability
without discarding the inferred type.

Four `@satisfies`-capable sites were deliberately left as `@type {any}`
(`types/function/compare`, `types/rtti/parse`): `@satisfies {any}` asserts
nothing, so an `any` cast is either load-bearing or it should go — it is never
a "check".

`npx tsc` and `fjs t` (2797 tests) pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQcZKuWSt2rEZrCj1jVZih
Re-measuring after steps 1–3 found 8 more casts that `npx tsc` now passes
without. They were not redundant before: each existed only to feed a cast that
has since gone — `types/nominal/proof` is the clearest case, where removing the
outer `_SymbolIntersectionBranded` cast left an inner `any` cast with nothing
to widen for.

The two in the rtti visitors again needed one at a time against a full
type-check, for the same instantiation-depth reason as step 1.

218 of the original 357 casts are now gone; 142 remain. `npx tsc` and `fjs t`
(2797 tests) pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQcZKuWSt2rEZrCj1jVZih
Step 4 of todo/inline-type-casts.md, first half: the 17 casts outside the
`unknown`-in-proofs group. Each was a claim the compiler took on faith; each is
now checked where it is made.

- 6 nullish lookups (`bnf/ll1` dispatch map, `sul/id` point-at-infinity,
  `sul/level/hash` root and `storage.at(-1)`, `types/rtti/proof` entries) become
  `assertNotNullish`.
- 3 in `media/json/parser` were narrowing `state.top` to one arm of a
  discriminated union. `assert(top !== null && top.kind === 'object')` narrows
  the same way and checks the tag; `pushKey` grows a block body to hold it.
- 3 literal-range casts (`asn.1` class/PC bits, `Index<3>`, `Index<5>`) become
  an `assert` over the alternatives. Narrowing a number to a literal union is
  exactly what a compiler cannot verify and a check can.
- 2 in `emergent_testing/proof` become `assert(result instanceof Array)`, which
  also replaces the `Array.isArray` assertion that was not narrowing.
- 2 rtti visitors dispatch on a `'const'` tag the compiler cannot correlate with
  the payload. `Type` is `Const | Thunk` and a `Thunk` is a function, so
  `assert(typeof c !== 'function')` *is* the definition of `Const`.

`npx tsc` and `fjs t` (2797 tests) pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQcZKuWSt2rEZrCj1jVZih
Step 4 of todo/inline-type-casts.md, second half, for
`fjs/protocol/mcp/proof.f.mjs`: 33 casts down to none.

A step's response is `unknown`, and its shape is exactly what these proofs
exist to check — so each cast was assuming the thing being proved. Seven
accessors (`hasError`, `errorCode`, `errorId`, `resultOf`, `protocolVersion`,
`toolNames`, `nextCursor`, `firstText`) check their way in with `assert` and
`in`, so a malformed response now fails at the read with the offending value
attached, instead of surfacing as a confusing mismatch further along — or not
at all.

Six `unknown`-to-`Unknown` casts on the test messages went a different way:
the local `step1`/`step2`/`step3` helpers declared `msg: unknown` when every
caller passes a JSON-RPC object literal. Annotating the parameter as `Unknown`
is what the casts were compensating for.

`npx tsc` and `fjs t` (2797 tests) pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQcZKuWSt2rEZrCj1jVZih
Continues step 4 of todo/inline-type-casts.md in `fjs/cas/proof.f.mjs` (8 casts
to none) and `fjs/mcp/proof.f.mjs` (11 to one).

`cas/proof` asserted a write failed by casting the driver's `unknown` result to
`IoResult<Vec>` and reading `[0]` and `[1]` — the shape being the very claim
each of those four proofs makes. One `errorMessage` accessor checks the pair.

`mcp/proof` gains `has`, `errorCode`, `idOf` and `toolsOf`, and `item0` now
returns a `ContentItem` so `textOf` discriminates on `type` rather than casting
to `{ text: string }`.

One cast remains, in `resultOf`, and it is now the only place a response
crosses from `unknown` to a typed `ToolsCallResult`. Everything checkable is
checked around it; the final step is on trust because rtti's `parse` reads
`Unknown`, and reaching `Unknown` from `unknown` is the same problem one level
down. Concentrating it in a single documented site is the point.

`npx tsc` and `fjs t` (2797 tests) pass. 274 of the original 357 casts are gone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQcZKuWSt2rEZrCj1jVZih
`isNotFound` and its proof read `code` off an unknown rejection; `'code' in e`
narrows without a cast and is the check the comparison already implied.

`fjs run` cast a module's `main` straight to `NodeProgram`. A module named on
the command line can export anything, so it now asserts `main` is callable and
fails with the file name rather than as `main is not a function` from inside
the effect runner. The narrowing from `Function` to `NodeProgram` still needs a
cast — that is the boundary `assert` cannot cross — but it is now reached only
after a check.

`npx tsc` and `fjs t` (2797 tests) pass. 283 of the original 357 casts are
gone; 74 remain.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQcZKuWSt2rEZrCj1jVZih
283 of the 357 casts are gone. Replaces the audit's per-site table with the 74
that remain and the reason each stays, re-measured against the current tree —
none can be deleted, and none can become a meaningful `@satisfies`, so this is
a fixed point rather than a stopping point.

The largest remaining group, 36 TS2322/TS2345 sites, is deliberately untouched:
those casts override a real mismatch, so each needs an issue against the API it
papers over rather than a different cast syntax.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQcZKuWSt2rEZrCj1jVZih
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
functionalscript c5bd2b4 Commit Preview URL

Branch Preview URL
Aug 15 2026, 07:52 PM

Comment thread fjs/types/nominal/proof.f.mjs Fixed
Comment thread fjs/types/nominal/proof.f.mjs Fixed

@o2alexanderfedin o2alexanderfedin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code is sound — both findings below are bookkeeping.

Checked the parts tsc doesn't cover:

  • instanceof Array (8 Dir guards) — safe. Dir is the test-only virtual FS; every importer is a proof.f.mjs or fjs/dev, and every value is an in-process literal. No node:vm, worker_threads, new Worker, structuredClone or createContext anywhere under fjs/, and the real node effects never construct a Dir. No cross-realm path, so the Array.isArray semantics aren't load-bearing here.
  • The 17 runtime checks — none can fire on input that previously worked. Traced each: media/json/parser (top is set for every status reaching pushKey/endArray/endObject), sul/level/hash (nullish only for an empty stack, already guarded), asn.1 (x & 0b111_00000n is always one of the 8 asserted values), bnf/ll1, index3/index5.
  • Proof accessors still assert; several are strictly stronger than the casts they replaced.

npx tsc exit 0, fjs t → 2838 pass / 0 fail. No suppressions added; the 7 added any lines are pre-existing double casts collapsed to one (net −6).

Two things to fix:

  1. No changelog/unreleased/1589.md — AGENTS.md §5 requires one for a code change.
  2. The remaining count is 85, not 74. todo/inline-type-casts.md's table omits fjs/mcp/cas/proof.f.mjs (10 casts: 33, 38, 65, 85, 93×2, 101, 121, 132, 149) and fjs/types/patricia_trie/module.f.mjs:49 — both arrived from main via #1582/#1583 after the sweep. The 283-removed figure is exact (368 on main → 85 here); only the baseline and allowlist are 11 short.

Minor: whitespace-only lines left at fjs/mcp/evo/module.f.mjs:124,147 where a JSDoc was deleted.

claude and others added 4 commits August 15, 2026 18:57
…dant

`github-code-quality` flagged two unused variables in `types/nominal/proof`.
Following that up found the removals there had deleted the proof itself, and a
declaration-emit diff against `main` found four more removals that had changed
the published API. All ten type-checked; none were safe.

The API regressions, found by emitting `.d.mts` before and after:

- `effects/node` `createServer`: `<O extends Operation>(listener:
  RequestListener<O>) => Effect<O | CreateServer, Server>` became
  `(...payload: never) => Effect<Operation, never>`
- `effects/node` `log`/`error`: `Console` became the structural type
- `cas/evo` `emptyCache`: `Cache` became `{ bySubject: {} }`
- `types/range_map`: `RangeMapArray<T>` became `[T, number][]` — a purely
  functional library publishing a mutable array type

Three are now annotated declarations; `createServer` goes back to an inline
cast, which is what do-generic-operation-signatures.md is about.

`types/nominal/proof` demonstrates, per branding strategy, whether `<` compiles
between two branded values. The casts *are* the demonstration, and a brand is
unconstructible by design, so the declaration form rejects what the inline cast
accepted — `const a = {}` compared against `const b = {}` proves nothing. In a
proof about types the annotation is the test, and "does it still compile"
cannot see it being deleted.

Also clears the 36 `@import` entries the cleanup orphaned, so
`--noUnusedLocals` reports exactly what it did on `main`, and records both new
checks in the audit's method.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQcZKuWSt2rEZrCj1jVZih
* todo: open per-API issues for the casts the cleanup deliberately left

`todo/inline-type-casts.md` ends with 36 sites where the cast overrides a real
type mismatch, and says each needs an issue against the API it papers over
rather than a different cast syntax. This opens those issues for the 20 that
share a cause, and records that the remaining one-offs do not.

- `do_` has nowhere to put a type parameter, so the three generic effect
  constructors are cast while their non-generic neighbours in the same file are
  annotated declarations.
- Six `step`/`okStep` continuations cast their way to the operation union the
  caller wants, hiding any operation the runner cannot interpret.
- `memoryOperationMap()` is not assignable to `ToAsyncOperationMap<O>`, and that
  cast is the exact hazard AGENTS.md describes: it stops each handler being
  checked against `O`.
- Keyword and operator collections typed over `string` mean `has` cannot narrow
  to a `JsToken` kind, so four token constructions are cast.
- `btree/find` casts every tuple it builds or indexes.
- `mockRun`'s operation map reaches for `Parameters<typeof mockRun<…>>[0]`,
  which is what a call that cannot infer its type arguments looks like.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQcZKuWSt2rEZrCj1jVZih

* Address review: restore ten casts that `tsc --noEmit` said were redundant

`github-code-quality` flagged two unused variables in `types/nominal/proof`.
Following that up found the removals there had deleted the proof itself, and a
declaration-emit diff against `main` found four more removals that had changed
the published API. All ten type-checked; none were safe.

The API regressions, found by emitting `.d.mts` before and after:

- `effects/node` `createServer`: `<O extends Operation>(listener:
  RequestListener<O>) => Effect<O | CreateServer, Server>` became
  `(...payload: never) => Effect<Operation, never>`
- `effects/node` `log`/`error`: `Console` became the structural type
- `cas/evo` `emptyCache`: `Cache` became `{ bySubject: {} }`
- `types/range_map`: `RangeMapArray<T>` became `[T, number][]` — a purely
  functional library publishing a mutable array type

Three are now annotated declarations; `createServer` goes back to an inline
cast, which is what do-generic-operation-signatures.md is about.

`types/nominal/proof` demonstrates, per branding strategy, whether `<` compiles
between two branded values. The casts *are* the demonstration, and a brand is
unconstructible by design, so the declaration form rejects what the inline cast
accepted — `const a = {}` compared against `const b = {}` proves nothing. In a
proof about types the annotation is the test, and "does it still compile"
cannot see it being deleted.

Also clears the 36 `@import` entries the cleanup orphaned, so
`--noUnusedLocals` reports exactly what it did on `main`, and records both new
checks in the audit's method.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQcZKuWSt2rEZrCj1jVZih

---------

Co-authored-by: Claude <noreply@anthropic.com>

@o2alexanderfedin o2alexanderfedin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ten restorations are correct, and worth stating why since tsc --noEmit disagrees: package.json ships **/*.d.mts and prepack runs tsc --noEmit false --emitDeclarationOnly, so declaration emit is a check the repo runs that plain npx tsc never sees. Emitting declarations at 2aa5299 and at this head and diffing: emptyCache was { bySubject: {} }, now Cache; createServer was (...payload: never) => Effect<Operation, never>, now the generic signature; log/error were structural, now Console; fromRange returned [T, number][], now RangeMapArray<T>. Real published-API regressions, not cargo-cult. Across the whole emitted fjs/ tree vs main: 149 differing lines, all JSDoc, zero type differences. The types/nominal/proof restorations are right too — those casts are the branding demonstration.

Count is now accurate: 273 removed / 84 audited-remaining, all 95 sites listed with the 11 from main separated. My independent recount is 95 — exact match.

Still open (the last three now include #1590's content, folded in here):

  1. Changelog — no changelog/unreleased/1589.md, and no Changelog: line in the description. AGENTS.md §5 requires one or the other.
  2. PR title and body are stale — still "283 of 357 removed" / "74 remain" against the doc's corrected 273/84.
  3. The doc's "Reason | Count" summary sums to 90, not 95any bridge is 24 not 23, brand 8 not 6, overrides 37 not 35.
  4. Whitespace-only lines at fjs/mcp/evo/module.f.mjs:123 and :146 (8 spaces each), both introduced here.
  5. fjs/js/todo/token-kind-narrowing.md still overreaches — carried over unchanged. Deleting the cast at fjs/js/tokenizer/module.f.mjs:262 gives TS2322 showing kind is already the 49-member literal union, not string; it fails to distribute over _KeywordToken | _TrueToken | _FalseToken | _NullToken | _UndefinedToken. Retyping the collection won't remove that one, so "the four casts go" holds for three of the four.
  6. Minor: the doc cites fjs/djs/tokenizer/module.f.mjs at 294/392/405; the casts are at 295/393/406.

npx tsc exit 0; fjs t → 2838 pass / 0 fail.

claude added 2 commits August 15, 2026 19:42
Follows @o2alexanderfedin's review on #1589. All three counting points were
right; the summary was computed against a pre-restoration measurement and had
gone stale.

- Adds `changelog/unreleased/1589.md` (AGENTS.md §5).
- Recounts the remaining sites against the current tree, and totals the
  summary so it cannot drift again: 95 sites, of which 37 TS2322/TS2345, 24
  `any` bridges and 8 unconstructible brands — not the 35/23/6 the doc claimed,
  which summed to 90 rather than 95. The 273-removed and 84-audited-remaining
  figures were already right.
- Regenerates the per-site table from a fresh scan, fixing line numbers that
  had shifted by one under the `djs/tokenizer` casts.
- Drops two whitespace-only lines left in `fjs/mcp/evo/module.f.mjs` where a
  JSDoc comment was deleted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQcZKuWSt2rEZrCj1jVZih
# Conflicts:
#	todo/inline-type-casts.md
@sergey-shandar sergey-shandar changed the title Implement the inline @type cast cleanup: 283 of 357 removed or converted Implement the inline @type cast cleanup: 273 of 357 removed or converted Aug 15, 2026

Copy link
Copy Markdown
Contributor Author

@o2alexanderfedin — thanks, all six addressed. Every counting point was right; the summary was computed against a pre-restoration measurement and had gone stale.

1. Changelog — added changelog/unreleased/1589.md.

2. PR title and body — updated to 273 removed / 84 audited-remaining, with the declaration-emit finding written up rather than left in a comment thread.

3. The summary summed to 90, not 95 — confirmed, and your three corrections are exactly right. Recounted against the current tree: 37 TS2322/TS2345 (not 35), 24 any bridges (not 23), 8 brands (not 6). The table now carries a **Total** | **95** row and is generated from the same classification as the per-site rows, so it cannot drift apart from them again.

4. Whitespace-only linesfjs/mcp/evo/module.f.mjs:123,146 cleared. grep -n " *$" is clean on the file.

5. token-kind-narrowing.md overreach — you're right, and thanks for actually deleting the cast to check. Reproduced: at js/tokenizer:262 keywords is already the 49-member literal union, and the failure is that { kind: <union> } does not distribute over _KeywordToken | _TrueToken | _FalseToken | _NullToken | _UndefinedToken — it has to match one member and matches none. Retyping the collection cannot fix that. The issue now splits the claim: three of four casts go by typing the djs collections, and js/tokenizer:262 is called out separately as a distribution problem needing the entry list built per-literal or JsToken restructured. The row in todo/inline-type-casts.md is annotated to match. (In #1590, since that file lives there.)

6. Line numbers — the per-site table is regenerated from a fresh scan; djs/tokenizer now reads 295/393/406. The stale ones came from a scan taken before the last main merge shifted them by one.

On your point about the 11 casts from #1582/#1583: agreed, and they're now listed in the table under "arrived on main after the audit" and excluded from the audited-set count, rather than silently missing. I've left them unfixed deliberately — new code arriving with new casts mid-cleanup is the concrete argument for todo/eslint.md, and folding them in would keep this PR chasing main.

npx tsc exit 0; fjs t → 2838 pass / 0 fail; emitted declarations show zero type differences against main.


Generated by Claude Code

@o2alexanderfedin o2alexanderfedin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Five of the six items are resolved:

  1. changelog/unreleased/1589.md present and matching changelog/README.md; body ends with the Changelog: line.
  2. Title and body now say 273 of 357 / 84 remaining / 95 rows.
  3. Reason table sums correctly: 37+24+8+6+4+2+3+11 = 95, matching the 95 per-site rows, with a **Total** row added.
  4. Whitespace-only lines gone; git diff --check clean.
  5. fjs/djs/tokenizer citations now 295/393/406.

Independent recount at this head: 320 inline @type casts under fjs/, 225 {const}, 95 non-const — matches the doc. I also diffed all 95 file:line citations against the actual sites: exact set match, zero discrepancies either direction. #1591's merge left the accounting intact. npx tsc exit 0; fjs t → 2844 pass / 0 fail (body still says 2838 — harmless drift from the merges).

Item 5 is still open but not worth holding this up, since it describes future work rather than anything in this diff: fjs/js/todo/token-kind-narrowing.md is unchanged, and fjs/js/keywords exports keywords = /** @type {const} */ ([...]), so that collection is not typed over string. Deleting the cast at fjs/js/tokenizer/module.f.mjs:262 still gives TS2322 — kind is already the 49-literal union and fails to distribute over _FalseToken | _KeywordToken | _NullToken | _TrueToken | _UndefinedToken. Worth fixing whenever that todo is picked up; three of its four sites are correctly characterized.

@sergey-shandar
sergey-shandar added this pull request to the merge queue Aug 15, 2026
@sergey-shandar
sergey-shandar removed this pull request from the merge queue due to a manual request Aug 15, 2026
@sergey-shandar
sergey-shandar added this pull request to the merge queue Aug 15, 2026
Merged via the queue into main with commit c2001be Aug 15, 2026
19 checks passed
@sergey-shandar
sergey-shandar deleted the claude/cast-type-annotations-refactor-hq8l8o branch August 16, 2026 03:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants