Monorepo for Aesthetic.Computer aesthetic.computer
core reports js-tooling-audit.md
15 kB

JS/TS Code-Intelligence Tooling Audit — 2026-06-10 #

Machine: MacBook (Apple Silicon), 8 GB RAM — every recommendation below was filtered through "one process at a time, low resident memory."

Target repos:

  • /Users/jas/aesthetic-computer — large JS/.mjs runtime, no TS (~8,600 .mjs files, 75 MB in system/public/aesthetic.computer)
  • /Users/jas/Developer/fuser — TypeScript 5.8.3 pnpm/turbo monorepo, 22 packages, ~5,900 .ts/.tsx files

1. Inventory (before → after) #

Tool Before After Notes
ripgrep (rg) 15.1.0 — already best-in-class baseline
ugrep "missing" on PATH — Claude Code embeds it: the agent-shell grep function execs the claude binary as ARGV0=ugrep (ugrep 7.5.0). So plain grep in agent shells is already smart.
fd missing 10.4.2 installed (brew)
fzf missing 0.73.1 installed (brew) — interactive, marginal for agents
ast-grep (ast-grep / sg) missing 0.43.0 installed (brew); binary is ast-grep (no sg symlink — sg is a Unix builtin name)
comby missing not installed superseded by ast-grep (not AST-aware, weak query logic)
semgrep missing not installed Python, slow startup, heavier RSS; overkill vs ast-grep for grokking
universal-ctags only BSD /usr/bin/ctags 6.2.1 (brew, with readtags) TS/TSX parser maintained upstream
tree-sitter CLI missing not installed ast-grep covers the use cases
jq 1.x present — fine
gron missing not installed jq suffices
node / tsc v22.22.2 / TS 6.0.3 global; fuser pins TS 5.8.3 —
deno 2.7.14 —
bun missing not installed no current need
typescript-language-server 5.1.3 (global) — used by the Claude Code typescript-lsp plugin
vtsls missing 0.3.0 (npm -g @vtsls/language-server) installed as an option; see proposals
tsgo (@typescript/native-preview) missing 7.0.0-dev.20260610.1 (npm -g) the headline install
oxlint missing 1.69.0 (brew) instant whole-package sanity linting
biome missing not installed oxlint is ~2x faster for lint-only; fuser keeps eslint as canonical
eslint repo-local only (fuser node_modules/.bin/eslint) — canonical but slow
difftastic (difft) missing 0.69.0 (brew) semantic diffs
scip-typescript / scip missing not installed — deliberately docs recommend --max-old-space-size=16000; a non-starter on 8 GB. ctags is the cheap index instead.
Claude Code 2.1.170 — LSP tool is built-in (shipped ~2.0.74, no env flag needed)
Claude plugins typescript-lsp@claude-plugins-official + swift-lsp installed & enabled — .in_use markers show live sessions using it
MCP servers (~/.claude.json) asana, chrome-devtools ×3, sketchup — no code-intel MCP servers

2. Research findings #

Structural/AST search → ast-grep wins #

  • Rust, multi-core, "tens of thousands of files in seconds"; semgrep is slow as a CLI; comby is not AST-aware and can't express logic (and/not/inside) — ast-grep tool comparison, TS catalog.
  • Measured here: 49 MB peak RSS scanning the whole AC public tree. Perfect 8 GB citizen.
  • Gotcha found in testing: --lang ts does not match .tsx files. Omit --lang and ast-grep infers per-file — that's the right default idiom.

Fast typechecking → tsgo is real and a 7–40x win #

  • Native Go port of tsc, nightly @typescript/native-preview, becomes TypeScript 7; --build/project-references/--incremental ported; ~10x typical speedups, lower memory (npm, typescript-go repo, announcement, Dec 2025 progress, early-2026 release target).
  • Verified locally (numbers in §3): 5.35 s vs 37.2 s on the same fuser package, similar RSS. The 8 GB swap-fest tsc typecheck becomes a coffee-sip.
  • Blocker found: tsgo (TS7) removed baseUrl → error TS5102 and the check aborts. Most fuser packages (incl. core, flow-types, flow) set baseUrl: ".". Eight packages don't and check cleanly today. Fix is mechanical: replace baseUrl with "paths": {"*": ["./*"]} (or "@/*": ["./*"] where used) — that's a fuser-repo change to propose, not made here.

