UNPKG

pi-lens

Version:

Real-time code feedback for pi — LSP, linters, formatters, type-checking, structural analysis & booboo

140 lines (115 loc) • 7.76 kB
# Architecture observations — Fable, 2026-07-08 First-look assessment of pi-lens by Claude Fable 5 (fresh eyes, after reading AGENTS.md, the source layout, the dispatch core, the engine seam, and the entry point). Ranked by leverage; nothing here is urgent. ## Status update — end of day 2026-07-08 The write-path performance thread that grew out of this assessment shipped in full, measurement-first (479 real edits from latency.log/cascade.log: mean edit 6.4s, of which ~1.67s / 26% was the awaited cascade): - **#450/#452 — cascade deferred off the write hot path.** The ~26% of every edit spent computing turn_end-only output now runs concurrently after the edit returns; settled at turn_end with a bounded wait + carry-over (a late compute survives `beginTurn`, never dropped). Watch `cascade_settle_wait` in latency.log for residual contention. - **#453/#455 — micro batch.** ESLint autofix single-spawn (was dry-run+fix, double cold-start), parallel LSP codeAction lookups (~6× RTT off the blocking path), lsp-runner content reuse. - **#454/#456 — logger consolidation SHIPPED** (item 3 below): all eight NDJSON loggers on one buffered async writer (`clients/ndjson-logger.ts`); no sync appendFileSync on the hot path; shared exit-flush. - **#451/#457 — review-graph seq fast path.** The per-build O(project) walk+stat sweep (157ms p50 even on reuse) is skipped when coordinator seq state proves only pi-observed edits occurred; periodic re-verify (20 builds/5 min) + kill switch `PI_LENS_GRAPH_SEQ_FASTPATH=0`. Watch `seqFastpathFallback` in cascade.log graph_build metadata for hit rate. - **#449 filed** — multi-agent LSP resource sharing (registry → budget → same-root warm attach over the existing IPC seam; protocol multiplexing explicitly rejected). Strategic, not started. Second wave (same day, evening): - **#460/#390 → PR #463 MERGED** — per-server clean-signal probe (phase-aware 4-way: 2 / 2\* / 3 / unknown) + the LSP-docs nightly commit-back that #390 showed was missing (merge-don't-regress guard, auto-PR on `bot/lsp-docs-refresh`). Measured: typescript's clean silence is diagnostic-set-dependent (re-publishes while dirty, silent once clean — the production budget-wait case confirmed); opengrep/yaml are 2\* (version-less publish still resolves the wait — NOT #458 targets). - **Filed:** #458 (learned clean-signal deadlines — the ~1–1.5s median win on clean TS edits; build AFTER dogfooding + first nightly matrix), #459 (skip reverse-deps rebuild+write when the graph didn't change — quick), #462 re-scoped (slow-FS mode via latency probe, not mount-regex). Dogfooding follow-up: after a few days, re-run the latency analysis to confirm the p50 edit dropped from ~5.8s toward ~4.3s and check the two new observability signals above; also check the first `bot/lsp-docs-refresh` nightly PR behaves (populates ci columns, no row regressions). Everything below is the original assessment; items 1, 2, and the smaller observations (config-finder family, errorDebtBaseline, legacy-client headers) remain OPEN, as does the strategic thread (#236). ## Overall The discipline axis is unusually strong: invariants are documented with their *why*, registries are guarded by consistency tests, the diagnostic model insists on "affirmative clean, never silence," and blocked grammars are guard-driven rather than hand-maintained. The recurring winning pattern: **membership in a list is enforced by a check, not by memory.** The real risks are structural concentration, not correctness. ## 1. `index.ts` is a ~2,250-line god-adapter that violates the seam rule `clients/lens-engine.ts` is *the* seam and the MCP adapter obeys it; the pi adapter doesn't (the engine header admits "index.ts can adopt incrementally"). Concretely, the `tool_call` handler spans roughly lines 1249–2097: ~850 lines of read-guard preflight logic inline in the entry point — the most safety-critical path (it mutates the agent's edits before they land), living in the file that's hardest to unit-test. **Suggested move:** extract the `tool_call` body into `clients/runtime-tool-call.ts`, matching the existing `runtime-session` / `runtime-tool-result` / `runtime-turn` pattern (the other three hooks already made this move). Mechanical, low-risk, makes the autopatch pipeline directly testable instead of only via its helpers. ## 2. The dual-artifact build is fenced, not fixed TS compiles in-place to sibling `.js`, tests import `.js` specifiers, `dist/` is a second compile, and staleness silently shadows source edits. Three fences exist around this one hole: the vitest `globalSetup` freshness gate (#198), the "build before test" working rule, and an AGENTS.md warning. Each fence is a recurring tax. **Root-cause option:** let vitest transform TS directly (native support) so tests run against source; reserve compilation for `dist/` only. One-time migration cost (import-specifier handling, plugin resolution), touches everything — a deliberate project, not a drive-by. Worth a cost/benefit issue even if the answer is "not yet." ## 3. `clients/` is a 121-file flat junk drawer `dispatch/`, `lsp/`, `mcp/` are foldered; everything else sits at one level — read-guard (6+ files), security scanners, project intelligence, and **eight bespoke NDJSON loggers** (`cascade-logger`, `dead-code-logger`, `read-guard-logger`, `latency-logger`, `tree-sitter-logger`, `diagnostic-logger`, `actionable-warnings-logger`, `ast-grep-tool-logger`). - **Loggers are the genuine consolidation target** — eight hand-rolled implementations of "append NDJSON, rotate at ~1 MiB." One `createNdjsonLogger(name, opts)` factory collapses them and centralizes rotation policy. Cheap, contained, real win. - **The foldering itself ranks lower** — churny (every import path changes, open PRs conflict, concurrent worktree agent), and flat-but-well-named is livable. Do it opportunistically, not as a campaign. ## Smaller observations - **Per-tool config finders are a copy-paste family**: `opengrep-config.ts`, `typos-config.ts`, `zizmor-config.ts` all implement "walk up looking for one of these filenames." A shared `findLocalToolConfig(cwd, names[])` would make the next auxiliary LSP a one-liner. - **Dead plumbing**: `runtime.errorDebtBaseline` is never set in production (already flagged in AGENTS.md). Wire it or delete the green→red machinery — dormant features are expensive because they *look* load-bearing. - **Legacy client classes** (`ruff-client`, `go-client`, `rust-client`, …) survived the #197 cull but their naming still implies they're the primary path. A one-line header comment on each ("legacy surface — hot path is `dispatch/runners/<x>`") is nearly free and stops contributors extending the wrong layer. - **Deliberately not touching**: the hand-rolled MCP transport (the zero-dep rationale is sound), the dispatcher (1,038 lines but well-factored into named phases), and the auxiliary-LSP profile registry (recent additions slotted in cleanly — the sign of a good abstraction). ## Strategic thread with the highest leverage Several capabilities are **built but not self-consumed** (also flagged in AGENTS.md): the word index is paid for in every session scan but nothing internal uses it; blast-radius is transitive BFS while the in-pi cascade is still one-hop (#236, the "mirror into pi-lens" principle). That's financed inventory earning nothing. Feeding transitive impact into cascade neighbor selection is probably the highest-leverage *feature* work available — ideally paired with the #202 structural-hash short-circuit so the expansion prunes when a changed file's exported interface is unchanged.