pi-lens 3.8.69 → 3.8.71
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +99 -0
- package/README.md +31 -13
- package/dist/clients/bootstrap.js +3 -1
- package/dist/clients/bus-events-logger.js +53 -0
- package/dist/clients/bus-publish.js +45 -2
- package/dist/clients/cache/rule-cache.js +9 -5
- package/dist/clients/cache-manager.js +19 -13
- package/dist/clients/code-quality-warnings.js +27 -0
- package/dist/clients/dead-code-client.js +5 -23
- package/dist/clients/diagnostic-dispositions.js +298 -0
- package/dist/clients/diagnostics-publish.js +38 -2
- package/dist/clients/dispatch/auxiliary-lsp.js +144 -6
- package/dist/clients/dispatch/dispatcher.js +6 -1
- package/dist/clients/dispatch/facts/function-facts.js +83 -0
- package/dist/clients/dispatch/integration.js +19 -4
- package/dist/clients/dispatch/rules/async-noise.js +1 -1
- package/dist/clients/dispatch/rules/async-unnecessary-wrapper.js +1 -1
- package/dist/clients/dispatch/rules/cors-wildcard.js +1 -1
- package/dist/clients/dispatch/rules/error-obscuring.js +2 -1
- package/dist/clients/dispatch/rules/error-swallowing.js +2 -1
- package/dist/clients/dispatch/rules/framework-call-noise.js +67 -0
- package/dist/clients/dispatch/rules/high-complexity.js +12 -1
- package/dist/clients/dispatch/rules/high-fan-out.js +14 -3
- package/dist/clients/dispatch/rules/high-import-coupling.js +1 -1
- package/dist/clients/dispatch/rules/missing-error-propagation.js +1 -1
- package/dist/clients/dispatch/rules/no-commented-credentials.js +1 -1
- package/dist/clients/dispatch/rules/pass-through-wrappers.js +1 -1
- package/dist/clients/dispatch/rules/placeholder-comments.js +1 -1
- package/dist/clients/dispatch/rules/unsafe-boundary.js +1 -1
- package/dist/clients/dispatch/runners/ast-grep-napi.js +75 -18
- package/dist/clients/dispatch/runners/lsp.js +23 -34
- package/dist/clients/dispatch/runners/oxlint.js +1 -1
- package/dist/clients/dispatch/suppress-writer.js +68 -0
- package/dist/clients/dispatch/utils/lsp-diagnostics.js +4 -1
- package/dist/clients/disposition-logger.js +39 -0
- package/dist/clients/disposition-publish.js +109 -0
- package/dist/clients/file-utils.js +94 -33
- package/dist/clients/format-events-publish.js +299 -0
- package/dist/clients/git-tracked-ignore.js +208 -0
- package/dist/clients/gitleaks-client.js +40 -5
- package/dist/clients/installer/index.js +123 -6
- package/dist/clients/instance-reaper.js +229 -2
- package/dist/clients/instance-registry.js +141 -2
- package/dist/clients/json-cache-read.js +65 -0
- package/dist/clients/knip-client.js +35 -38
- package/dist/clients/language-profile.js +11 -11
- package/dist/clients/lens-engine.js +12 -0
- package/dist/clients/lens-map.js +1079 -0
- package/dist/clients/log-cleanup.js +61 -19
- package/dist/clients/lsp/aggregation.js +69 -2
- package/dist/clients/lsp/cascade-tier.js +31 -19
- package/dist/clients/lsp/client.js +124 -14
- package/dist/clients/lsp/config.js +27 -1
- package/dist/clients/lsp/index.js +1028 -68
- package/dist/clients/lsp/server-strategies.js +17 -8
- package/dist/clients/lsp/server.js +80 -28
- package/dist/clients/lsp/tsserver-sync.js +145 -0
- package/dist/clients/lsp/workspace-diagnostics-cache.js +172 -0
- package/dist/clients/lsp-budget.js +148 -0
- package/dist/clients/mcp/session.js +9 -0
- package/dist/clients/middle-man-analysis.js +260 -0
- package/dist/clients/module-report.js +173 -11
- package/dist/clients/ndjson-logger.js +24 -0
- package/dist/clients/opengrep-client.js +217 -0
- package/dist/clients/opengrep-config.js +2 -10
- package/dist/clients/path-utils.js +66 -0
- package/dist/clients/pipeline.js +5 -1
- package/dist/clients/project-diagnostics/cache.js +5 -12
- package/dist/clients/project-diagnostics/extractors.js +21 -1
- package/dist/clients/project-diagnostics/fresh-fetch.js +275 -0
- package/dist/clients/project-diagnostics/runner-adapters/opengrep.js +40 -0
- package/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +46 -0
- package/dist/clients/project-snapshot.js +3 -6
- package/dist/clients/quiet-window.js +57 -2
- package/dist/clients/read-expansion.js +2 -3
- package/dist/clients/read-guard-tool-lines.js +10 -0
- package/dist/clients/read-guard.js +1 -1
- package/dist/clients/resource-sampler.js +254 -0
- package/dist/clients/review-graph/builder.js +322 -42
- package/dist/clients/review-graph/import-resolvers.js +53 -19
- package/dist/clients/review-graph/symbol-id.js +57 -0
- package/dist/clients/runtime-agent-end.js +26 -0
- package/dist/clients/runtime-coordinator.js +9 -1
- package/dist/clients/runtime-session.js +93 -17
- package/dist/clients/runtime-tool-call.js +789 -0
- package/dist/clients/runtime-tool-result.js +15 -2
- package/dist/clients/runtime-turn.js +62 -13
- package/dist/clients/safe-spawn.js +48 -2
- package/dist/clients/session-state-store.js +6 -9
- package/dist/clients/sgconfig.js +2 -9
- package/dist/clients/source-filter.js +17 -25
- package/dist/clients/source-walker.js +66 -0
- package/dist/clients/startup-scan.js +105 -43
- package/dist/clients/symbol-containment.js +24 -0
- package/dist/clients/test-runner-client.js +544 -29
- package/dist/clients/tree-sitter-symbol-extractor.js +12 -5
- package/dist/clients/typos-config.js +2 -11
- package/dist/clients/widget-state.js +83 -1
- package/dist/clients/write-ordering-guard.js +58 -0
- package/dist/clients/zizmor-config.js +29 -10
- package/dist/index.js +25511 -19414
- package/dist/mcp/server.js +95 -19
- package/dist/tools/activate-tools.js +72 -0
- package/dist/tools/ast-dump.js +2 -8
- package/dist/tools/lens-diagnostic-mark.js +158 -0
- package/dist/tools/lens-diagnostics.js +327 -33
- package/dist/tools/lsp-diagnostics.js +525 -105
- package/dist/tools/module-report.js +18 -5
- package/docs/agent-tools.md +136 -0
- package/docs/ast-grep_rules_catalog.md +564 -0
- package/docs/astplayground.md +178 -0
- package/docs/audit1.md +177 -0
- package/docs/custom-rules.md +231 -0
- package/docs/dependencies.md +54 -0
- package/docs/dispositions.md +85 -0
- package/docs/env_variables.md +10 -0
- package/docs/environment-variables.md +83 -0
- package/docs/fable.md +140 -0
- package/docs/features.md +338 -0
- package/docs/globalconfig.md +79 -0
- package/docs/language-coverage.md +48 -0
- package/docs/lsp-capability-matrix.md +157 -0
- package/docs/lsp-latency-benchmark.md +169 -0
- package/docs/mcp.md +438 -0
- package/docs/module-report-read-symbol.md +258 -0
- package/docs/servercapabilities.md +118 -0
- package/docs/subagent-compat.md +281 -0
- package/docs/tools.md +16 -0
- package/docs/tools_improvement2.md +345 -0
- package/docs/tree-sitter_rules_catalog.md +296 -0
- package/docs/usage.md +129 -0
- package/docs/word-index.md +150 -0
- package/package.json +10 -1
- package/rules/ast-grep-rules/rule-tests/find-import-file-without-extension-test.yml +4 -0
- package/rules/ast-grep-rules/rule-tests/no-javascript-url-js-test.yml +5 -0
- package/rules/ast-grep-rules/rule-tests/no-javascript-url-test.yml +9 -0
- package/rules/ast-grep-rules/rules/array-callback-return-js.yml +1 -1
- package/rules/ast-grep-rules/rules/find-import-file-without-extension.yml +3 -1
- package/rules/ast-grep-rules/rules/jwt-no-verify-js.yml +1 -1
- package/rules/ast-grep-rules/rules/no-extra-boolean-cast-js.yml +1 -1
- package/rules/ast-grep-rules/rules/no-implied-eval-js.yml +1 -1
- package/rules/ast-grep-rules/rules/no-javascript-url-js.yml +13 -1
- package/rules/ast-grep-rules/rules/no-javascript-url.yml +12 -0
- package/rules/ast-grep-rules/rules/no-sql-in-code-js.yml +1 -1
- package/rules/ast-grep-rules/rules/weak-rsa-key-js.yml +1 -1
- package/skills/pi-lens-ast-grep/SKILL.md +3 -1
- package/dist/clients/production-readiness.js +0 -493
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# LSP capability matrix — affirmative-clean-signal strategy
|
|
2
|
+
|
|
3
|
+
How pi-lens knows a just-edited file is **clean** (no diagnostics) vs the server
|
|
4
|
+
simply **hasn't answered yet** (cold/crashed/silent). pi-lens waits *synchronously*
|
|
5
|
+
for a verdict, so — unlike an editor, which renders asynchronously and never needs
|
|
6
|
+
to decide — it must have a positive signal. There is no signal for "silence", so we
|
|
7
|
+
classify each server and pick a per-server strategy. (Background: #240; mechanism
|
|
8
|
+
confirmed against Neovim's LSP client, which sidesteps this entirely by being async.)
|
|
9
|
+
|
|
10
|
+
Generate/refresh this matrix with `node scripts/characterize-lsp.mjs [--install]`
|
|
11
|
+
(the `mode` column) and `node scripts/probe-clean-signal.mjs [--install]` (the
|
|
12
|
+
`clean-behavior` column — 4-way: 2 / 2* / 3 / unknown). Both **merge** in place: a server the
|
|
13
|
+
running host couldn't spawn keeps its prior row, so an ubuntu-poor run can't
|
|
14
|
+
regress a richer one (#390). The nightly **tool-smoke** workflow runs both (plus
|
|
15
|
+
`server-capabilities.mjs`) and opens/updates a single auto-PR
|
|
16
|
+
(`bot/lsp-docs-refresh`, "docs(nightly): refresh LSP capability docs") with the
|
|
17
|
+
regenerated docs — so this file self-populates from CI without manual copy-paste.
|
|
18
|
+
|
|
19
|
+
`probe-clean-signal.mjs` also runs a **drift check** (#529): it compares each
|
|
20
|
+
probed server's observed `clean-behavior` against the hand-set `silentOnClean`
|
|
21
|
+
marker in `clients/lsp/server-strategies.ts` and writes any mismatch to the
|
|
22
|
+
`## silentOnClean drift` section below. This is telemetry only — never a CI
|
|
23
|
+
gate — because the probe is a timing-based negative observation; a mismatch
|
|
24
|
+
just tells a human the marker may need updating. `unknown` observations are
|
|
25
|
+
never compared in either direction (a slow/absent server isn't evidence of
|
|
26
|
+
anything). The native TS7 launch variant (`typescript7`/`typescript7-clean`,
|
|
27
|
+
#524/#526) is deliberately excluded from comparison against classic's marker —
|
|
28
|
+
they share a server id but not a verified clean-signal behavior.
|
|
29
|
+
|
|
30
|
+
## The strategies
|
|
31
|
+
|
|
32
|
+
| Tier | Signal | Affirmative clean? | Example |
|
|
33
|
+
|---|---|---|---|
|
|
34
|
+
| **1 — pull** | `textDocument/diagnostic` returns an authoritative report (empty = clean) | YES, deterministic | rust-analyzer |
|
|
35
|
+
| **2 — push, publishes-versioned** | `publishDiagnostics([])` **with version** on every scan, incl. clean→clean | YES, currency-proven via version | ast-grep |
|
|
36
|
+
| **2\* — push, publishes-unversioned** | re-publishes on a clean scan but **version-less** — the wait still early-returns (the client accepts a version-less publish as fresh: it can't be proven stale), but currency is only *temporally correlated*, not proven | YES at runtime, with a staleness-risk caveat (not a latency cost) | opengrep |
|
|
37
|
+
| **3 — push, silent on clean** | server publishes nothing when nothing changed | **NO** — budget-wait floor (safe; a timeout is *not* a false clean). **This tier is #458's learned-deadline target set.** | typescript-language-server |
|
|
38
|
+
|
|
39
|
+
Detection is **cached** at `initialize` (`detectWorkspaceDiagnosticsSupport` →
|
|
40
|
+
`state.workspaceDiagnosticsSupport.mode`, upgraded on `client/registerCapability`),
|
|
41
|
+
so the tier is free at collection time — no per-edit probe.
|
|
42
|
+
|
|
43
|
+
## Matrix (dev box + CI nightly; mode last refreshed 2026-06-17 from run 27713958681, clean-behavior probed on the dev box 2026-07-08 — #460)
|
|
44
|
+
|
|
45
|
+
`mode` from cached capabilities; `clean-behavior` from the phase-aware publish-trace
|
|
46
|
+
probe. The probe attributes publishes to two phases — the **dirty touch** (proves the
|
|
47
|
+
server is live) and the **clean transitions** (the discriminator) — and classifies
|
|
48
|
+
4-way: `publishes-versioned` (tier 2: publish WITH version on a clean transition —
|
|
49
|
+
affirmative + currency-proven), `publishes-unversioned` (tier 2\*: version-less
|
|
50
|
+
publish on a clean transition — the wait still early-returns at runtime, currency
|
|
51
|
+
only temporally correlated), `silent` (tier 3: alive on dirty, silent on clean —
|
|
52
|
+
budget-wait, the #458 target), `unknown` (no publish at all — slow/absent,
|
|
53
|
+
conservatively not classified). `src` = where a row was measured: **ci** = the
|
|
54
|
+
nightly steps, **dev** = the dev box (a row measured on both reads `dev+ci`).
|
|
55
|
+
Merges never blank a prior good value, so a CI non-result leaves the dev
|
|
56
|
+
classification standing.
|
|
57
|
+
|
|
58
|
+
| lang | server | mode | clean-behavior | tier | src |
|
|
59
|
+
|---|---|---|---|---|---|
|
|
60
|
+
| json | vscode-json-language-server | pull | — | 1 | dev+ci |
|
|
61
|
+
| css | vscode-css-language-server | pull | — | 1 | dev+ci |
|
|
62
|
+
| html | vscode-html-language-server | pull | — | 1 | dev+ci |
|
|
63
|
+
| rust | rust-analyzer | pull | — | 1 | dev |
|
|
64
|
+
| svelte | svelte-language-server | pull | — | 1 | dev+ci |
|
|
65
|
+
| deno | deno (alt of typescript) | pull | — | 1 | dev+ci |
|
|
66
|
+
| ruby | ruby-lsp | pull | — | 1 | ci |
|
|
67
|
+
| csharp | csharp-ls | pull | — | 1 | ci |
|
|
68
|
+
| typescript | typescript-language-server | push-only | silent | 3 | dev+ci |
|
|
69
|
+
| python | pyright | push-only | publishes-versioned | 2 | dev+ci |
|
|
70
|
+
| jedi | jedi-language-server (alt of python) | push-only | publishes-versioned | 2 | ci |
|
|
71
|
+
| yaml | yaml-language-server | push-only | publishes-unversioned | 2* | dev+ci |
|
|
72
|
+
| shell | bash-language-server | push-only | publishes-versioned | 2 | dev+ci |
|
|
73
|
+
| dockerfile | docker-langserver | push-only | publishes-unversioned | 2* | dev+ci |
|
|
74
|
+
| toml | taplo | push-only | publishes-unversioned | 2* | dev+ci |
|
|
75
|
+
| terraform | terraform-ls | push-only | TBD | 2/3? | dev+ci |
|
|
76
|
+
| prisma | @prisma/language-server | push-only | publishes-unversioned | 2* | dev+ci |
|
|
77
|
+
| php | intelephense | push-only | TBD | 2/3? | dev |
|
|
78
|
+
| zig | zls | push-only | publishes-unversioned | 2* | dev+ci |
|
|
79
|
+
| vue | @vue/language-server | push-only | TBD | 2/3? | dev+ci |
|
|
80
|
+
| dart | dart language-server | push-only | publishes-unversioned | 2* | ci |
|
|
81
|
+
| gleam | gleam lsp | push-only | publishes-unversioned | 2* | ci |
|
|
82
|
+
| clojure | clojure-lsp | push-only | publishes-unversioned | 2* | ci |
|
|
83
|
+
| opengrep | opengrep (aux) | push-only | publishes-unversioned | 2* | dev+ci |
|
|
84
|
+
| ast-grep | ast-grep (aux) | push-only | publishes-versioned | 2 | dev+ci |
|
|
85
|
+
|
|
86
|
+
**Unknown — fixture exists, mode not yet captured.** The toolchain-gated family
|
|
87
|
+
(no auto-install today; tracked in #241) — `go` (gopls), `java` (jdtls),
|
|
88
|
+
`kotlin`, `swift` (sourcekit-lsp), `lua`, `cpp` (clangd), `haskell`, `elixir`,
|
|
89
|
+
`ocaml`, `nix` (nixd), `fsharp`. Their servers don't install in the nightly, so
|
|
90
|
+
characterize reports `unknown` (a non-failure ⚠). Once #241 lands they'll fill in
|
|
91
|
+
the same way clojure-lsp/gleam now do (both auto-install via the github strategy
|
|
92
|
+
and were characterized `push-only` in the run above).
|
|
93
|
+
|
|
94
|
+
## Key findings
|
|
95
|
+
- **Mode ≠ tier, and the split needs BOTH axes.** Push-only further splits along
|
|
96
|
+
latency (does anything publish on a clean transition? — silence is the only
|
|
97
|
+
budget-wait case, because pi-lens's publish handler emits and early-returns the
|
|
98
|
+
wait on EVERY publish, versioned or not) and currency-proof (is the publish
|
|
99
|
+
versioned, i.e. provably about the live edit?). The 4-way
|
|
100
|
+
`probe-clean-signal.mjs` measurement drives this: ast-grep → 2, yaml/opengrep →
|
|
101
|
+
2\*, typescript (clean file) → 3.
|
|
102
|
+
- **opengrep is 2\*, not 3 and not plain 2.** It *does* re-publish on a clean scan
|
|
103
|
+
(the wait early-returns at runtime — fast), but every push carries
|
|
104
|
+
`pubVersion=undefined`, so currency is only temporally correlated, not proven —
|
|
105
|
+
a staleness-risk note, not a latency cost. An earlier hand-note called it Tier 2
|
|
106
|
+
on the "re-publishes" observation alone; the phase-aware probe refines it to 2\*.
|
|
107
|
+
- **typescript's clean behavior is diagnostic-set-dependent (major probe finding).**
|
|
108
|
+
On a DIRTY file it re-publishes (version-lessly) after every change — the dirty
|
|
109
|
+
fixture measures 2\*. On a genuinely CLEAN file (the `typescript-clean` fixture)
|
|
110
|
+
it publishes nothing on a clean→clean edit — silent, Tier 3. The clean-file
|
|
111
|
+
behavior is the production case (the observed budget-wait timeouts), so the
|
|
112
|
+
matrix row records the clean fixture's verdict; the probe prefers `clean: true`
|
|
113
|
+
fixtures for exactly this reason. Corollary: a 2\* measured only on a dirty
|
|
114
|
+
fixture may overstate a server whose publishes stop when its set goes empty —
|
|
115
|
+
langs without a clean fixture carry that caveat.
|
|
116
|
+
- **#458's learned-deadline target set = the tier-3 rows only.** 2\* rows resolve
|
|
117
|
+
the wait at runtime and must NOT be given learned deadlines.
|
|
118
|
+
- **Tier 3 is budget-bound by necessity**, not laziness: a silent server's silence is
|
|
119
|
+
ambiguous (clean-unchanged vs still-analyzing), so shortening the wait or reusing
|
|
120
|
+
`lastKnownDiagnostics` would risk a false clean. The wait *is* the safety mechanism.
|
|
121
|
+
- **ast-grep (Phase 2 / #239) is Tier 2** — it self-signals clean on every scan, so it
|
|
122
|
+
is not the bottleneck. The cost on a clean with-auxiliary touch is the *silent
|
|
123
|
+
primary* (typescript), a pre-existing Tier-3 cost independent of ast-grep.
|
|
124
|
+
|
|
125
|
+
## Completing the matrix
|
|
126
|
+
The fixtures (`tests/fixtures/tool-smoke/<lang>/`) are durable and cover every
|
|
127
|
+
registered server. `mode` is read from the server's advertised capabilities at
|
|
128
|
+
`initialize`, so it is **content-independent** — for languages that already had a
|
|
129
|
+
tool-layer fixture we point `characterize-lsp.mjs` at the existing (deliberately
|
|
130
|
+
dirty) `bad.*` source rather than a colliding clean duplicate; new languages get a
|
|
131
|
+
minimal clean source. Either way the mode reported is the same.
|
|
132
|
+
|
|
133
|
+
The nightly **tool-smoke** workflow runs `characterize-lsp.mjs --install` **and**
|
|
134
|
+
`probe-clean-signal.mjs --install` (after the LSP handshake layer) on `ubuntu-latest`,
|
|
135
|
+
then opens/updates an auto-PR with the regenerated docs (#390) — so both the `mode`
|
|
136
|
+
and `clean-behavior` columns self-populate in CI without manual copy-paste. They fill
|
|
137
|
+
for servers that either auto-install (npm/pip/github — including clojure-lsp and gleam,
|
|
138
|
+
both github-strategy as of f263cf3) or whose toolchain the workflow provisions
|
|
139
|
+
(Ruby/Dart/Zig + .NET→csharp). The remaining `unknown` rows are the toolchain-gated
|
|
140
|
+
family (#241): until `runtimeInstall` + canonical-bin discovery land, their servers
|
|
141
|
+
don't install in CI and stay ⚠. The **merge guard** means a CI run that can't reach a
|
|
142
|
+
server never blanks its dev-measured row.
|
|
143
|
+
|
|
144
|
+
The `clean-behavior` split (4-way: 2 publishes-versioned / 2\* publishes-unversioned /
|
|
145
|
+
3 silent / unknown) is now measured per-server by `probe-clean-signal.mjs`, no longer a
|
|
146
|
+
manual one-off. Locally confirmed: ast-grep + ast-grep-baseline → 2, yaml + opengrep →
|
|
147
|
+
2\*, typescript → 3 on its clean fixture (2\* on the dirty fixture — see Key findings);
|
|
148
|
+
the rest fill in as the nightly reaches them.
|
|
149
|
+
|
|
150
|
+
## silentOnClean drift (nightly-generated)
|
|
151
|
+
|
|
152
|
+
Telemetry only — never a CI gate. Compares each probed server's observed
|
|
153
|
+
`clean-behavior` against `clients/lsp/server-strategies.ts`'s `silentOnClean`
|
|
154
|
+
marker; a mismatch means the marker may need a human update (#529). `unknown`
|
|
155
|
+
observations are never compared (a slow/absent server is not evidence either way).
|
|
156
|
+
|
|
157
|
+
_None observed as of the last probe run._
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# LSP latency benchmark
|
|
2
|
+
|
|
3
|
+
Cold/warm per-edit latency for pi-lens's registered language servers, measured to
|
|
4
|
+
gate the ast-grep LSP consolidation (#239 Gate A: an auxiliary scanner is
|
|
5
|
+
acceptable as long as its warm latency is in the range of the primary language
|
|
6
|
+
servers pi-lens already tolerates).
|
|
7
|
+
|
|
8
|
+
- **Reproduce:** `npm run build:dist && node scripts/bench-lsp.mjs [lang …] [--install]`
|
|
9
|
+
- **Machine:** Windows 11, dev box, warm npm cache. Numbers are **indicative, not
|
|
10
|
+
absolute** — single machine, small fixtures, one run.
|
|
11
|
+
- **cold** = first touch: spawn + `initialize` handshake + first scan.
|
|
12
|
+
- **warm** = re-touch after a content edit (the per-edit latency the agent waits on),
|
|
13
|
+
averaged over 3 edits.
|
|
14
|
+
- Source fixtures: `tests/fixtures/tool-smoke/*` (driven through the real
|
|
15
|
+
`LSPService.touchFile`, same entry the dispatch path uses).
|
|
16
|
+
- **Isolation (since #242):** each fixture measures **one server alone**. The
|
|
17
|
+
service + workspace config are reset between fixtures (no lingering server from a
|
|
18
|
+
previous measurement), and every *other* server matching the file is disabled —
|
|
19
|
+
so an auxiliary number is the auxiliary by itself (primaries off) and a primary
|
|
20
|
+
number is the default by itself (alternates + auxiliaries off). This removes the
|
|
21
|
+
primary↔auxiliary cross-contamination that produced the old "20557ms" artifact
|
|
22
|
+
(see the resolved-history note below).
|
|
23
|
+
|
|
24
|
+
> **Coverage:** this run measured **31 servers** (27 primary/alternate + 4
|
|
25
|
+
> auxiliary) — those whose toolchain is installed on this box. **Newly measuring
|
|
26
|
+
> since the previous run:** **clangd** (cpp) and **zizmor** (auxiliary) now
|
|
27
|
+
> auto-install via the archive/github strategies (#241/#272), **PowerShell Editor
|
|
28
|
+
> Services** via its pwsh-bootstrapped bundle (#278), and **marksman** (markdown,
|
|
29
|
+
> #274). **11** fixtures remain toolchain-gated and reported `unavailable` here
|
|
30
|
+
> (csharp-ls, jdtls [java + java-lombok], kotlin-language-server, sourcekit-lsp,
|
|
31
|
+
> dart, lua-language-server, haskell-language-server, elixir-ls, ocamllsp, nixd) —
|
|
32
|
+
> run with `--install` on a box with their runtimes to measure them (#241).
|
|
33
|
+
>
|
|
34
|
+
> **Most fixtures are intentionally broken** (they carry a known defect so the smoke
|
|
35
|
+
> harness can assert a diagnostic). That means such a server always has a diagnostic
|
|
36
|
+
> to early-return on — see the clean-file note below, which a broken fixture masks.
|
|
37
|
+
> The `typescript-clean` fixture exists precisely to expose that cost.
|
|
38
|
+
|
|
39
|
+
## Results (2026-06-21 run, isolated)
|
|
40
|
+
|
|
41
|
+
Sorted by warm/edit. `role`: primary = the file's language server; alternate =
|
|
42
|
+
second server for a language (reached when the default is disabled); auxiliary =
|
|
43
|
+
cross-cutting, attaches alongside the primary in production but measured alone here.
|
|
44
|
+
|
|
45
|
+
| lang | role | cold | warm/edit | server |
|
|
46
|
+
|---|---|---:|---:|---|
|
|
47
|
+
| ruby | primary | 5608ms | 159ms | ruby-lsp |
|
|
48
|
+
| deno | alternate | 1521ms | 165ms | deno (alternate of typescript) |
|
|
49
|
+
| **ast-grep-baseline** | auxiliary | 1906ms | **394ms** | ast-grep (no-sgconfig baseline) |
|
|
50
|
+
| **ast-grep** | auxiliary | 1614ms | **399ms** | ast-grep |
|
|
51
|
+
| dockerfile | primary | 1715ms | 470ms | docker-langserver |
|
|
52
|
+
| toml | primary | 1622ms | 471ms | taplo |
|
|
53
|
+
| go | primary | 1777ms | 473ms | gopls |
|
|
54
|
+
| prisma | primary | 1935ms | 473ms | @prisma/language-server |
|
|
55
|
+
| zig | primary | 1237ms | 496ms | zls |
|
|
56
|
+
| yaml | primary | 1908ms | 506ms | yaml-language-server |
|
|
57
|
+
| python | primary | 2025ms | 520ms | pyright |
|
|
58
|
+
| rust | primary | 1015ms | 576ms | rust-analyzer |
|
|
59
|
+
| gleam | primary | 1219ms | 585ms | gleam lsp |
|
|
60
|
+
| typescript | primary | 1811ms | 605ms | typescript-language-server |
|
|
61
|
+
| **cpp** | primary | 983ms | 607ms | clangd (#241) |
|
|
62
|
+
| clojure | primary | 5496ms | 624ms | clojure-lsp |
|
|
63
|
+
| **opengrep** | auxiliary | 2156ms | **651ms** | opengrep |
|
|
64
|
+
| css | primary | 2204ms | 967ms | vscode-css-language-server |
|
|
65
|
+
| json | primary | 2014ms | 972ms | vscode-json-language-server |
|
|
66
|
+
| html | primary | 2234ms | 981ms | vscode-html-language-server |
|
|
67
|
+
| shell | primary | 2327ms | 1012ms | bash-language-server |
|
|
68
|
+
| svelte | primary | 3311ms | 1028ms | svelte-language-server |
|
|
69
|
+
| typescript-clean | primary | 1827ms | 1043ms | typescript-language-server (clean file) |
|
|
70
|
+
| php | primary | 1703ms | 1167ms | intelephense |
|
|
71
|
+
| jedi | alternate | 2477ms | 1278ms | jedi (alternate of pyright) |
|
|
72
|
+
| **powershell** | primary | 5053ms | 1401ms | PowerShell Editor Services (#278) |
|
|
73
|
+
| markdown | primary | 2979ms | 1725ms | marksman (#274) |
|
|
74
|
+
| terraform | primary | 2331ms | 1729ms | terraform-ls |
|
|
75
|
+
| fsharp | primary | 2958ms | 1730ms | fsautocomplete (`dotnet tool`, #241) |
|
|
76
|
+
| vue | primary | 5545ms | 1731ms | @vue/language-server |
|
|
77
|
+
| **zizmor** | auxiliary | 962ms | **2158ms†** | zizmor (#272) |
|
|
78
|
+
|
|
79
|
+
**Primary/alternate warm:** min 159ms · avg 870ms · max 1731ms (n=27).
|
|
80
|
+
**Auxiliary warm:** ast-grep 399ms · ast-grep-baseline 394ms · opengrep 651ms —
|
|
81
|
+
all at the **fast end** of the primary band. **zizmor 2158ms†** is a bench
|
|
82
|
+
artifact, not its real per-edit cost — see the caveat below; its true warm/edit is
|
|
83
|
+
**~680ms**, also fast-end.
|
|
84
|
+
|
|
85
|
+
> **† zizmor caveat (the bench number is a no-signal artifact).** The harness's
|
|
86
|
+
> warm edit appends `\n// bench edit N\n`, but `//` is **not** a YAML comment
|
|
87
|
+
> (`#` is) — so each warm touch hands zizmor an *unparseable workflow*. zizmor
|
|
88
|
+
> collects no auditable input and publishes nothing, so the touch has no diagnostic
|
|
89
|
+
> to early-return on and runs to its `aggregateWaitMs` budget (the same class of
|
|
90
|
+
> "no-signal, budget-bound" cost as a clean file — see below). Re-measured with a
|
|
91
|
+
> *valid* edit (appending a `#` comment so the workflow stays parseable + still
|
|
92
|
+
> flagged), zizmor early-returns on the version-matched republish:
|
|
93
|
+
>
|
|
94
|
+
> | edit kind | cold | warm/edit | diags |
|
|
95
|
+
> |---|---:|---:|---:|
|
|
96
|
+
> | bench `//` (unparseable) | 962ms | 2158ms | 1 cold, 0 warm |
|
|
97
|
+
> | valid `#` (parseable, flagged) | 2376ms | **682ms** | 1 every touch |
|
|
98
|
+
>
|
|
99
|
+
> So zizmor's real per-edit latency sits with opengrep (~650ms), not at 2158ms.
|
|
100
|
+
|
|
101
|
+
> **fsautocomplete caveat:** it returned **0 diagnostics** (the fixture isn't a
|
|
102
|
+
> restored .NET project, so the server surfaces nothing), so its 1730ms is the
|
|
103
|
+
> *clean-file* near-budget cost — see the clean-file note below — not a real
|
|
104
|
+
> per-edit-with-diagnostic latency. It is a heavy server regardless; this number is
|
|
105
|
+
> an upper bound, not a typical edit.
|
|
106
|
+
|
|
107
|
+
### Auxiliary verdict (#239 Gate A, re-confirmed #272)
|
|
108
|
+
**Pass, decisively.** Measured in isolation, every auxiliary's real warm latency is
|
|
109
|
+
at or below the median primary (ast-grep 399ms, opengrep 651ms, zizmor ~682ms vs
|
|
110
|
+
primary avg 870ms). None regresses the hot path.
|
|
111
|
+
|
|
112
|
+
### Shared with-auxiliary floor on YAML (#272 re-measure)
|
|
113
|
+
Editing a workflow YAML now attaches **three** auxiliaries alongside the yaml
|
|
114
|
+
primary — opengrep, ast-grep, **and zizmor** (all include YAML in their kind set).
|
|
115
|
+
The concern (#272) was that each new auxiliary joins the same `with-auxiliary`
|
|
116
|
+
`Promise.all` and could extend the per-edit floor. It does **not**, because of the
|
|
117
|
+
#242 fix: each attached server gets its **own** deadline bounded by the caller cap
|
|
118
|
+
as a ceiling (`min(callerCap, aggregateWaitMs)`), and they run concurrently — so
|
|
119
|
+
the touch waits for the *slowest single* aux, not their sum. opengrep
|
|
120
|
+
(`min(2500, 3500) = 2500`) already sets that ceiling; zizmor
|
|
121
|
+
(`min(2500, 2000) = 2000`) sits under it and never extends the floor. On a normal
|
|
122
|
+
(parseable) edit all three early-return well inside the cap (~400–680ms). zizmor's
|
|
123
|
+
`aggregateWaitMs` is intentionally left at 2000ms for online-mode (GitHub-API)
|
|
124
|
+
headroom; a late online finding is cached and surfaces on the next edit.
|
|
125
|
+
|
|
126
|
+
## Resolved: the with-auxiliary measurement artifact (#240 + #242)
|
|
127
|
+
|
|
128
|
+
An earlier full-suite bench reported **ast-grep warm = 20557ms** (and ~2000–3700ms
|
|
129
|
+
at lower caps). That was **never ast-grep being slow** — it was two compounding bugs,
|
|
130
|
+
both now fixed:
|
|
131
|
+
|
|
132
|
+
1. **The benchmark co-spawned the primary.** An auxiliary fixture was touched with
|
|
133
|
+
`clientScope: "with-auxiliary"`, which spawns the file's primary language server
|
|
134
|
+
*and* the auxiliary, then waits for **all** of them (`Promise.all`). The fixtures
|
|
135
|
+
are clean JS with no TypeScript errors, so the primary (typescript, push-silent on
|
|
136
|
+
a clean file) never published and never early-returned — the touch ran to the
|
|
137
|
+
deadline even though ast-grep published in ~0.5s. **Fix:** the bench now disables
|
|
138
|
+
the primary and measures each server alone (this doc, isolation note above).
|
|
139
|
+
2. **The with-auxiliary deadline was a floor, not a ceiling.** The collection used
|
|
140
|
+
`timeoutMs = max(callerCap, maxStrategyWait)` for *every* attached server, so a
|
|
141
|
+
large cap *became* the deadline and a slow auxiliary could override the per-edit
|
|
142
|
+
cap. **Fix (#242):** each server now gets its own deadline bounded by the caller
|
|
143
|
+
cap as a ceiling — `min(callerCap, strategyWait)` — so a clean/push-silent primary
|
|
144
|
+
can no longer hold the touch and an auxiliary can't blow the per-edit budget.
|
|
145
|
+
ast-grep's `aggregateWaitMs` was also raised 1000 → 1800 (its scan is ~1.3s; 1000
|
|
146
|
+
was under-budgeted, masked before only by the global floor).
|
|
147
|
+
|
|
148
|
+
Correctness of the early-return signal was hardened separately in **#240**: pull
|
|
149
|
+
diagnostics now return a discriminated `found | clean | unavailable` outcome and a
|
|
150
|
+
failed pull (dead/null/threw) is **never** read as clean — only an affirmative,
|
|
151
|
+
version-matched publish (or authoritative empty pull) ends the wait early. A
|
|
152
|
+
crashed/cold/stale/errored server is never painted "clean".
|
|
153
|
+
|
|
154
|
+
### Clean-file cost is real and irreducible for push-silent servers
|
|
155
|
+
A server that processes an edit and finds **no new diagnostics** gives the wait
|
|
156
|
+
nothing to trip on, so a clean file costs more than a broken one on the *same* server:
|
|
157
|
+
|
|
158
|
+
| fixture | warm/edit |
|
|
159
|
+
|---|---:|
|
|
160
|
+
| typescript (broken — persistent error, re-published each edit) | 605ms |
|
|
161
|
+
| typescript-clean (no diagnostics) | 1043ms |
|
|
162
|
+
|
|
163
|
+
The clean file is ~1.7× slower: with a persistent diagnostic the server re-publishes
|
|
164
|
+
(version bump → early-return); with nothing to report, a Tier-3 push-silent server
|
|
165
|
+
(typescript publishes nothing on clean) gives no signal, so the touch waits its
|
|
166
|
+
budget. This is **budget-bound by necessity** — silence is irreducibly ambiguous, so
|
|
167
|
+
the cap is the only safe backstop. Tier-2 servers that re-publish empty-with-version
|
|
168
|
+
(ast-grep, opengrep) *do* early-return on clean files, which is why both measure fast
|
|
169
|
+
above even though their fixtures could be clean on a given edit.
|