LSP for agents → already wired; vtsls is the upgrade path #

  • Claude Code has built-in LSP support (changelog ~v2.0.74; no ENABLE_LSP_TOOL needed on 2.1.170) and this machine already has typescript-lsp@claude-plugins-official installed/enabled, backed by global typescript-language-server 5.1.3 (plugin page, Scott Spence, field report).
  • vtsls (wraps the actual VS Code TS extension) is faster/more correct on big monorepos and is now Zed's default TS server (vtsls, helix issue, zed issue). Caveat for 8 GB: tsserver-family servers grow with project size (VS Code defaults the cap to 3 GB) — on fuser, prefer opening one package at a time, and lean on rg/ast-grep/ctags for repo-wide questions instead of repo-wide LSP.
  • Serena MCP (oraios/serena) layers symbol-level navigation/edit tools over LSP; popular with Claude Code in 2026. On this machine it would run another Python process + a tsserver — redundant with the built-in LSP plugin. Verdict: skip unless doing heavy cross-file refactors, and never alongside a typecheck.

Symbol indexing → universal-ctags yes, scip no #

  • universal-ctags has a maintained TS parser (ctags repo); measured: full fuser index in 16.7 s / 36 MB RSS, lookups via readtags in <1 s / 1.4 MB.
  • scip-typescript runs the real TS compiler to index; upstream guidance includes node --max-old-space-size=16000 and --no-global-caches to avoid OOM (scip-typescript, SCIP announcement). Wrong tool for 8 GB; precision delta over ctags+LSP isn't worth it here.

Fast linting as comprehension → oxlint #

  • oxlint 50–100x faster than eslint, ~2x faster than biome; ~300 rules (oxc benchmarks, bench-linter, LogRocket, PkgPulse 2026 comparison). Use as a zero-config "is this package internally sane / what's dead code here" probe; the repo's eslint stays canonical for CI.
  • biome skipped: its formatter would fight prettier (AC) and fuser's eslint config.

New-in-2025/26 misc #

  • difftastic — tree-sitter-based semantic diffs; installed, verified (§3). Memory scales with file size: 212 MB on the 16k-line bios.mjs, trivial on normal files. Use per-file, not git difftool --dir-diff on huge trees.
  • Repomix/codebase-map context packers — built for stuffing cloud-model contexts; for a local agent with rg/ast-grep/LSP they just burn tokens. Skip.
  • GritQL — overlaps ast-grep with a smaller community and stalled momentum; ast-grep's YAML rules + relational constraints cover the same ground. Skip.

3. Benchmarks (wall clock + /usr/bin/time -l max RSS) #

Query Tool / scope Time Peak RSS
All setTimeout($FN, $MS) (311 hits) ast-grep, whole AC public tree 6.5 s 49 MB
setTimeout with delay > 5000 (inline YAML rule, 5 hits) ast-grep, AC lib/ ~1 s <50 MB
Callers of requestAppPreviewReload ast-grep, fuser packages/flow/src 1.9 s 16 MB
Build full symbol index ctags, fuser packages/ (22 pkgs) 16.7 s 36 MB (5.9 MB tags file)
Build full symbol index ctags, AC public tree 10.0 s 36 MB (23 MB tags file)
"Where is executionData defined?" readtags on fuser index 0.8 s 1.4 MB → packages/core/src/sanitizeUtils.ts interface SanitizableNodeDataShape
Typecheck packages/app-node-protocol tsgo --noEmit -p 5.4 s 119 MB
Typecheck packages/app-node-protocol tsc 5.8.3 --noEmit -p 37.2 s 125 MB
Typecheck packages/flow-types tsc 5.8.3 256.8 s (pulls Next.js types; machine contended) 257 MB RSS / 880 MB footprint
Typecheck packages/flow-types tsgo 6.4 s — but aborts on baseUrl (TS5102), so not a completed check 306 MB
Lint AC disks/ (~1,000 pieces) oxlint 2.6 s 47 MB
Lint fuser packages/core/src oxlint 1.4 s 12 MB
Semantic diff of dirty bios.mjs (16k lines) difft 9.1 s 212 MB
File find -e mjs pattern fd, whole AC repo 1.0 s 13 MB
tsgo error-detection sanity bad /tmp/bad.ts instant exit 1, both TS2322 caught

Honest pairing: tsgo vs tsc on the same clean package = 6.9x faster, same memory. The flow-types row shows the worst-case tsc behavior the 8 GB machine suffers (4+ min, ~900 MB footprint for a 41-file package, because the shared nextjs.json config drags in the Next type universe).

4. Playbook — how agents should grok TS/JS on this machine #

Golden rule: one heavy process at a time. rg/fd/ast-grep/readtags/oxlint are all "free" (<50 MB). tsgo, difft-on-huge-files, and any tsserver are the heavies — never overlap them with ffmpeg/Chromium/whisper or each other.

Structural search (ast-grep) #

# Callers of a function (omit --lang: it auto-infers per file, and
# --lang ts does NOT match .tsx — easy silent miss)
ast-grep run -p 'requestAppPreviewReload($$$ARGS)' packages/flow/src

# Pattern with metavariable capture
ast-grep run -p 'setTimeout($FN, $MS)' system/public/aesthetic.computer/lib

