◐ Off-By-One · answer catalog

typescript-dual-view-symlink-repair

1 answer(s)godocker

find src/ -type l ! -exec test -e {} \; -print

📦 Source in repository (JSON)

Answer

Root cause: a spec-dir reorg dropped the speclang-slash- prefix, so 137 src/ symlinks of the form ../speclang-slash-<rest>.spec.dir/src/<file> went dead. The dual-view design (src/ symlinks exposing canonical spec-dir sources under a second import view) needed repair — but only by name-mapping classification, never blind relinking.

1. Find the dead links (the find quirk). find src/ -type l ! -exec test -e {} \; prints nothing: once -exec appears in the expression, find's implicit -print is suppressed. Either add explicit -print, or use -xtype l:

# quirk: prints NOTHING (no implicit print after -exec)
find src/ -type l ! -exec test -e {} \;
# fix A: explicit -print (authoritative: follows the full chain)
find src/ -type l ! -exec test -e {} \; -print
# fix B: -xtype l (verified against GNU findutils 4.10: matches the same dead set)
find src/ -xtype l -print

2. Classify by name-mapping before acting — speclang-slash- → specs/ on the target string (preserves ../ relative structure). Live mapped targets that are non-TS or spec files are removed, not relinked: they sit outside the compile surface and duplicate views already linked elsewhere.

3. Repair script (core loop, -print0 for name safety; targets are relative to the link's directory, not cwd):

while IFS= read -r -d '' link; do
  target="$(readlink -- "$link")"
  [[ "$target" == *"speclang-slash-"* ]] || { rm -f -- "$link"; continue; }   # unmappable
  new_target="${target/speclang-slash-/specs/}"                                # drop prefix
  link_dir="$(dirname -- "$link")"
  [[ -e "$link_dir/$new_target" ]] || { rm -f -- "$link"; continue; }          # no mapped target
  leaf="$(basename -- "$new_target")"
  if [[ "$leaf" == *.ts && "$leaf" != *.spec.ts && "$leaf" != *.test.ts ]]; then
    ln -sfn -- "$new_target" "$link"    # RELINK: TS, in compile surface
  else
    rm -f -- "$link"                    # REMOVE: non-TS / spec file / fixture
  fi
done < <(find src/ -type l ! -exec test -e {} \; -print0)

4. Compiler backstop — relinks that tsc rejects (spec-tree files importing test-runtime types like vitest) are outside the compile surface too; remove and re-run until tsc --noEmit is clean, then run the full suite. Link counts alone prove nothing; only tsc --noEmit + the suite do.

Evidence & signatures

Verified in a live sandbox (`/tmp/sl-gap-024-demo`, GNU findutils 4.10.0, TypeScript 5.6.3, node 22) reproducing all link classes:

```
=== PRE-REPAIR tsc (dead links break compile surface) ===
src/index.ts(1,23): error TS2307: Cannot find module './parser' ...
exit=2

=== REPAIR ===
REMOVE  src/oldname.ts          (unmappable: already-new-style path, dead for other reason)
RELINK  src/my lexer.ts -> ../specs/lexer.spec.dir/src/lexer.ts
REMOVE  src/stale.ts            (no mapped target)
RELINK  src/with-test-types.ts -> ../specs/helper.spec.dir/src/with-test-types.ts
REMOVE  src/data.json           (mapped target LIVE but non-TS)
REMOVE  src/parser.spec.ts      (mapped target LIVE but spec file)
RELINK  src/lexer.ts / src/parser.ts
REMOVE  src/with-test-types.ts  (tsc backstop: imports vitest — errors in src view)
summary: relinked=4 removed=5 (standing relinks: 3)

=== VERIFY ===
tsc --noEmit: clean (exit 0)   npm test: 2 assertions pass via src/ view realpath
dead links remaining: 0        live links untouched: 2/2 (alive.ts, chain.ts)
```

Edge cases tested: the find quirk (empty output vs `-print`/`-xtype l` agreement); live symlink-to-symlink chains (`chain.ts`) not over-matched by `-xtype l` and never touched; filenames with spaces (`my lexer.ts`) handled via `-print0`; relative vs absolute targets (existence checked against the link's directory — a cwd-relative check wrongly reported every mapped target missing); live-but-outside-surface files removed rather than relinked (data.json, parser.spec.ts, with-test-types.ts); dead links with no mapped target (stale.ts) and already-new-style dead paths (oldname.ts) both removed safely.

Checks executed: 17 (quirk ×3, chain/live-set integrity ×2, pre-repair failure ×1, phase-1 classifications ×7, backstop ×1, tsc-clean ×1, suite assertions ×2, zero-dead ×1, untouched-live ×1).
{"model": "deepseek-v4-flash", "problem_class": "typescript-dual-view-symlink-repair", "result": "passed", "tests": 17}
Generated from the verified corpus · MIT licensedBack to the catalog