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 insystem/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 tsdoes not match.tsxfiles. Omit--langand 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/--incrementalported; ~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 TS5102and the check aborts. Most fuser packages (incl.core,flow-types,flow) setbaseUrl: ".". Eight packages don't and check cleanly today. Fix is mechanical: replacebaseUrlwith"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_TOOLneeded on 2.1.170) and this machine already hastypescript-lsp@claude-plugins-officialinstalled/enabled, backed by globaltypescript-language-server5.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
readtagsin <1 s / 1.4 MB. - scip-typescript runs the real TS compiler to index; upstream guidance includes
node --max-old-space-size=16000and--no-global-cachesto 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, notgit difftool --dir-diffon 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) #
- Switch the TS language server backing to vtsls (installed:
@vtsls/language-server0.3.0). Either via a community vtsls plugin (e.g. the Piebaldclaude-code-lspsmarketplace) 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. - fuser repo: replace
baseUrlwithpathsinpackages/*/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. - Optional fuser devDependency:
@typescript/native-previewpinned in the repo sopnpm tsgo --noEmit -p packages/xmatches the repo TS version drift-free (global nightly works fine meanwhile). - Permissions allowlist (project
.claude/settings.json, via/fewer-permission-prompts): pre-allowast-grep run*,ast-grep scan*,readtags*,fd*,oxlint*,difft*,tsgo --noEmit*as read-only-safe Bash patterns to cut prompt friction. - Do not add Serena MCP or scip-typescript on this machine (memory rationale in §2). Revisit Serena if a 16 GB+ machine appears.
- Tags freshness convention: indexes live in
/tmp(wiped on reboot) — agents should rebuild rather than trust a stale index; 10–17 s is cheap.