# One-shot rule with constraints (e.g. numeric delay >= 5000)
ast-grep scan --inline-rules '
id: long-settimeout
language: js
rule: { pattern: "setTimeout($FN, $MS)" }
constraints:
  MS: { regex: "^([5-9][0-9]{3}|[0-9]{5,})$" }
' system/public/aesthetic.computer/lib

# Relational logic: awaits inside a loop
ast-grep scan --inline-rules '
id: await-in-loop
language: ts
rule:
  pattern: "await $X"
  inside: { kind: for_statement, stopBy: end }
' packages/core/src

# Rewrite preview (no file changes without --update-all / -U)
ast-grep run -p 'console.log($$$A)' -r 'console.debug($$$A)' lib/

Single-package typecheck (the big workflow change) #

# fuser: tsgo on a package WITHOUT baseUrl — 5s instead of 37s
tsgo --noEmit -p /Users/jas/Developer/fuser/packages/app-node-protocol

# baseUrl-free today: app-node, app-node-protocol, canvas-runtime,
# code-gen, flow-agents, flow-chats, harness, node-enrichment
# Packages WITH baseUrl (core, flow, flow-types, …) abort with TS5102
# until baseUrl → paths is landed in fuser; fall back to repo tsc:
cd /Users/jas/Developer/fuser && node_modules/.bin/tsc --noEmit -p packages/<pkg>

# Single-file sanity (no tsconfig needed):
tsgo --noEmit path/to/file.ts

Never run tsgo/tsc --build across the whole fuser monorepo casually; per-package only, one at a time.

Symbol lookup (ctags index — rebuild is cheap, do it per session) #

# Index (fuser: ~17s; AC: ~10s)
ctags -R --languages=TypeScript,JavaScript \
  --exclude=node_modules --exclude=dist --exclude=.next --exclude=.turbo \
  -f /tmp/fuser.tags /Users/jas/Developer/fuser/packages
ctags -R --languages=JavaScript --exclude=node_modules \
  -f /tmp/ac.tags /Users/jas/aesthetic-computer/system/public/aesthetic.computer

# Exact symbol → definition site + kind + owning interface/class
readtags -t /tmp/fuser.tags -e executionData
# Prefix search
readtags -t /tmp/fuser.tags -e -p execution

Whole-package sanity sweep #

oxlint packages/core/src          # fuser, ~1.4s — unused vars/dead code map
oxlint system/public/aesthetic.computer/disks   # AC, ~2.6s
# eslint stays canonical for fuser CI; oxlint is the comprehension probe.

Semantic diffs #

difft --display inline /tmp/old.mjs current.mjs   # ignores formatting noise
GIT_EXTERNAL_DIFF=difft git -C <repo> diff -- path/file.ts   # ad hoc, per-file
# Caution: ~212 MB RSS on 16k-line files (bios.mjs/disk.mjs scale) — fine
# solo, don't run while a typecheck is live.

Fast file finding #

fd -e mjs kidlisp /Users/jas/aesthetic-computer     # respects .gitignore
fd --type f -e tsx AppNode /Users/jas/Developer/fuser/packages

LSP (already on) #

The built-in Claude Code LSP tool + typescript-lsp plugin already serve go-to-definition / find-references / hover for .ts/.tsx/.js/.mjs in both repos. Use it for "what is this symbol exactly" questions; use rg/ast-grep/ ctags for repo-wide enumeration (cheaper than forcing tsserver to load the whole monorepo into an 8 GB machine).

5. Proposed Claude Code / repo config changes (NOT applied) #

  1. Switch the TS language server backing to vtsls (installed: @vtsls/language-server 0.3.0). Either via a community vtsls plugin (e.g. the Piebald claude-code-lsps marketplace) or when the official plugin grows a server option. Faster/more correct completions on the fuser monorepo; keep typescript-language-server as fallback. Human applies — plugin config untouched.
  2. fuser repo: replace baseUrl with paths in packages/*/tsconfig.json ("paths": {"*": ["./*"]}; keep existing @/* mappings) so tsgo can check every package. This is also the TS7 migration path — needed eventually regardless. Coordinate with the agent working in fuser-genrecovery before touching shared config.
  3. Optional fuser devDependency: @typescript/native-preview pinned in the repo so pnpm tsgo --noEmit -p packages/x matches the repo TS version drift-free (global nightly works fine meanwhile).
  4. Permissions allowlist (project .claude/settings.json, via /fewer-permission-prompts): pre-allow ast-grep run*, ast-grep scan*, readtags*, fd*, oxlint*, difft*, tsgo --noEmit* as read-only-safe Bash patterns to cut prompt friction.
  5. Do not add Serena MCP or scip-typescript on this machine (memory rationale in §2). Revisit Serena if a 16 GB+ machine appears.
  6. Tags freshness convention: indexes live in /tmp (wiped on reboot) — agents should rebuild rather than trust a stale index; 10–17 s is cheap.