Added
- A unified file I/O lifecycle bridge for extensions (refs #3654)
- Opt-in one-line widget summary (
ui.compactWidget,--lens-compact-widget) (refs #3959) - Verify archive-installed language servers by a spawn-free manifest and add a managed Kotlin Language Server. (refs #3400)
- Shell dialect ownership: dialect-aware ShellCheck runner, builtin shuck, and
lsp.servers.<id>.covers(closes #3968)
Changed
- Host pi-agent staging directories no longer start LSP servers, and real git checkouts under the temporary directory keep full analysis with a faster LSP idle teardown while their project data lives in a per-process directory that is removed when the process exits.
@ast-grep/cliis pinned to an exact version, and the install diagnostics find the bundled grammars (refs #1185)- Idle pyright, marksman and opengrep servers release their memory (refs #1332)
- Declare the
@earendil-works/pi-tuipeer as"*", like pi-coding-agent (refs #2682, #3805) - The npm package no longer ships the extension twice. (refs #3219)
- Bump the jscpd installer pin to 5.3.2 so it matches the devDependency.
- Idle eviction is named for every server, with a generic window variable (refs #3645)
- Append context injection to the active user prompt to preserve KV cache (refs #3693)
- pi 1.0.x and 1.1.x are now verified release-QA hosts, and the published pi-tui peer no longer excludes pi 1.x; the certified host window is
>=0.80.10 <1.2.0. - Bump the jscpd installer pin to 5.4.0 so it matches the devDependency (refs #3805).
latency.lognow carries oneturn_end_test_selectionrecord per turn that edited files: how many test targets were selected, why the rest were not (no test file, excluded, missing, over the cap), and the count per owning checkout (.for the session checkout,.worktrees/xfor a linked worktree), so a turn that ran 0 tests can be explained after the fact (#3871).latency.lognow records the session-scope decisions that left no trace:session_handoff_slot(a hand-off slot stashed, replaced, taken, left for another start, forwarded or never consumed),session_handoff_adopt(the candidates a start tried and why each fell through),session_store_action(each store's adopt, reset or skip with items in and kept),session_scope_transitionendanddemote, andsession_end_fence_rollup(writes each generation fence admitted or dropped);read_guard_branch_retainedgainspayloadReads,session_start_totalgainsbasis,gapMsandlineageMatch,agent_nudgegainsfileKeys,originSessionIds,scopeIdandqueueEpoch, a no-clientlsp_touch_filegainscandidates, and a formatter give-up writes oneformat_late_resync_chainedrow per file (#3873).- The official Docker Language Server is now a built-in fallback (refs #3939)
- Ten more language servers release idle memory (refs #3952)
- Document a V8 heap ceiling for long pi sessions (refs #1999)
Fixed
- Skills you exclude with
packages[].skillsstay excluded (refs #1416) - A file with no project marker (no
package.json,Cargo.tomland the like above it) no longer re-walks every ancestor directory on each lookup whenever any of them changes. (refs #2560) - Global-only project config notices no longer suggest a CLI flag for settings without one.
- Say why a delta-promoted unused finding blocks (#3218)
- Name blockers resolved since the agent was last told, and stop claiming a demoted one still blocks (#3218, #3748)
- The install diagnostics block's unresolved-dependency note now names the compiled-binary cause (
bun build --compile, the way pi ships) alongside the package-manager layouts, so a user on that host is no longer sent to inspect their package manager (refs #3424). - Record terraform-ls's measured empty-first diagnostics behavior and hold its provisional first publish during indexing.
- A pi-lens session whose instance-registry entry went missing — its registration dropped because the registry lock was busy past its wait bound, or the registry file was cleared — now re-registers on its next heartbeat, using the session root it originally registered. (refs #3447)
- Clear stale incomplete re-verify markers after a confirmed observation.
- Durable-store lock no longer steals a lock that is still being created (closes #3475)
- The durable-store lock (dispositions and actionable warnings) no longer lets two sessions commit at once after a third died holding it. (refs #3476)
- The shared tools install lock no longer lets two sessions install at once after a third died holding it. (refs #3476)
- The quarantine lock (the probe cache, the tool-refresh state and the orphan backstop sweep) no longer lets two sessions in at once after a third died holding it. (refs #3476)
- The instance registry lock no longer lets two sessions write the registry at once after a third died holding the lock. (refs #3476)
- Renaming a file no longer leaves the old path open on the language server, or sends it an edit after closing it (closes #3477)
- A same-length edit in the middle of a long file now reaches the language server (closes #3480)
- An older read of a file no longer overwrites a newer write on the language server (closes #3481)
- Late auxiliary findings (a slow scanner such as opengrep answering after its grace window) are less likely to be delivered against a newer revision of the file. (refs #3482)
- Late-auxiliary coverage is marked at the touch's notify time (refs #3482)
- opengrep's save rescan and a scan queued across a rename no longer answer a later edit (refs #3482)
- A cascade run no longer loses a dependent file when the LSP idle reset lands while neighbours are still being checked. (closes #3483)
- Diagnostics from the YAML and PHP servers no longer answer for the edit before (closes #3484)
- opengrep's rule-load republish no longer answers a running scan (closes #3490)
- Ending a session no longer freezes pi for 500ms when this process's own registry write is in flight. (refs #3498)
- When pi replaces a session inside the same process (for example, resuming a session from another directory), the ended session's project root no longer stays in, or comes back into, the instance registry. (closes #3498)
- A cascade finding from the previous session is no longer delivered after
/new, a fork or a resume in the same project. (closes #3499) - A language server that crashes or is evicted between two touches of the same content no longer leaves its replacement without the file. (closes #3501)
- A language server respawned after a crash, or back from a notify-stall demotion, is no longer treated as warm, or as known cold, because its predecessor was. (closes #3502)
- A dependency or file edited while its analysis was still running now demotes the finding (closes #3503)
- A same-size rewrite of a file under a tree-sitter or ast-grep blocker now demotes the blocker (closes #3504)
- A workspace pull answer is cached only when it is tied to the content pi-lens sent (closes #3505)
- An edit that lands during a
lens_diagnostics mode=fullsweep no longer caches a stale result for the file (refs #3505) - pi-lens' autofix and formatting no longer erase, or take credit for, an edit the agent makes to the same file at the same time (closes #3506)
- A slower, older analysis of a file no longer erases or replaces the newer edit's blocker (closes #3507)
- One edit no longer runs its analysis twice when pi-lens is still starting up (closes #3508)
- A sibling process's older project snapshot no longer replaces a newer one (closes #3509)
- A second pi-lens process no longer deletes a live process's staged project snapshot (closes #3510)
- A project snapshot that missed a sibling process's edit is no longer judged fresh (closes #3511)
- A cascade finding from the previous session is no longer delivered after
/new, a fork or a resume in the same project on three remaining paths. (closes #3512) - The shared tools install lock no longer ages out under a legitimate ERESOLVE npm install (refs #3515)
- A heartbeat landing between session_start's registration and its own registry write no longer records a spurious missing-registration and re-registers redundantly. (refs #3518)
- An edit at the line numbers of an attached autofix is no longer refused or moved onto other lines (closes #3519)
- A file the agent never wrote is no longer treated as its own because its modification time is newer (closes #3520)
- The read-before-edit guard now follows the conversation across
/tree,/fork,/cloneand resume. (refs #3521) - The read guard no longer lets a stale line through when a later read only context-covers it or the edit spans two reads (closes #3522)
- Re-editing a line the agent just wrote with a positional edit is no longer refused (refs #3523)
- A write that lands while pi is reading a file is no longer credited as read (refs #3524)
- The read guard no longer credits bytes the agent never saw (refs #3525, refs #3524)
- Starting a new session while pi-lens is still formatting no longer lets the old session's formatting authorize edits in the new one (closes #3528)
- The language server no longer keeps an older copy of a file after pi-lens' end-of-run formatting (closes #3529)
- The review graph's seq fast path now records each changed file's
size:mtimeMsbefore it reads the file, not after. (refs #3535) - A forced review-graph persist (the exit hook and
pi-lens build-graph) no longer writes a snapshot generation that a newer one has already replaced. (refs #3536) - A restarted language server no longer inherits its predecessor's write timeouts (closes #3537)
- The orphan reaper no longer kills a process that merely reuses a dead session's recorded pid. (refs #3538)
- On Linux and macOS, a language server orphaned by a crashed session is reaped even after its registry record is lost. (refs #3539)
- A file's first edit in a new turn replaces its diagnostics from the previous turn (closes #3540)
- pi-lens' language-server edits no longer erase an edit the agent makes to the same file at the same time (closes #3541)
- The LSP notify queue reports only content it sent, and no longer loses a newer edit behind an older read (closes #3543, closes #3544)
- The LSP client's change path still sends a save it merged (closes #3545)
- typos' close-triggered publish no longer answers an in-flight scan, and can no longer overwrite a real answer after a reopen (refs #3548)
- The LSP client's change path marks a re-opened file open again (closes #3549)
- Give the review graph a per-run private fact store for its per-file extraction, so a concurrent dispatch for the same file can no longer make the graph extract imports and functions from two different versions, and the graph no longer writes or deletes the dispatch's
file.contentand derived facts (closes #3552). - A read pi-lens widened now says so, and the read guard's switch turns the widening off (closes #3555)
- A formatter pi-lens is still installing no longer holds the agent's edits to that file back (closes #3558)
- An analysis that finishes after its turn ended is recorded under the turn it finished in (closes #3559)
- Opening a file on a dead language server no longer marks it as in sync (closes #3564)
- A tool result that pi-lens was still analysing when a new session started no longer records its blocker, cascade, warnings or late runner findings in the new session (closes #3568)
- Scan-written widget findings go stale when their file changes mid-scan (closes #3573)
- Blockers on non-UTF-8 files no longer go stale on their own (closes #3574)
- pi-lens' end-of-run work stays in the session it started in, and starts no language server after quitting (closes #3576)
- The first logged edit after a slow session start no longer holds the change-log lock while it reads the whole log (closes #3577)
- A stuck sibling holding the change-log or snapshot cache lock costs one 500 ms wait, not one per edit (closes #3578)
- A declined secondary worktree's root no longer stays in the instance registry for the rest of a live session. (closes #3587)
- A stuck holder of the durable-store sync lock cost one 2 s wait per call, not one total (refs #3594)
- Late writes from a replaced session no longer land in the new one (closes #3596, closes #3620, closes #3709)
- An agent edit made while
cargo clippy --fixordart fix --applyruns is no longer erased (closes #3598) lens_diagnostics mode=fullno longer re-stamps folded project rows at scan time (closes #3600)lsp_navigation'srenameno longer rewrites a file that changed after the language server computed the rename, for the rename's own file and for every file the language client has open (refs #3601)- A web-tree-sitter runtime trap (
table index is out of bounds,memory access out of bounds,null function or function signature mismatch) while one file's symbols were being extracted no longer fails the whole review-graph build. (closes #3605) - Session scopes and the lineage handle (refs #3611)
- Session state follows
/reload,/fork,/clone, resume andpi --fork(closes #3612, #3653, #3604, #3589) analyzeFile's MCP result now consumes the exact latency report carried byDispatchResult, so a 100-entry ring and concurrent same-path dispatches cannot cause cross-call attribution or a foreign fallback (refs #3642, refs #3643).- Restore post-edit diagnostics for tools that spell the target
filePathorfile_path(refs #3650) - Reading an empty file no longer blocks the first edit to it (refs #3652) — a zero-line read of a genuinely empty file now authorizes the subsequent edit instead of failing with an unresolvable
zero-readblock. - After a
/tree,/fork,/newor a resume, the actionable-warning quick fix no longer lets the new branch edit a file it never read: the fix is credited only with the read-guard epoch and lineage its report entries were built under. - Keep a mutation-bridge
readGuardBranchEpochcaptured before a session reset from being credited to the new session, and record an ignored malformed value once per session. pi-lens build-graphnow prints a degraded line naming how many files a tree-sitter wasm trap cost its symbols, and reports the process-wide wasm abort that disables tree-sitter until restart, instead of the clean success line. (refs #3678)- A process-wide tree-sitter WASM abort now keeps review-graph cascades indeterminate until restart instead of reporting a silent all-clear.
- Automatic-test checkout ownership no longer lets a deleted test hide a real identity failure (refs #3691)
- Preserve fixed diagnostics when long paths are truncated in degradation ledger reasons.
- Keep degradation diagnostics and remedies visible when recorded paths exceed the ledger reason field limit (closes #3712).
- rust-clippy reports the execution outcome in
status(closes #3751) pilens_analyzelists every finding its counts report (closes #3752)- Work that finishes after
/newno longer lands in the next session (refs #3758, #3763) - Persisting the project snapshot no longer copies the whole object graph into the persist worker (refs #3789, reported by Renzo Oliveira)
- Every
pilens_analyzepull reports an unanalysable file (#3791) - A slow runner that could not parse its tool's output is now reported as a runner failure at turn end (refs #3796)
- An MCP call with
cwdPath,maxFiles,outFileorincludeFilesis no longer refused as a mistypedpathorfile. (refs #3809) - A subagent's own /reload or /fork no longer takes the primary's session hand-off (closes #3819)
lsp_navigation renameno longer refuses a file the language client first opened after the rename was computed (closes #3827)- A whole-package fixer's restore (
cargo clippy --fix,dart fix --apply) no longer overwrites an agent edit of a sibling file that lands while it writes: it runs under pi's queue for that file, stays registered until it ends, and compares bytes before writing. - Preserve structured bash exit results when pi-lens rewrites tool results in codemode; scripts now receive the host's raw structured output, without the notes and diagnostics pi-lens appends to the text result (#3832).
- A subagent that reloads, forks, resumes or starts a new session while the main session is between its reload, fork, resume or new-session shutdown and its next start no longer takes over as the main session. (#3855)
- turn_end now selects and runs tests for an edit in a linked worktree of the session's repository in that worktree's own root, with its own config and
node_modules(no more zero turn-end tests when the session cwd is the main checkout); a worktree failure is located relative to the session checkout, a worktree without its own runner install is skipped and counted asturn-end-test-root-skippedinstead of fetching throughnpx, independent clones and submodules stay excluded, andtest-target-foreign-checkoutrows now carrysameCommonDir(#3871) - A project whose linked worktrees are not gitignored no longer makes every turn end wait the full hook budget on a knip scan that cannot finish in it: a root whose last two scans both outlasted the budget is started but not awaited, and the knip row and a counted
turn-end-knip-nested-worktreesrecord name how many worktrees knip walked and how many files with issues were dropped from them. (#3872) - A reload that interrupts a session start no longer drops the session's activations (closes #3881)
- Review-graph snapshot keeps emoji whole (refs #3913).
- Report unrunnable
pi-lens-analyzeinvocations on stdout and inpilens_health. (#3922) lsp_diagnosticsno longer counts unchecked files as clean (closes #3954)- Fix Windows Gradle test runs failing to start a repository-local
gradlew.batwrapper. lens_diagnostics/pilens_diagnosticswithsource: "lsp"now expands a directory inpathsinto its eligible files through the same bounded walk the singlepathroute already uses, instead of reporting onefailed"not a file" outcome and dropping the whole subtree; a requested directory with no eligible file is named in the result and a request that reaches the 100-file bound says(capped at 100)(fixes #3965).read_enclosing,read_symboland structural analysis no longer fail on bash test commands like[ a == b ](refs #3996)- Pre-push targeted-test results are now retained per pushed head for fourteen days, including selection reasons and Vitest counts.
pi install git:…no longer fails under--strict-allow-scriptswhile bundling (refs #4062)guard-bashnow follows the fullnode_modulessymlink chain and fails closed when its target cannot be resolved, preventing destructive npm and delete commands from reaching a shared install (#4080).- A subagent whose new session is reloaded before pi-lens has seen it start no longer passes for the main session when it shuts down, so it cannot take over the main session's reload or fork and strip the reloaded conversation of its activated tools (#4106).
- A
/forkthat is reloaded before pi-lens has seen it start no longer loses the state a fork carries over, such as its activated tools: the reloaded fork now picks it up (#4113). - A slow Python dead-code scan no longer holds the end of a turn, and vulture and jscpd leave every linked worktree out of their scan (refs #4117)
- A turn-end that was still running when you started a new session (
/new, or a resume) no longer takes the new session's pending findings (cascade results, late scanner findings, cut advisories) and loses them; they now reach the new session's next turn. - Deliver a queued agent advisory (such as the fix-run lost-edit notice) only to the
contextcall of the session that queued it, instead of to whichever session called first. (refs #3748) - Key the tree-sitter rule batch compiles per query source and stop caching a batch built across a wasm trap: a rule whose compile always traps now spends one trap-budget unit however often its batch is rebuilt (LRU eviction, a rule edit) instead of aborting the runtime on the fourth, and one transient compile trap no longer leaves the rule skipped, or the batch on the slower per-rule path, until restart (closes #3707).
- A collect-later runner's blocking finding now gates
git commitandgit pushunder--lens-guard, before and after the turn end that delivers it, until a later edit resolves it (#3814). (#3896) - Deliver a collect-later runner's findings at turn end when it reports
failedwith diagnostics. (refs #3796) - A retired LSP client's drain-latency estimate no longer prices its replacement, and the workspace sweep no longer counts a refused write as backlog (refs #3585)
- Clamp native read offsets at 1 when recording read-guard evidence, matching pi's behavior for non-positive offsets.
- The post-exit LSP resync no longer waits forever on an abandoned formatter (closes #3599)
- A subagent started during a reload no longer demotes the main session (closes #3662)
- Probe-home redirect degradation reasons retain the redirect trigger when a long cwd reaches the ledger field cap (closes #3696).
- Runner status contract: findings-failed runs carry
failureKind: "blocking_diagnostics"(closes #3781) - A formatter that writes after the post-exit wait gave up no longer leaves an open language-server document behind the disk (closes #3828)
- A formatter that writes after the
--immediate-formatbudget gave up on it no longer leaves an open language-server document behind the disk (closes #3858) - A pull of a file whose only primary linter timed out or failed to spawn now reports the coverage notice instead of a silent empty result. (refs #3867)
- Linked-worktree test locations (refs #3871)
- A native re-read supersedes a stale bridge read (#3962)
- A language server launched by a host that cannot read its own start time no longer carries a dead foreign owner's tag (refs #3986)
- Parallel bash writes keep their blockers (closes #4137)
- A late dead-code scan stays with the session that started it, and a failed scan no longer replaces a good knip or dead-code baseline (closes #4154)
- The production dependency audit passes again: brace-expansion is updated to 5.0.12 (closes #3659)
- Run turn-end knip in the linked worktree that owns an edit instead of the session checkout, leave nested linked worktrees out of the session checkout's knip result, and stop a slow knip scan from holding the turn-end hook past its budget.
lsp_diagnosticsno longer reports "confirmed clean" for an empty result from a language server that needs a project and was given none (rust-analyzer on a.rsfile with noCargo.toml; csharp-ls, OmniSharp and FSAutocomplete likewise). (closes #3750)- Keep the LSP service generation in one per-process counter, so a reset reached through a second module evaluation retires every handle captured through the first and late work cannot build a server for a retired service (refs #3733).
- Report unknown MCP tool arguments instead of silently ignoring them: a call whose argument keys are not in the tool's advertised
inputSchema(for examplepilens_diagnostics {"filePath": ...}where the schema sayspath) now starts with anIgnored unknown argument(s) for <tool>: ...line naming the keys and the nearest valid key, carriesstructuredContent.ignoredArguments, and is counted in themcp-ignored-argumentsdegradation row. (closes #3749) - Memory samples distinguish settled heap and major native holders (closes #4123)
- Keep parallel nested tool calls apart under a long parent call id: pi 0.99 codemode names nested calls
<parent>/<n>and an OpenAI Responses parent id is about 80 characters, so the 64-character slice insanitizeCorrelationIdgave<parent>/1and<parent>/2one key, merged their path attributions and let turn_end reportcleanwhile both files held blockers. (closes #3833) - Node tool agreement now reads pnpm and yarn lockfiles (refs #3655)
- The review graph no longer loses every relative import edge when the project root is spelled through a Windows 8.3 short name, a junction or a
substdrive (or, on a case-insensitive mount, a mis-cased directory): impact cascades,directImportersand import-based call resolution keep working, and graph feature hints are derived from the project-relative path instead of an absolute one (#4101). - A subagent running in the same pi process no longer starts a new turn for the main session: its
turn_startused to advance the main session's turn and clear the code-quality and actionable warnings the main session's edits had recorded, so the main session's turn end reported none of them. - Automatic tests no longer select files from a separate Git checkout, including through filesystem aliases.
- Decay a tree-sitter input's wasm trap count when the parse, query compile or consumer that trapped later succeeds, so a one-off trap no longer disables an unchanged file for the rest of the process. (refs #3678)
- Re-offer a knip, dead-code or call-graph impact advisory on the next turn when the turn-end cap cut it, instead of losing it because its baseline or edited-file worklist was already overwritten; each item is offered once more and only while a fresh scan still reports it (#3813, #3901).
- Keep one-shot turn-end state (past-EOF retirements, dependency-drift delivery counts, cascade runs, late runner findings, late auxiliary pairs, repeated resolved retirements) pending when the turn-end cap cuts its part, so it is shown on the next turn instead of consumed unseen (#3813).
- Windows advisory failures now report the real Vitest failed-test count when the runner log contains ANSI-colored, padded output.
Plus 141 internal changes: tests, CI, tooling, and refactors.
🙏 Thanks
Thanks to the external contributors in this release:
- @AngriestBird — #3454, #3455, #3456, #3640, #3641, #3935, #3947, #3948, #3949, #3950, #3951, #3998, #3999, #4000, #4001, #4144
- @Don-Yin — #3622, #3955
- @drakeo338 — #3486, #3643, #3997
- @EightDoor — #3960
- @georgeharker — #3969
- @LucaBarrella — #3702
- @Rianico — #3656, #3933, #3934, #4145, #4147
- @thomasfsteeples — #3649