After #924, home-tsc --watch tracks every file present in Program's initial import closure. A later edit that adds a new import still only calls Program.updateSource and Program.recompileChanged; the CLI does not reload the import closure or add the newly admitted dependency to Watcher. New imports may therefore be absent from compilation and subsequent watch events. Implement incremental closure admission and watcher registration with stable Program identities, and verify import added/removed/replaced, transitive addition, repeated edits, and importer diagnostics on a real virtual project. Do not rebuild the whole program as a shortcut or substitute empty source. Existing-source removal is tracked separately in #923; parent #66.
fix(ts-watch): admit and track imports introduced after startup
#925Guarded real-CLI red control on current main 40cfdba: launched home-tsc --watch --noEmit --pretty false --ignoreConfig on a temporary root importing first.ts. Startup printed 0 errors. Editing root to import second.ts printed an incremental-compilation event. Editing second.ts (number -> string, mtime and size changed) printed no event over the next 5 seconds. Editing the originally loaded/tracked first.ts printed the expected event, proving the loop was alive. The 45-second run_bounded deadline then stopped the watch process (exit 124 is the intentional bound, not a test pass). Temporary fixture files were removed.
Design constraint: Program.loadImportClosureImpl binds/discovers the new source but, if any source was already checked and files were added, calls recompileAll(options) unconditionally (ts_program.zig ~4267). Invoking it after every watch edit would recheck unrelated files, contrary to this issue's incremental acceptance. A correct fix must retain the pre-discovery invalidated importer set (because bind-only discovery repopulates compilation/import edges), admit new file IDs, and recheck the changed/new reverse closure; program-wide declarations/ambient augmentation require separately justified broader invalidation. No code change or passing claim yet.
Pushed 3dc9ceb (fix(ts-watch): register source batches atomically (#925)) directly to main as glennmichael123, with exactly three files and no coauthors. Watcher.trackMany stages deduplicated path stats and owned keys, reserves the target map, then commits without further allocation. A late stat error or any injected allocation failure leaves the old tracked set intact; already tracked roots retain pending changes. The CLI uses it for root and Program-file registration. Tests cover late stat error, allocation failures, duplicate paths and baseline preservation. Post-rebase guarded results: watch 23/23 on pinned Zig 0.17 and Zig 0.18; CLI 51/51 on pinned Zig. Pre-rebase CLI 51/51 and home-tsc builds also passed on both versions. All heavy runs were serial under 3,840 MB (highest observed peak 2,262 MB). This is a prerequisite only: the real watch-mode red control for imports introduced after startup remains, and incremental closure admission/recheck is still open in this issue.
Pushed dfad597 directly to main (fix(ts-watch): admit imports added during incremental builds (#925), exactly 3 files, glennmichael123 author/committer, no coauthors). Program.recompileChanged now runs bind-only closure discovery before final checking, preserves the pre-discovery invalidation set, and rebinds the new reverse-importer closure so stale TS2307 binding state is not reused. Ordinary imported modules keep unrelated checked owners; newly admitted script/ambient/global contributors trigger a semantically justified broader recheck. The CLI registers the resulting paths before publishing diagnostics.
Red control before the fix: the new transitive Program regression expected 3 recompiles and got 1; the real watch loop ignored a later edit to the new dependency while still noticing edits to the originally tracked one. Green control on the rebuilt pinned-Zig CLI: replacing the root import with a two-hop chain produced an incremental event with 0 errors; editing the newly admitted leaf from number to string produced TS2322 in the root; editing back cleared it. The 60-second supervisor ended the intentionally persistent watch run with exit 124; this is not counted as a suite pass. One transient memory-probe deadline warning appeared during the control, so uninterrupted sampling is not claimed. Temporary fixtures were removed.
Guarded test/build results under 3,840 MB, serial: Program 322/322, watch 25/25, CLI 51/51 on pinned Zig 0.17 and Zig 0.18; both home-tsc Debug builds passed. Peak observed tree footprint was 2,340 MB. Formatting/diff checks passed. #925 remains open for robust retry/reconciliation if a read, stat, or allocation error interrupts a watch update; #923 separately tracks deletion and physical-source invalidation. No full TypeScript/Bun parity is claimed.
Pushed 5d54d1a directly to main as glennmichael123 (4 files, no coauthors): fix(ts-watch): retry failed change observations (#925).
Watcher.observe now stages a complete filesystem batch without advancing baselines; the CLI commits it only after reads, source updates, incremental recompilation, new Program-file registration, and output emission succeed. Failed reads/updates/recompiles/registration leave the batch pending for the next poll. Observation updates use owned paths and re-lookup at commit, so trackProgramFiles may grow/rehash the map safely.
Red control: new lifecycle regression failed to compile because Watcher.observe did not exist. Green guarded results, one heavy process at a time under 3,840 MB: watch family 28/28 and CLI 51/51 on pinned Zig 0.17 and Zig 0.18; standalone home-tsc builds passed on both. Peak observed guarded tree footprint 2,204 MB. Tests inject allocation failure at the start of Program.updateSource and Program.recompileChanged, then verify the event is retried and the importer gets TS2322; they also cover complete-batch retry and map growth before commit.
I am leaving #925 open: these tests do not prove recovery from arbitrary mid-recompile allocation failures or exercise the CLI read-failure path by live fault injection. Existing-source deletion remains separately tracked in #923. No broad TS/Bun parity claim.
Pushed bbe07c5 directly to origin/main as glennmichael123 (3 files, no coauthors): fix(ts-watch): invalidate resolver caches between revisions (#925). After a successful watch read/update batch, the CLI disposes its checker resolver adapter and clears filesystem-dependent positive/negative resolution memos, relative-directory snapshots, package-format/file-existence caches, and their arena before incremental recompilation. This is a source-resolution fix, not a deletion implementation.
Evidence on the committed source, one heavy process at a time under the unchanged 3,840 MB ceiling: complete ts_watch_program target passed on pinned Zig 0.17 and Zig 0.18; complete ts_resolver target passed on both; ts_cli target and standalone home-tsc build passed on pinned Zig. The new virtual-Program regression starts with a cached missing import, creates dep.ts, updates the importer, and requires admission, the correct FileId edge, and TS2322. An actual rebuilt home-tsc --watch control reported TS2307 before creation and TS2322 after creation plus an importer edit. Its intentionally persistent run ended at the 35-second supervisor deadline (124), not a suite pass. Temporary fixture removed. Green guarded peaks were at most 2,064 MB.
Still open: creation of a never-tracked file alone is not a watch event, and removal/recreation remains unimplemented in #923. A separate positive-cache probe (dep.js already admitted, then dep.ts appears) now admits dep.ts but the incremental Program lacks the fresh Program TS2322; I did not weaken the regression or claim that case fixed. Retained-source reconciliation needs its own diagnosis and tests. No full #925/#66 completion claim.
Follow-up red control on pushed bbe07c5, using the rebuilt standalone CLI and a separate temporary project: main.ts imported dep.js and started watch with 0 errors. I then created higher-priority dep.ts exporting string and edited main.ts; the watch emitted an incremental event but still reported 0 errors. The earlier isolated Program probe had admitted dep.ts but its importer lacked the fresh Program TS2322. This confirms a real CLI incremental discrepancy, not merely a resolver unit-test artifact. The 35-second persistent watch exited at its intentional wall bound 124; one memory-probe deadline warning appeared near shutdown, so I do not claim uninterrupted memory sampling or a suite pass. The guard is fail-closed after eight consecutive failed probes; this was one warning. Temporary fixture was removed, tracked worktree remains clean, and no fix or TypeScript-reference parity is claimed for the positive-cache case.
Read-only source trace after the real-CLI red control: Checker.programImportResolution at packages/ts_checker/src/check.zig:115579-115584 stores only stripProgramModuleExtension(resolved.path). Checker.programImportTargetsPath at :115600-115610 compares the extensionless base to every Program exported-value target. When Program retains both dep.js and newly admitted dep.ts, both paths share that base; programExportedValueTypeForImportBinding at :111634-111644 returns Primitive.any on a second matching export route. This is a concrete likely mechanism for the missing TS2322, but I have not yet asserted the two actual routes in a direct checker test or qualified a fix. Next change must preserve exact resolved-source identity while retaining legitimate project-reference/virtual-path matching, then use the unchanged real watch control and broad guarded suites. No source edit or passing claim in this note.
Pushed d4c7c5a directly to origin/main as glennmichael123 (exactly 3 changed files, no coauthors): fix(ts): preserve exact import owner across watch revisions (#925). Checker now retains the resolver's exact resolved source path for Program export-route matching. The extensionless fallback is used only when no resolver result exists. This prevents an old admitted dep.js and newly preferred dep.ts from both matching the same named export and collapsing its type to any.
Red/green evidence: before the source change, the new unfiltered Program regression was the sole failure (327 pass, 1 fail) at its required TS2322 assertion; after the change, the unfiltered Program target passed on Zig 0.17 and 0.18, and the expanded virtual watch target passed on both. The watch regression asserts the dep.ts FileId edge, TS2322 on the first revision, tracking of the new file, and removal of TS2322 after its next revision. The ts_cli target passed; unfiltered ts_driver passed 214/214. The ts_checker command returned exit 0 and an explicit follow-up summary reported 3/3 successful cached steps; no exact checker test count is claimed. Standalone home-tsc built successfully. Every heavy run was serial under the 3,840 MB guard; highest reported green peak was 2,382 MB.
The exact rebuilt real-CLI control, unchanged from the red probe, now reports 0 errors with dep.js, TS2322 after dep.ts appears and the importer changes, and 0 errors after dep.ts is corrected. It was a persistent watch intentionally stopped by the 20-second wall bound (exit 124), not a suite pass. A single memory-probe deadline warning appeared at shutdown, so uninterrupted sampling is not claimed. Temporary fixtures were removed.
#925 remains open: creation of a never-tracked file alone still has no watch event; retained old sources require broader reachability/reconciliation; CommonJS export-route paths have not been proven collision-free. Source deletion/recreation stays in #923. No full TypeScript or #66 parity claim.
Read-only follow-up on current main after d4c7c5a: the ESM exported-value route now compares the resolver's exact path, but CommonJS export routing still indexes ProgramCommonJsExport.module_path by stripProgramModuleExtension in Checker.programCommonJsExportsForPath (check.zig around 115620-115658). Its callers at 64909-65112 use resolution.external_base and sometimes the relative fallback even when an external path was resolved. A retained dep.js and newly preferred dep.ts therefore remain candidates for the same basename group in CommonJS paths; this is a source-level risk, not a demonstrated diagnostic mismatch.
Existing checker controls at 266918-267021 intentionally require some cross-extension/declaration and fallback matching. An exact-path-only rewrite would break those controls, so the next step is a reference-checked CommonJS watch reduction that distinguishes a real source owner from legitimate declaration/virtual aliases before changing the lookup. No source or test assertions were changed and no CommonJS fix is claimed.
Pushed 9d4d8e3 directly to origin/main as glennmichael123 (3 changed files, no coauthors): fix(ts-watch): bind CommonJS exports to admitted source. The retained CommonJS table still groups extensionless paths for declaration/virtual aliases, but when the resolver points to an admitted Program source, lookup now requires the exact module_path and does not borrow an old sibling extension or relative fallback. The production CLI adapter reports source admission; a Program regression uses a source-backed resolver, and the checker regression preserves the sibling-miss and same-owner-hit cases.\n\nRed/green control: with dep.js initially admitted, creating a preferred dep.ts and editing the importer formerly left watch at 0 errors, while fresh Home and TypeScript 5.7.3 reported TS2322. The rebuilt production home-tsc watch now reports TS2322 on that incremental revision. The persistent watcher was intentionally stopped by its 45-second bound (exit 124), not counted as a test-suite pass. Pinned Zig 0.17 guarded results: ts_program 329/329, focused CommonJS checker 51/51, ts_cli 81/81, ts_driver 214/214; standalone home-tsc build, Zig formatting, and diff checks pass. The unfiltered 4,475-test checker target did not finish within its 360-second bound and is not claimed green. All heavy commands ran one at a time below the 3,840 MB ceiling.\n\n#925 remains open: creation of a never-tracked dependency alone still needs a watch event; broader retained-source reachability and reconciliation remain. Source deletion/recreation is tracked in #923; full Bun parity remains under #66.
Creation-only red control on pushed main 8510bbc: a two-file temporary project has main.ts import ./dep with a number target and dep.js export a number, with allowJs/noEmit/watch enabled. Both TypeScript 5.7.3 and freshly built Home start at 0 errors. Without editing any tracked file, I then create higher-priority dep.ts exporting string. TypeScript emits an incremental event and TS2322 at main.ts(2,14); Home emits no event over 12 seconds and remains silent until its 35-second guard ends. Both persistent watchers intentionally exited 124, not a suite pass; each ran alone under the 3,840 MB guard. Temporary fixtures were removed.
Source trace: Watcher.observe only stats paths in its tracked map, and trackProgramFiles registers admitted Program files. Resolver probes missing candidate paths through fileExistsTraced but does not retain them as watch interests. A fix must make the resolver-derived failed/high-priority candidates observable, then reconcile those interests when imports change and invalidate the exact importer when a candidate appears; it must not rely on editing the importer or on a broad forced rebuild. No production source was changed in this control.
Pushed 64023e2 directly to origin/main as glennmichael123 (five files, conventional commit, no coauthors): fix(ts-watch): observe failed resolution candidates. In watch mode, Resolver records failed file probes before a successful or failed import resolution; WatchInterest atomically registers them alongside Program paths and retires stale candidates after import replacement. A newly created candidate can now cause Program to invalidate the exact checked importer even when its source text and stat are unchanged. RealStatFs treats an existing directory at a candidate path as absent, avoiding a perpetual read-as-file retry.
Reference-checked red/green control on an unchanged temporary project: main.ts imports dep.js as a number. Creating higher-priority dep.ts with a string export, without editing main.ts, makes TypeScript 5.7.3 report TS2322. Home on the prior commit emitted no event; the rebuilt final-source Home binary now emits an incremental event and the same TS2322 at main.ts(2,14). Persistent watches were intentionally stopped at their 35/30-second guard bounds (exit 124), not counted as suite passes. Fixtures were removed.
Serial guarded verification below 3,840 MB: resolver 128/128, Program 332/332, full watch family 34/34, tsc_main 52/52, ts_cli 81/81, and final home-tsc build. ts_driver reported cached success for current sources; no full checker or Bun-suite pass is claimed. New virtual controls cover higher-priority creation, formerly unresolved creation clearing TS2307, stale-candidate retirement after import replacement, and atomic failure/retry on candidate stat error.
Leaving #925 open for broader package/directory resolution and retained admitted-source reachability qualification; this commit proves the relative-file creation-only gap, not universal watch parity.
New reference-checked retained-source red control on pushed main 64023e2, using one temporary explicit-root three-file project at a time under the unchanged 3,840 MB guard. Startup main.ts imports first.ts and both TypeScript 5.7.3/Home report 0 errors. Changing only main.ts to import second.ts triggers a clean incremental event in both. Changing first.ts afterward (number export -> string export) produces no further event in TypeScript over the next 22 seconds, but Home starts another incremental compilation and reports 0 errors. Both persistent watchers ended at their intentional 50-second wall bound (exit 124), not a suite pass; fixtures were removed.
Source trace: trackProgramFiles/WatchInterest still registers every historical Program File.path, while Program retains old admitted compilations and source owners after an import edge disappears. Simply untracking first.ts would suppress the extra event but leave stale diagnostics and output state; a correct fix needs root-vs-discovered ownership plus reachability reconciliation of the Program itself, preserving stable identities, explicit roots, compiler libraries, references and package redirects. No source or assertion was changed in this control. #925 remains open.
Pushed c505a65 directly to origin/main: watch-mode Program now distinguishes explicit/compiler roots from discovered imports, rebuilds non-import dependency edges, retires no-longer-reachable sources and their compilations (including orphaned cycles), and reconciles watcher paths with the live graph plus failed resolution probes. Verification under the 3840 MB guard: Program 333/333, watch family 35/35, tsc main 52/52, CLI 81/81; standalone home-tsc built. In a live watch fixture, replacing an import cleared TS2322, and editing the now-unreachable old source produced no subsequent rebuild during the 70-second guarded run. Keeping this issue open for remaining broader watch/reachability parity cases.
Pushed 60aa00a directly to origin/main as glennmichael123 (three files, no coauthors). A package.json-only types-field change was a confirmed watch gap: TypeScript 5.7.3 rebuilt and reported TS2322, while previous Home emitted no event over a guarded 45-second run. Resolver now records each consulted package.json against actual importing source paths; WatchInterest atomically snapshots those dependencies with watched paths so a failed incremental attempt can retry after resolver caches are cleared. Metadata events recompile the recorded importers rather than treating package.json as a TypeScript source. When an import is removed, stale metadata watches are retired. Red watch control was 10/11; final watch 36/36, resolver 129/129, Program 333/333, tsc main 52/52, CLI 81/81. Fresh final-source home-tsc build passed; live package metadata edits produced TS2322 and then cleared it on reversal. The persistent watch intentionally ended at its 45-second wall guard (exit 124), not a suite pass. Broader package/directory and config-watch parity remains open.
Pushed e7a0b31 directly to origin/main as glennmichael123 (four-file conventional commit, no coauthors). This closes a package-metadata watch gap alongside import-closure tracking: containing-package probes now belong to each source (including cached lookups and missing nearer package.json paths); incremental Program revisions refresh the implied Node module format; WatchInterest retains metadata dependencies of untouched files across resolver cache invalidation and retires dependencies of changed/removed files.
Verification on the committed source: resolver 130/130, Program 334/334, CLI 81/81, all watch suites 38/38, final home-tsc build 3/3; all guarded at 3,840 MB with one heavy process at a time. The integration regression tests CommonJS to ESM to CommonJS diagnostic flips and unrelated-package retention. A live final-binary watch observed package.json editing alone change 0 errors to TS1309; an earlier same-source live run observed the reverse flip. The 25-second live watcher ended at its intentional bound, not a test pass.
This is a scoped advance, not a claim that every #925 import-add/remove/transitive acceptance path is finished. A separate static real-file diagnostic gap exposed by the differential fixture is tracked in #926; no checker change was bundled into this watch commit.
Scoped progress: 8325f77 closed #936, a transitive imported-const type-inference gap uncovered while testing imports introduced after startup. Its Program regression exercises introduction, replacement/retirement, and reintroduction of a three-file import graph; guarded ts_program 354/354, tsc_main 60/60, ts_cli 88/88, and the production Home CLI matched pinned TS 6.0.3/native TS 7.0.2 on positive and TS2322 negative controls. This does not by itself complete watch-mode admission/tracking; #925 remains open for its full acceptance criteria.
Current-main live watch comparison on 8325f77 (Darwin arm64) using one unchanged five-file project per run, under the 3,840 MB guard: Home and pinned TypeScript 6.0.3 both start at 0 errors; adding main -> middle -> leaf reports exactly TS2322 at main.ts(2,14); editing the newly admitted leaf to number clears it; replacing the import with an alternate number source remains clean; editing the now-retired leaf produces no rebuild over the next 10 seconds; reintroducing the middle import reports TS2322 again; a second leaf edit clears it. Each persistent watch intentionally reached its 90-second wall bound (exit 124), which is not a suite pass. Existing Program regression on this main also asserts stable FileIds and retirement/reintroduction; guarded ts_program 354/354, tsc_main 60/60, ts_cli 88/88 passed during #936 qualification. This verifies the listed import-add/replacement/transitive/repeated-diagnostic path on this host, not every package/config/watch/platform edge; leaving #925 open for its broader parity review.
The stated incremental-import acceptance is now implemented on main (notably dfad597 and the subsequent #925 watch/reachability commits) and reverified today on 771c052. Under the fixed 3,840 MB guard, the complete dedicated watch-program artifact and full watch-family artifacts exited 0 (peak tree footprints 1,989 MB and 317 MB). The committed virtual-project controls cover later direct/transitive import admission, watcher registration and subsequent leaf events, replacement/retirement/reintroduction with stable FileIds, repeated edits, unrelated-owner preservation, importer diagnostics, and failure retry. The previous live five-file Home-vs-TS6 watch sequence is recorded in the 2026-10-21 comment; it ended at its intentional wall bound, not as a suite pass. Closing #925 for this specific scope, not claiming all watch/config/platform parity or CI. #753 remains open: current GitHub run list still has no workflow after 2026-06-01.
Sign in to comment on this issue.