pi-lens 3.8.67 → 3.8.69

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.
Files changed (74) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/README.md +57 -38
  3. package/dist/clients/agent-nudge.js +262 -0
  4. package/dist/clients/biome-client.js +2 -2
  5. package/dist/clients/bus-publish.js +110 -0
  6. package/dist/clients/deps/ast-grep-napi.js +20 -1
  7. package/dist/clients/deps/pi-tui.js +4 -1
  8. package/dist/clients/deps/typebox.js +5 -2
  9. package/dist/clients/deps/web-tree-sitter.js +22 -1
  10. package/dist/clients/diagnostic-logger.js +2 -2
  11. package/dist/clients/diagnostics-publish.js +180 -0
  12. package/dist/clients/dispatch/dispatcher.js +2 -0
  13. package/dist/clients/dispatch/integration.js +153 -2
  14. package/dist/clients/dispatch/runners/ast-grep-napi.js +58 -13
  15. package/dist/clients/dispatch/runners/yaml-rule-parser.js +68 -19
  16. package/dist/clients/file-utils.js +20 -7
  17. package/dist/clients/installer/index.js +32 -1
  18. package/dist/clients/instance-reaper.js +516 -0
  19. package/dist/clients/instance-registry.js +238 -0
  20. package/dist/clients/jscpd-client.js +2 -2
  21. package/dist/clients/lens-config.js +17 -0
  22. package/dist/clients/lens-engine.js +44 -10
  23. package/dist/clients/lsp/cascade-tier.js +254 -0
  24. package/dist/clients/lsp/client.js +81 -3
  25. package/dist/clients/lsp/index.js +3 -0
  26. package/dist/clients/lsp/launch.js +2 -0
  27. package/dist/clients/lsp/server-strategies.js +71 -0
  28. package/dist/clients/lsp/server.js +119 -6
  29. package/dist/clients/mcp/analyze.js +110 -1
  30. package/dist/clients/module-report.js +163 -18
  31. package/dist/clients/path-utils.js +25 -0
  32. package/dist/clients/persist-debounce.js +63 -0
  33. package/dist/clients/pipeline.js +82 -2
  34. package/dist/clients/project-diagnostics/extractors.js +30 -4
  35. package/dist/clients/project-snapshot.js +7 -2
  36. package/dist/clients/quiet-window.js +168 -0
  37. package/dist/clients/recent-touches.js +233 -0
  38. package/dist/clients/review-graph/builder.js +20 -2
  39. package/dist/clients/runtime-agent-end.js +51 -1
  40. package/dist/clients/runtime-coordinator.js +21 -0
  41. package/dist/clients/runtime-session.js +154 -48
  42. package/dist/clients/runtime-tool-result.js +46 -0
  43. package/dist/clients/runtime-turn.js +9 -0
  44. package/dist/clients/session-lifecycle.js +252 -0
  45. package/dist/clients/sgconfig.js +246 -38
  46. package/dist/clients/slow-fs.js +137 -0
  47. package/dist/clients/source-filter.js +62 -14
  48. package/dist/clients/subagent-mode.js +87 -0
  49. package/dist/clients/tree-sitter-symbol-extractor.js +108 -0
  50. package/dist/clients/tui-fit.js +54 -0
  51. package/dist/clients/turn-summary-render.js +72 -0
  52. package/dist/clients/turn-summary.js +132 -0
  53. package/dist/clients/widget-state.js +27 -30
  54. package/dist/clients/word-index.js +296 -1
  55. package/dist/index.js +62809 -1633
  56. package/dist/mcp/build-staleness.js +123 -0
  57. package/dist/mcp/server.js +377 -43
  58. package/dist/tools/ast-grep-search.js +1 -1
  59. package/dist/tools/lens-diagnostics.js +42 -12
  60. package/dist/tools/lsp-diagnostics.js +117 -4
  61. package/dist/tools/module-report.js +14 -11
  62. package/dist/tools/symbol-search.js +110 -0
  63. package/package.json +3 -2
  64. package/rules/ast-grep-rules/rule-tests/hardcoded-url-js-test.yml +1 -0
  65. package/rules/ast-grep-rules/rule-tests/no-typeof-undefined-js-test.yml +8 -0
  66. package/rules/ast-grep-rules/rule-tests/no-typeof-undefined-test.yml +8 -0
  67. package/rules/ast-grep-rules/rules/hardcoded-url-js.yml +1 -1
  68. package/rules/ast-grep-rules/rules/no-typeof-undefined-js.yml +9 -7
  69. package/rules/ast-grep-rules/rules/no-typeof-undefined.yml +9 -7
  70. package/skills/{ast-grep → pi-lens-ast-grep}/SKILL.md +1 -1
  71. package/skills/{lsp-navigation → pi-lens-lsp-navigation}/SKILL.md +1 -1
  72. package/skills/{write-ast-grep-rule → pi-lens-write-ast-grep-rule}/SKILL.md +1 -1
  73. package/skills/{write-tree-sitter-rule → pi-lens-write-tree-sitter-rule}/SKILL.md +1 -1
  74. package/dist/clients/tree-sitter-fixer.js +0 -127
package/CHANGELOG.md CHANGED
@@ -10,6 +10,64 @@ All notable changes to pi-lens will be documented in this file.
10
10
 
11
11
  ### Fixed
12
12
 
