pi-lens 4.4.2 → 4.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +306 -0
- package/README.md +13 -1
- package/dist/{chunk-VITUCP5A.js → chunk-24AKHFGP.js} +9 -7
- package/dist/{chunk-JSQFTSC6.js → chunk-2FN63LPV.js} +11 -11
- package/dist/{chunk-CF24OIYI.js → chunk-2KXTHF3M.js} +20 -20
- package/dist/{chunk-ESBQHFMJ.js → chunk-3DA2JDOL.js} +543 -349
- package/dist/{chunk-RWWYJ623.js → chunk-3KAZU3VR.js} +1 -1
- package/dist/chunk-3PTHXJXR.js +28 -0
- package/dist/chunk-3QED2P4V.js +31 -0
- package/dist/{chunk-N2H3ZPZY.js → chunk-3RKP3FMS.js} +1 -0
- package/dist/{chunk-XVF2VVXP.js → chunk-5HSRT26B.js} +2 -2
- package/dist/{chunk-XKIZ2HNN.js → chunk-5IUSNGOC.js} +18 -16
- package/dist/{chunk-EJIUXPBX.js → chunk-5OFV52AM.js} +1 -1
- package/dist/{chunk-AW7R2GWG.js → chunk-5VSF4UUQ.js} +12 -7
- package/dist/{chunk-NW7BOILN.js → chunk-5WRPDP2A.js} +18 -18
- package/dist/{chunk-7ULNCDCO.js → chunk-6DLWKFEX.js} +35 -34
- package/dist/{chunk-WOA4LN2R.js → chunk-7D6L4MTX.js} +2 -2
- package/dist/{chunk-GAEGEPOC.js → chunk-7HXE67BN.js} +2 -2
- package/dist/chunk-AGSS7277.js +84 -0
- package/dist/{chunk-R3PCY3YS.js → chunk-AMERCH6V.js} +13 -13
- package/dist/{chunk-WU567RSC.js → chunk-ATKJFDQN.js} +14 -10
- package/dist/{chunk-VSSBEUFT.js → chunk-AWSCPCN2.js} +16 -16
- package/dist/{chunk-VCHXCQFG.js → chunk-BWD5EPKO.js} +3 -3
- package/dist/{chunk-ODBNXWT4.js → chunk-CT4MN5IU.js} +1271 -262
- package/dist/{chunk-34WSVGEM.js → chunk-CUHSHSB6.js} +83 -5
- package/dist/{chunk-CHOW74WE.js → chunk-CYRKQRPM.js} +7 -10
- package/dist/{chunk-Q7DUK4LX.js → chunk-DEL2U6CM.js} +5 -5
- package/dist/{chunk-6C7JWPCP.js → chunk-DGUKIYSI.js} +26 -23
- package/dist/{chunk-PHDMFAKM.js → chunk-DUO2TU77.js} +2 -2
- package/dist/{chunk-MRXKYPCS.js → chunk-EBC36LNN.js} +2 -2
- package/dist/chunk-ED36RF6Y.js +17 -0
- package/dist/{chunk-76MWC2K6.js → chunk-EUXZEFTV.js} +90 -97
- package/dist/{chunk-QOZXJ4NA.js → chunk-FM4RDMKT.js} +11 -8
- package/dist/{chunk-66VMUL7Q.js → chunk-GAQ7OQG6.js} +31 -28
- package/dist/{chunk-Q6ZWTCLY.js → chunk-GL5LUMZE.js} +23 -24
- package/dist/{chunk-BKE7E7TC.js → chunk-GMX5OS7B.js} +9 -9
- package/dist/{chunk-ZHFZWTZV.js → chunk-GSLNCYGG.js} +11 -11
- package/dist/{chunk-ARJ6PLFH.js → chunk-GSM5UU7A.js} +26 -34
- package/dist/{chunk-724UL2TJ.js → chunk-GXFV6MYU.js} +3 -3
- package/dist/chunk-HAD7QRQI.js +30 -0
- package/dist/{chunk-QOBUHWHX.js → chunk-HRNHZA3N.js} +6 -6
- package/dist/{chunk-VWCXW2E7.js → chunk-HWCCCQNX.js} +1 -1
- package/dist/{chunk-7DYBLXYA.js → chunk-HYGZEMX6.js} +3 -3
- package/dist/chunk-IJYGIDF4.js +186 -0
- package/dist/{chunk-4W4VLAPJ.js → chunk-IQWN5JCP.js} +11 -11
- package/dist/{chunk-GZYBXISR.js → chunk-IUE7SSKV.js} +9 -9
- package/dist/{chunk-IBGIOBEE.js → chunk-IWDLU6K3.js} +68 -5
- package/dist/{chunk-XCJN5IJU.js → chunk-IYNJ6YPZ.js} +4 -4
- package/dist/{chunk-JAU72XNY.js → chunk-J64T55EI.js} +3 -3
- package/dist/{chunk-2BBLFX75.js → chunk-J6MPGQTS.js} +5 -5
- package/dist/chunk-JORED7EX.js +44 -0
- package/dist/{chunk-6SDEP3AJ.js → chunk-JUKMK5Y4.js} +8 -8
- package/dist/{chunk-IBRALRBR.js → chunk-JW3YWBVB.js} +15 -14
- package/dist/{chunk-L3BLXZG7.js → chunk-KAPJINKN.js} +30 -69
- package/dist/{chunk-2B73ZPOK.js → chunk-KN35K52N.js} +3 -3
- package/dist/{chunk-C34ZXZ7O.js → chunk-KUA4OJ5Y.js} +24 -11
- package/dist/chunk-KYUB6QDC.js +72 -0
- package/dist/{chunk-67WMNWEE.js → chunk-LLJNQKQW.js} +12 -10
- package/dist/chunk-LZSEQW6A.js +57 -0
- package/dist/{chunk-ZANLILYD.js → chunk-M2IFVFWU.js} +2 -2
- package/dist/chunk-M2UD3LPU.js +88 -0
- package/dist/{chunk-WOKKRCJ5.js → chunk-MBKQHGA7.js} +15 -14
- package/dist/{chunk-P7266HLM.js → chunk-MPNJLASA.js} +2 -2
- package/dist/{chunk-6GFRNVYR.js → chunk-MZGQNL3Y.js} +10 -10
- package/dist/{chunk-QZVW3AAK.js → chunk-N5FLEO7W.js} +33 -31
- package/dist/{chunk-3JNKMP3B.js → chunk-O4NWQT6I.js} +4 -4
- package/dist/{chunk-PFNSLWU5.js → chunk-OHKJ7PQF.js} +33 -31
- package/dist/{chunk-VRZP76YM.js → chunk-P63GXC37.js} +4 -1
- package/dist/{chunk-XTMJ7XDT.js → chunk-POMLXPMY.js} +1 -1
- package/dist/{chunk-QQNQ7SJW.js → chunk-QBFBRV2B.js} +10 -4
- package/dist/{chunk-3UUPUE3O.js → chunk-R3T7CLQ2.js} +84 -60
- package/dist/{chunk-23WOO7TX.js → chunk-R7YOCSNV.js} +10 -10
- package/dist/{chunk-2CD5RS6M.js → chunk-RKBXZN3A.js} +23 -2
- package/dist/{chunk-OG5UUPN7.js → chunk-RKKOYPRA.js} +14 -14
- package/dist/{chunk-52FUZI6K.js → chunk-RMLC67LF.js} +1 -1
- package/dist/{chunk-XMSXXAHW.js → chunk-ROYSX2S5.js} +2 -2
- package/dist/{chunk-PV6LR5P5.js → chunk-RVK72CZX.js} +9605 -14776
- package/dist/{chunk-FLKWCIZH.js → chunk-S2HEXIWI.js} +5 -5
- package/dist/{chunk-UZ3TUTRV.js → chunk-S67KYG37.js} +1 -1
- package/dist/{chunk-JWA5I3SR.js → chunk-SD4I7ZO4.js} +16 -18
- package/dist/{chunk-Z527N6YQ.js → chunk-SKAMFC7A.js} +12 -12
- package/dist/{chunk-MQLUAEOC.js → chunk-SRYEJNES.js} +2 -2
- package/dist/{chunk-BRGDSBJE.js → chunk-SYE6T2EB.js} +12 -12
- package/dist/{chunk-GXW6K4YJ.js → chunk-T57LYLZT.js} +1 -1
- package/dist/{chunk-ZXYDQ3TZ.js → chunk-T7FEBKXN.js} +48 -15
- package/dist/{chunk-IXXDFY73.js → chunk-TIQ2PKDW.js} +3 -3
- package/dist/{chunk-QILIYSJF.js → chunk-TLX72JTG.js} +1 -1
- package/dist/{chunk-TYM6OUIT.js → chunk-TOY6QKOH.js} +12 -12
- package/dist/{chunk-IZSHAIJ7.js → chunk-TRETHTQO.js} +12 -12
- package/dist/{chunk-3VPVG5SU.js → chunk-U44DJXQP.js} +10 -6
- package/dist/{chunk-PNQDPDRC.js → chunk-UA7ZXYDR.js} +1 -1
- package/dist/{chunk-OXXKQCSV.js → chunk-UBOQLVT2.js} +4 -4
- package/dist/{chunk-QGQ5CCCG.js → chunk-UMS2MFPZ.js} +2 -2
- package/dist/{chunk-QHJ6DQ2R.js → chunk-US7ECLD7.js} +55 -13
- package/dist/{chunk-EETY4LF7.js → chunk-UZ2XCLYS.js} +8 -8
- package/dist/chunk-V35JG4JB.js +2667 -0
- package/dist/{chunk-N3X6Q4YT.js → chunk-VOOAPROD.js} +3 -3
- package/dist/{chunk-SAEFPSB6.js → chunk-W66O5E25.js} +21 -11
- package/dist/{chunk-KBG47GHP.js → chunk-WMYE35PX.js} +15 -11
- package/dist/{chunk-5NGR67U5.js → chunk-XBJVAEEO.js} +32 -5
- package/dist/chunk-XHJBS436.js +4217 -0
- package/dist/{chunk-GBPP5P7X.js → chunk-XTBDCR27.js} +4 -4
- package/dist/{chunk-4UNXBNE2.js → chunk-XTFDJI3T.js} +11 -11
- package/dist/{chunk-UXGETRP4.js → chunk-XYURS6EN.js} +13 -12
- package/dist/{chunk-RPLINGVB.js → chunk-Y6GZHWSJ.js} +6 -6
- package/dist/{chunk-2ZGIZPDG.js → chunk-YCWYO6TQ.js} +41 -38
- package/dist/{chunk-66SXUJCA.js → chunk-YH5MVY2U.js} +10 -8
- package/dist/{chunk-AOHZU2BM.js → chunk-YNE3RP52.js} +1 -1
- package/dist/{chunk-GOYRYZ2R.js → chunk-ZHH37KZL.js} +72 -20
- package/dist/{chunk-ZGDGW2V3.js → chunk-ZIVJBOO3.js} +2 -2
- package/dist/{chunk-GHB6AYCP.js → chunk-ZVUL7QYY.js} +29 -28
- package/dist/index.js +11559 -6213
- package/dist/mcp/analyze-cli.js +10 -9
- package/dist/mcp/cli.js +41 -38
- package/dist/mcp/server.js +1142 -495
- package/dist/mcp/worker.js +61 -57
- package/dist/probes/installer.js +19 -19
- package/dist/workers/project-snapshot-persist-worker.js +28 -9
- package/dist/workers/review-graph-persist-worker.js +1 -1
- package/docs/configuration.md +11 -0
- package/docs/engineering-principles.md +32 -0
- package/docs/lsp-capability-matrix.md +1 -1
- package/docs/lsp-idle-eviction.md +15 -26
- package/docs/pi-lens-fixer.md +117 -13
- package/docs/pi-lens-merge-policy.md +109 -11
- package/docs/pi-lens-monitor.md +8 -2
- package/docs/pi-lens-retro.md +4 -1
- package/docs/pi-lens-reviewer.md +114 -3
- package/docs/pi-lens-subagent.md +27 -14
- package/docs/release-qa-baseline.md +3 -1
- package/docs/servercapabilities.md +9 -19
- package/package.json +7 -5
- package/scripts/download-grammars.js +40 -7
- package/scripts/lib/latency-log-phases.mjs +95 -0
- package/scripts/rpc-load-check.mjs +570 -81
- package/dist/chunk-3WA4HP4I.js +0 -55
- package/dist/chunk-466Z26MG.js +0 -30
- package/dist/chunk-5JM2524W.js +0 -84
- package/dist/chunk-CO2G5PHH.js +0 -84
- package/dist/chunk-LOP6GRPH.js +0 -31
- package/dist/chunk-UOPVJQ4N.js +0 -103
- package/dist/chunk-XPZPXCBV.js +0 -28
- /package/dist/{chunk-FC4RP4I2.js → chunk-6HWVI6MI.js} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -16,6 +16,312 @@ All notable changes to pi-lens will be documented in this file.
|
|
|
16
16
|
|
|
17
17
|
### Security
|
|
18
18
|
|
|
19
|
+
## [4.4.4] - 2026-10-19
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- **Preserve unchanged findings across file touches.** Share bounded content stamps while retaining strict report timestamps and existing pipeline byte limits (#4541).
|
|
24
|
+
|
|
25
|
+
- **pi 1.2 and 1.3 are supported (refs #4513)** — the tested host range now runs from pi 0.80.10 up to, but not including, 1.4.0. Both new minor lines pass the release-QA matrix against a real pi, a resumed session keeps the lazy tools pi-lens remembers, and the development host moves to pi 1.3.
|
|
26
|
+
|
|
27
|
+
- **Read-guard accounting reads a file once per tool call hook (#1649)** — a `read` used to re-read and re-split the whole file three times when it was called and three more when it returned. Now one bounded pass per hook counts its lines and hashes only the lines the read covers, in 64 KiB chunks, behind a size gate. Five search hits in one file now cost one pass instead of five. A bash `tail`/`head`/`cat` of a vendor or ignored file is dropped before its lines are counted. Read-guard verdicts are unchanged, except that a clipped bash view of a file over 512 MB now records one line instead of throwing.
|
|
28
|
+
|
|
29
|
+
- **Review-graph builds read each function body once (#1740)**. Function facts walked every function body seven times, once per summary field. One pass now computes all of them, with identical summaries. On a 719-file repository a full graph build dropped from 6.76 s to 5.65 s (median, fresh process), and a warm full rebuild from 5.39 s to 4.37 s.
|
|
30
|
+
|
|
31
|
+
- **The source walk answers compiled-twin lookups from the directory listing it already read (#1979)**. A `.js`, `.mjs`, `.cjs` or `.jsx` file no longer costs up to seven filesystem existence checks for a `.ts` or `.vue` twin; a twin-less file costs none, and a file that has one is still checked once against the real filesystem. Which files the walk keeps does not change. The review-graph size-skip record now also reports `entriesVisited` and `walkMs`, so a slow over-cap first build can be told apart as a slow collection or a large tree. `walkMs` covers the whole source collection, including the tracked-file index prime. The verdicts match the old walk on a tree that does not change during the walk. Refs #1979 (the cold-IO cost of the first walk is reduced, not removed).
|
|
32
|
+
|
|
33
|
+
- **Sending a document to a language server counts its lines natively (#2066)** — each `didOpen`/`didChange` walked the whole document one character at a time to count lines and find the last one, about 70% of a send. A document with no carriage return is now counted with the native `indexOf`, about 10x faster (3.0 to 0.3 ms scan on a 1.96 MB document). Line counts and ranges sent to the server are unchanged, and a document with a carriage return takes the same loop as before.
|
|
34
|
+
|
|
35
|
+
- **Per-file complexity baselines no longer stay in memory for the whole session** (#4125). The tool_call memo kept every file's full complexity result and only ever asked whether a path was present. It now keeps the path alone, which measured 28.0 MB down to 2.4 MB retained at 900 files.
|
|
36
|
+
|
|
37
|
+
- **Project scans skip ast-grep rules that cannot match a file (#4496)**. A rule whose required literal text is absent from the file no longer pays a native match call. A 300-file scan of this repository went from 16.3 s to 10.5 s warm, with identical diagnostics. The per-edit ast-grep runner uses the same check.
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
|
|
41
|
+
- **Stop re-pushing byte-identical LSP content, and stop losing a save or a revert to the same gate.** The touch debounce now decides by the bytes the client sent and by whether that document's notify queue is still sending them, so a touch whose own work outlived the window no longer sends a redundant `didChange` that clears the server's diagnostics (#4532). A still-running save does not count as sending them either: it cannot change the document, so the cached diagnostics survive it. A skipped save still reaches the server, and its mark names the content the `didSave` landed for, so a retry that saves other bytes cannot suppress the save the next saved touch owes. That mark outlives the payload bound: Incremental's eviction strips the text it keeps, never the save evidence. A standalone save whose notification fails is retried once; a failed save carried by a content push remains owed by the next saved touch. Every failed send records it (`lsp-did-save-send-failed`, `lsp-did-save-recovery-exhausted`). A client that cannot report what it holds is re-sent rather than skipped.
|
|
42
|
+
|
|
43
|
+
- **`~/` paths now resolve where the shell, the disk, and the tool argument really point (closes #4576)** — a `tool_result` shares the `tool_call` resolver, so writes through `~/…` no longer dispatch runners against a `<projectRoot>/~/…` path that never existed; a bash operand follows shell rules again (each operand retains its own quoting, so a quoted `'~/x'` stays literal, and `@x`, a `file://` spelling or a no-break space are the file names a shell opens); a write recovered from disk keeps its byte-exact name; and `module_report`, `lens_diagnostic_mark`, `lsp_diagnostics` and `lsp_navigation` expand a `~` path argument instead of looking for it under the session cwd.
|
|
44
|
+
|
|
45
|
+
- **`symbol_search` answers as soon as a cold word-index build finishes (closes #4583)** — a query that arrived during the build cached "index is building" for that root, and nothing refreshed it, so every later call in the same process replayed the building hint and re-triggered a build over the index already on disk. A completed build (cold, or the session-start rebuild and reuse paths) now hands its index to that warm entry, so the next query searches it.
|
|
46
|
+
|
|
47
|
+
- **Count a multi-file bash command once in agent-behavior tracking.** Per-file diagnostic replays no longer cause false BLIND WRITE warnings or displace the real command from recent history. Each file still runs its diagnostics.
|
|
48
|
+
|
|
49
|
+
- **Marked findings stay hidden on every diagnostics route, and hidden ones are counted (#1634)** — a finding marked false-positive or won't-fix with `lens_diagnostic_mark` still came back from `lsp_navigation workspaceDiagnostics` (with or without a path) and from `pilens_project_scan` over MCP. Both now apply the same finding policy as `lens_diagnostics` and say how many findings it hid, and `lsp_navigation`'s no-path snapshot gives each file's age (`scanned 5m ago`), since it replays what each language server last reported. `lens_diagnostics` mode=full and mode=all now state how many findings marks hide in their scope, and a second `mode=full` no longer erases a mark from mode=all's count and the footer's `suppressed: N` chip, so neither mode reads "No issues ✓" with no reason right after a sweep listed those findings.
|
|
50
|
+
|
|
51
|
+
- **Keep the host responsive between files during project scans.** Diagnostic scans now let pending requests and cancellation callbacks run after each completed file. Individual file analysis can still block, and full graph builds still run without a break. (Refs #1740)
|
|
52
|
+
|
|
53
|
+
- **A language-server leader dropped from the held-pid set is now recorded (#3096)** — When 256 language-server groups are all still alive, the next launch drops the oldest held pid, and a group kill after that leader dies is refused. `holdOwnChildPid` now counts that drop as `held-own-pid-evicted` in the degradation ledger, once per launch that evicts, with the launch site as the subject and the dropped pid in the reason. A launch that evicts nothing records nothing. `tests/clients/lsp/kill-process-tree-real-child.test.ts` fills the set through the real `launchLSP` path and reads the record back from `latency.log`.
|
|
54
|
+
|
|
55
|
+
- **Save-triggered language servers now hear a save from MCP hosts and from cold queries (refs #3405)** — an MCP host's edit (the PostToolUse hooks and `pilens_analyze`) re-synced the file without `textDocument/didSave`, and a first `lsp_diagnostics` query on a cold server lost its save to the touch debounce after its own warm-up pushed the same bytes. A server that compiles on save, such as Expert for Elixir, answered neither. Both now send the save to servers that declared `textDocumentSync.save`; servers that declared none see no change. With auxiliary scanners on (the default), the diagnostics step after an edit also re-sent the bytes the post-write sync had just sent, clearing the server's diagnostics for every edit; it now reuses that push.
|
|
56
|
+
|
|
57
|
+
- **A dead export an edit introduced is no longer missed when turn end joins an older knip scan (closes #4166)**. A knip scan can already be running when the turn's edits land: the previous turn's slow scan, or a `lens_diagnostics` fetch. Turn end took that scan's answer as covering the edits and stored it as the baseline, so an export the edit left unused was never reported. Turn end still delivers that scan's findings. It now also carries the turn's files to the root's next successful scan, which reports what the edits added. The knip row in `latency.log` gains `joined` and `carried` for that turn.
|
|
58
|
+
|
|
59
|
+
- **A module created mid-session now clears the importer's missing-import (closes #4320)** — Language servers that cache a failed module lookup (Pyrefly) kept reporting `missing-import` and a definition at the import line after the module appeared, until the server restarted. pi-lens now announces a file created after the server started as Created, once, before any cached answer in the same call, whatever the path order; `rename_file` announces the moved-in path as Created too, and so do sibling packages served by the same server. pi-lens's own edits stay announced as edits, and only cached findings of a server that heard of a new file are re-checked.
|
|
60
|
+
|
|
61
|
+
- **Tab-indented files keep the formatter's own width (closes #4321)** — With no formatter config, pi-lens pinned tab style but also forced a tab width of 1 on Biome, Prettier and Ruff, so deferred formatting re-wrapped lines near the 80-column limit in files the formatter's defaults had already formatted. Tab evidence now pins the style only; the width is the formatter's default or your explicit config.
|
|
62
|
+
|
|
63
|
+
- **A file whose language server cannot start is no longer reported clean (closes #4322)** — When a file's language server could not be started, or was still starting, and only an auxiliary scanner such as typos-lsp answered, `lens_diagnostics source=lsp` and pi's write-time LSP check reported the file as confirmed clean, so invalid JSON passed. `lens_diagnostics` did the same when nothing answered at all. The check now names the language server as not available and reports the file as unconfirmed, for a single file, a batch, a directory and a primary-only check; the auxiliary's own findings are still shown, and a batch says which files have only auxiliary findings, and a file nothing answered for is counted as unavailable rather than cached as clean. Such an answer no longer clears a language-server blocker or the footer's language-server rows. The LSP workspace diagnostics cache moves to version 4, so a clean result cached by an earlier version is checked again once.
|
|
64
|
+
|
|
65
|
+
- **An explicit LSP check no longer starts Opengrep when it is switched off (closes #4323)** — `lens_diagnostics source=lsp` in pi, `pilens_diagnostics` over MCP and `lsp_navigation workspaceDiagnostics` started the Opengrep security scanner for an all-server check even with `--no-opengrep` or `opengrep.enabled=false` in `~/.pi-lens/config.json`. They now leave every auxiliary scanner the configuration switches off unstarted, as the write-time check and `pilens_analyze` already did, for a single file, a batch, a directory and the primary-only fallback. A primary-only check no longer shows a switched-off scanner's earlier findings, a batch cached before the switch is not served to the narrower check, and `lsp_navigation workspaceDiagnostics` without a path leaves out the rows of a switched-off scanner that is still running. The turn-end actionable-warnings report (`lens-actionable-warnings`) no longer starts a switched-off scanner either, and an explicit single-file check names the scanner it skipped: `No auxiliary findings. Switched off, not run: opengrep.`
|
|
66
|
+
|
|
67
|
+
- **A module created outside pi-lens now clears its importer's missing import (refs #4346)** — A module written by a shell, `cp` or codegen reached no language server, so pyright and Pyrefly kept the importer's missing import for the server's life when only the importer was diagnosed. Before `lsp_diagnostics` answers, pi-lens now announces the files an importer resolves to as Created to any server that has not heard of them. This covers every file that is not answered from a fresh clean cache entry, including one pi's write hook just touched, and a module moved into place with `mv`. Workspace edits that create, rename or delete files now send their watched-file events too. The review graph adds the importer's edge for a newly created module, so a cached clean answer is refused when that module later changes. Extracting a large file's symbols no longer grows with the square of its size: a 12,000-line Python file now takes 0.4 s in a review-graph build (was 9.6 s) and 0.09 s for its imports (was 9 s).
|
|
68
|
+
|
|
69
|
+
- **A new file now refuses only the cached diagnostics that depend on it (refs #4346)** — Before, any file created under a language server's root made `lsp_diagnostics` and `lens_diagnostics mode=full` re-check every cached file with findings from that server, and never a cached clean file. Now a cached answer is refused when the new file is in its dependency scope: a file its imports resolve to or name, a TypeScript or JavaScript ambient file (a `.d.ts`, `declare global`, `declare module`, or a script) for a JS/TS file, or a file of the same package for a Go file. That includes clean answers, so a new global or package file that breaks a clean file is checked again. Other cached answers keep serving. A cached Go answer is also refused when a file of its package is edited or removed. Before a check answers, pi-lens now announces a Go file's new package files to its servers, as it already did for import targets. Languages whose implicit scope is not modelled (Python, Java and others) keep the old rule for answers with findings, and so does a file with a dependency pi-lens cannot follow (a dynamic `import()`, `require`, or an imported file that re-exports).
|
|
70
|
+
|
|
71
|
+
- **The ast-grep export scan waits out its startup defer again** (refs #4352) — Its defer entry was keyed under a name no task used, so the scan started on the next tick and could freeze the TUI right after startup or `/new`. The entry now matches the task, and a misspelled task name fails the build.
|
|
72
|
+
|
|
73
|
+
- **MCP warm socket restarts after a kill on a long TMPDIR; a second pi session keeps its own endpoint.** The server and the warm-attach host now unlink only the socket file Node bound, never an unrelated file, and the pid-scoped endpoint is `pi-lens-mcp-<pid>-diagnostics-<hash>.sock` so the pid survives the path cut. The tmp-fixture hygiene owner also stops excusing creator-run leaks (#4353, #4354).
|
|
74
|
+
|
|
75
|
+
- **The read guard no longer refuses edits of a file the agent wrote itself (#4387)** — when the agent's own `edit` (oldText/newText) touched a file it had written whole, the read guard ended that file's authorship. It logged `authorship_retired` with `writer: "bridge"`, and after a bash write the next edit was refused with "File modified since your write" although no other process had touched the file. The authorship now advances to the edit's bytes. Separately, after a bash write and one own positional edit, an edit of another line was refused with "File modified since read". The guard again records the whole file as read when it allows that first edit, and that record survives `/tree`, `/fork` and resume exactly when the authorship does. A change by another writer still ends the authorship, also across a branch move. An own oldText edit inside a range-scoped authorship now asks for a read instead of blaming another writer.
|
|
76
|
+
|
|
77
|
+
- **pi no longer exits when a persist worker reports a result without a staged-file path (closes #4400)** — the project-snapshot and review-graph persist workers hand their result back with the path of the file they staged. A reporter on 4.4.3 hit a result without that path in an RPC chat, and the host removed it unchecked, so the resulting `ERR_INVALID_ARG_TYPE` ended pi. Why that result had no path is not yet known. The host now cleans up and promotes the stage path it sent itself and only trusts the path in a result when no request is left to look it up. A stage file the host cannot remove (a directory in its place, a Windows handle held by antivirus) no longer ends pi either: the snapshot persist continues and a `project-snapshot-stage-reap-failed` degradation count records it. Reported by timges.
|
|
78
|
+
|
|
79
|
+
- **A file no language server answered for no longer reads as clean on any diagnostics route (#4403)** — a language server turned off in `lsp.disabledServers` now leaves its files unconfirmed on write-time checks, `lsp_diagnostics`, MCP `pilens_diagnostics`, `lsp_navigation workspaceDiagnostics` and `mode=full`, and each says it is disabled in config. Write-time checks give one such note per server per session, and a later write of an unchecked file prints a one-line "unconfirmed" status naming the cause instead of "clean". `lsp_navigation workspaceDiagnostics` no longer returns a clean-looking empty result when no server answered, and a `mode=full` sweep says the server did not answer instead of blaming a timeout. The same holds when a disabled server's fallback (deno for `typescript`, for example) cannot start. Cross-file checks name such a server once per session instead of listing each importing file as inconclusive, counted from when the note reaches the agent. `lsp_navigation` file operations such as hover and documentSymbol say no server answered, and why, instead of "No results" or an install hint.
|
|
80
|
+
|
|
81
|
+
- **TypeScript language server starts for a compiler found only on PATH.** A trusted project whose `tsc` resolves through PATH to a normal TypeScript package (nvm, Homebrew, npm-global) now gets the TypeScript server, where it was reported as "mark the project trusted" (#4414). When the PATH `tsc` is TypeScript 7 or a shim with no package beside it (volta, asdf, mise, pnpm-global), pi-lens installs its own pinned classic compiler if installs are allowed. A missing compiler now warns that no compiler could be resolved, and a genuine trust refusal still prints only the trust notice. The first line of multi-match Windows `where` output is read without its carriage return.
|
|
82
|
+
|
|
83
|
+
- **A stalled grammar download now fails after 15 s, not 300 s** (#4425). The runtime fetch of a missing tree-sitter grammar and the install-time `download-grammars.js` both give up once no bytes arrive for 15 s. A slow connection that keeps delivering still completes. A black-holed CDN used to hold the first parse of a language such as Kotlin for undici's 300 s headers timeout.
|
|
84
|
+
|
|
85
|
+
- **Blockers and the cascade now see Go peers, ambient files and dynamic imports (#4438)** — The inline-blocker sweep demotes an LSP blocker to `[stale — re-run to confirm]` when the target of an `import("x")` or `require("x")` changes, or when pi-lens records a write, under the session root and after the verdict, to a same-package Go file known to be in the host build (test files excluded; a build constraint it cannot evaluate is no evidence) or to a file that feeds the TS/JS global scope (a `.d.ts`, `declare global`, a script). A file whose role cannot be read (deleted, over the LSP sync limits, unparsable) never demotes. The cascade checks a Go file's package peers and, for a changed ambient file, the 10 most central JS/TS files, and says when it stopped there. The diagnostics cache announces a dynamic-import target created outside pi-lens. All three read the one implicit-scope test in `clients/lsp/implicit-scope.ts`.
|
|
86
|
+
|
|
87
|
+
- **A Go type error or a Dart compile error is no longer reported as clean (#4451, #1937)** — `go vet` prefixes a type-check error with `vet: `, and the runner read that prefix as part of the file path, matched no target and returned `succeeded` with no findings. The prefix is now stripped before the path is compared. `dart analyze` exits 3 when it reports an error, but the runner's exit table stopped at 2, so the finding was dropped as a rejected run; the table now accepts 3. `tests/clients/dispatch/smoke-fixture-coverage.test.ts` now requires every dispatch runner to have captured real-binary output or a reasoned exemption, and `tests/clients/dispatch/runners/captured-real-output.test.ts` replays 14 more captured runners (Go 1.27.2, Dart 3.13.5 and others).
|
|
88
|
+
|
|
89
|
+
- **helm-lint, fish-indent and golangci-lint now read their tools' real output (#4453, #4454, #4455, #1937)** — helm 4 prints a template parse error as `templates/: parse error at (<chart>/templates/bad.yaml:5)`; the parser built the finding's path from the prose around the file and recorded no line, and now reports the template and line with the chart name stripped. `fish_indent --check` prints the file's own name on stderr and exits 1 for a file that only needs formatting; the runner reported that as a blocking `fish-indent-parse-error` and now reports the non-blocking `fish-indent-unformatted` finding. golangci-lint v2, which the installer downloads, rejects `--out-format=json` (exit 3, unknown flag); the runner now asks for `--output.json.path=stdout --show-stats=false`, so the JSON document is not followed by run statistics. golangci-lint v1 binaries do not understand these flags. helm 4 has no `--version` flag, so helm-lint and helm-render reported helm as unavailable and never ran against a real helm 4; they now probe with `helm version --short`. The installer's helm and SpotBugs rows had the same wrong probe (SpotBugs takes `-version`) and now use the argv each tool accepts. A golangci-lint config that also writes text, tab, sarif or another format to stdout no longer corrupts the JSON the runner parses: the runner now switches every other output format off. A helm template whose name holds a space, such as `templates/my file.yaml`, is reported on that file, not on a file named `file.yaml`. A fish file with a syntax error and an indent change is now a non-blocking `fish-indent-unformatted` warning; it used to be a blocking `fish-indent-parse-error` by accident. A well-formatted fish file with a syntax error passes `fish_indent --check` silently, before and after; fish-lsp reports it as an error through `lsp_diagnostics`.
|
|
90
|
+
|
|
91
|
+
- **A fish syntax error now reaches the agent as a blocking finding (#4454)** — `fish_indent --check` never reports a syntax error: it exits 0, silently, on a missing `end`, an unterminated quote and a stray `end` in a formatted file. The fish-indent runner now also runs `fish --no-config --no-execute <file>`, which parses without executing anything, and reports each error as a blocking `fish-syntax-error` on the real file and line. A file that is merely unformatted still gets only the non-blocking `fish-indent-unformatted` warning, and a valid formatted file gets no finding. When `fish` is not found next to `fish_indent`, the format check still runs and one `fish-syntax-check-unavailable` degradation row says syntax errors are not being checked. This supersedes the note in the #4453 entry that a well-formatted fish file with a syntax error passes silently. Checked on fish 3.3.1 (Ubuntu 22.04) and 4.9.3 by captured output, and on 3.7.0 and 4.2.1 by review probes. fish 3.1 rejects `--no-config`, so there the syntax check is skipped and leaves a `runner-empty-result` row.
|
|
92
|
+
|
|
93
|
+
- **Language servers see installs, codegen and outside edits before the next check (closes #4476, closes #4507)** — after a bash command rewrote several files the language server already held, pi-lens checked them one at a time, so a dependent could be checked against the old copy of a dependency the same command had rewritten and report a phantom error. The new bytes of every held file a command wrote now reach the language servers first, and a server with measured in-order replies answers one request before the first check to prove it has read them; threaded or unmeasured replies are recorded as undrained; the wait has the edit hook's time budget, and a server that does not answer in time is recorded. A branch switch or reset that rewrites held files gets the same budget, so a slow server no longer misses the move's new bytes. TypeScript 7's native server hands file watching to pi-lens and re-reads a closed file only when told about it by exact path. pi-lens now looks for disk changes at each bash result and each tool call. A dependency install (a lockfile or a package manager's install marker changed) restarts that project's language servers and refuses every diagnostics-cache entry scanned before it. A `.d.ts` file created, edited or deleted by codegen or outside pi, in gitignored directories too, is reported to the live servers by path and refuses the cache entries that could see it. Tool-call looks walk the declaration files at most once every `PI_LENS_DECLARATION_LOOK_MS` milliseconds (default 2000, 0 to walk at every look); a walk over 4096 directories or 2048 declaration files records a degradation instead of passing as clean.
|
|
94
|
+
|
|
95
|
+
- **Refresh diagnostics across JSDoc imports and module augmentations.** Capture their dependency specifiers and conservatively invalidate cached findings for alias and package dependencies.
|
|
96
|
+
|
|
97
|
+
- **ast-grep no longer stops on old findings away from an edit (refs #4481)** — on a file's first edit in a session, every existing `severity: error` ast-grep finding became a `🔴 STOP` blocker. Code the edit never touched blocked too. The in-process ast-grep runner and the ast-grep language server now block only on a finding whose lines overlap the edited lines. The tree-sitter rules use the same overlap test instead of the finding's first line. An edit inside a multi-line finding still blocks. A rule that reports a neighbouring node can still be missed on that write, such as the first of two duplicate object keys when the edit adds the second. A whole-file `write` still blocks on every finding. Type errors and linter findings still block on any line, because an edit can cause them at lines it did not touch. Thanks @kwisser for the report.
|
|
98
|
+
|
|
99
|
+
- **Linters no longer stop the first edit on errors the file already had (refs #4483)** — on a file's first write or edit in a session, every existing linter error became a `🔴 STOP` blocker, because the delta baseline only existed after that first check. Before the host writes, pi-lens now runs the file's linters over its current bytes once, and the first check reports their old errors as `ℹ Pre-existing: … not blocking` instead. An error the edit caused still blocks, even at a line it did not change, and so does an old error on a line the edit changed or a second copy of one. A whole-file `write` follows the same rules. The capture waits at most 500 ms on the first edit of each file; at most four captures run concurrently and abandoned captures are cancelled. If it runs out or capacity is full, that write blocks on every error as before. Language-server and type-checker errors still block on the first write, and linters that run off the edit path (collect-later) are not covered. Pre-existing linter errors no longer close the `--lens-guard` commit gate.
|
|
100
|
+
|
|
101
|
+
- **A second write no longer clears an unfixed blocker (closes #4484)** — When a write left an earlier error in place (it edited another line, or the next turn edited the file again), pi-lens treated "nothing new" as fixed: the write said `✓ clean`, the `--lens-guard` commit gate opened, `lens_diagnostics source=session` and the widget showed no issues, and turn end reported the error as cleared. The error now stays blocking until a check of the file no longer finds it: the write says `🔴 Still unfixed: N blocker(s) reported earlier (L…)` instead of clean, the commit gate stays shut, turn end lists it as unresolved, and the session store and widget keep it. This also holds for linters whose ids are positions (helm-lint), where fixing one error renumbers the next, and for a script run without authorship that leaves the error in place. The error also stays blocking when the write did not touch its lines (tree-sitter and ast-grep check only the edited lines for new blockers), when the language server or linter failed, timed out or did not answer on that write, when a slow checker's re-check was still running, and for an unused declaration that blocked when it was new. A write that removes the error still reports it resolved. A slow checker's re-check that finds the error gone clears it, at the commit gate or at turn end; one that failed clears nothing. A check that skipped the file without reading it no longer reads as clean: a file over a size or line limit, a crashed parser, an unavailable grammar or server. Fixing an error now clears it even when the same rule still matches an untouched line elsewhere. An error that moves with an edit above it keeps its new line number. Once a checker that could not run answers again, it reports an error first written meanwhile. A new error that takes a fixed error's place shows as a new STOP, not as "still unfixed". An error stays blocking after an edit too large to line up with the file's earlier version, after writes to hundreds of other files, and in a file over 2 MiB. After such an edit, a fixed error no longer blocks on an untouched line elsewhere with other text, and an error its checker skipped stays blocking once the file lines up again. A checker that skipped a file with no errors now re-checks only the lines it skipped, so an untouched old finding no longer becomes a new STOP. Turn end lists an error once after a slow checker's re-check settles. An ast-grep error found before the ast-grep language server started stays blocking after an edit elsewhere, when that server does not answer, and when a slow re-check settles, and still clears when fixed. A shellcheck or taplo error stays blocking when the language server that stands in for that linter does not answer.
|
|
102
|
+
|
|
103
|
+
- **Cascade checks report broken importers on every turn, not once or never (refs #4485, #4486, #4487)** — After an edit, the impact cascade often said nothing about the files that import the edited one. Classic TypeScript neighbours were sent to a background check that usually never finished, so the run read as clean. A snapshot published before the edit could stand in for the neighbour after it. An error shown once was hidden on later runs. Now classic TypeScript neighbours are answered in the same turn. Other servers that only publish when something changes get a 1 s wait. A neighbour that gets no answer is listed as unconfirmed instead of clean. Only diagnostics published after the edit are used. A persistent neighbour error is listed every turn and marked "still present since turn N". An error shown in an earlier turn and not checked since stays on a short "still unresolved" line until a check or an edit shows it gone. If that file was changed outside pi after the check, the line says so once and stops. If the file was deleted, the line is dropped. Neighbour errors stay advisory and never block a commit.
|
|
104
|
+
|
|
105
|
+
- **Language servers see a branch switch made with git (refs #4493)** — a clean-tree `git checkout`, `reset --hard`, or completed rebase or cherry-pick left the language server holding the old bytes of every open file, so edits were blocked on errors from the previous branch. pi-lens now notices each move of HEAD, at the bash call that made it or at the next tool call for a move made outside pi. Before that tool returns, it sends the new bytes of the open files the move rewrote to the primary language server, which is the one the next edit is checked against. Auxiliary scanners catch up after the tool returns. A tool call that starts while that send is running waits for it, up to 500 ms. One `opaque_mutation_head_moved` row records each move.
|
|
106
|
+
|
|
107
|
+
- **A subagent session in a linked worktree now recovers its own opaque bash writes** instead of reporting `no-pending-snapshot` for a command that did write (fixes #4504). The opaque prescan keyed its `tool_call` record on the host's session cwd and its `tool_result` take on the coordinator's project root, which are the same thing only for the primary session; a concurrent secondary session (a subagent in a worktree) wrote its baseline under one root and looked it up under another. Both hooks now resolve one root through `opaquePrescanRoot` (`clients/opaque-mutation-scan.ts`), and a witness through the real `index.ts` handlers over a real linked worktree pins it red-first. The same owner now keys the head-move resync and the held-primary send, so a subagent's recovered files reach the servers that hold them; a write it names in the main checkout is no longer cleared by a scan of its own worktree.
|
|
108
|
+
|
|
109
|
+
- **A fixed ast-grep error no longer stays blocking after the ast-grep server starts (closes #4509)** — An error the built-in ast-grep check found before the ast-grep language server started could block for the rest of the session once fixed: after an edit on its line that the server missed, or after an edit too large to line up with the file's earlier version, the fix still read `🔴 Still unfixed`, and the `--lens-guard` commit gate stayed shut. The built-in check now re-checks the file while it holds such an error, so the fix clears it; where the server reports the same rule on the same line, its copy is listed once, also when a slow re-check settles later. Another rule the server reports on that line, or a weaker copy of the same rule, no longer hides the error or opens the commit gate. An error kept through an edit too large to line up no longer cites a line number from the old file, is no longer cleared because its old line now holds the same text in a comment, and is no longer dropped at turn end or when a slow re-check settles. A lone closing brace or other line with no letters or digits no longer turns an older, untouched finding into a new STOP.
|
|
110
|
+
|
|
111
|
+
- **Language servers start from the managed install when project trust is unknown (closes #4515)** — on a host that does not report project trust (an older pi, a third-party embedder), the refusal of the project-local `node_modules/.bin` server ended the launch before the managed install was tried, so npm-installed servers (yaml, bash, Docker, Prisma) never started and no diagnostics appeared. The refusal now falls through to the managed install. A refused project binary is never run, not even for a `--version` check. When `PATH` reaches into the project (`npm run`, an activated venv, direnv), only pi-lens's own tool store is read and nothing is installed. Untrusted projects are unchanged: installs stay off there.
|
|
112
|
+
|
|
113
|
+
- **Tool crashes and timeouts no longer read as clean files; ktlint findings are preserved (refs #4522)** — shellcheck, shfmt and fish_indent now report no verdict when they fail to answer, and ktlint 1.8.0 output is parsed correctly. A tool-state matrix over captured runner output holds the line: only a clean run may read clean.
|
|
114
|
+
|
|
115
|
+
- **Opaque bash rewrites remain analyzed with `--no-read-guard`** — Dispatch and LSP resync continue while read evidence stays disabled (#4534).
|
|
116
|
+
|
|
117
|
+
- **Nested Python projects keep their own language server (closes #4547)** — For Python language servers, a directory marked only by `requirements.txt`, `setup.py` or `setup.cfg` is now a project boundary, so its Python files are no longer served by an ancestor's pyright client without the project's own config or virtualenv. Before, such files could report unresolved imports, depending on which Python file was opened first.
|
|
118
|
+
|
|
119
|
+
- **A process serving several live sessions releases its registry entry and stale primary registration** — One pi-lens activation can serve more than one live session: pi re-runs the extension factory per session, but a host can also deliver several `session_start` events to a single activation, where the first session is `primary` and each later one is a declined `concurrent-secondary`. The activation recorded only the LAST classification it saw, so a secondary's start overwrote the primary's role and every later shutdown, the primary's included, took the local-only branch. `cancelLSPIdleReset`, `resetLSPService`, `deregisterInstance` and the primary registration release therefore never ran for the process, which kept its `instances.json` entry and its stale primary registration until it exited. The shared teardown now belongs to the last live session of the activation that still owns the primary registration. Replacement removes superseded primary membership, reload hands off the departing primary's own scope, and every leave ends its own turn and cache bookkeeping. A superseded primary's delayed shutdown is not the activation's last leave, so it can no longer stop a live replacement, and a departing primary's saved state is written under its own session id rather than the identity a later session registered (#4554).
|
|
120
|
+
|
|
121
|
+
- **`rename_file` sends a `~/…` destination to your home directory, not a `~` folder in the project (#4578)** — the destination now resolves through the same host-path seam pi's own write tools use, so `~`, a leading `@` mention prefix, pi's unicode space class and a `file://` URL all resolve as they do on a read, and a destination outside the workspace is refused by the existing rename confinement instead of being created.
|
|
122
|
+
|
|
123
|
+
- **A held document keeps its drift record at the record cap, so an untracked edit still heals**: the capped record set evicted by insertion order alone, so a workspace sweep (one touch per project file, every document still open) unbound the earliest files — the server kept answering from their pre-edit bytes with nothing left to compare disk against (closes #4503). Eviction now skips every record whose document a live server still holds, and the held-document count bounds the set instead of the cap alone; one `lsp_document_drift_cap` row per session names the first crossing.
|
|
124
|
+
|
|
125
|
+
- **A malformed persist worker message no longer ends pi (#4402)** — The project-snapshot and review-graph persist hosts read a worker result only through `readGzipStageWorkerResult`. A message they cannot route, or whose handling threw, is counted as `persist-worker-message-contained` in the degradation ledger, and a result with a wrongly typed field takes the main-thread write. The persist worker answers a malformed request with an error result instead of dying. `tests/clients/worker-message-boundary-ratchet.test.ts` reds on any `message` handler in `clients/`, `mcp/`, `tools/` or `index.ts` whose parameter is not `unknown` or whose body is not one non-rethrowing `try`. `tests/clients/persist-worker-message-fault-injection.test.ts` sends malformed, error-shaped and foreign messages through the real `Worker`.
|
|
126
|
+
|
|
127
|
+
- **LSP status shows the whole project root (refs #4449)** — `/lens-health` and the engine status cut a client's root at its second colon, so a Windows root such as `c:/work/proj` read as `c` and a POSIX root such as `/srv/a:b/proj` as `/srv/a`. The root is now read the same way as in the breaker status, and evicting an adopted (foreign) project root no longer shuts down the language server of a different root whose path ends in `:` followed by the evicted root's path.
|
|
128
|
+
|
|
129
|
+
### Internal
|
|
130
|
+
|
|
131
|
+
<details>
|
|
132
|
+
<summary>66 internal changes: tests, CI, tooling, and refactors</summary>
|
|
133
|
+
|
|
134
|
+
- The `Finalize CI rerun classification` job now gives `gh` a repository source (`GH_REPO`) instead of relying on a checkout it does not have, so the terminal `ci:real` / `ci:infra` label swap runs. Every job that reached that write had failed with `failed to run git: fatal: not a git repository` since 2026-10-10, and the census in `tests/config/github-token-write-gates.test.ts` reds a workflow job that calls `gh` with no `GH_REPO`, checkout, or `-R` (refs #4382). The census now shares command-position and quote-aware stdin parsing with the Bash guard, admits only data-only stdin consumers, refuses cyclic repository expressions and invalidates exported sources across `eval`, judges the last explicit repository operand, and reports inline scripts it cannot read instead of skipping them.
|
|
135
|
+
|
|
136
|
+
- The out-of-band disk look now publishes one `DiskChange` (`clients/disk-changes.ts`) instead of calling the LSP service and the diagnostics-cache clock itself, and the project sequence gains a bulk writer (`recordProjectMutations`) that allocates N paths in one locked change-log append with the single writer's lock-failure semantics. This is slice 1 of #4535 (publisher foundation): the migrated wires are the out-of-band look's two consumers, and the remaining detectors follow in later slices.
|
|
137
|
+
|
|
138
|
+
- Share the session-scoped reset block with fresh-module test seams to prevent prior-cell availability and timeout state leaking into later cells.
|
|
139
|
+
|
|
140
|
+
- Plant the shared project-root marker on every test fixture, widen the tmp-hygiene census to writes at the tmp root (#4560).
|
|
141
|
+
|
|
142
|
+
- The bounded-eviction idiom sweep (`tests/config/bounded-eviction-idiom-sweep.test.ts`) now matches the iterator-result spelling (`const oldest = m.keys().next()` with `.value` read on a later line), the one the pre-#4503 drift cap used and all four earlier spellings missed, and it matches it on the semantic rather than on an accessor name. The five production sites the wider matcher found (`clients/cascade-format.ts`, `clients/lsp/cascade-tier.ts`, `clients/mcp/session.ts`, `clients/read-guard.ts`, `clients/test-runner-delivery.ts`) move their cap into `BoundedFifoMap` instead of hand-rolling the evict-oldest loop; their eviction order and degradation records are unchanged. The second round widens the same sweep to an accessor chain a formatter wraps across lines, `$`-initial names, iterator destructuring, a re-assignment, a binding wrapped after `=`, and an eviction whose victim is picked by a comparator over a spread accessor: the session-fact tombstone trim moves its cap into `BoundedSet`, and six live sites whose policy no bounded class expresses are admitted with reasons. Rounds three and four separate balanced initializers, cap evidence and ordered element consumption, following transformed sequences, local aliases and multi-victim removal; newly visible policy-specific evictions and diagnostic-only rankings have explicit admissions. Round five stops widening the matcher and states what it cannot see: the census is the detector's population, and the count-bounded array truncations it does not follow (`ranked.slice(0, over)` deleted one victim at a time, `slice(-n)`, `length = cap`, `slice(...).forEach(delete)`) are named as residuals with five live sites, each re-read and re-probed by the suite so a row cannot go stale. Round six closes the read that made that promise hollow: each residual anchor is matched in comment- and string-blanked source, so a comment that quotes a removed block no longer keeps its row alive, a control pins that a quoted anchor matches nothing, and the accounting test now asserts how many plants and live sites it actually checked, so an emptied loop cannot read as a clean census. Refs #4570.
|
|
143
|
+
|
|
144
|
+
- Add a `test-server-teardown` ratchet sweep for #4589: a test that starts a real listener reds unless the close promise is proven settled where the hook actually runs it — in a teardown hook, a `finally`, the start's own function, or a local helper that returns it and whose every call site settles it. `await xs.forEach(...)`, `await xs.map(...)`, `Promise.race`, a helper that drops the promise, and an `await` in a function, callback, or branch the hook never runs all report instead of passing. A `bind`/`serve` start on a socket or runtime handle is in the population, including the computed `socket["bind"](0)` spelling and a runtime global's own `serve`; a verb, receiver, or construction the scan cannot read is reported as `unresolvable`, never counted clean. The sink drain sweep attributes a sink path at every parameter position of a same-file helper and judges the read where it runs against the call site that supplied the sink, so a helper that suspends on its own path before the read voids the caller's drain, and one that never runs that await does not.
|
|
145
|
+
|
|
146
|
+
- Add a `test-env-restore` ratchet sweep: an env write in test source with no restore in the same file reds, the AST enumerator follows TypeScript wrappers and local env bindings, cleanup copies cannot claim to delete new keys, and every admitted row carries a reason that the sweep re-checks (#4589).
|
|
147
|
+
|
|
148
|
+
- Pin a fixture `HOME` outside every root the harness deletes (`pinFixtureHome`), so a child a test never awaits can no longer re-create a `pi-lens-*` temp root after its file's teardown sweep, publish the fixture-home root each worker derives instead of the value it inherited so a nested Vitest child cannot remove its parent's root, and record a leaked entry's birth time and first-level children so the next such leak is attributable (refs #4591).
|
|
149
|
+
|
|
150
|
+
- The declaration-file walk under a root now records `lsp-declaration-walk-unreadable` when it cannot list a subtree or stat a file for a reason other than absence, and keeps that entry's previous fingerprint instead of reporting its files deleted or created; an unresolved implicit-scope question records `lsp-implicit-scope-unknown` with the file and cause, and a rejected root deregistration records `instance-registry-deregister-root-failed` with the error in its durable metadata. A new `tests/config/silent-catch-sweep.test.ts` ratchets every `catch` handler in the shipped trees to call a record seam on every path or carry a `none: <reason>` line, pinning each file's admitted set by content identity (#4596, #4603).
|
|
151
|
+
|
|
152
|
+
- `scripts/check-pr-body.mjs` now refuses a body that names a record literal under `## Observability` unless a test vitest collects and runs reads it back: the record's full name in an assertion, a comparison, a `case` label or a record-keyed row — never a test title, a `console.log` argument, a skipped test or an excluded tree — over blanked test source, with the collected set read from `vitest.config.ts` (#4597). A record is named whatever the spelling (a backticked span, a bare word, a link label), the declaration that holds the read-back may be spelled with a bracket link or an alias, and a declaration form the rule cannot classify contributes nothing: a skipped or unparsable declaration never counts as a read. The rule is the mechanical half of `docs/pi-lens-fixer.md`'s record rule; `none: <reason>` stays valid, and a probe that cannot run says so instead of passing.
|
|
153
|
+
|
|
154
|
+
- **The eviction sweep asks the question structurally instead of matching spellings (#4606).** `tests/config/bounded-eviction-idiom-sweep.test.ts` recognised five lexical spellings of a hand-rolled evict-oldest block, and every review round found a neighbouring shape the previous spelling could not see: a wrapped accessor chain, an alias, an indirect accessor, a binding split across statements, destructured iterator results, an ordering that finishes in its own statement. The question now lives in `tests/support/eviction-programs.ts`, a recognizer over the TypeScript AST built on the S0 census seam, which tests the four stages an eviction program passes through: a cap bound, an order, a victim selection, and an effect on state. It admits four forms (`iterator-head`, `ordered-victim`, `ordered-scan`, `count-truncation`) and refuses everything else; the state table is the module header's and the worktree's `STATE_TABLE.md`. Selection is structural, not a spelling: an iterator's `.next()` head, an array-destructured head, and a `for…of` head that breaks are the same eviction, and a victim reached through a second binding or an alias is resolved before the removal test. The population is 24 flagged programs over 790 census files (`iterator-head 3, ordered-victim 11, ordered-scan 4, count-truncation 6`); eight of them the lexical detector could not see (`clients/sgconfig.ts`, `clients/cache-manager.ts`, `clients/mutation-attribution.ts`, `clients/widget-state.ts`'s retention cap, `clients/lens-map.ts`, `clients/observed-mutation.ts`, `clients/resource-sampler.ts`, `scripts/analyze-pi-lens-logs.mjs`) with retained policy reasons. Three rows that recorded lexical NON-members are deleted rather than re-pinned, because the structural recognizer does not flag a median, a percentile, or a lockfile diagnostic. Six live retention programs the recognizer still refuses are tabled with their own statement anchors and asserted unflagged: insertion-order `push`-then-`slice(-N)` retention in two shapes, an ordered array of edits, a runtime line budget, a minimum reduction under a tracked-count cap, and a runtime-bound suffix retention.
|
|
155
|
+
|
|
156
|
+
- **A pull request that touches RPC, install, LSP launch or trust paths runs the deep install smoke (#4291)** — `install-smoke.yml` runs all four pi-load cells (npm-global, pnpm-global, bun-global, curl) on ubuntu and an advisory LSP handshake job on such a PR. Other PRs that reach the workflow keep the one pnpm-global cell. One path list (`PR_SMOKE_PATHS` in `scripts/ci-changed-files.mjs`) drives both, pinned by `tests/config/pr-smoke-paths.test.ts`.
|
|
157
|
+
|
|
158
|
+
- **Cache the nightly smokes' tool installs and prune the Actions cache (#4358)** — tool-smoke and parser-smoke restore their managed tool tree from a cache keyed on the installer registry's pins and the ISO week and saved once their install steps ran, a nightly `cache-prune.yml` removes caches of closed pull requests and superseded copies, and a config test fails a `pull_request_target` or `workflow_run` workflow that restores or saves a cache, whether through `actions/cache` or a `setup-*` action's own caching.
|
|
159
|
+
|
|
160
|
+
- Hot-path latency lane (#4363). `.github/workflows/hot-path-latency.yml` times `session_start`, `dispatchLint` and the diagnostics-cache replay 15 times on the checked-out commit and on its first parent (`HEAD^1`: master's tip on a pull request's merge ref, the previous commit on a push), and reports a bootstrapped interval per metric. It fails only when a metric is significantly slower and at least 1.25x and 5 ms above the base. It is advisory until 10 consecutive master push runs are green.
|
|
161
|
+
|
|
162
|
+
- The close-keyword lint (`--lint-pr`) also scans every PR commit message, merge commits included, because GitHub closes an issue for a keyword in a commit that reaches master (#4371). It flags any issue a commit would close that the PR title or a clean body keyword does not close, so a commit trailer alone is not intent, and it fails closed when the commit list cannot be read in full. The PR-metadata scripts now share one GitHub REST read, `scripts/lib/github-rest.mjs`.
|
|
163
|
+
|
|
164
|
+
- A PR's added or changed test files run alone and under two fixed shuffle seeds in `Targeted tests (advisory)` (#4397). `scripts/ci-shuffle-changed-tests.mjs` reds a file whose cases share state, within a 6-minute budget and a 20-file cap. A start that ran no test is unproven, not clean. Ten order-dependent cases on master are fixed, in `lens-diagnostics`, `flake-shape-ratchet`, `lsp-diagnostics` and three `lsp-diagnostics-*` test files.
|
|
165
|
+
|
|
166
|
+
- **The registry-package install-smoke legs run in a project that carries its own `typescript` (#4401)** — `pi-load` and `mise-repro` run the published pi-lens, which cannot start the TypeScript server when the only compiler is a bare `tsc` on PATH (#4414, fixed on master, unreleased). The RPC smoke now waits for the TypeScript server to start or fail, instead of a fixed second after the turn, and fails when it did not start.
|
|
167
|
+
|
|
168
|
+
- The PR-body check accepts `Gap: exists: <path>` only when the path is a file on the PR's base commit (#4404). A test the PR itself adds is not an existing layer; the check refuses it and points at an issue ref or `none: <reason>`. When the base cannot be resolved, the check fails closed with a fetch hint. The class-sweep composition test also stops scanning the whole test tree (#4385).
|
|
169
|
+
|
|
170
|
+
- **Pin the tool-tree cache action to actions/cache v6.1.0 (#4417)** — the composite action's restore and save steps moved from the v4.3.0 SHA to the v6.1.0 SHA that `ci.yml` already pins, so one action repository has one SHA across the workflow tree, as `tests/config/action-pin-split.test.ts` requires.
|
|
171
|
+
|
|
172
|
+
- Four review-found shapes now red instead of waiting for a reviewer (#4427). `tests/config/lsp-index-config-import-pin.test.ts` pins the exact symbols `clients/lsp/index.ts` takes from `./config.js`. A rise in the `vi.mock` sweep's missing-export count fails `tests/config/vi-mock-export-sweep.test.ts` (the baseline admits the 68 rises the old warning hid; the detector now accepts a `vi.importActual` spread of the mocked module, which drops 13 false-positive rows). `tests/config/live-pin-literal-sweep.test.ts` reds on a literal in `tests/config` that spells a live workflow action pin, and the heavy-gate and writer-governance tests derive their pins with `siblingPin`. `tests/config/github-bearer-ratchet.test.ts` reds on a `Bearer` or `Authorization:` header in `scripts/` outside `scripts/lib/github-rest.mjs` (no exceptions are admitted), and every script that sends a GitHub request builds its headers with `githubHeaders` or `githubFetcher`. `lint:js:promises` also denies `typescript/await-thenable` (#4428).
|
|
173
|
+
|
|
174
|
+
- The PR body `## Detection` lint requires `Shape:` and `Enforcer:` lines on a bug-closing PR (#4439), and `tests/config/shape-history-governance.test.ts` requires each `AGENTS.md` catalog shape to have a `HISTORY.md` entry, with a shrink-only baseline of the shapes that lack one.
|
|
175
|
+
|
|
176
|
+
- `Changed test files, alone and shuffled` runs the files a PR adds alone first, before the shuffled starts, so a new witness test no longer sorts past a cap (#4456). The 20-file cap is gone and the time budget is the only bound. The budget is 7 minutes, measured over two real plans that needed about 361 s, and every unproven line names the files it skipped.
|
|
177
|
+
|
|
178
|
+
- **Markdown table cells escape the backslash before the pipe (#4490)** — `escapeMarkdownCell` in `scripts/lib/markdown-cell.mjs` escapes the backslash, then the pipe, in the census issue body, the per-test candidate table and the mutation-table row written by `scripts/mutate.mjs`. A test title or error holding `a\|b` no longer splits its row. The rule-catalog generator now imports the helper with its output unchanged. The mutation report keeps its code-span escaping. The identifier-only `$` escapes in `tests/clients/finding-delivery-gate.test.ts` use the shared `escapeRegExp`. Pinned by `tests/scripts/markdown-cell.test.ts`, the census and stryker row cases, and the mutate title case.
|
|
179
|
+
|
|
180
|
+
- **`scripts/mutate.mjs` prints REFUSED for a refused edit and BROKEN for a failed build (#4510)** — the mutation transcript and table row used to print `RED` for any thrown error, so a mutation that never ran read as a test that catches it. `RED` now means a test run failed, `REFUSED` a no-op or comment-only edit, `BROKEN` a failed build, and `ERROR` any other failure without a verdict, including a vitest killed from outside and a `--tests` path with no test file. A signal to the run now kills vitest's whole process group, so its worker fork no longer keeps spinning. Exit codes are unchanged. Pinned by the REFUSED, BROKEN, ERROR and signal cases in `tests/scripts/mutate.test.ts`.
|
|
181
|
+
|
|
182
|
+
- **`Unit tests Windows` gates instead of advising (#4518)** — the lane drops its `(advisory)` suffix, its `needs: heavy-gate` deferral and its `continue-on-error` step, so it starts on every event like the Unit shards and a failing row gates in `ci-verdict` (an absent or job-skipped row still reads green until branch protection lists the name); the two tests that kept it red on master (a 60 s budget on a 10,000-path pipeline population, and the tmp-fixture census on win32) are fixed or quarantined, and branch protection still needs `Unit tests Windows` added by hand.
|
|
183
|
+
|
|
184
|
+
- The `## Detection` PR-body check accepts `TLA+`, `mutation`, `pre-push`, `lint/type check` and `nightly:<workflow>` as layer words and refuses `unknown` on a bug-closing PR (#4519).
|
|
185
|
+
|
|
186
|
+
- Tie PR-body evidence to changed test files, changed production guards, and Windows-sensitive launch and path seams (#4585). Row 1 is file-level: one fenced red-first transcript naming the file, or a Controls row naming it with a reason. A whole-file deletion owes no row, a move owes nothing while a copy does, a `mutation:` fence needs an explicit RED/FAIL result, and a Windows row that names a bare basename is not a generic read (#4605).
|
|
187
|
+
|
|
188
|
+
- **Role contracts read bot threads and code-scanning alerts when they exist** — the fixer contract disposes of each thread before the hand-back, the reviewer contract rates each one and refuses `merge-ready` while any stays undisposed, and the merge-policy mistakes table gains the seven 2026-10-17 retro rows. A missing bot review is never evidence of anything.
|
|
189
|
+
|
|
190
|
+
- **Record the 2026-10-17 retro rules and a principles digest.** AGENTS.md gains shapes 72 and 73, a digest linking `docs/engineering-principles.md` by heading, and the plegma publish rule; the merge policy gains a bot-thread disposition rule (#4565).
|
|
191
|
+
|
|
192
|
+
- `AGENTS.md` gains shape 74, split host-path resolution: a path resolves by its provenance (host argument, shell operand, recovered from disk, generated by pi-lens), each at one seam. `docs/pi-lens-fixer.md` now says a new shape is a PR-body proposal, never an `AGENTS.md` edit (#4576, #4577).
|
|
193
|
+
|
|
194
|
+
- **LSP client registry extracted (refs #2372)** — `LSPService`'s client identity and lifecycle (the client map and its key, single-flight spawn, the circuit breaker, leases, capacity and idle eviction, adopted roots, and the client half of shutdown) moved into `clients/lsp/client-registry.ts`. The service composes it, and the `LspCapabilities` lifecycle group's read members route to it. Keys, timings, records and log phases are unchanged.
|
|
195
|
+
|
|
196
|
+
- **Session start ensures madge once (refs #3753)** — The deferred availability probes skip tools a startup scan already covers, but madge was probed exactly when the startup scans run, so every such session ensured madge twice. The probe is gone; the startup madge scan still ensures it, and `tests/clients/runtime-session.test.ts` pins one ensure past the exports defer.
|
|
197
|
+
|
|
198
|
+
- The local PR-body preflight (`check-pr-body.mjs --lint-local`) now reads the closed issue's labels through `gh api` and validates any present `## Detection` section, so it agrees with CI's "PR body (advisory)" check (refs #4288).
|
|
199
|
+
|
|
200
|
+
- CI: the LSP census and ast-grep version steps call the installed binaries (`node_modules/.bin/vitest`, `node_modules/.bin/ast-grep`) instead of an on-demand `npx`, which reds the nightly SonarCloud master gate on S6505/S8543 (refs #4339).
|
|
201
|
+
|
|
202
|
+
- Test grammars are prefetched once, in parallel, before any worker starts (#4342). The globalSetup fetches every grammar in the download list that no runtime grammar directory already holds. Each fetch stops after 15 s with no bytes and retries once on a retryable failure. A grammar still missing fails the run under CI and warns once locally, naming every grammar. The LSP symbol fallback test uses a python fixture instead of dart, which is not a core grammar.
|
|
203
|
+
|
|
204
|
+
- The real-factory bridge-rebind test no longer strands a `pi-lens-jscpd-*` directory in the shared tmpdir: its unawaited session_start scans now write their report roots under a root the test harness owns. The ast-grep temp-rule scan joins the same scanner temp-root seam. The tmp-fixture hygiene exemption scan now reads code, so a comment or string that mentions `os.tmpdir()` or `REPO_ROOT` no longer exempts a mkdtemp site (#4347).
|
|
205
|
+
|
|
206
|
+
- `guard-bash.mjs` denies agent writes under `/tmp` (redirects, `tee`, `cp`/`mv`/`install` targets, `mkdir`, `touch`), exempting the harness's `/tmp/claude-<uid>/` and reads (#4348).
|
|
207
|
+
|
|
208
|
+
- Make the `removeTempDirSync` give-up test deterministic by failing the directory's removal in the test instead of racing a live writer that a starved CI runner could not keep ahead of the first removal pass (closes #4349).
|
|
209
|
+
|
|
210
|
+
- CodeQL init steps run the same v4.38.3 codeql-action commit as analyze and upload-sarif again, and a config test reds when one action repository carries two SHAs anywhere in the workflow tree (#4350).
|
|
211
|
+
|
|
212
|
+
- The close-keyword lint rejects a closing keyword inside a markdown table row or after a negation or hedge ("not fixed: #N", "Whether this closes #N"), and `--verify-merged` reports an issue GitHub closed only through such a line (#4371).
|
|
213
|
+
|
|
214
|
+
- The infra-kill rerun workflow takes a PR number only from a `pull_request` run's entry of this repository at the run's own head, so a master push run no longer targets another repository's namesake PR (issue 23, HTTP 403), and a refused PR comment write logs one warning without stopping the classify-and-rerun step (#4382).
|
|
215
|
+
|
|
216
|
+
- **The tool-smoke handshake lane reports an alternate row whose server is missing as unavailable again (#4465)** — since #4411, a touch that no language server answered returns the `primary-unavailable` verdict, not nothing, when a primary is off in `lsp.disabledServers`. Every alternate row disables its default, so the official Docker row failed with "got no warm client" on a runner without the binary instead of skipping. The LSP scripts now share one reader of a touch result: that verdict means "no client ready", and a requested auxiliary counts only while it runs. `bench-lsp` stops reporting every installed server unavailable, and stops timing auxiliaries that never started; the clean-signal probe counts its dirty diagnostics again. The PR install smoke drops its copied official-Docker install step.
|
|
217
|
+
|
|
218
|
+
- The pre-push test selection keeps the change's own tests when it caps (#4489). A changed test file and the mirrored test of each changed source file now survive the 25-file cap and do not count against it. The cap keeps the best 25 import matches instead of none, ranked by fewest importers of the changed file first (specific files outrank hubs), ties by path. The capped warning and the header describe the rule. Replaying the 20 newest merged PRs recalls 113 of 113 own test files, up from 31.
|
|
219
|
+
|
|
220
|
+
- **Test drain waits for the jscpd scan (#4505)** — `drainBackgroundWritesForTests` now waits for a resident jscpd scan, and `JscpdClient.shutdown()` also waits for a pending availability probe and the scan behind it, so a test file that ends right after `session_start` no longer strands a `pi-lens-jscpd-*` report directory in the pre-push batch. Pinned by `tests/clients/test-utils-drain-jscpd-4505.test.ts` and the probe cases in `tests/clients/jscpd-client.test.ts`.
|
|
221
|
+
|
|
222
|
+
- Allow the nightly matrix cell floor to accept expiry justified by HEAD's first-missed date while retaining the hold on unexplained losses (#4537, #4538).
|
|
223
|
+
|
|
224
|
+
- One predicate now answers whether a mutation event is a bash-derived
|
|
225
|
+
replay, and both readers (`mutating-tool` and `runtime-tool-result`) call
|
|
226
|
+
it instead of spelling the marker check inline (closes #4623). Behaviour
|
|
227
|
+
is unchanged. Contributed by @Don-Yin.
|
|
228
|
+
|
|
229
|
+
- A `verify-pi-lens` skill gives reviewers and fixers reusable real-path probes (MCP launch, doctor, wire and diagnostics evidence, cleanup) for LSP diagnostics with Pyrefly, the formatter, the review graph and `rename_file`, seeded with the #4344 probe shapes (#4359).
|
|
230
|
+
|
|
231
|
+
- The flake-shape ratchet gains two detectors: `in-process-mock` pins which non-boundary modules each test file mocks with `vi.mock`/`vi.doMock`, and `service-double` pins how many hand-rolled LSP client and service doubles it holds. Process and host boundaries (child processes, the filesystem, the network, the pi SDK) stay free, and a new in-process mock or double needs a `// flake-shape:` admission header. For every detector, a baseline row past the digest-pinned frozen mint now needs its `ADMITTED_AFTER_BASELINE` entry and header; before, a baseline row alone admitted anything (#4362).
|
|
232
|
+
|
|
233
|
+
- A manual `stryker-pertest` workflow mutates one `clients/` module with the vitest runner and per-test coverage, and `scripts/stryker-pertest.mjs candidates` lists the tests that cover mutated code but kill no mutant; four no-throw-only tests it flagged and a stronger sibling subsumes are removed (#4365).
|
|
234
|
+
|
|
235
|
+
- Tests drain a log sink before reading its offset, line count or rows (#4381). `config-resolved-phase` and its session-wiring sibling read a `sessionstart.log` offset while their own lines were still queued. The new `tests/config/sink-read-drain-sweep.test.ts` fails any test read of an in-process sink that no awaited drain of that sink precedes.
|
|
236
|
+
|
|
237
|
+
- The 37 code-choice constants of `formal/dispatch-pipeline` and `formal/format-drain` are now pinned in `formal/switch-pins.json`, each to a live test that drives the real code, and both families join `FULLY_PINNED` (#4393). A new test pins the target hold of a whole-package fixer, which no test asserted. `tests/config/tla-switch-pins.test.ts` now compares a constant only with the pass configs of modules that name it, so a family with two modules can be pinned. `narrowedBy` now names the configs it admits, so a flipped value in one config is no longer hidden by a sibling's narrowing, and the `SiblingRestoreQueued.cfg` model now matches the code's three report-history switches.
|
|
238
|
+
|
|
239
|
+
- Every TLA+ config constant is now classified in `formal/switch-pins.json`, and each read-guard switch that mirrors a code choice names a test that drives the real code (#4393). `tests/config/tla-switch-pins.test.ts` fails when a constant is unclassified, a pin's test title is gone, or an `expect: pass` config disagrees with the pinned value, so a model can no longer keep proving a design the code dropped (#4387). Test-existence detectors now share `testDeclarations` (tests/support/sweep-kit.ts), so a skipped, conditional or uncalled test no longer counts as a pin.
|
|
240
|
+
|
|
241
|
+
- The install smoke's RPC check now runs a session the way an RPC child does and reds on any host exit, `extension_error`, warning or error notice, or pi-lens failure log line from start through the first turn (#4401). `scripts/rpc-load-check.mjs` drives `pi --mode rpc` against a local fake provider, waits for the warm-up and sends a prompt. `host-range-smoke` also runs it on the packed tarball with the snapshot persist worker forced to fail (`PI_LENS_TEST_SNAPSHOT_WORKER_FAIL`, a test-only seam in the worker), the #4400 shape. The old check passed #4314 because a failed warm is only a log line. The smoke project is pinned trusted through pi's own `defaultProjectTrust` setting, so the language-server spawn and warm are exercised; every warning fails, and a failed run prints the offending records and the log tails. `release-qa` and the compat smoke now share the one driver.
|
|
242
|
+
|
|
243
|
+
- Repo-wide shuffle census for order-dependent test files (#4405). `scripts/ci-shuffle-census.mjs` runs every test file under seeds 202 and 424 (and alone, weekly) through the changed-files job's planner and runner, re-runs each failing member alone under its seed, reruns a chunk whose member passed alone, and diffs the member files against `scripts/shuffle-census-ratchet.json`, keyed by file: a new member reds, an entry clean two nights running must leave the list, and a planned start with no record is unproven, never clean. The census test also checks that the census plans exactly the files the `vitest.config.ts` projects collect, so a config change that hides a file from every seed reds its own PR. `shuffle-census-nightly.yml` runs it as six parallel parts and keeps one tracking issue. The first pass found 33 order-dependent files of 1449 (seeds 202, 424 and 606); the leaked `vi.doMock` registrations in `index-integration` and `tree-sitter-runner`, the heartbeat case in `tmp-fixture-hygiene` (found by the CI rehearsal) and the leaked degradation ledger in `bounded-pid-file-lock` are fixed; the other 30 are listed by seam.
|
|
244
|
+
|
|
245
|
+
- **Population-deriving ratchets fail closed on an unclassified spelling (refs #4429)** — the single-flight, run-outcome, project-trust seam, delivery-surface and strictness scanners now classify every needle hit or report it with its file and line: single-flight and run-outcome read the TypeScript AST (a constructor parameter property is a field; a primitive counts only where the runner references the value at runtime), the project-trust seam scan keys on the mocked specifier (a seam string in argument position counts unless the callee is proven non-vitest, and any other position reds), delivery-surface reads markers by decoded value with comments blanked and bracket keys read like dots, and the strictness parser reports unlocated tsc diagnostics and ratchets every repo-root file the baseline does not exclude with a reason.
|
|
246
|
+
|
|
247
|
+
- The backstop grace-retry cases own their follow-up sweep instead of racing its wall-clock timer (#4488, recurrence of #2669). A `graceRetryScheduler` seam hands a case the retry options production composed, so a loaded runner can no longer fail "grace retry did not complete" and leak a 5 ms sweep into the next case. The `onComplete` seam now fires after the sweep's `finally` released the backstop lock, so a case that awaits it no longer ends with the release still in flight (#4581 review, thread 4303835287).
|
|
248
|
+
|
|
249
|
+
- **Nightly LSP scripts adopt project trust and report how much they evaluated (refs #4514)** — `characterize-lsp`, `probe-clean-signal`, `server-capabilities`, `measure-lsp-idle-eviction` and `bench-lsp` now adopt trust through `adoptFixtureProjectTrust`, so the launch gate stops refusing their servers (30 of 54 rows had read `unknown` since 2026-10-10); a census over `scripts/*.mjs` fails a script that reaches `dist/clients/lsp/` without it. A refused row reads `refused: <reason>`. A dedicated `assert-evaluated` step fails the nightly when a producer evaluates less than its measured floor, the handshake layer carries `--min-pass=40`, and the refresh PR does not open when a regenerated doc's count drops more than 20% against HEAD.
|
|
250
|
+
|
|
251
|
+
- **The continue-on-error census now covers the whole producer tree (refs #4518)** — every `continue-on-error` site in `.github/workflows/*.{yml,yaml}` and in a composite `.github/actions/**/action.yml|action.yaml` is registered in `tests/fixtures/nightly-floors/continue-on-error-sites.json` as floored (naming the gating step, whose floor must sit in the argument list of a named measuring invocation on a top-level line of its `run:`), no-floor (the reader that makes an empty result visible), or governed-by (the issue and the live `it`/`test` declarations of the tests that own it). The test derives the population from the parsed files, and both of its predicates are allowlists, so a shape neither lists refuses: an unregistered site, a producer file it cannot classify, a key outside a job or step, floor evidence that is not a measuring invocation's argument (a comment, a quoted command, a heredoc body, a printed command, a function or block body, a subshell, a chained command, or a line after an `exit`), and a floor step whose `if:` is anything but absent or a conjunction of `always()`, `success()`, `!cancelled()` and one `github.event_name` or `matrix.<name>` comparison the workflow's own `on:` block or that job's `strategy.matrix` lists, of a job whose `if:` the document cannot prove never true. A `\`-continued or `&&`-continued line is one command, so a floor must start the joined command, and an `exit` ends the script only in command position. The Swift crash-watch step also prints `evaluated=<0|1>` to its log, so a night read from the job log alone states whether swift was evaluated.
|
|
252
|
+
|
|
253
|
+
- **Every continue-on-error producer outside the LSP nightly proves it evaluated something (refs #4518)** — the APFS anchors leg, the Swift crash-watch, the PR-time LSP handshake and the complexity report now fail on a measured floor through `assertEvaluated`, which gained a `--count <producer>=<n> --floor <m>` door; the `--min-pass` pass floor, the shuffle census's missing-start check and the complexity file count fold onto it. The strictness, jscpd, yamllint, typos, taplo and mise-repro jobs carry a header naming why they need no floor. The helper no longer imports `dist/` (through `clean-signal.mjs`), so the refresh-PR job's `assert-evaluated --docs` runs on its fresh checkout instead of failing every night.
|
|
254
|
+
|
|
255
|
+
- The nightly LSP witness's born-after-start phase now waits for the server to report the importer's missing import, creates the module only then, and polls to a deadline for the verdict to change instead of reading one call (refs #4521). The one-call row red-flaked on pyright (2 of 5 local runs) while a server that never picks the file up still reds at the deadline; a vitest case drives the phase over a real stdio language-server wire.
|
|
256
|
+
|
|
257
|
+
- **Runner captures now record the availability probe and a real clean run, and one gate decides whether a record is evidence.** Fixtures under `tests/fixtures/runner-output/` carry the argv, binary, and exit status their runner's availability checker receives, plus the bytes of a clean run of the fixture's own invocation in `tests/fixtures/runner-output-clean/`; the replay stops mocking the checker, so a tool that rejects its own probe reaches it, and the tool-state matrix's clean cell replays captured bytes instead of `""`. `recordProblems` (`scripts/capture-runner-output.mjs`) refuses a probe whose recorded version its own answer never printed, and a clean run whose argv is not this fixture's invocation: the capture calls it before writing, and the fixture census calls it over every committed fixture. `scripts/capture-runner-fixtures.mjs --records-only` retrofits the records onto an existing fixture without re-recording its work bytes, keyed on the fixture's own case, and there is no prose escape for a missing clean run.
|
|
258
|
+
|
|
259
|
+
- The shared Git fixture helpers point `GIT_CONFIG_GLOBAL` at one checked-in
|
|
260
|
+
policy file, `tests/fixtures/git-fixture-global.gitconfig`, which disables
|
|
261
|
+
background maintenance (`maintenance.auto=false`, `gc.auto=0`) for every
|
|
262
|
+
fixture Git process. A fixture `git commit` therefore no longer forks the
|
|
263
|
+
detached `git maintenance run --auto --quiet --detach` child that outlived
|
|
264
|
+
the cell and wrote inside the fixture's `.git` while `afterEach` teardown
|
|
265
|
+
removed it, so the gating Unit shard stops failing
|
|
266
|
+
`tests/scripts/check-pr-body.test.ts` in teardown with `ENOTEMPTY` instead of
|
|
267
|
+
in an assertion (closes #4552).
|
|
268
|
+
|
|
269
|
+
- **Nine order-dependent test files now reset the process state their siblings left behind (#4561)** — the shuffle census ratchet drops from 29 files to 19: the ast-grep, dispatch-reset and managed-bin cases restore the mocks or stubs they install, the sgconfig, batch-cache, index-2992, cwd-probe, word-index and cooldown cases reset the shared ledger, latch or telemetry ring through its own seam, and the read-bridge row is deleted now that #4549 keeps its file clean under every seed.
|
|
270
|
+
|
|
271
|
+
- **Six more shuffle-census ratchet rows are gone: three declaration-order files isolate their own cases, three opt out per `describe` (#4561)** — the io-bridge and process-bridge-registration files capture their "no bridge yet" observation in a file-level hook, the packaging manifest suite shares one real `npm pack` through a memo so either order pays it, and the two `onTestFinished` backstop suites plus the fake-timer drain suite register in the ratchet's `byDesign` rows with `{ shuffle: false }` because the order of their cases is the mechanism under test. The pinned ratchet count drops from 19 files to 13, and `tests/scripts/ci-shuffle-census.test.ts` now checks the by-design registry in both directions: an unregistered opt-out, or a registry row with no `{ shuffle: false }` describe, reds. The reader behind that check admits only the minimal marker over the parsed syntax tree: an options object is read as a sequence (a spread or an unnameable computed key refuses even beside a readable `shuffle`, and the last key wins), and a callee resolves through any number of aliases.
|
|
272
|
+
|
|
273
|
+
- A `tests/config` sweep now censuses how the tree composes the `PATH`
|
|
274
|
+
environment variable, because Windows' environment block is
|
|
275
|
+
case-insensitive while a `ProcessEnv` object is not: a composition that
|
|
276
|
+
writes both `PATH` and `Path`, or writes `PATH` over a spread that supplied
|
|
277
|
+
`Path`, or `Object.assign`/`Object.fromEntries` builds one over a base,
|
|
278
|
+
hands a child two names for one variable and one shadows the other
|
|
279
|
+
(closes #4590). The rule is decided from the syntax tree — which literal
|
|
280
|
+
holds the key, which object the key is written into, how a binding
|
|
281
|
+
resolves — not from key spellings, so a formatted multi-line literal or an
|
|
282
|
+
indirect write cannot slip past it. The 68 current sites (13 shipped, 55 in
|
|
283
|
+
test fixtures) are seeded as debt with their reason; a new site, a stale row
|
|
284
|
+
or a changed reason reds. Commented env members and later constants keep
|
|
285
|
+
their evidence, and a spread of a parenthesised, asserted, satisfied or
|
|
286
|
+
non-null `process.env` is still the ambient block. A computed key is either
|
|
287
|
+
proved — a quoted string, a substitution-free template, or a `const` chain
|
|
288
|
+
of those literals — or reported: a cast, a substituted template, a
|
|
289
|
+
concatenation, a call, a non-null or comma key, a mutable
|
|
290
|
+
binding, a cycle or a wrapper is `unclassified-spelling`, never a silent
|
|
291
|
+
pass. A key is read by its cooked value, so `"Pa\x74h"` is `Path` and
|
|
292
|
+
`\u0050ATH` is `PATH` whatever the source spells; a string the reader cannot
|
|
293
|
+
decode (a legacy octal escape) reports rather than passing, and a comment
|
|
294
|
+
never decides that a name is about the variable.
|
|
295
|
+
|
|
296
|
+
- **The AST governance sweeps share one census seam (#4606).** `tests/support/ast-census.ts` owns the pre-order walk, the single parse behind it, and the census file list with its `.js`-twin rule; each sweep keeps its own roots and admission tables, so no detector rule and no admission row moves. `tests/support/silent-catch-scan.ts`, `tests/support/worker-message-scan.ts` and `tests/clients/socket-error-listener-sweep.test.ts` load the same seam instead of a copy of the walk. A new guard derives both of its populations from code rather than from lists: the consumer set is every file whose parsed import or re-export resolves to the seam, and the site set is the child-accessor family, so a second traversal — a `namedChildren().forEach` recursion, a `child(i)`/`childCount()` loop, a destructured or reflective accessor, a call through a computed subscript key — reds by file and line. Admission is per live site with a declared count, so a verbatim copy of an admitted line reds, and a second module instance, or a list that stops matching the tree, reds too.
|
|
297
|
+
|
|
298
|
+
</details>
|
|
299
|
+
|
|
300
|
+
## [4.4.3] - 2026-10-11
|
|
301
|
+
|
|
302
|
+
### Added
|
|
303
|
+
|
|
304
|
+
- **Adopted out-of-root projects now get global, config-free per-file linters** — when an edit lands in a marked project outside the session root, pi-lens admits only the explicit admission list of dispatch runners that read no project config and cannot execute project code (`php-lint`, `fish-indent`), and only from global or pi-lens-managed binaries; the advisory now says the LSP plus these per-file linters run. Executable paths inside the adopted or session root are refused, including PATH entries and symlink targets. Formatting, autofix, other dispatch runners, whole-project scanners, and automatic tests stay off (refs #4242).
|
|
305
|
+
|
|
306
|
+
### Fixed
|
|
307
|
+
|
|
308
|
+
- **LSP warms again in the published package (closes #4314)** — 4.4.2 loaded the LSP stack through a computed `import()` that esbuild could not inline, so every session logged `LSP warm failed: Cannot find module './lsp/capabilities.js'` and the LSP-backed tools went dead. The import is a literal again, and a packaging test now fails the build if any shipped file dynamic-imports something the tarball does not carry.
|
|
309
|
+
|
|
310
|
+
### Internal
|
|
311
|
+
|
|
312
|
+
<details>
|
|
313
|
+
<summary>4 internal changes: tests, CI, tooling, and refactors</summary>
|
|
314
|
+
|
|
315
|
+
- **tmp-hygiene attributes a leaked root to the test file that created it** — the tmp-root interposer records one creator per entry, the serialized owner's failure message names that file beside the prefix owner, and an entry with no observed creator is labelled `created outside the test process, or the creators record was unreadable` instead of taking a prefix owner's blame. (#2912)
|
|
316
|
+
|
|
317
|
+
- Add an end-to-end witness that a save-only LSP server's diagnostics reach pi-lens exactly when `textDocument/didSave` is sent: the fake stdio server publishes only from its `didSave` branch (`FAKE_LSP_PUBLISH_ON_SAVE`), and a real `createLSPClient` reads the seeded diagnostic after a save touch and none without one (refs #3405).
|
|
318
|
+
|
|
319
|
+
- Keep a killed scanner scan's report directory inside a harness-owned root: one shared seam (`PI_LENS_TEST_SCANNER_TMPDIR`, `clients/scanner-temp-root.ts`) now parents jscpd, gitleaks, opengrep, and trivy report directories, and the MCP and real-pi test harnesses apply owned scanner roots after caller environment overrides, so even a TMPDIR-overriding child SIGKILLed mid-scan no longer strands a `pi-lens-<scanner>-*` entry in the shared tmpdir and reds `tmp-fixture-hygiene` (refs #4133). The scan-setup cleanup guard is retained as defence-in-depth.
|
|
320
|
+
|
|
321
|
+
- Flake-shape ratchet: yield the event loop between files so `@ast-grep/napi` releases each parsed tree's native memory, cutting the test file's peak RSS from ~2,049 MB (over the 2,048 MB per-worker budget) to ~313 MB (refs #4292).
|
|
322
|
+
|
|
323
|
+
</details>
|
|
324
|
+
|
|
19
325
|
## [4.4.2] - 2026-10-10
|
|
20
326
|
|
|
21
327
|
### Added
|
package/README.md
CHANGED
|
@@ -468,7 +468,7 @@ Thanks goes to these wonderful people:
|
|
|
468
468
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/marioSoftmedic"><img src="https://avatars.githubusercontent.com/u/44755030?v=4" width="100px;" alt=""/><br /><sub><b>marioHealthSoft</b></sub></a><br /><a href="#ideas-marioSoftmedic" title="Ideas & Planning">🤔</a></td>
|
|
469
469
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/OrestesK"><img src="https://avatars.githubusercontent.com/u/56927612?v=4" width="100px;" alt=""/><br /><sub><b>Orestes Kassian</b></sub></a><br /><a href="#ideas-OrestesK" title="Ideas & Planning">🤔</a></td>
|
|
470
470
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/vanyastaff"><img src="https://avatars.githubusercontent.com/u/24690216?v=4" width="100px;" alt=""/><br /><sub><b>vanyastaff</b></sub></a><br /><a href="#bug-vanyastaff" title="Bug reports">🐛</a></td>
|
|
471
|
-
<td align="center" valign="top" width="14.28%"><a href="https://github.com/EightDoor"><img src="https://avatars.githubusercontent.com/u/35022264?v=4" width="100px;" alt=""/><br /><sub><b>周凯</b></sub></a><br /><a href="#code-EightDoor" title="Code">💻</a></td>
|
|
471
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/EightDoor"><img src="https://avatars.githubusercontent.com/u/35022264?v=4" width="100px;" alt=""/><br /><sub><b>周凯</b></sub></a><br /><a href="#code-EightDoor" title="Code">💻</a> <a href="#bug-EightDoor" title="Bug reports">🐛</a></td>
|
|
472
472
|
</tr>
|
|
473
473
|
<tr>
|
|
474
474
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/pikachu01"><img src="https://avatars.githubusercontent.com/u/1090194?v=4" width="100px;" alt=""/><br /><sub><b>pikachu01</b></sub></a><br /><a href="#ideas-pikachu01" title="Ideas & Planning">🤔</a></td>
|
|
@@ -483,6 +483,18 @@ Thanks goes to these wonderful people:
|
|
|
483
483
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/pandas9"><img src="https://avatars.githubusercontent.com/u/81386636?v=4" width="100px;" alt=""/><br /><sub><b>pandas9</b></sub></a><br /><a href="#bug-pandas9" title="Bug reports">🐛</a></td>
|
|
484
484
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/renzooliveira"><img src="https://avatars.githubusercontent.com/u/10881850?v=4" width="100px;" alt=""/><br /><sub><b>Renzo Oliveira</b></sub></a><br /><a href="#ideas-renzooliveira" title="Ideas & Planning">🤔</a></td>
|
|
485
485
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/rastarr"><img src="https://avatars.githubusercontent.com/u/2160922?v=4" width="100px;" alt=""/><br /><sub><b>Martin</b></sub></a><br /><a href="#bug-rastarr" title="Bug reports">🐛</a> <a href="#ideas-rastarr" title="Ideas & Planning">🤔</a></td>
|
|
486
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/LiangNiang"><img src="https://avatars.githubusercontent.com/u/20347803?v=4" width="100px;" alt=""/><br /><sub><b>Hershel Hale</b></sub></a><br /><a href="#code-LiangNiang" title="Code">💻</a></td>
|
|
487
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/loishi"><img src="https://avatars.githubusercontent.com/u/59756145?v=4" width="100px;" alt=""/><br /><sub><b>Loishi</b></sub></a><br /><a href="#code-loishi" title="Code">💻</a> <a href="#bug-loishi" title="Bug reports">🐛</a></td>
|
|
488
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/gkgoat1"><img src="https://avatars.githubusercontent.com/u/65402989?v=4" width="100px;" alt=""/><br /><sub><b>gkgoat</b></sub></a><br /><a href="#code-gkgoat1" title="Code">💻</a></td>
|
|
489
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/op15066212"><img src="https://avatars.githubusercontent.com/u/108321891?v=4" width="100px;" alt=""/><br /><sub><b>15066212pp</b></sub></a><br /><a href="#bug-op15066212" title="Bug reports">🐛</a></td>
|
|
490
|
+
</tr>
|
|
491
|
+
<tr>
|
|
492
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/SlanyCukr"><img src="https://avatars.githubusercontent.com/u/35649784?v=4" width="100px;" alt=""/><br /><sub><b>Marek Hovadík</b></sub></a><br /><a href="#bug-SlanyCukr" title="Bug reports">🐛</a></td>
|
|
493
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/timges"><img src="https://avatars.githubusercontent.com/u/13063135?v=4" width="100px;" alt=""/><br /><sub><b>Tim Gesemann</b></sub></a><br /><a href="#bug-timges" title="Bug reports">🐛</a></td>
|
|
494
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/SamYue1"><img src="https://avatars.githubusercontent.com/u/30651023?v=4" width="100px;" alt=""/><br /><sub><b>SamYue</b></sub></a><br /><a href="#bug-SamYue1" title="Bug reports">🐛</a></td>
|
|
495
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/VladimirGVP"><img src="https://avatars.githubusercontent.com/u/322008768?v=4" width="100px;" alt=""/><br /><sub><b>VladimirGVP</b></sub></a><br /><a href="#bug-VladimirGVP" title="Bug reports">🐛</a></td>
|
|
496
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/bear0328"><img src="https://avatars.githubusercontent.com/u/32119214?v=4" width="100px;" alt=""/><br /><sub><b>bear0328</b></sub></a><br /><a href="#bug-bear0328" title="Bug reports">🐛</a></td>
|
|
497
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/beardrux"><img src="https://avatars.githubusercontent.com/u/310295749?v=4" width="100px;" alt=""/><br /><sub><b>beardrux</b></sub></a><br /><a href="#bug-beardrux" title="Bug reports">🐛</a></td>
|
|
486
498
|
</tr>
|
|
487
499
|
</tbody>
|
|
488
500
|
</table>
|
|
@@ -1,28 +1,30 @@
|
|
|
1
1
|
import { createRequire as __pilensCreateRequire } from "node:module"; const require = __pilensCreateRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
scannerReportParentDir
|
|
4
|
+
} from "./chunk-ED36RF6Y.js";
|
|
2
5
|
import {
|
|
3
6
|
getSecretsLaneAllowlistPaths,
|
|
4
7
|
isUnderSecretsLaneScratchTree,
|
|
5
8
|
nestedWorktreeOffsets
|
|
6
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-BWD5EPKO.js";
|
|
7
10
|
import {
|
|
8
11
|
SecurityScanClient
|
|
9
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-TIQ2PKDW.js";
|
|
10
13
|
import {
|
|
11
14
|
collectTrackedFiles,
|
|
12
15
|
collectUntrackedIgnoredIds,
|
|
13
16
|
safeSpawnAsync
|
|
14
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-KUA4OJ5Y.js";
|
|
15
18
|
import {
|
|
16
19
|
escapeRegExp
|
|
17
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-J64T55EI.js";
|
|
18
21
|
import {
|
|
19
22
|
normalizeEphemeralMapKey,
|
|
20
23
|
normalizeMapKey
|
|
21
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-3RKP3FMS.js";
|
|
22
25
|
|
|
23
26
|
// dist/clients/gitleaks-client.js
|
|
24
27
|
import * as fs from "node:fs";
|
|
25
|
-
import * as os from "node:os";
|
|
26
28
|
import * as path from "node:path";
|
|
27
29
|
import { mkdtempSync } from "node:fs";
|
|
28
30
|
var EMPTY_RESULT = {
|
|
@@ -185,7 +187,7 @@ var GitleaksClient = class _GitleaksClient extends SecurityScanClient {
|
|
|
185
187
|
async runScan(cwd) {
|
|
186
188
|
const scannedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
187
189
|
const bin = this.binaryPath ?? "gitleaks";
|
|
188
|
-
const outDir = mkdtempSync(path.join(
|
|
190
|
+
const outDir = mkdtempSync(path.join(scannerReportParentDir(), "pi-lens-gitleaks-"));
|
|
189
191
|
const reportPath = path.join(outDir, "gitleaks-report.json");
|
|
190
192
|
try {
|
|
191
193
|
const configPath = writeScopedGitleaksConfig(outDir, cwd);
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
import { createRequire as __pilensCreateRequire } from "node:module"; const require = __pilensCreateRequire(import.meta.url);
|
|
2
2
|
import {
|
|
3
3
|
collectSourceFiles
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
6
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-DGUKIYSI.js";
|
|
5
|
+
import "./chunk-XBJVAEEO.js";
|
|
6
|
+
import "./chunk-KUA4OJ5Y.js";
|
|
7
7
|
import "./chunk-QYF5S6P6.js";
|
|
8
|
-
import "./chunk-
|
|
9
|
-
import "./chunk-
|
|
10
|
-
import "./chunk-
|
|
8
|
+
import "./chunk-DUO2TU77.js";
|
|
9
|
+
import "./chunk-J6MPGQTS.js";
|
|
10
|
+
import "./chunk-5HSRT26B.js";
|
|
11
11
|
import "./chunk-2EECBNC5.js";
|
|
12
|
-
import "./chunk-
|
|
13
|
-
import "./chunk-
|
|
12
|
+
import "./chunk-J64T55EI.js";
|
|
13
|
+
import "./chunk-POMLXPMY.js";
|
|
14
14
|
import "./chunk-JMYYOTQG.js";
|
|
15
|
-
import "./chunk-
|
|
16
|
-
import "./chunk-
|
|
15
|
+
import "./chunk-QBFBRV2B.js";
|
|
16
|
+
import "./chunk-YNE3RP52.js";
|
|
17
17
|
import "./chunk-LJDODBBZ.js";
|
|
18
18
|
import "./chunk-ABBOA7UD.js";
|
|
19
|
-
import "./chunk-
|
|
19
|
+
import "./chunk-3RKP3FMS.js";
|
|
20
20
|
import "./chunk-NXL4FFQQ.js";
|
|
21
21
|
|
|
22
22
|
// dist/clients/todo-scanner.js
|