One path. No fallback. No ceremony.
- One path — no mode flags or opt-out env vars. A fallback nobody sets is an untested code path that rots while the default gets all the coverage. If two behaviors are both defensible, pick one and delete the other; verify it with the gates, not with a switch.
- No fallback — do not degrade around hypothetical edges. Enumerate real edges and kill them by construction (idempotency, mechanism guarantees). If a failure mode cannot occur on the supported floor (Node ≥ 20, Go 1.26, current V8), do not code for it — fail loudly so real problems surface instead of hiding behind a silent degraded mode.
- No ceremony — if the language/runtime contract already provides the behavior (e.g. returning a NULL-initialized value propagates a pending napi exception), do not restate it in code. Comments explain why (ownership, contracts, invariants), never what the next line does.
Reference implementation of the style: patches/typescript-go/overlay/bridge/napi_shim.c.
- Env knobs need a committed consumer — otherwise they're deleted on sight. One name per knob.
- No debug scaffolding in product code — instrumentation and its witness live and die together.
- The pinned bundle is the floor — call its API directly; no presence guards or compat branches. A mismatch must throw.
- Abandoned approaches die in the same commit — dead code is part of the pivot's diff.
- Triage scripts are disposable — wired gate, exit-coded probe, or deleted when the issue closes.
- One harness, parameterized — never fork a script to vary it.
- Mechanism changes sweep their vocabulary — grep the old name across patches/, tools/, comments in the same change.
- Deletions are verified by the gates, not switches.
- No framework hardcoding — behavior keys on registered mechanisms (
extraFileExtensions/supportedTSExtensionsFlat), never on literal framework suffixes or names (.vue,vue,svelte…). If a fix needs a framework literal, the mechanism is what's missing. - One Go→JS file-name boundary —
wireFileNameToHost(tsgoLibPaths.ts) is the only normalization point for wire-decoded file names; every new transport/payload must funnel through it at its decode point, call sites never re-normalize. (Key folding iscanonicalSourceFilePath; JS→Go input isresolveHostFileName.) - Review the landed diff for elegance before committing — top smells: duplicated truth (call the source instead), caches that hide the problem, hidden contracts. Don't defend the first draft.
TNB is a tsgo-backed TypeScript fork: upstream microsoft/TypeScript and microsoft/typescript-go pinned as submodules, a small patch set on top (patches/), and a NAPI bridge (bridge.node) that runs the tsgo checker in-process. Sources of truth: patches/typescript/overlay + patches/typescript/*.patch (edit via the submodule then npm run save-ts-patches), patches/typescript-go/overlay + *.patch (npm run save-patches). Build: npm run build:lib (fork bundles) and npm run build:bridge (NAPI addon).
npm run check:lib/check:enums/check:sourcefile-guard- Witnesses:
node tools/triage-sim-xfile.mjs,triage-sim-edit.mjs,triage-quickinfo-emptyparity.mjs,triage-crossgen-reuse.mjs(issue #11: cross-generation RemoteSourceFile reuse + edit invalidation),triage-framework-checks.mjs(svelte-check/astro-check/glint vs stock parity — cached fixture installs under /tmp/tnb-fw-fixtures), f2hl/f2r6 series,triage-refs-exportspec.mjs,triage-display-tokens.mjs,triage-computed-literal.mjs,triage-napi-fuzz.mjs,triage-arena-parity.mjs(arena-vs-JSON transport differential: every arena-capable method's result byte-equal across the two transports),triage-bridge-thread-race.mjs(worker_threads hosts: cross-thread text-response integrity + teardown-unload survival),triage-nuxtui-exportstar.mjs(issue #26:./X.vuespecifier with an on-diskX.d.vue.tsmust resolve to the declaration, not the literal SFC — the @nuxt/ui dist pattern),triage-program-info.mjs(program-info API stock differential: missing paths, include reasons + explainFiles reason lines, resolution caches, ATA names/resolutions, usesUriStyleNodeCoreModules, classifiable names, isEmittedFile, ref lookups — the TnbProgramCoverage surface),triage-type-field-audit.mjs(type-payload exhaustiveness: every data field stock puts on a Type must cross the bridge equal or carry an inline exemption — the #16/#19/#22/#23 gap class),triage-checker-differential.mjs(checker-API stock differential: one self-contained corpus queried through stock 6.0.3 and the TNB watch/builder path, byte-equal canon per method@location; known divergences keyed in-file with upstream/lib-delta/tracked-bug classes, stale exemptions fail),triage-electron-abi.mjs(wired in CI vianpm i --no-save electron@42),triage-cli-only-flags.mjs(CLI-only options vs stock differential:--strict/--noUncheckedIndexedAccess/--target/--modulefamily — effective options ride updateSnapshot to Go; a flag that only exists on the CLI must move diagnostics exactly like stock),triage-eslint-typeref-target.mjs(issue #35: typescript-eslint's isTypeReference→target→getSymbol() chain — nested Type/Symbol wire handles must read back as registry-identical objects, never raw ids, including getBaseTypes() results that never pass an adapter method) - Big net: sim-nav vs
test/baselines/(npm run check:sim-nav— 4 parallel shards from isolated tools copies, merged and baseline-gated bytools/sim-nav-parallel.mjs) — no new divergences allowed. Baselines are slim (keys/counters/labels only) with test-workspace-relative keys — machine-local paths in a committed baseline make the nightly gate red by construction. Refresh by running check:sim-nav thennode tools/sim-nav-merge.mjs --slim /tmp/tnb-simnav-merged-4.json test/baselines/nav-results-<sha>-t<N>.json(refuses absolute keys), re-pinBASELINEin.github/workflows/nightly.yml, delete the superseded baseline. The local gate cannot catch non-portable keys (same machine, same prefix) — nightly is the clean-machine gate, check its result after a refresh. - volar suite:
npm testin the volar checkout (205 tests)
- Commits: plain-language, component-prefixed (
fix(bridge):,perf(bridge):,ci:,docs(readme):), says what and why, no filler. - Issue replies: written by a human or reviewed by the maintainer before posting. No templated "thanks for the detailed report" tone.
- Memory and behavior parity with stock
typescriptare the two hard constraints; measure before claiming either. TNB fixes only bridge-introduced divergences. That category includes the bridge contract surface (stock-API semantics the bridge must implement so stock's own services/session code runs correctly — additive APIs likegetSymbolOfExpando, wire shapes, symbol identity) and bridge-only-triggered engine bugs (engine invariants no upstream caller relies on but the bridge's call patterns violate — fix locally, link the upstream report). - For engine-owned behavior, the reference is tsgo's actual behavior (its own LSP/IDE output), not stock — deliberate engine design is documented, not overridden, and engine bugs go upstream (file the issue, link it, document the divergence as a known issue) rather than being patched here. Any "is it tsgo's or the bridge's?" verdict requires verification against pristine, unpatched tsgo — never the patched submodule, never TNB-vs-stock inference.
- Exceptions: a crasher or a silent wrong result on the headline path may carry a stopgap patch — each must be listed in the README's tsgo-behavior-changes section with the upstream issue link, and removed once upstream lands the fix. Every TNB change to tsgo behavior is listed in the README, and the list is gate-enforced against the patch contents — the mechanism is annotation-driven: the change-site hunk carries a
Ledger(<key>)comment andtools/check-readme-ledger.mjsfails on either direction of drift (annotation without a README row, row without an annotation). A large alignment cost is never the default action — it needs maintainer sign-off.