13
+ ## [3.8.69] - 2026-07-11
14
+
15
+ ### Fixed
16
+
17
+ - **Warm MCP server no longer silently serves stale code after a rebuild** (#535, refs #514/#256) — the long-lived warm server loads its code once at process start and never re-reads disk, so a `npm run build:dist`/merge that lands after the server started went completely undetected: dogfooding a post-#517 rebuild through an already-running server still returned the pre-#517 `pilens_module_report` schema, the exact "plausible-but-wrong" failure the #240/#511 honesty doctrine exists to prevent. Fix: at startup, `mcp/build-staleness.ts`'s `computeBuildStamp` captures the mtime of the server's OWN entry file (resolved via `import.meta.url`, never a hardcoded repo path — the server may run from an installed package); every `tools/call` and the IPC side-channel handler re-check via a `StalenessGate` (one `fs.stat`, cached at most once/second — same shape as the #492 cross-process reader, so a burst of calls costs one stat). On a detected mismatch: `pilens_analyze` (a stateless per-file dispatch with no warm-only dependency) force-routes through the EXISTING `mode=fresh` worker fork even when the caller asked for warm, tagging the result `servedBy: "fresh (warm code stale — restart the Claude session to re-warm)"`. Every other tool depends on state that only exists inside this process (the in-memory review graph behind `pilens_module_report`/`pilens_symbol_search`, the warm LSP fleet behind `pilens_lsp_navigation`/`pilens_lsp_diagnostics`, the CacheManager/latency log behind the rest) — a fresh fork would answer with an EMPTY graph, a worse result than a stale-but-populated one, so those get an honest `warmCodeStale: true` warning appended instead of routing. The PostToolUse hook's warm-IPC-first path (`clients/mcp/ipc.ts`) gets the same protection for free: on stale, the IPC handler replies with an error, which the hook bin (`mcp/analyze-cli.ts`) already treats as "no usable warm server" and falls back to its own cold, load-fresh-from-disk analysis — no new fresh-fork plumbing needed there. Kill switch: `PI_LENS_WARM_STALENESS_CHECK=0`.
18
+ - **`lsp_diagnostics`/`lens_diagnostics` no longer render an unanswerable scope as "0 diagnostics"** (#533) — dogfooding saw a live session render `workspace — 0 diagnostics` from `lsp_diagnostics` against classic typescript-language-server, a push-only server that publishes NOTHING on a clean→clean transition (`silentOnClean`, `server-strategies.ts`) — that "0" is unverifiable: it can mean clean, still-analyzing, or never-asked (the #240 doctrine, now applied to the tool surface). Root cause: `collectFileDiagnosticResult` (`tools/lsp-diagnostics.ts`) treated an empty diagnostics array as unconditionally clean, with no path for "the server never confirmed this." Fix: a new `classifyEmptyResult` helper reuses the #458 cascade lane's own classifier (`classifyCascadeWaitTier`, `clients/lsp/cascade-tier.ts`) — the same live capability-snapshot + `silentOnClean` check already trusted there — to mark an empty result `"unconfirmed"` when it came from a push-only, silent-on-clean server, vs. `"clean"` otherwise (pull servers, or push servers not known to be silent). Batch and directory aggregation now tally clean vs. unconfirmed per file and surface both in the tool result text (`"7 clean · 2 unconfirmed (server cannot confirm — push-only, silent-on-clean...)"`) and the compact render (`lsp_diagnostics across 12 files — 3 diagnostics · 7 clean · 2 unconfirmed`) — an unconfirmed-containing result can never compact-render as a bare diagnostic/clean count. `lsp_diagnostics` has no workspace-pull mode today (only file/paths/directory), so there is no workspace-pull path to gate on the capability snapshot's mode; the directory/batch per-file scan itself IS the fallback the issue's directive asks for, now made honest. Separately, `lens_diagnostics mode=full`'s cache-only extractor registry (`clients/project-diagnostics/extractors.ts`, knip/jscpd/madge/gitleaks/govulncheck/trivy/dead-code) silently skipped any analyzer with no cache entry — indistinguishable from an analyzer that ran and found nothing clean. `extractCachedProjectDiagnostics` now also returns which extractor ids are cold (never populated this session), and the tool appends an actionable note naming each cold analyzer and what warms it (following #511/#514's honesty-warning shape), both in the result text and the compact render's `(N cold: knip, jscpd, ...)` suffix — a fully-cold registry can no longer render as a plain "clean". Fail-safe throughout: any classification error defaults to the pre-#533 behavior (clean / no cold note) rather than manufacturing a new failure mode.
19
+ - **Test hermeticity for `~/.pi-lens` machine-global state + reaper heartbeat-staleness gap** (#525, refs #515/#474/#449) — dogfooding found a test-fixture instance (`Temp/pi-lens-turn-summary-*` projectRoot) in the developer's REAL `~/.pi-lens/instances.json`, alongside the genuine live session, ~17h after the test run that created it. Two fixes: (1) every machine-global writer (`instances.json`, `probe-cache.json`, all loggers, managed tool/bin dirs, LSP server storage) already routed through the single `getGlobalPiLensDir()` helper (`clients/file-utils.ts`) except four stragglers that bypassed it with a direct `os.homedir()` call (`diagnostic-logger.ts`, the installer's `PROBE_CACHE_PATH`, `biome-client.ts`/`jscpd-client.ts`'s managed-bin lookups, and `lsp/server.ts`'s `tryGemInstall`) — all four now route through it too, and the helper gained a `PI_LENS_HOME` env override (the machine-scoped sibling of the existing project-scoped `PILENS_DATA_DIR`), same pattern as #515's `PI_LENS_CONFIG_PATH` for `config.json`. `tests/support/vitest-setup.ts` now points `PI_LENS_HOME` at a per-worker `mkdtemp` directory (not a nonexistent path — the instance registry actively writes into this root during normal operation), so no test can leak into the real homedir again; a regression test (`tests/clients/pi-lens-home-hermeticity.test.ts`) proves `registerInstance` never touches the real registry file. (2) Root-caused why the fixture entry survived a reap 13h later: `decideOrphanReaping` (`clients/instance-reaper.ts`) only ever classified a parent instance as dead via raw `process.kill(pid, 0)` pid-liveness — unlike child LSP pids, which get a command-line/marker identity check to guard against a recycled pid, the parent pid had NO identity verification at all (there was nothing to check it against). Windows recycles pids far more aggressively than POSIX (no zombie/wait-reaping semantics holding a dead pid "reserved"), so over a long enough window a dead parent's pid is very plausibly reassigned to a live, unrelated process, and `isPidAlive` — correctly, per its own conservative contract — reports it alive forever. `decideOrphanReaping` now also checks heartbeat staleness (new `STALE_HEARTBEAT_MS`, 6 hours), with a deliberate ASYMMETRY by consequence: **staleness cleans registry ENTRIES, never enables kills**. A pid-alive-but-stale instance goes into a new `staleInstances` bucket — its entry is dropped from `instances.json`, but nothing is killed and its children stay marker-protected. Why the asymmetry: heartbeats only fire at turn end (`runtime-turn.ts`) and run settle (`quiet-window.ts`) — no timer exists — so a pi session left open but unused overnight legitimately goes >6h stale while genuinely alive with a warm LSP fleet; killing on staleness would take that fleet down under the idle session, and `matchProcess` identity verification would NOT save it (the children really are that instance's servers — the matcher guards against pid reuse, not against misclassifying a live parent). Process kills still require a pid-confirmed-DEAD parent, exactly as before. Note for anyone reading the real `~/.pi-lens/instances.json` on this machine: any lingering test-fixture entries from before this fix age out of the registry automatically at the next session_start sweep (kills still require a dead pid) — no manual cleanup needed.
20
+ - **Bundled skills namespaced with a `pi-lens-` prefix to avoid user-skill collisions** (#519, reported by @orest-tokovenko-block) — pi discovers both extension-bundled and independently-installed user skills by their frontmatter `name`, and pi-lens's generic skill names collided with unrelated user skills sharing the same name; on a collision, discovery precedence silently skips one copy with a conflict warning (`"ast-grep" collision: ... pi-lens/skills/ast-grep/SKILL.md (skipped)`), so the bundled skill simply stopped being offered. All four bundled skills are renamed with their directories (`git mv`, history preserved) and frontmatter `name` updated to match: `skills/ast-grep` → `skills/pi-lens-ast-grep`, `skills/lsp-navigation` → `skills/pi-lens-lsp-navigation`, `skills/write-ast-grep-rule` → `skills/pi-lens-write-ast-grep-rule`, `skills/write-tree-sitter-rule` → `skills/pi-lens-write-tree-sitter-rule`. Behavior is otherwise unchanged — only the discovery name/path moved. User-facing: anyone who previously invoked the bundled skill explicitly (e.g. `/ast-grep`) must now invoke it by its namespaced name (e.g. `/pi-lens-ast-grep`). `tests/index-wiring.test.ts`'s skill-resolution test now asserts all four namespaced directories exist AND that none of the old generic names exist (a regression guard against renaming back).
21
+ - **Project ast-grep rule precedence follow-ups** (refs #497) — the shared raw-LSP/NAPI discovery seam now walks project and bundled native/CodeRabbit rule trees recursively in deterministic project-primary → project-secondary → bundled-native → bundled-CodeRabbit order. Mutable project-rule caches fingerprint relative paths and contents, so equal-size or preserved-mtime edits, ID changes, renames, additions, and removals invalidate correctly. Synthesized configs and merged rule artifacts are isolated per workspace root in multi-root processes, while NAPI receives the explicit dispatch project root instead of relying on incidental `process.cwd()`. Cross-layer same-ID rules still keep the higher-precedence winner; same-layer duplicates remain raw `sg` errors and now produce equivalent blocking NAPI configuration diagnostics without exposing private paths. Note: recursive bundled discovery also activates the vendored CodeRabbit CWE catalog (~184 rules, all under language subdirectories) for the first time — on the previous top-level-only discovery it silently loaded zero rules in both the NAPI and raw-LSP paths; a regression test now pins that a nested CodeRabbit rule actually fires.
22
+ - **Turn-summary renderer no longer crashes pi on over-width lines** (#513) — both render paths in `clients/turn-summary-render.ts` ignored the `width` parameter of pi-tui's `Component.render(width)` contract, and pi-tui hard-crashes the whole host (`uncaughtException: Rendered line N exceeds terminal width`) on any rendered line wider than the terminal — a 133-column collapsed summary line took down a live 120-column session in the first real-world dogfooding run after #500. The dual-signature `truncateToWidth` shim previously private to the footer widget (`widget-state.ts`) is extracted to a shared `clients/tui-fit.ts` (`fitLine`/`fitLines`), and every turn-summary line — collapsed and expanded — is now fitted to the width the TUI hands us (non-positive/non-finite widths pass through untruncated rather than emitting empty lines). Regression tests measure with the real `visibleWidth`, since the mock-based renderer tests were exactly what let this ship.
23
+ - **`module_report`'s `usedBy`/`semantic` degradation is now honest instead of silent** (#511) — dogfooding found `pilens_module_report` (MCP, warm server) on a recently-added file returning `provenance.usedBy: "none"` and `semantic.source: "none"` with no explanation, indistinguishable from a fully cold (never-built) review graph. Root cause: `moduleReport` (`clients/module-report.ts`) reads the review graph read-only by contract (#256, never builds) — legitimate when the graph just hasn't been rebuilt since a file was added, producing a genuinely stale-but-warm graph (the reported case: the persisted `review-graph.json` predated the file's addition by 12 days, so it had 9,121 nodes but none for the new file). Not a wiring bug — the MCP server read the correct, current graph for that project (`getProjectDataDir`/`normalizeMapKey` keying checked out fine); it was simply stale. Fix: `moduleReport` now distinguishes "no graph at all" (`!graph`, an honest cold start) from "a graph exists but has no node for this file" (`graph && !hasGraphNode`) and pushes an actionable warning in the latter case naming `pilens_rebuild` as the fix, instead of looking identical to the fully-cold case.
24
+
25
+ - **Subagent light mode now also detects avtc-pi-subagent children** (#507) — `isSubagentSession()` (`clients/subagent-mode.ts`) only recognized `PI_SUBAGENT_CHILD=1`, the marker nicobailon/pi-subagents sets; avtc-pi-subagent (the spawn engine under avtc-pi-feature-flow) is the same real child-process execution model but never sets that var — it sets `PI_SUBAGENT_CHILD_AGENT` + `PI_SUBAGENT_PARENT_PID` instead (grep-verified against avtc-pi-subagent@1.0.3). Consequence: light mode silently never engaged for its children, which run the full heavyweight session-start scan suite, multiplied by feature-flow's parallel-reviewer fan-out (up to ~9 subagents per review round). Detection now also treats the PAIR `PI_SUBAGENT_CHILD_AGENT` + `PI_SUBAGENT_PARENT_PID` (both non-empty) as a subagent signal — requiring the pair rather than either var alone is a deliberate false-positive guard, since a lone var set by some unrelated tool must not trigger light mode. `PI_LENS_SUBAGENT_FULL=1` remains the universal opt-out for both vocabularies. `getSubagentIdentity()` now also reports which vocabulary matched (`marker: "pi-subagents" | "avtc-pi-subagent"`), surfaced in the `subagent_light_mode` latency phase so dogfooding can tell the ecosystems apart. The issue's Layer A pinned-contract + Layer B behavioral compat-smoke additions for avtc-pi-subagent are a deferred M-effort follow-up, not part of this fix.
26
+ - **Generated ast-grep/LSP config now honors project-first same-id rule precedence** (#497, reported by @anasalsbey-glitch) — the in-process NAPI runner (`evaluateAstGrepRules`) has always deduped same-id rules project-first (a project rule dir shadows a bundled rule with the same `id`), but the generated raw sgconfig for the ast-grep LSP (`clients/sgconfig.ts`'s `resolveBaselineSgconfig`) listed the project and bundled rule dirs side by side in `ruleDirs`, and raw `sg`/the ast-grep LSP hard-errors ("Duplicate rule id") the instant two listed dirs share an id — verified against a real `sg scan` repro before touching any code. Fix: `resolveBaselineSgconfig` now materializes a single merged, deduped rule directory (doc-level, so multi-rule YAML files are handled correctly) from the SAME project-first-ordered dir list the NAPI runner now also derives from (`shippedRuleDirsInPrecedenceOrder`, shared between both surfaces so they can never drift and disagree on the winner) — `ruleDirs` in the generated config lists just that one directory. Same-layer duplicates (two files in the SAME source dir sharing an id) are deliberately copied through unmodified rather than deduped, so `sg` still hard-errors on them exactly as before — this only resolves the CROSS-layer (project vs. bundled) collision the NAPI runner already tolerated. The merged directory is content-fingerprinted (source dir mtimes) so a mid-session project rule change invalidates the cached config instead of serving a stale winner set. Windows-safe: copies files rather than relying on symlinks (unavailable without elevation on Windows) or junctions (directory-level only, unusable for filtering individual files). — both `no-typeof-undefined` twins previously exempted only `__dirname`/`__filename` from the "use `=== undefined`" hint, so guard-clause idioms like `typeof window === "undefined"` (the standard SSR pattern for browser-only globals, since directly referencing an absent global throws a `ReferenceError`) were flagged as findings. Both rules' `rule.any` now carry a shared `constraints.X.not.regex` excluding `window`, `document`, `navigator`, `self`, `location`, `localStorage`, and `sessionStorage` alongside the existing `__dirname`/`__filename` — a metavariable constraint rather than enumerated `not:` patterns, verified identical (via an napi AST dump) across both the TypeScript and JavaScript grammars, so the twins stay behaviorally aligned. `typeof <declared identifier> === "undefined"` still fires. Separately, `hardcoded-url-js.yml` declared `language: TypeScript` instead of `JavaScript`, so JavaScript/JSX-only syntax never ran through its intended grammar; fixed to `language: JavaScript`, with a new JSX fixture (`const render = () => <a href="http://localhost:3000">...</a>`) proving localhost/API URL detection still fires. Fixture-first: `rule-tests/no-typeof-undefined(-js)-test.yml` and `hardcoded-url-js-test.yml` gained the issue's repro cases, confirmed RED against the pre-fix rules (3 fixtures failing), GREEN after (251/251 `ast-grep test` fixtures pass). Canonical rule catalog (`docs/ast-grep_rules_catalog.md`) regenerated via `npm run docs:rule-catalogs` to move `hardcoded-url-js` from the TypeScript to the JavaScript section.
27
+
28
+ ### Added
29
+
30
+ - **`silentOnClean` drift check on the nightly clean-signal probe** (#529) — the #458 tier-aware cascade classification hinges on the hand-set `silentOnClean` marker in `clients/lsp/server-strategies.ts` (today set only for classic `typescript`, measured manually on 2026-07-08); a server update could silently change the answer with no automated re-check. `scripts/lib/clean-signal.mjs` gains a pure `checkCleanSignalDrift`/`findCleanSignalDrift` pair that compares an observed `clean-behavior` classification against the marker: `marked-not-silent` when the marker says silent but the probe saw a real publish (marker too pessimistic — cascade is skipping a wait it doesn't need to), `silent-not-marked` when an unmarked server probes silent (the pre-#458 tsserver situation — cascade burns the full in-lane wait it could skip). Per the #240 doctrine applied to the check itself, `unknown` observations are NEVER treated as drift evidence in either direction — a slow/absent server isn't proof of anything. `scripts/probe-clean-signal.mjs` (already run nightly by tool-smoke, riding the existing `LSP_FIXTURES` clean-fixture infrastructure) now runs this check after classification, resolved through the same clean-fixture-wins `targetLang` logic the matrix merge already used (so the console report, the matrix row, and the drift footnote can never disagree), and writes any mismatch to a new `## silentOnClean drift (nightly-generated)` section in `docs/lsp-capability-matrix.md` — telemetry only, **never a CI gate**, matching the rest of the probe's best-effort/always-exit-0 design. The native TS7 launch variant (`typescript7`/`typescript7-clean`, #524/#526) is deliberately excluded from the comparison: it shares the "typescript" server-strategy key with classic, but the marker is documented classic-only, so comparing it would produce misleading drift rather than a real signal. Live-verified locally: classic `typescript-clean` probes `silent` (tier 3), consistent with the existing marker (no drift reported); the native `typescript7-clean` variant (via a real `typescript@7` install into the fixture's temp workspace) also probes `silent`, correctly excluded from comparison rather than silently validated. 21 new unit tests (`tests/scripts/clean-signal.test.ts`) cover both drift directions, the consistent case, and the never-collapse-unknown guard.
31
+ - **Nightly smoke coverage for the native TypeScript 7 LSP path** (#530, follow-through on #524/#526) — the native `tsc --lsp --stdio` selection previously shipped verified-by-documentation only (the repo pins typescript 6.x), so upstream drift in the `--lsp` flag, stdio handshake, or publish behavior had no regression guard. `scripts/smoke-tools.mjs` gained two fixture-level extensions: an optional `setup` step (string/argv command run in the COPIED temp workspace before `touchFile`, bounded by a 120s timeout — new `typescript7`/`typescript7-clean` fixtures use it to `npm install typescript@7 --no-save --no-audit --no-fund`, since typescript-go's per-platform native binary can't be a committed static fixture; a setup failure reports a distinct `setup-failed` status and never a false pass) and an optional `expectLaunchVariant` assertion (checks the live `getCapabilitySnapshots(file)` `launchVariant`, e.g. `"native-ts7"` — a silent fallback to the classic `typescript-language-server` now FAILS even when a diagnostic arrived, since native and classic share the same `"typescript"` server id and a diagnostic alone can't distinguish them). Verified live against a real `typescript@7.0.2` install: `tsc --lsp --stdio` genuinely speaks LSP framing over stdio and PR #526's assumed invocation is correct. `typescript7-clean` doubles as the future #529 clean-signal probe workspace for the native variant.
32
+ - **`symbol_search` pi tool + always-warm word index (#348 phase 1)** — the word index (identifier inverted index + BM25, #162) previously had exactly one build path (a full-mode-only deferred session task), so it was absent in quick-only sessions and stale after that unless something else happened to rebuild it; empirically `symbolSearch("moduleReport", cwd)` on this repo returned `available: false`. It now gets the same load → rebuild-if-stale → persist lifecycle the call-graph task already uses (`clients/runtime-session.ts`'s new shared `buildOrRefreshWordIndex`, reusing `isProjectSnapshotFresh`/project `seq` — the same edit-provenance signal, not a new one), wired into BOTH the full-mode `runTask("word-index", …)` and the existing quick-mode cold-start warmup pass (the same ~2s-deferred background pass that already warms scan-context + language-profile after a quick first session) — no new mechanism, per the ratified #348 decisions. The file-walk-and-read step is now one shared `collectWordIndexDocs` helper (`clients/word-index.ts`) instead of three near-duplicate copies. A pre-existing snapshot-merge bug is fixed alongside this: `saveRuntimeProjectSnapshot` could "launder" a stale snapshot's leftover word index into looking fresh by re-stamping it with the current `seq` on an unrelated intermediate save — it now only carries the prior index forward when it was built at the SAME seq. Second, `symbol_search` is now a registered **pi tool** (mirroring the existing MCP-only `pilens_symbol_search`) — the entry point of the discovery funnel: `symbol_search` finds ranked candidate files by identifier, `module_report` explains one, `read_symbol` reads the exact body. A cold query (no index yet, e.g. an MCP-only session that never ran `pilens_session_start`) never blocks: it triggers one bounded background build per cwd (deduped via an in-flight guard) and returns `available: false` with an actionable retry hint immediately. Both the new pi tool and the existing MCP `pilens_symbol_search` (which predates #517) now return the slimmed #517 payload: hits carry `startLine`/`endLine` (the best-matching line; read derivation `offset=startLine, limit=endLine-startLine+1`, documented in both tool descriptions) instead of a raw `lines[]` array or a per-hit `read` block, and MCP's JSON is compact (unindented) like `module_report`'s. The `ast_grep_search` 0-match hint and the session-start orientation guidance now route name/usage lookups toward `symbol_search`/`module_report`/LSP `findReferences` instead of only suggesting another AST retry or grep. Phase 2 (per-edit incremental word-index maintenance) remains out of scope and tracked on the still-open #348.
33
+ - **Warm per-edit word-index maintenance, review-graph style (#348 phase 2, closes #348)** — phase 1 kept the word index fresh only via a full session-scoped rebuild; a burst of edits mid-session went unreflected in `symbol_search` rankings until the next rebuild. `clients/word-index.ts` gains a forward index (`WordIndex.forward`: file → per-token distinct-line counts, optional so a pre-phase-2 index/persisted snapshot deserializes as `forward: undefined`) plus `updateWordIndexDocument`/`removeWordIndexDocument`, which use it to do a single-document replace mechanically — subtract this file's own contribution from postings/docLengths/totalTokens/docCount via the forward entry, then add the new one, without rescanning unrelated files. A caller handed a forward-index-less index (old shape) must fall back to a full rebuild — never migrated in place. The per-edit seam lives at the SAME call site as the review graph's `buildOrUpdateGraph` (`computeCascadeForFile` in `clients/dispatch/integration.ts`), reusing content the pipeline already read (no extra I/O); it is keyed by `path.resolve(filePath)` — deliberately NOT the cascade's own `normalizeMapKey`-based key, since the word index's own keys come from the file-walk's plain `path.resolve()` shape and using the wrong key would silently create an orphaned duplicate entry instead of replacing the doc. Cold-session handoff rule: `wordIndex` `null`/absent (nothing loaded yet) is a documented no-op — phase 1's lifecycle/background build owns "cold", never this seam; the update function itself has no `await`, so two overlapping deferred cascades (#450) can never interleave mid-mutation. Files over the existing `WORD_INDEX_MAX_BYTES` cap are removed/absent from the index, never partially indexed; a file the pipeline couldn't read (deleted, transient race) is also a no-op — deletions age out at the next full rebuild, an accepted scope boundary matching the review graph's own. Persistence reuses (generalized, not copied) the review graph's #260 debounced-persist circuit-breaker: `clients/persist-debounce.ts`'s new `createDebounceScheduler` factors out the coalesce-and-flush timer bookkeeping both caches need, while each owns its own serialize+write (the graph writes its own cache file; the word index merges into the shared project-snapshot file via `saveProjectSnapshot`, preserving unrelated fields and honoring the existing seq-laundering guard, extended with a regression test for the previously-untested stale-seq case). New `PI_LENS_WORD_INDEX_PERSIST_DEBOUNCE_MS` env override and `flushWordIndexPersistsForTests()` mirror the graph's `PI_LENS_GRAPH_PERSIST_DEBOUNCE_MS`/`flushReviewGraphPersistsForTests`; `tests/support/vitest-setup.ts` defaults the new debounce to 0 for the same reason it already does for the graph's. The load-bearing test is an equivalence-property check: k randomized incremental edits/additions/removals against an index must produce identical state (postings/df/N/avgdl) AND identical query rankings to a from-scratch `buildWordIndex` over the same final corpus — covering a term disappearing entirely from a doc, a doc shrinking, a doc growing, a doc removal, a brand-new doc, and an unrelated doc verified untouched via forward-index reference identity.
34
+ - **Warm-mode `pilens_analyze` maintains the review graph + word index; MCP graph-staleness signal; `pilens_read_enclosing` (#536, closes #536, refs #522)** — the 2026-07-11 capability-depth parity audit's cheapest-first follow-ups, one PR. (1) **DECISION: retires the #256 "read-only facade" contract for warm mode.** `pilens_analyze`'s warm path (the long-lived MCP server; `fresh` stays read-only — it's an ephemeral forked worker, see `mcp/worker.ts`) now calls `buildOrUpdateGraph` for the analyzed file on a successful, blocker-free dispatch — the SAME call pi's per-edit cascade path makes (`computeCascadeForFile`, `clients/dispatch/integration.ts`), gated by the same `CASCADE_GRAPH_KINDS` file-kind set (now exported for this reuse) and the same "skip on blockers" rule. `buildOrUpdateGraph` owns its own debounced persist/seq machinery internally, so this is the only call needed. Consequence: `pilens_module_report`'s usedBy/blastRadius and `pilens_symbol_search`'s centrality now reflect files analyzed via MCP, not just session-start state — verified end-to-end on a two-file fixture where warm-analyzing the importer flips the imported file's `semantic.source` from `"none"` to `"review-graph"`. Rides the SAME seam for the #348 phase 2 word-index per-edit primitive (`updateWordIndexDocument`/`removeWordIndexDocument`), mirroring (not reusing directly — that function is module-private) `updateWordIndexForCascade`'s rules: a per-cwd live `WordIndex`, loaded once from the persisted snapshot and mutated in place thereafter (the MCP-process equivalent of `runtime.wordIndex`, since MCP has no RuntimeCoordinator to hold it); no forward index cached ⇒ no-op; an oversized file is removed, never partially indexed; a successful update schedules the existing `scheduleWordIndexPersist` debounce (`PI_LENS_WORD_INDEX_PERSIST_DEBOUNCE_MS`) — no second persist mechanism; keyed by `path.resolve(absPath)` to match the word index's own build-path key shape, not `normalizeMapKey`. `symbolSearch()` (`clients/lens-engine.ts`) now prefers this warm in-memory copy over a fresh disk read when one exists, so a `pilens_symbol_search` query immediately following a warm analyze in the SAME process sees the update without waiting on the debounce or a rebuild — verified with a smoke test seeding a snapshot missing a new identifier, warm-analyzing a new file containing it, then confirming `pilens_symbol_search` ranks it with no `pilens_rebuild`/`pilens_session_start` in between. (2) **Graph-staleness signal [S].** `pilens_module_report` and `pilens_symbol_search` gain a staleness hint when their backing data is present but old — extends #514/#511's honesty-warning shape from "missing node" to "aging graph". Surfaces the ALREADY-persisted timestamps (`ReviewGraph.builtAt` for module_report, additively threaded through as `ModuleReport.graphBuiltAt`; `ProjectSnapshot.generatedAt` for symbol_search, additively returned as `SymbolSearchResult.snapshotGeneratedAt`) — no new timestamp had to be invented. A `> 10min` age appends `"<label> last updated <Nm/h/d> ago; run pilens_analyze on recently-changed files, pilens_session_start, or pilens_rebuild to refresh it."` to both the text summary and the JSON payload. MCP-only per the decision: pi's own graph is per-edit warm, so the same line there would be pure noise. (3) **`flushPending` parity for `pilens_diagnostics` [S] — investigated, not wired, because there's genuinely nothing to flush.** pi passes `() => flushDebouncedToolResults()` as `createLensDiagnosticsTool`'s 4th arg; the MCP instantiation passes none. Traced `flushDebouncedToolResults` (`clients/runtime-tool-result.ts`) to a module-level `debouncedPipelines` map that ONLY `handleToolResult` populates — pi's `tool_result` event handler, which the MCP process never calls (`pilens_analyze` routes through the independent `clients/mcp/analyze.ts` facade, calling `dispatchLintWithResult` directly). That map is therefore provably always empty in the MCP process; wiring the flush would be a no-op dressed as a fix. Documented in place at the `mcp/server.ts` instantiation site rather than silently left unexplained. (4) **`pilens_read_enclosing`** (closes #522 item 1) — `readEnclosing` is now re-exported from `clients/lens-engine.ts` and mirrored as a new MCP tool with the same file+line(+kinds/maxLines/onOversize/aroundLine) shape as the pi `read_enclosing` tool and the same header-line-then-body rendering convention as `pilens_read_symbol` (#512); MCP has no read-guard, so — like `pilens_read_symbol` — it returns the body with no coverage recording, an intentional gap, not a bug. `docs/agent-tools.md`'s mirror-exceptions note now lists only `ast_grep_outline`/`ast_grep_dump` as pi-only; `AGENTS.md`'s MCP tool count moves 15 → 16 (both the server.ts file-header comment and the tool-list bullet).
35
+ - **Native TypeScript 7 language-server selection** (#524) — TypeScript projects now inspect the nearest workspace-local `typescript` package before starting an LSP, including dependencies hoisted above a nested monorepo package root. Version 7+ launches that package's matching `node_modules/.bin/tsc --lsp --stdio`, avoiding the previous silent fallback to pi-lens's managed TypeScript 5/6 `tsserver.js`; a nearer TypeScript package always shadows an ancestor (including when that nearer install is malformed/partial — a `node_modules/typescript/` directory with no `package.json` stops resolution there instead of silently falling through to an ancestor TS 7 hoist), and TypeScript <=6 retains `typescript-language-server --stdio` with the existing `TSSERVER_PATH` initialization. Resolution is deliberately workspace-relative (including Windows `.cmd`/`.exe` shims), never a bare global `tsc`, and missing binaries or invalid metadata fall back to the classic discovery path. The launched variant is now recorded on the capability snapshot (`launchVariant: "classic" | "native-ts7"`), so the #458 cascade-lane tier classifier no longer inherits the classic server's `silentOnClean` tier-3 marker for the native TS7 binary — an unverified Go-native server falls back to the fail-safe full in-lane wait instead of risking a dropped clean→clean diagnostic (refs #529, the pending clean-signal probe).
36
+ - **`module_report` doc-comment summaries + `view: "compact"` (#512, slices 1/3/4)** — dogfooding measured `module_report` costing ~1,900 tokens vs ~2,100 to read a representative 266-line file whole, only a ~10% saving; this closes most of the gap. Each `ModuleSymbolEntry` now carries a `doc` field — the first sentence/line of an attached doc comment (whitespace-collapsed, capped ~120 chars) — extracted structurally in the SAME tree-sitter pass that already builds decorators/visibility (`tree-sitter-symbol-extractor.ts`'s new `extractDocComment`, preceding-sibling `comment`-node traversal, position-matched since web-tree-sitter materializes a fresh node object per `.children`/`.parent` access with no stable identity); JS/TS is the primary target, and any grammar sharing the conventional `comment` node shape (Python confirmed) gets it for free. New `view: "compact"` (pi tool + `moduleReport()` engine option, opt-in — default stays JSON) renders the full report as line-oriented text (one line per symbol/member/callback, e.g. `77-81 fn _resetAgentNudgeForTests() — Test-only: clear accumulator state.`) via new `renderCompactModuleReport`, at roughly half the byte size of the JSON view for the same file. Also cut real duplication from the JSON schema: per-symbol `read: {path, offset, limit}` blocks are gone (both tool descriptions now document the derivation — `offset = startLine`, `limit = endLine - startLine + 1`, path = the report's own `path`); `recommendedReads` entries carry `{reason, symbol, startLine, endLine}` instead of repeating the read block; `flags` no longer duplicates `"exported"` (the boolean field already carries it) — cross-file sections (`blastRadius.files[].read`, `usedBy[].file`) are untouched since they legitimately need their own path. The MCP mirror (`pilens_module_report`/`pilens_read_symbol`) now matches the pi tool's compact (unindented) JSON instead of pretty-printing, gained `focus`/`view` passthrough, and `pilens_read_symbol` no longer restates name/kind/startLine/endLine in a trailing JSON block after a header line that already carries them. Deliberate schema break — existing tests updated for the new shape. Follow-ups tracked in #512: MCP parity for `read_enclosing`/`view:"summary"` (slice 2) and size-aware honesty when the outline would cost more than reading the file (slice 5).
37
+ - **Expert LSP for Elixir** (#498) — Expert is now an auto-installed alternate to ElixirLS for `.ex` and `.exs` files. ElixirLS remains the default; add `"elixir"` to `disabledServers` in `.pi-lens/lsp.json` to select Expert. pi-lens launches Expert with its required `--stdio` flag and downloads the official bare GitHub release binary for macOS, Linux, or Windows (Windows arm64 uses the x64 build through emulation).
38
+ - **`pilens:files:touched` bus event** (#482) — pi-lens's first `pi.events` broadcast surface. Every autonomous file write pi-lens makes outside the agent's own tool calls — dispatch autofix (biome/ruff/eslint/stylelint/sqlfluff/rubocop/ktlint/rust-clippy/dart-fix/golangci-lint/detekt/ktfmt/markdownlint/oxlint) and formatter runs (immediate or deferred-at-`agent_end`), plus the conservative actionable-warnings LSP autofix — now emits a versioned `{ v: 1, source: "pi-lens", reason: "autofix" | "format", paths, cwd }` payload via `clients/bus-publish.ts`'s `publishFilesTouched`, one event per logical write batch. Fire-and-forget (never affects write-path success/latency) and null-safe when unwired (unit tests, the MCP server's no-pi-host path). Deliberately excludes agent-authored edits (partial-edit-apply preflight, ast-grep/lsp-navigation tool calls) — the host already knows about those. Kill switch `PI_LENS_BUS_PUBLISH=0`. See `docs/features.md` ("Bus Events") for the full contract; refs #478.
39
+ - **`agent_settled` quiet window** (#483) — pi 0.80.6 added an `agent_settled` extension event, emitted once the whole agent run (including any retry/continue loop) goes fully idle, on both normal completion and aborts. New `clients/quiet-window.ts` registers a handler (feature-detected — the SDK's `pi.on` accepts any event string with no validation, so this is a safe no-op on older pi hosts) that schedules deferred, expensive work in that guaranteed-quiet gap, additive to the existing `turn_end` settle (unchanged). Ships with two built-in tasks run through a small sequential task registry (`registerQuietWindowTask`, so #458/#236 can plug in later without touching the scheduler): a second, more generous settle attempt (`PI_LENS_QUIET_WINDOW_WAIT_MS`, default 15000ms) for cascade computes still carried over past the `turn_end` cap, and the #449 instance-registry heartbeat refresh moved off the turn hot path. Tolerates the event firing multiple times per session (re-entrant runs are skipped, never queued); every task failure is isolated and swallowed; the handler itself never awaits the task chain (kicked off fire-and-forget) so it can't hold up the SDK returning control to the next turn. Kill switch `PI_LENS_QUIET_WINDOW=0`. Logs a `quiet_window` latency phase with per-task `{name, durationMs, ok}` plus a `skipped: "in-progress" | "disabled"` marker.
40
+ - **Tier-aware cascade-lane LSP waits** (#458, re-scoped from the original learned-deadline design after dogfooding: `docs/lsp-capability-matrix.md`'s nightly-refreshed capability matrix already answers the classification question, and #483's quiet window gives the cascade lane somewhere to resolve the ambiguity out-of-lane) — the deferred cascade neighbor-touch fan-out (`clients/dispatch/integration.ts`'s `computeCascadeForFile`) used to actively wait up to its per-touch budget (~1000-2000ms) for `textDocument/publishDiagnostics` on every neighbor, even for a Tier-3 (push-only, silent-on-clean) server that can never distinguish "clean" from "still analyzing" that way — typescript-language-server is the lone core-set instance, and dogfooding measured ~221 such `lsp_diagnostics_timeout` events/day. New `clients/lsp/cascade-tier.ts` classifies each cascade touch's primary server from the LIVE capability snapshot (`workspaceDiagnosticsSupport.mode`) combined with a new `silentOnClean` marker on that server's `DiagnosticStrategy` (`server-strategies.ts`, set only for `typescript`) — never a hardcoded server-name check at the call site, and any ambiguous/missing snapshot fails safe to today's full wait. A Tier-3 touch still fires its didOpen/didChange notify (the server starts real work) but skips the in-lane wait and records the touch as outstanding; a new quiet-window task (`cascade_tier3_reconcile`) checks each outstanding touch against the client's diagnostics cache at the `agent_settled` idle point — diagnostics arrived since the touch ⇒ `resolved-found`/`resolved-clean`, nothing arrived ⇒ `unresolved` (never silently treated as clean — the #240 doctrine holds). Kill switch `PI_LENS_TIER_AWARE_CASCADE=0` restores the old full-wait behavior outright. Logs a `cascade_tier3_skip` cascade-log phase per skipped touch and a `cascade_tier3_reconcile` phase at the quiet window with resolved-found/resolved-clean/unresolved counts and touch ages. Review follow-up (refs #458) hardened the reconcile path: outcomes are decided by the client's PER-FILE publish timestamp (`getAllDiagnostics()`'s `ts`), not the client-wide `diagnosticsVersion` counter — a cascade touches multiple neighbors on the same tsserver client, so a counter advanced by neighbor A's publish could falsely "prove" a silent neighbor B `resolved-clean` (#240 violation); `touchedAt` is sampled BEFORE the notify so a publish racing the record can't be misread as pre-touch; and the quiet-window reconcile looks clients up via `getWarmClientForFile` (warm-only) instead of the get-or-create accessor, so it can never resurrect an idle-reaped server just to write a log line — a warm-miss reconciles as `unresolved`.
41
+ - **Inline agent nudge for out-of-view file mutations** (#485) — deferred-cascade autofixes and formatter writes that land AFTER a tool result (turn_end settling, #483's quiet window) were invisible to the model; an agent running `git status` at the top of a fresh run would find working-tree changes it never made and burn turns investigating. New `clients/agent-nudge.ts` subscribes read-only to the `pilens:files:touched` bus event (#482) via `pi.events.on` (feature-detected — no-op on older pi hosts with no `pi.events`/`.on`), accumulates touched paths across the session, filters them down to files the session actually read or edited (the read-guard's `getReadHistory`/`getEditHistory`, keyed via `normalizeMapKey` for every map access), and injects at most one terse context message per delivery via the same `context` extension event `clients/runtime-context.ts` already uses for turn-end findings: `pi-lens: 2 file(s) were autofixed after your last turn: a.ts, b.ts — working-tree changes to these are expected; re-read before editing.` (capped at 5 names + "and N more"). The accumulator is cleared only on actual injection — never on `turn_start`/`agent_end`/`agent_settled` — so files touched at one run's `turn_end` still nudge at the very next run's first turn in the same session (`context`/`transformContext` fires before every provider call, including the first one of a new `agent_start`). This subscriber never emits back to the bus, so the #482 loop guard's write side has nothing to trip. Kill switch `PI_LENS_AGENT_NUDGE=0`. Logs an `agent_nudge` latency phase with `{filesTotal, filesShown, filesFiltered, reasonMix}` on injection. See the "Three channels, three audiences" doctrine in `AGENTS.md` (bus events → extensions #482, display-only entries → the human #484, context nudges → the model, this feature).
42
+ - **Opt-in per-run transcript summary** (#484) — pi-lens's write-path effects (diagnostics found, autofixes applied, autoformats applied) previously only surfaced as transient inline text or the `/lens-health` command; nothing persisted in the transcript for a human reviewing the session later. New `turnSummary.enabled` config key (default **false** — opt-in; also `lens-turn-summary` CLI flag) turns on a `clients/turn-summary.ts` collector that accumulates `{file → events}` across the RUN from the SAME seams that already produce these signals — no new collection plumbing: the immediate write/edit pipeline result (`clients/runtime-tool-result.ts`, newly-surfaced `PipelineResult.diagnostics`/`formattersUsed`/`fixedCount`/`autofixTools`), the `agent_end` deferred-format completion and the experimental actionable-warnings LSP autofix pass (both in `clients/runtime-agent-end.ts`). The single `pi.sendMessage({customType: "pilens:turn-summary", display: true, details})` entry is emitted at the **`agent_settled` quiet window** (#483 scheduler, `turn_summary_emit` task), NOT at turn_end — a load-bearing choice verified against the installed pi 0.80.6 SDK: `sendCustomMessage` STEERS the live model conversation when the session is streaming, and a mid-run turn_end plausibly fires while streaming; at settle the session is idle, so sendMessage takes the safe append branch. The collector therefore survives turn boundaries (NOT cleared in `beginTurn`) and is consumed exactly once per settle; grain is one entry per RUN (never per-file or per-turn). Honest SDK caveat: this entry is NOT display-only — a `CustomMessageEntry` participates in LLM context (`display` only controls TUI rendering; `buildSessionContext` converts every such entry into a user message on later context builds), so the entry `content` is kept to the single ~80-char collapsed line (an accepted, owner-approved residue, largely redundant with the #493 agent nudge); the structured `details` payload never reaches the model. A registered `registerMessageRenderer` (`clients/turn-summary-render.ts`) renders it natively collapsible/expandable via pi's own entry-expansion toggle: collapsed is one tool-grouped line in the pi-lens brand accent (`pi-lens: 3 diagnostics (eslint 2, tsserver 1) · 2 autofixed (ruff 1) · 1 reformatted (prettier 1)`), expanded is FILE-MAJOR — each touched file lists its formats/autofixes/diagnostics (tool + rule id + line) in its own block, answering "what happened to x.ts?" rather than "what happened this run?". Both `pi.sendMessage` and `pi.registerMessageRenderer` are feature-detected (no-op, never throws, on older pi hosts). The redundant info-level "pi-lens deferred format applied to..." toast in `runtime-agent-end.ts` is suppressed when this entry is opted in (the failure/warning toast is untouched either way). Logs a `turn_summary` latency phase with `{files, diagnostics, autofixes, formats}` on emit. See the "Three channels, three audiences" doctrine in `AGENTS.md` (bus events → extensions #482, this feature → the human, context nudges → the model #485).
43
+ - **Cross-process touched-files nudge** (#492) — #485's inline nudge only covered ONE process: a subagent spawned as a real child `pi` process (the nicobailon/pi-subagents model) never saw the parent's autoformats, and the parent never saw a child's — both real pain cases (`git status` finding unexplained `M` files; a parent's next edit hitting stale `oldText` after a child's pi-lens reformatted on top of its edits). New `clients/recent-touches.ts` adds a project-scoped `recent-touches.json` (via `getProjectDataDir(cwd)`) that every pi-lens instance both writes to and reads from: a ~50-entry ring buffer of `{path, reason, ts, pid, sessionId?}`, atomic tmp+rename writes (same pattern as the #474 instance registry). The producer is wired into the EXISTING `publishFilesTouched` seam (`clients/bus-publish.ts`) — parent and child run identical code, and the record is populated even when no `pi.events` bus is wired (bare/MCP hosts), since the on-disk record is the only one of the two deliveries that survives a process boundary. Two consumers feed the SAME #485 accumulator (`clients/agent-nudge.ts`'s new `recordCrossProcessTouches`) so exactly one batched context message is ever injected regardless of how many files came from which channel: a **child at `session_start`** reads entries from other pids within a 15-minute freshness window whose file still exists (no read-guard history exists this early, so relevance is recency + existence only); a **parent at `turn_start`** does a single mtime-gated `fs.stat` (zero reads/parses when nothing changed since the last turn) and, on a genuine change, applies the SAME shared baseline filter (foreign pid + 15-minute freshness + file still exists — one private helper both readers call, so they can never drift) plus a consumed-ts cursor; beyond that baseline the parent has deliberately no read-guard drop path — a parent about to commit needs attribution even for files it hasn't read yet this session. `AccumulatedFile` gained an `origin: "local" | "cross-process"` field; a file seen via both channels always reads as `"local"` (sticky — once the session's own bus has reported a touch, the local wording is the more precise, more actionable framing, and "another instance" framing no longer applies). Attribution is three-way and never assigns a local file to another instance: a pure-local batch keeps the unchanged #485 wording ("after your last turn"), a pure cross-process batch reads "by another pi-lens instance (e.g. a subagent's)", and a mixed batch reads "after your last turn (N of them by another pi-lens instance)" — always one message, never split. The `agent_nudge` latency phase gained `originLocal`/`originCrossProcess` counts. Reuses the existing `PI_LENS_AGENT_NUDGE=0` kill switch for the producer and both consumers (no new env var); `PI_LENS_BUS_PUBLISH=0` also silences the record append, since the producer lives inside `publishFilesTouched` (both deliveries of a touch die together behind that gate). Deliberately NOT gated on subagent light mode (#449), since this is a cheap file read, not a heavyweight scan. No IPC, no daemon, no `fs.watch` — a passive file, per the #449 no-daemon doctrine.
44
+
45
+ - **`pilens:diagnostics` bus event + `pilens:files:touched` fix provenance** (#502) — extends the #482 producer family from "which files changed" to "what pi-lens knows about them", so terminal-native diff/review extensions can render pi-lens's findings as inline annotations in their own views instead of pi-lens owning a review UI. New `clients/diagnostics-publish.ts` publishes a versioned `pilens:diagnostics` event — `{v: 1, source: "pi-lens", cwd, seq, ts, files: [{path, diagnostics: [{ruleId?, severity, line?, col?, message, tool, fixable?}], truncated?}]}` — once per write batch, immediately after the batch's final per-file diagnostic set is committed (post-format, post-autofix, post-dispatch), so it always reflects the LATEST state rather than an intermediate runner result. Follows LSP `publishDiagnostics` staleness semantics: full-replace per file (never a delta), explicit `diagnostics: []` on a dirty→clean transition (fired exactly once, tracked via a module-level reported-paths set), monotonic `seq`+`ts` so out-of-order receipt resolves deterministically, and `pilens:files:touched` (#482) documented as an invalidation hint (a touched path's held diagnostics are provisional until the next diagnostics event mentions it). Capped at 12 diagnostics per file per event (errors prioritized), file contents never inline; reuses the `PI_LENS_BUS_PUBLISH=0` kill switch. The `PilensDiagnosticsPayload` schema is reserved for #478's future `pilens:rpc:diagnostics` pull response (push and pull share one shape). Separately, `FilesTouchedPayload` (#482) gains an additive optional `fixes?: {path, tool, ruleId?, kind: "autofix" | "format"}[]` field for fix provenance — lets a diff/review consumer distinguish a pi-lens-mechanical hunk from an agent edit; old consumers ignore the field. Before/after file content is intentionally omitted from v1. Full contract in `docs/features.md` ("Bus Events").
46
+
47
+ ### Changed
48
+
49
+ - **Native TS7 LSP variant reclassified `silentOnClean` (closes #541, follow-through on #458/#526/#529)** — PR #526 excluded the native TypeScript 7 launch variant (`tsc --lsp --stdio`) from the #458 tier-3 cascade classification as a fail-safe, since `silentOnClean` had only been measured against classic `typescript-language-server`. The #529/#540 clean-signal probe has since measured native-ts7 directly (`typescript7-clean` fixture, repeated local runs): silent on clean transitions, same as classic. Per the maintainer's decision (prefer fast cascade waits), `clients/lsp/cascade-tier.ts`'s classifier no longer branches on the snapshot's `launchVariant` — both variants now skip the in-lane wait. `scripts/lib/clean-signal.mjs`'s nightly drift check also lifts its typescript7 exclusion, routing native rows to the shared `typescript` strategy marker instead of skipping them — the rollback safety net: if a future TS7 build starts publishing on the clean fixture, the drift check now emits a `marked-not-silent` warning instead of silently missing the regression.
50
+ - **Per-walk `isBuildArtifact` sibling-probe memo** (#191, item 1 of 4) — `findSourceSibling`/`isBuildArtifact` (`clients/source-filter.ts`) probe for a higher-precedence source sibling (e.g. does `foo.ts` exist next to `foo.js`?) via `fs.existsSync`; call sites with repeated/overlapping lookups (e.g. `filterSourceFiles` handed an overlapping candidate list) re-issued identical probes. `#191` deliberately deferred this because a *persistent* memo has an awkward invalidation problem — siblings can change between scans, and a stale key risks silently misclassifying a file (lost detection). This ships the narrower, invalidation-free version instead: an optional per-walk `ArtifactProbeCache` (`createArtifactProbeCache()`), created at the start of one `collectSourceFiles`/`collectSourceFilesAsync`/`filterSourceFiles` call and discarded when it returns — no persistent or module-global cache, nothing to invalidate. Callers that don't pass a cache get exactly today's behavior. Keyed via a new cheap, syntactic-only `normalizeEphemeralMapKey` (slash-fold + win32 lowercase, no `realpathSync`) rather than the existing `normalizeMapKey` — using the latter here was measured ~11x *slower* than the `existsSync` probe it would replace, because it resolves nonexistent candidate paths via its own ancestor-walking `existsSync` calls; `normalizeEphemeralMapKey` is intentionally scoped to ephemeral, single-process, single-walk caches only. Measured ~70% faster on a fixture modeling realistic overlapping-lookup call shapes (500 pairs × 4x duplication); near break-even on this repo's own tree, which is pure TypeScript with no compiled `.js` siblings on disk to re-probe — an honest finding, not a regression.
51
+
52
+ ## [3.8.68] - 2026-07-10
53
+
54
+ ### Added
55
+
56
+ - **Subagent light mode** (#449) — the nicobailon/pi-subagents extension spawns each subagent as a child `pi` CLI process and sets `PI_SUBAGENT_CHILD=1` unconditionally in every child's environment, so a fan-out of N subagents in the same cwd previously paid N full LSP pre-warms plus N sets of heavyweight startup scans — mostly wasted on short-lived task agents. `clients/subagent-mode.ts` adds `isSubagentSession()`, detected once at session start; when engaged, both the LSP pre-warm (explicit `warmFiles` and the dominant-language auto-warm) and the knip/jscpd/madge/dead-code/govulncheck/gitleaks/trivy startup scans are skipped, extending the same `skipHeavyweightScans` gate #462 introduced for slow filesystems. Per-edit LSP dispatch and the in-process scans (todo/call-graph/codebase-model/ast-grep-exports/word-index) are untouched, so a subagent that actually edits code still gets diagnostics and symbol search. Escape hatch: `PI_LENS_SUBAGENT_FULL=1` forces full behavior even inside a detected subagent session. Logged to the latency log as a `subagent_light_mode` phase with the subagent's `runId`/`agentName` (from `PI_SUBAGENT_RUN_ID`/`PI_SUBAGENT_CHILD_AGENT`) when present.
57
+ - **Cross-process instance registry** (#449 slice 1) — a tiny machine-global registry (`~/.pi-lens/instances.json`, `clients/instance-registry.ts`) now records every live pi-lens process: pid, project root, live LSP child servers (pid/serverId/command/spawn marker), RSS, and a heartbeat. Registered at `session_start`, updated opportunistically at `turn_end` (piggybacked on the existing per-turn touchpoint, no new timer), and deregistered synchronously at `session_shutdown`. Pure observability substrate for now (zero dispatch/behavior change) — the groundwork later slices (cross-process LSP budget, same-root warm attach) will build on. Reads are corruption-safe (garbage/missing file ⇒ empty, never throws); writes are atomic tmp+rename. `PI_LENS_INSTANCE_REGISTRY=0` disables it entirely.
58
+ - **Slow-filesystem mode** (#462) — WSL 9p mounts (`/mnt/c/...`) measure ~1.3ms/`stat` vs ~17µs native (75x), so an unbounded synchronous tree walk (e.g. a 5,000-file project) could cost ~6.5s of stat time alone and freeze the TUI. `clients/slow-fs.ts` adds a cheap session-start probe (median of up to 15 `fs.statSync` calls under the project root) that classifies the workspace by measurement, not path shape, so it also catches drvfs/NFS/SMB rather than 9p-only. In slow-FS mode the sync `collectSourceFiles` walker clamps to a reduced 500-file cap (the async twin is unaffected), and the knip/jscpd/madge/dead-code/govulncheck/gitleaks/trivy background scans are skipped at session start with a visible notice instead of silently returning stale/empty results. Escape hatches: `PI_LENS_ALLOW_SLOW_FS_SCAN=1` disables slow-FS mode entirely; `PI_LENS_FORCE_SLOW_FS=1` forces it on for testing or when the probe under-fires; `PI_LENS_SLOW_FS_THRESHOLD_US` overrides the 500µs default. The verdict is logged to the latency log as a `slow_fs_probe` phase for dogfooding.
59
+ - **Subagent-extension compat smoke** (#476) — pi-lens's subagent-compatibility features (#473/#474/#475) were built on reverse-engineered facts about the nicobailon/pi-subagents and `@tintinweb/pi-subagents` extensions plus the pi SDK itself. A new nightly `.github/workflows/compat-smoke.yml` makes that compatibility empirical: Layer A (`scripts/compat-contracts.mjs`) npm-installs the real third-party packages and mechanically re-verifies six pinned contracts with resilient pattern matchers (`scripts/lib/compat-contracts.mjs`) against the installed source — no `pi` process, no LLM. Layer B (`scripts/compat-smoke-behavioral.mjs`) installs the packed pi-lens tarball into a real `pi` (the same mechanism `install-smoke.yml`'s `pi-load` job uses) and drives `pi --mode rpc` to assert, through pi-lens's own latency log, that subagent light mode engages under `PI_SUBAGENT_CHILD=1`, that `PI_LENS_SUBAGENT_FULL=1` overrides it off, and that zero LSP-server processes survive a graceful pi exit (the #472 orphan class). Both layers run `continue-on-error`; a failure opens/refreshes a single tracking issue rather than reddening the nightly. `docs/subagent-compat.md` records the exact pinned contracts (file + version last verified) and the three env levers (`PI_LENS_SUBAGENT_FULL`, `PI_LENS_CONCURRENT_SESSION_GUARD`, `PI_LENS_INSTANCE_REGISTRY`).
60
+
61
+ ### Changed
62
+
63
+ ### Fixed
64
+
65
+ - **Orphaned LSP server processes no longer survive abnormal session exit** (#472) — the #234 teardown constraint (no child spawn during `session_shutdown`, else libuv aborts) only covers CLEAN shutdown; a crashed/hard-killed/OOM'd session never runs teardown, and Windows does not kill children when a parent dies, so the whole LSP fleet could leak (7 orphaned ast-grep pairs found in the wild, up to 13 days old, ~700MB). `killProcessTree`'s `processExiting` branch only ever killed the DIRECT child (for shell/`.cmd`-wrapped servers that's the wrapper, not the real server) — its comment claiming Windows grandchildren "are reaped by the OS as the host exits" was false and is now corrected in place (behavior unchanged: still direct-child-only, per #234). The real fix is the #449 instance registry's orphan reaper: every LSP child (core and auxiliary, uniformly, at the shared `clients/lsp/launch.ts`/`client.ts` spawn/kill seam — no per-server special casing) is now recorded with its pid, resolved command, and — when the launch args carry a temp-config-style value (e.g. ast-grep's `--config <tmp sgconfig path>`) — a per-spawn-unique marker for command-line re-identification when the pid chain is broken (the synthesized baseline sgconfig now embeds the owning pid in its filename — `baseline-<pid>.sgconfig.yml`, with age-based cleanup of stale siblings — so the marker really is unique per instance; the previous shared `baseline.sgconfig.yml` would have made the marker fallback match every live ast-grep on the machine). `clients/instance-reaper.ts` sweeps at every `session_start`: a pure `decideOrphanReaping` function (conservative liveness — ESRCH-only counts as dead, EPERM/ambiguous never does; markers claimed by any live instance are never search-killed; pid kills are identity-verified against a batched command-line lookup so a recycled pid is never killed blind) decides what to kill, and an impure `sweepOrphans` executes it (`taskkill /F /T` on Windows, process-group kill on POSIX) plus a marker-based `Get-CimInstance` command-line search fallback for the broken-pid-chain case. Also resolves ast-grep's platform-native exe directly (`resolveAstGrepNativeExe`, `@ast-grep/cli-<platform>-<arch>` packages) ahead of the node-bin-wrapper candidate — one less orphanable process layer.
66
+ - **Concurrent in-process subagent binds no longer tear down the parent's LSP fleet/runtime state (#473)** — extensions that build a fresh `AgentSession` and call `session.bindExtensions()` *inside the same Node process* as the parent pi session (tintinweb/pi-subagents-style) reuse pi's process-global extension-loader cache, so the subagent's `session_start` re-invoked pi-lens's SAME module-scope singletons the parent was still using — `resetLSPService({fast:true})` killed every live LSP client and `runtime.resetForSession()` bumped the session generation, silently orphaning the parent's in-flight continuations (parked cascades, diagnostics waits) mid-turn, with no visible error. New `clients/session-lifecycle.ts` classifies each `session_start`/`session_shutdown` as `primary` / `sequential-replacement` / `concurrent-secondary` by probing whether the previously-registered ctx is still active (an SDK-wrapped accessor throws pi's own stale-ctx error only for real sequential replacement — `newSession`/`fork`/`switchSession`/`reload` — never for a concurrently-live sibling). A `concurrent-secondary` session_start now skips `handleSessionStart` and the runtime-identity update entirely and rides the already-initialized shared infra; its later shutdown skips the destructive teardown too. Classification is fail-safe: any inconclusive signal (probe failure, no prior session) falls back to today's full-reset behavior, and `PI_LENS_CONCURRENT_SESSION_GUARD=0` disables the guard outright. Zero behavior change for the common single-session process.
67
+ - **Review-graph snapshot persist is now atomic (tmp + rename)** — the debounced cache write went straight to `review-graph.json` with a plain `fs.writeFile`, so a concurrent reader (another process's blind load, or the tier-2 disk load under CI's parallel test runners) could observe a created-but-partially-written file, fail the JSON parse, and silently fall open to a full whole-repo rebuild. The write now lands in a `.tmp-<pid>` sibling and is renamed into place (atomic on POSIX and Windows), so a snapshot either doesn't exist yet or is complete; the process-exit flush uses the same pattern. This was the flaky `expected 'cached' to be 'full'` CI failure in the #300 git-stamp tests.
68
+ - **`servercapabilities.md` merge guard survives schema changes and merges bullet sections (#469)** — the #390 nightly merge guard for `scripts/server-capabilities.mjs` required the prior and freshly-generated table headers to be byte-identical before merging, so adding the `ws-pull` column silently disabled the guard and last night's ubuntu-only run dropped the rust and php rows (and their capability-key / executeCommand bullets) entirely. Fixed by reshaping prior rows onto the new header **by column name** (`reshapeRowsByName` in `scripts/lib/md-matrix.mjs` — columns the prior doc lacked are filled with the `·` placeholder, columns dropped from the new schema are simply not carried) and by merging the two bulleted sections ("Raw advertised capability keys", "Advertised executeCommand allowlists") for preserved servers, which the original guard never touched at all (`parseBulletSection`/`mergeBulletSection`). The whole merge is now a pure, unit-tested function (`mergeServerCapabilitiesDoc`) that never spawns an LSP server and fails open (writes the fresh doc, logs to stderr) if either doc's table is unparseable.
69
+ - **pi-lens loads under pi's Bun-compiled binary again** (#335) — pi ships as a `bun build --compile` single-file executable and loads extensions inside that embedded runtime, whose module resolver does not traverse the extension's on-disk `node_modules` for a bare specifier. So a static `import { minimatch } from "minimatch"` in `file-utils.js` (and every other third-party bare import) failed with `Cannot find package`, dropping the jscpd/todo/complexity analyzers into degraded mode. `dist/index.js` is now bundled into one self-contained file (`scripts/bundle-dist.mjs`, wired into `build:dist`) that inlines the pure-JS deps (minimatch, js-yaml, vscode-jsonrpc and transitives), so nothing is imported by bare specifier at load time. Host-provided packages (typebox, `@earendil-works/pi-coding-agent`, `@earendil-works/pi-tui`) and the native/wasm packages (`@ast-grep/napi`, web-tree-sitter) stay external; the two lazy native/wasm accessors resolve to an absolute path via `createRequire` before dynamic-importing (a bare specifier fails under the compiled host, an absolute path does not), with the bare specifier kept as a fallback for other runtimes. esbuild is run through `node <npm-cli> exec` (shell-free, not the `npx.cmd` shim), installing into npm's cache rather than the project tree, so the build adds no dependency and works on a from-source `--omit=dev` install; a `createRequire` banner is prepended so the bundled CJS deps load under pure-ESM Node. The reporter's Windows junction workaround does not port to the Linux compiled host, so bundling is the cross-platform fix.
70
+
13
71
  ## [3.8.67] - 2026-07-09
14
72
 
15
73
  ### Added
package/README.md CHANGED
@@ -16,6 +16,8 @@ pi-lens gives AI coding agents fast, language-aware feedback while they write/ed
16
16
  - Agent-facing tools for LSP navigation/diagnostics, AST search/replace,
17
17
  diagnostics state, and project intelligence
18
18
  - Review-graph intelligence for supported languages via bundled tree-sitter WASMs
19
+ - Ranked identifier search (`symbol_search`) over an always-warm word index,
20
+ feeding the discovery funnel (symbol_search → module_report → read_symbol)
19
21
  - Read-guard and edit-autopatch support to reduce bad edits
20
22
  - Background security/dependency scans for opted-in projects
21
23
 
@@ -37,6 +39,8 @@ pi install git:github.com/apmantza/pi-lens
37
39
  - [Usage guide](docs/usage.md) — lifecycle, tool behavior, MCP notes, and
38
40
  troubleshooting
39
41
  - [Features](docs/features.md) — detailed feature reference
42
+ - [Word index](docs/word-index.md) — identifier search (`symbol_search`) and
43
+ the discovery funnel
40
44
  - [Tools and commands](docs/tools.md) — runtime flags and slash commands
41
45
  - [Configuration](docs/globalconfig.md) — global and project config files
42
46
  - [Environment variables](docs/env_variables.md) — common env vars and full
@@ -62,98 +66,113 @@ Thanks goes to these wonderful people:
62
66
  <!-- markdownlint-disable -->
63
67
  <table>
64
68
  <tbody>
65
- <tr>
66
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/wastedC"><img src="https://avatars.githubusercontent.com/u/917574?v=4" width="100px;" alt=""/><br /><sub><b>wastedC</b></sub></a><br /><a href="#code-wastedC" title="Code">💻</a> <a href="#ideas-wastedC" title="Ideas & Planning">🤔</a> <a href="#maintenance-wastedC" title="Maintenance">🚧</a> <a href="#review-wastedC" title="Reviewed Pull Requests">👀</a></td>
69
+ <tr>
67
70
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/apmantza"><img src="https://avatars.githubusercontent.com/u/247365598?v=4" width="100px;" alt=""/><br /><sub><b>Apostolos Mantzaris</b></sub></a><br /><a href="#code-apmantza" title="Code">💻</a> <a href="#doc-apmantza" title="Documentation">📖</a> <a href="#ideas-apmantza" title="Ideas & Planning">🤔</a> <a href="#maintenance-apmantza" title="Maintenance">🚧</a> <a href="#review-apmantza" title="Reviewed Pull Requests">👀</a></td>
68
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/apps/dependabot"><img src="https://avatars.githubusercontent.com/in/29110?v=4" width="100px;" alt=""/><br /><sub><b>Dependabot</b></sub></a><br /><a href="#maintenance-dependabot[bot]" title="Maintenance">🚧</a></td>
69
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/silvanshade"><img src="https://avatars.githubusercontent.com/u/11022302?v=4" width="100px;" alt=""/><br /><sub><b>silvanshade</b></sub></a><br /><a href="#code-silvanshade" title="Code">💻</a></td>
70
71
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/anh-chu"><img src="https://avatars.githubusercontent.com/u/34973633?v=4" width="100px;" alt=""/><br /><sub><b>Anh Chu</b></sub></a><br /><a href="#code-anh-chu" title="Code">💻</a></td>
71
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/Istar-Eldritch"><img src="https://avatars.githubusercontent.com/u/3746468?v=4" width="100px;" alt=""/><br /><sub><b>Ruben Paz</b></sub></a><br /><a href="#code-Istar-Eldritch" title="Code">💻</a> <a href="#bug-Istar-Eldritch" title="Bug reports">🐛</a></td>
72
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/loss-and-quick"><img src="https://avatars.githubusercontent.com/u/39405619?v=4" width="100px;" alt=""/><br /><sub><b>minicx</b></sub></a><br /><a href="#code-loss-and-quick" title="Code">💻</a></td>
73
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/silvanshade"><img src="https://avatars.githubusercontent.com/u/11022302?v=4" width="100px;" alt=""/><br /><sub><b>silvanshade</b></sub></a><br /><a href="#code-silvanshade" title="Code">💻</a></td>
72
74
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/tifandotme"><img src="https://avatars.githubusercontent.com/u/33323177?v=4" width="100px;" alt=""/><br /><sub><b>Tifan Dwi Avianto</b></sub></a><br /><a href="#code-tifandotme" title="Code">💻</a></td>
73
- </tr><br />
75
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/amit-gshe"><img src="https://avatars.githubusercontent.com/u/7383028?v=4" width="100px;" alt=""/><br /><sub><b>Amit</b></sub></a><br /><a href="#code-amit-gshe" title="Code">💻</a> <a href="#bug-amit-gshe" title="Bug reports">🐛</a></td>
76
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/fractiunate"><img src="https://avatars.githubusercontent.com/u/78024279?v=4" width="100px;" alt=""/><br /><sub><b>Fractiunate // David Rahäuser</b></sub></a><br /><a href="#code-fractiunate" title="Code">💻</a></td>
77
+ </tr>
78
+ <tr>
79
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/vkarasen"><img src="https://avatars.githubusercontent.com/u/7932536?v=4" width="100px;" alt=""/><br /><sub><b>Vitali Karasenko</b></sub></a><br /><a href="#code-vkarasen" title="Code">💻</a></td>
80
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/rfxlamia"><img src="https://avatars.githubusercontent.com/u/222023708?v=4" width="100px;" alt=""/><br /><sub><b>V</b></sub></a><br /><a href="#code-rfxlamia" title="Code">💻</a></td>
81
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/fabio-dee"><img src="https://avatars.githubusercontent.com/u/10808443?v=4" width="100px;" alt=""/><br /><sub><b>Fabio Dee</b></sub></a><br /><a href="#code-fabio-dee" title="Code">💻</a></td>
82
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/moj02090"><img src="https://avatars.githubusercontent.com/u/57255166?v=4" width="100px;" alt=""/><br /><sub><b>moj02090</b></sub></a><br /><a href="#code-moj02090" title="Code">💻</a></td>
83
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/mehalter"><img src="https://avatars.githubusercontent.com/u/1591837?v=4" width="100px;" alt=""/><br /><sub><b>Micah Halter</b></sub></a><br /><a href="#code-mehalter" title="Code">💻</a></td>
84
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/JulienEllie"><img src="https://avatars.githubusercontent.com/u/670518?v=4" width="100px;" alt=""/><br /><sub><b>Julien Ellie</b></sub></a><br /><a href="#code-JulienEllie" title="Code">💻</a></td>
85
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/Istar-Eldritch"><img src="https://avatars.githubusercontent.com/u/3746468?v=4" width="100px;" alt=""/><br /><sub><b>Ruben Paz</b></sub></a><br /><a href="#code-Istar-Eldritch" title="Code">💻</a> <a href="#bug-Istar-Eldritch" title="Bug reports">🐛</a></td>
86
+ </tr>
74
87
  <tr>
75
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/ricardoraposo"><img src="https://avatars.githubusercontent.com/u/50217712?v=4" width="100px;" alt=""/><br /><sub><b>Ricardo Raposo</b></sub></a><br /><a href="#code-ricardoraposo" title="Code">💻</a></td>
76
88
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/cjunxiang"><img src="https://avatars.githubusercontent.com/u/26619858?v=4" width="100px;" alt=""/><br /><sub><b>C.Junxiang</b></sub></a><br /><a href="#code-cjunxiang" title="Code">💻</a> <a href="#bug-cjunxiang" title="Bug reports">🐛</a></td>
77
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/chenxin-yan"><img src="https://avatars.githubusercontent.com/u/71162231?v=4" width="100px;" alt=""/><br /><sub><b>Chenxin Yan</b></sub></a><br /><a href="#code-chenxin-yan" title="Code">💻</a> <a href="#doc-chenxin-yan" title="Documentation">📖</a> <a href="#bug-chenxin-yan" title="Bug reports">🐛</a></td>
78
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/feoh"><img src="https://avatars.githubusercontent.com/u/330070?v=4" width="100px;" alt=""/><br /><sub><b>Chris Patti</b></sub></a><br /><a href="#code-feoh" title="Code">💻</a> <a href="#bug-feoh" title="Bug reports">🐛</a></td>
79
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/fractiunate"><img src="https://avatars.githubusercontent.com/u/78024279?v=4" width="100px;" alt=""/><br /><sub><b>Fractiunate // David Rahäuser</b></sub></a><br /><a href="#code-fractiunate" title="Code">💻</a></td>
80
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/bloodf"><img src="https://avatars.githubusercontent.com/u/1626923?v=4" width="100px;" alt=""/><br /><sub><b>Heitor Ramon Ribeiro</b></sub></a><br /><a href="#code-bloodf" title="Code">💻</a></td>
81
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/JayceFreeman"><img src="https://avatars.githubusercontent.com/u/92962110?v=4" width="100px;" alt=""/><br /><sub><b>JayceFreeman</b></sub></a><br /><a href="#code-JayceFreeman" title="Code">💻</a></td>
82
- </tr><br />
89
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/trvon"><img src="https://avatars.githubusercontent.com/u/6031322?v=4" width="100px;" alt=""/><br /><sub><b>Trevon</b></sub></a><br /><a href="#code-trvon" title="Code">💻</a></td>
90
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/cmptr"><img src="https://avatars.githubusercontent.com/u/32746889?v=4" width="100px;" alt=""/><br /><sub><b>Aaron Bell</b></sub></a><br /><a href="#code-cmptr" title="Code">💻</a> <a href="#bug-cmptr" title="Bug reports">🐛</a></td>
91
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/StartupBros"><img src="https://avatars.githubusercontent.com/u/16693591?v=4" width="100px;" alt=""/><br /><sub><b>Will Mitchell</b></sub></a><br /><a href="#code-StartupBros" title="Code">💻</a></td>
92
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/spyrosbazios"><img src="https://avatars.githubusercontent.com/u/37960233?v=4" width="100px;" alt=""/><br /><sub><b>spyrosbazios</b></sub></a><br /><a href="#code-spyrosbazios" title="Code">💻</a></td>
93
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/Roman-Galeev"><img src="https://avatars.githubusercontent.com/u/40388226?v=4" width="100px;" alt=""/><br /><sub><b>Roman Galeev</b></sub></a><br /><a href="#code-Roman-Galeev" title="Code">💻</a></td>
94
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/jerryfan"><img src="https://avatars.githubusercontent.com/u/2540814?v=4" width="100px;" alt=""/><br /><sub><b>jerryfan</b></sub></a><br /><a href="#code-jerryfan" title="Code">💻</a></td>
95
+ </tr>
83
96
  <tr>
84
97
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/shaDmx"><img src="https://avatars.githubusercontent.com/u/91132641?v=4" width="100px;" alt=""/><br /><sub><b>Max L.</b></sub></a><br /><a href="#code-shaDmx" title="Code">💻</a></td>
85
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/Roman-Galeev"><img src="https://avatars.githubusercontent.com/u/40388226?v=4" width="100px;" alt=""/><br /><sub><b>Roman Galeev</b></sub></a><br /><a href="#code-Roman-Galeev" title="Code">💻</a></td>
86
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/StartupBros"><img src="https://avatars.githubusercontent.com/u/16693591?v=4" width="100px;" alt=""/><br /><sub><b>Will Mitchell</b></sub></a><br /><a href="#code-StartupBros" title="Code">💻</a></td>
87
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/amit-gshe"><img src="https://avatars.githubusercontent.com/u/7383028?v=4" width="100px;" alt=""/><br /><sub><b>Amit</b></sub></a><br /><a href="#code-amit-gshe" title="Code">💻</a> <a href="#bug-amit-gshe" title="Bug reports">🐛</a></td>
98
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/radge"><img src="https://avatars.githubusercontent.com/u/129205?v=4" width="100px;" alt=""/><br /><sub><b>David Ryan</b></sub></a><br /><a href="#code-radge" title="Code">💻</a></td>
99
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/JayceFreeman"><img src="https://avatars.githubusercontent.com/u/92962110?v=4" width="100px;" alt=""/><br /><sub><b>JayceFreeman</b></sub></a><br /><a href="#code-JayceFreeman" title="Code">💻</a></td>
100
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/bloodf"><img src="https://avatars.githubusercontent.com/u/1626923?v=4" width="100px;" alt=""/><br /><sub><b>Heitor Ramon Ribeiro</b></sub></a><br /><a href="#code-bloodf" title="Code">💻</a></td>
101
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/feoh"><img src="https://avatars.githubusercontent.com/u/330070?v=4" width="100px;" alt=""/><br /><sub><b>Chris Patti</b></sub></a><br /><a href="#code-feoh" title="Code">💻</a> <a href="#bug-feoh" title="Bug reports">🐛</a></td>
102
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/chenxin-yan"><img src="https://avatars.githubusercontent.com/u/71162231?v=4" width="100px;" alt=""/><br /><sub><b>Chenxin Yan</b></sub></a><br /><a href="#code-chenxin-yan" title="Code">💻</a> <a href="#doc-chenxin-yan" title="Documentation">📖</a> <a href="#bug-chenxin-yan" title="Bug reports">🐛</a></td>
88
103
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/flowing-abyss"><img src="https://avatars.githubusercontent.com/u/98622217?v=4" width="100px;" alt=""/><br /><sub><b>flowing-abyss</b></sub></a><br /><a href="#code-flowing-abyss" title="Code">💻</a></td>
89
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/jerryfan"><img src="https://avatars.githubusercontent.com/u/2540814?v=4" width="100px;" alt=""/><br /><sub><b>jerryfan</b></sub></a><br /><a href="#code-jerryfan" title="Code">💻</a></td>
90
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/loss-and-quick"><img src="https://avatars.githubusercontent.com/u/39405619?v=4" width="100px;" alt=""/><br /><sub><b>minicx</b></sub></a><br /><a href="#code-loss-and-quick" title="Code">💻</a></td>
91
- </tr><br />
104
+ </tr>
92
105
  <tr>
93
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/spyrosbazios"><img src="https://avatars.githubusercontent.com/u/37960233?v=4" width="100px;" alt=""/><br /><sub><b>spyrosbazios</b></sub></a><br /><a href="#code-spyrosbazios" title="Code">💻</a></td>
94
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/trvon"><img src="https://avatars.githubusercontent.com/u/6031322?v=4" width="100px;" alt=""/><br /><sub><b>Trevon</b></sub></a><br /><a href="#code-trvon" title="Code">💻</a></td>
106
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/xuli500177"><img src="https://avatars.githubusercontent.com/u/62830942?v=4" width="100px;" alt=""/><br /><sub><b>Xu Yili</b></sub></a><br /><a href="#bug-xuli500177" title="Bug reports">🐛</a></td>
107
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/ricardoraposo"><img src="https://avatars.githubusercontent.com/u/50217712?v=4" width="100px;" alt=""/><br /><sub><b>Ricardo Raposo</b></sub></a><br /><a href="#code-ricardoraposo" title="Code">💻</a></td>
108
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/jsmestad"><img src="https://avatars.githubusercontent.com/u/16150?v=4" width="100px;" alt=""/><br /><sub><b>Justin Smestad</b></sub></a><br /><a href="#code-jsmestad" title="Code">💻</a></td>
109
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/anasalsbey-glitch"><img src="https://avatars.githubusercontent.com/u/283609702?v=4" width="100px;" alt=""/><br /><sub><b>anasalsbey-glitch</b></sub></a><br /><a href="#code-anasalsbey-glitch" title="Code">💻</a> <a href="#bug-anasalsbey-glitch" title="Bug reports">🐛</a></td>
110
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/PeraSite"><img src="https://avatars.githubusercontent.com/u/19837403?v=4" width="100px;" alt=""/><br /><sub><b>정제훈</b></sub></a><br /><a href="#code-PeraSite" title="Code">💻</a></td>
95
111
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/carolitascl"><img src="https://avatars.githubusercontent.com/u/26188349?v=4" width="100px;" alt=""/><br /><sub><b>Carolina</b></sub></a><br /><a href="#bug-carolitascl" title="Bug reports">🐛</a></td>
96
112
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/leakedby"><img src="https://avatars.githubusercontent.com/u/4213260?v=4" width="100px;" alt=""/><br /><sub><b>LeakedBy</b></sub></a><br /><a href="#bug-leakedby" title="Bug reports">🐛</a></td>
113
+ </tr>
114
+ <tr>
97
115
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/mdbooth"><img src="https://avatars.githubusercontent.com/u/1318691?v=4" width="100px;" alt=""/><br /><sub><b>Matthew Booth</b></sub></a><br /><a href="#bug-mdbooth" title="Bug reports">🐛</a></td>
98
116
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/Wiedzmin89"><img src="https://avatars.githubusercontent.com/u/61706855?v=4" width="100px;" alt=""/><br /><sub><b>Wiedzmin89</b></sub></a><br /><a href="#ideas-Wiedzmin89" title="Ideas & Planning">🤔</a></td>
99
117
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/grodingo"><img src="https://avatars.githubusercontent.com/u/244184972?v=4" width="100px;" alt=""/><br /><sub><b>Virgile</b></sub></a><br /><a href="#bug-grodingo" title="Bug reports">🐛</a></td>
100
- </tr><br />
101
- <tr>
102
118
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/finnvyrn"><img src="https://avatars.githubusercontent.com/u/90801772?v=4" width="100px;" alt=""/><br /><sub><b>Finn</b></sub></a><br /><a href="#ideas-finnvyrn" title="Ideas & Planning">🤔</a></td>
103
119
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/ywh555hhh"><img src="https://avatars.githubusercontent.com/u/121592812?v=4" width="100px;" alt=""/><br /><sub><b>Wayne E</b></sub></a><br /><a href="#bug-ywh555hhh" title="Bug reports">🐛</a></td>
104
120
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/RimuruW"><img src="https://avatars.githubusercontent.com/u/59136309?v=4" width="100px;" alt=""/><br /><sub><b>RimuruW</b></sub></a><br /><a href="#ideas-RimuruW" title="Ideas & Planning">🤔</a></td>
105
121
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/Bjynt"><img src="https://avatars.githubusercontent.com/u/22177300?v=4" width="100px;" alt=""/><br /><sub><b>Bjynt</b></sub></a><br /><a href="#bug-Bjynt" title="Bug reports">🐛</a> <a href="#ideas-Bjynt" title="Ideas & Planning">🤔</a></td>
122
+ </tr>
123
+ <tr>
106
124
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/pvtri96"><img src="https://avatars.githubusercontent.com/u/28696888?v=4" width="100px;" alt=""/><br /><sub><b>Tri Van Pham</b></sub></a><br /><a href="#bug-pvtri96" title="Bug reports">🐛</a></td>
107
125
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/BingWuJ"><img src="https://avatars.githubusercontent.com/u/117666511?v=4" width="100px;" alt=""/><br /><sub><b>BingWuJ</b></sub></a><br /><a href="#bug-BingWuJ" title="Bug reports">🐛</a></td>
108
126
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/tackleberrybey"><img src="https://avatars.githubusercontent.com/u/156954032?v=4" width="100px;" alt=""/><br /><sub><b>tackleberrybey</b></sub></a><br /><a href="#bug-tackleberrybey" title="Bug reports">🐛</a></td>
109
- </tr><br />
110
- <tr>
111
127
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/TaterDoge"><img src="https://avatars.githubusercontent.com/u/121467933?v=4" width="100px;" alt=""/><br /><sub><b>Mariann Abshire</b></sub></a><br /><a href="#bug-TaterDoge" title="Bug reports">🐛</a></td>
112
128
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/ProbabilityEngineer"><img src="https://avatars.githubusercontent.com/u/38498804?v=4" width="100px;" alt=""/><br /><sub><b>ProbabilityEngineer</b></sub></a><br /><a href="#ideas-ProbabilityEngineer" title="Ideas & Planning">🤔</a></td>
113
129
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/LeonardoRick"><img src="https://avatars.githubusercontent.com/u/17517057?v=4" width="100px;" alt=""/><br /><sub><b>Leonardo Rick</b></sub></a><br /><a href="#bug-LeonardoRick" title="Bug reports">🐛</a> <a href="#ideas-LeonardoRick" title="Ideas & Planning">🤔</a></td>
114
130
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/kenbanks-peng"><img src="https://avatars.githubusercontent.com/u/26904200?v=4" width="100px;" alt=""/><br /><sub><b>Ken Banks</b></sub></a><br /><a href="#bug-kenbanks-peng" title="Bug reports">🐛</a></td>
131
+ </tr>
132
+ <tr>
115
133
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/rstacruz"><img src="https://avatars.githubusercontent.com/u/74385?v=4" width="100px;" alt=""/><br /><sub><b>Rico Sta. Cruz</b></sub></a><br /><a href="#ideas-rstacruz" title="Ideas & Planning">🤔</a></td>
116
134
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/acidnik"><img src="https://avatars.githubusercontent.com/u/1227955?v=4" width="100px;" alt=""/><br /><sub><b>Nikita Bilous</b></sub></a><br /><a href="#bug-acidnik" title="Bug reports">🐛</a></td>
117
135
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/av1155"><img src="https://avatars.githubusercontent.com/u/117413846?v=4" width="100px;" alt=""/><br /><sub><b>Andrea Arturo Venti Fuentes</b></sub></a><br /><a href="#bug-av1155" title="Bug reports">🐛</a></td>
118
- </tr><br />
119
- <tr>
120
136
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/dacec354"><img src="https://avatars.githubusercontent.com/u/90093629?v=4" width="100px;" alt=""/><br /><sub><b>dacec354</b></sub></a><br /><a href="#bug-dacec354" title="Bug reports">🐛</a></td>
121
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/xuli500177"><img src="https://avatars.githubusercontent.com/u/62830942?v=4" width="100px;" alt=""/><br /><sub><b>Xu Yili</b></sub></a><br /><a href="#bug-xuli500177" title="Bug reports">🐛</a></td>
122
137
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/nodnarbnitram"><img src="https://avatars.githubusercontent.com/u/44812862?v=4" width="100px;" alt=""/><br /><sub><b>Brandon Martin</b></sub></a><br /><a href="#bug-nodnarbnitram" title="Bug reports">🐛</a></td>
123
138
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/alpertarhan"><img src="https://avatars.githubusercontent.com/u/50966980?v=4" width="100px;" alt=""/><br /><sub><b>Alper Tarhan</b></sub></a><br /><a href="#bug-alpertarhan" title="Bug reports">🐛</a></td>
124
139
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/asolopovas"><img src="https://avatars.githubusercontent.com/u/6893216?v=4" width="100px;" alt=""/><br /><sub><b>Andrius Solopovas</b></sub></a><br /><a href="#bug-asolopovas" title="Bug reports">🐛</a></td>
140
+ </tr>
141
+ <tr>
125
142
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/GrahamJenkins"><img src="https://avatars.githubusercontent.com/u/6607975?v=4" width="100px;" alt=""/><br /><sub><b>Graham Jenkins</b></sub></a><br /><a href="#bug-GrahamJenkins" title="Bug reports">🐛</a></td>
126
143
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/wings1848"><img src="https://avatars.githubusercontent.com/u/120104016?v=4" width="100px;" alt=""/><br /><sub><b>Wings Butterfly</b></sub></a><br /><a href="#bug-wings1848" title="Bug reports">🐛</a></td>
127
- </tr><br />
128
- <tr>
129
144
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/JohannesKlauss"><img src="https://avatars.githubusercontent.com/u/6214415?v=4" width="100px;" alt=""/><br /><sub><b>Johannes Klauss</b></sub></a><br /><a href="#ideas-JohannesKlauss" title="Ideas & Planning">🤔</a></td>
130
145
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/tan-yong-sheng"><img src="https://avatars.githubusercontent.com/u/64836390?v=4" width="100px;" alt=""/><br /><sub><b>Tan Yong Sheng</b></sub></a><br /><a href="#ideas-tan-yong-sheng" title="Ideas & Planning">🤔</a></td>
131
146
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/fmatray"><img src="https://avatars.githubusercontent.com/u/8267716?v=4" width="100px;" alt=""/><br /><sub><b>Frédéric</b></sub></a><br /><a href="#bug-fmatray" title="Bug reports">🐛</a></td>
132
147
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/fuentesjr"><img src="https://avatars.githubusercontent.com/u/9240?v=4" width="100px;" alt=""/><br /><sub><b>Salvador Fuentes Jr</b></sub></a><br /><a href="#bug-fuentesjr" title="Bug reports">🐛</a></td>
133
148
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/Viterkim"><img src="https://avatars.githubusercontent.com/u/17838985?v=4" width="100px;" alt=""/><br /><sub><b>Viktor</b></sub></a><br /><a href="#bug-Viterkim" title="Bug reports">🐛</a></td>
149
+ </tr>
150
+ <tr>
134
151
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/ortonomy"><img src="https://avatars.githubusercontent.com/u/6688676?v=4" width="100px;" alt=""/><br /><sub><b>Gregory Orton</b></sub></a><br /><a href="#bug-ortonomy" title="Bug reports">🐛</a></td>
135
152
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/jimallen"><img src="https://avatars.githubusercontent.com/u/868773?v=4" width="100px;" alt=""/><br /><sub><b>Jim Allen</b></sub></a><br /><a href="#bug-jimallen" title="Bug reports">🐛</a></td>
136
- </tr><br />
137
- <tr>
138
153
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/quantfiction"><img src="https://avatars.githubusercontent.com/u/49965454?v=4" width="100px;" alt=""/><br /><sub><b>quantfiction</b></sub></a><br /><a href="#bug-quantfiction" title="Bug reports">🐛</a></td>
139
154
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/Artawower"><img src="https://avatars.githubusercontent.com/u/16963833?v=4" width="100px;" alt=""/><br /><sub><b>Art</b></sub></a><br /><a href="#bug-Artawower" title="Bug reports">🐛</a></td>
140
155
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/chozandrias76"><img src="https://avatars.githubusercontent.com/u/2087677?v=4" width="100px;" alt=""/><br /><sub><b>Colin Swenson-Healey</b></sub></a><br /><a href="#bug-chozandrias76" title="Bug reports">🐛</a></td>
141
156
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/JustlyAI"><img src="https://avatars.githubusercontent.com/u/12634140?v=4" width="100px;" alt=""/><br /><sub><b>Laurent Wiesel</b></sub></a><br /><a href="#bug-JustlyAI" title="Bug reports">🐛</a></td>
142
157
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/stark-bit"><img src="https://avatars.githubusercontent.com/u/44064758?v=4" width="100px;" alt=""/><br /><sub><b>Rei Starks</b></sub></a><br /><a href="#bug-stark-bit" title="Bug reports">🐛</a></td>
158
+ </tr>
159
+ <tr>
143
160
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/robertoecf"><img src="https://avatars.githubusercontent.com/u/54923863?v=4" width="100px;" alt=""/><br /><sub><b>Roberto Freitas</b></sub></a><br /><a href="#bug-robertoecf" title="Bug reports">🐛</a></td>
144
161
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/tmttodd"><img src="https://avatars.githubusercontent.com/u/160077416?v=4" width="100px;" alt=""/><br /><sub><b>tmttodd</b></sub></a><br /><a href="#bug-tmttodd" title="Bug reports">🐛</a></td>
145
- </tr><br />
146
- <tr>
147
162
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/smaharnav"><img src="https://avatars.githubusercontent.com/u/263328627?v=4" width="100px;" alt=""/><br /><sub><b>smaharnav</b></sub></a><br /><a href="#bug-smaharnav" title="Bug reports">🐛</a></td>
148
163
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/MzaGuille"><img src="https://avatars.githubusercontent.com/u/151482698?v=4" width="100px;" alt=""/><br /><sub><b>Mza.Guille</b></sub></a><br /><a href="#bug-MzaGuille" title="Bug reports">🐛</a></td>
149
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/cmptr"><img src="https://avatars.githubusercontent.com/u/32746889?v=4" width="100px;" alt=""/><br /><sub><b>Aaron Bell</b></sub></a><br /><a href="#code-cmptr" title="Code">💻</a> <a href="#bug-cmptr" title="Bug reports">🐛</a></td>
150
164
  <td align="center" valign="top" width="14.28%"><a href="http://jasonrimmer.com/"><img src="https://avatars.githubusercontent.com/u/629?v=4" width="100px;" alt=""/><br /><sub><b>Jason Rimmer</b></sub></a><br /><a href="#bug-jrimmer" title="Bug reports">🐛</a></td>
151
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/moj02090"><img src="https://avatars.githubusercontent.com/u/57255166?v=4" width="100px;" alt=""/><br /><sub><b>moj02090</b></sub></a><br /><a href="#code-moj02090" title="Code">💻</a></td>
165
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/ArthurHeymans"><img src="https://avatars.githubusercontent.com/u/15137817?v=4" width="100px;" alt=""/><br /><sub><b>Arthur Heymans</b></sub></a><br /><a href="#ideas-ArthurHeymans" title="Ideas & Planning">🤔</a></td>
166
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/Blue-B"><img src="https://avatars.githubusercontent.com/u/55532956?v=4" width="100px;" alt=""/><br /><sub><b>Blue-B</b></sub></a><br /><a href="#bug-Blue-B" title="Bug reports">🐛</a></td>
152
167
  </tr>
153
- </tbody>
168
+ <tr>
169
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/bisko"><img src="https://avatars.githubusercontent.com/u/184938?v=4" width="100px;" alt=""/><br /><sub><b>Biser Perchinkov</b></sub></a><br /><a href="#bug-bisko" title="Bug reports">🐛</a></td>
170
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/xificurC"><img src="https://avatars.githubusercontent.com/u/4800719?v=4" width="100px;" alt=""/><br /><sub><b>Peter Nagy</b></sub></a><br /><a href="#bug-xificurC" title="Bug reports">🐛</a></td>
171
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/orest-tokovenko-block"><img src="https://avatars.githubusercontent.com/u/190554467?v=4" width="100px;" alt=""/><br /><sub><b>Orest Tokovenko</b></sub></a><br /><a href="#bug-orest-tokovenko-block" title="Bug reports">🐛</a></td>
172
+ </tr>
173
+ </tbody>
154
174
  </table>
155
175
 
156
-
157
176
  <!-- markdownlint-restore -->
158
177
  <!-- prettier-ignore-end -->
159
178