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.
Files changed (143) hide show
  1. package/CHANGELOG.md +306 -0
  2. package/README.md +13 -1
  3. package/dist/{chunk-VITUCP5A.js → chunk-24AKHFGP.js} +9 -7
  4. package/dist/{chunk-JSQFTSC6.js → chunk-2FN63LPV.js} +11 -11
  5. package/dist/{chunk-CF24OIYI.js → chunk-2KXTHF3M.js} +20 -20
  6. package/dist/{chunk-ESBQHFMJ.js → chunk-3DA2JDOL.js} +543 -349
  7. package/dist/{chunk-RWWYJ623.js → chunk-3KAZU3VR.js} +1 -1
  8. package/dist/chunk-3PTHXJXR.js +28 -0
  9. package/dist/chunk-3QED2P4V.js +31 -0
  10. package/dist/{chunk-N2H3ZPZY.js → chunk-3RKP3FMS.js} +1 -0
  11. package/dist/{chunk-XVF2VVXP.js → chunk-5HSRT26B.js} +2 -2
  12. package/dist/{chunk-XKIZ2HNN.js → chunk-5IUSNGOC.js} +18 -16
  13. package/dist/{chunk-EJIUXPBX.js → chunk-5OFV52AM.js} +1 -1
  14. package/dist/{chunk-AW7R2GWG.js → chunk-5VSF4UUQ.js} +12 -7
  15. package/dist/{chunk-NW7BOILN.js → chunk-5WRPDP2A.js} +18 -18
  16. package/dist/{chunk-7ULNCDCO.js → chunk-6DLWKFEX.js} +35 -34
  17. package/dist/{chunk-WOA4LN2R.js → chunk-7D6L4MTX.js} +2 -2
  18. package/dist/{chunk-GAEGEPOC.js → chunk-7HXE67BN.js} +2 -2
  19. package/dist/chunk-AGSS7277.js +84 -0
  20. package/dist/{chunk-R3PCY3YS.js → chunk-AMERCH6V.js} +13 -13
  21. package/dist/{chunk-WU567RSC.js → chunk-ATKJFDQN.js} +14 -10
  22. package/dist/{chunk-VSSBEUFT.js → chunk-AWSCPCN2.js} +16 -16
  23. package/dist/{chunk-VCHXCQFG.js → chunk-BWD5EPKO.js} +3 -3
  24. package/dist/{chunk-ODBNXWT4.js → chunk-CT4MN5IU.js} +1271 -262
  25. package/dist/{chunk-34WSVGEM.js → chunk-CUHSHSB6.js} +83 -5
  26. package/dist/{chunk-CHOW74WE.js → chunk-CYRKQRPM.js} +7 -10
  27. package/dist/{chunk-Q7DUK4LX.js → chunk-DEL2U6CM.js} +5 -5
  28. package/dist/{chunk-6C7JWPCP.js → chunk-DGUKIYSI.js} +26 -23
  29. package/dist/{chunk-PHDMFAKM.js → chunk-DUO2TU77.js} +2 -2
  30. package/dist/{chunk-MRXKYPCS.js → chunk-EBC36LNN.js} +2 -2
  31. package/dist/chunk-ED36RF6Y.js +17 -0
  32. package/dist/{chunk-76MWC2K6.js → chunk-EUXZEFTV.js} +90 -97
  33. package/dist/{chunk-QOZXJ4NA.js → chunk-FM4RDMKT.js} +11 -8
  34. package/dist/{chunk-66VMUL7Q.js → chunk-GAQ7OQG6.js} +31 -28
  35. package/dist/{chunk-Q6ZWTCLY.js → chunk-GL5LUMZE.js} +23 -24
  36. package/dist/{chunk-BKE7E7TC.js → chunk-GMX5OS7B.js} +9 -9
  37. package/dist/{chunk-ZHFZWTZV.js → chunk-GSLNCYGG.js} +11 -11
  38. package/dist/{chunk-ARJ6PLFH.js → chunk-GSM5UU7A.js} +26 -34
  39. package/dist/{chunk-724UL2TJ.js → chunk-GXFV6MYU.js} +3 -3
  40. package/dist/chunk-HAD7QRQI.js +30 -0
  41. package/dist/{chunk-QOBUHWHX.js → chunk-HRNHZA3N.js} +6 -6
  42. package/dist/{chunk-VWCXW2E7.js → chunk-HWCCCQNX.js} +1 -1
  43. package/dist/{chunk-7DYBLXYA.js → chunk-HYGZEMX6.js} +3 -3
  44. package/dist/chunk-IJYGIDF4.js +186 -0
  45. package/dist/{chunk-4W4VLAPJ.js → chunk-IQWN5JCP.js} +11 -11
  46. package/dist/{chunk-GZYBXISR.js → chunk-IUE7SSKV.js} +9 -9
  47. package/dist/{chunk-IBGIOBEE.js → chunk-IWDLU6K3.js} +68 -5
  48. package/dist/{chunk-XCJN5IJU.js → chunk-IYNJ6YPZ.js} +4 -4
  49. package/dist/{chunk-JAU72XNY.js → chunk-J64T55EI.js} +3 -3
  50. package/dist/{chunk-2BBLFX75.js → chunk-J6MPGQTS.js} +5 -5
  51. package/dist/chunk-JORED7EX.js +44 -0
  52. package/dist/{chunk-6SDEP3AJ.js → chunk-JUKMK5Y4.js} +8 -8
  53. package/dist/{chunk-IBRALRBR.js → chunk-JW3YWBVB.js} +15 -14
  54. package/dist/{chunk-L3BLXZG7.js → chunk-KAPJINKN.js} +30 -69
  55. package/dist/{chunk-2B73ZPOK.js → chunk-KN35K52N.js} +3 -3
  56. package/dist/{chunk-C34ZXZ7O.js → chunk-KUA4OJ5Y.js} +24 -11
  57. package/dist/chunk-KYUB6QDC.js +72 -0
  58. package/dist/{chunk-67WMNWEE.js → chunk-LLJNQKQW.js} +12 -10
  59. package/dist/chunk-LZSEQW6A.js +57 -0
  60. package/dist/{chunk-ZANLILYD.js → chunk-M2IFVFWU.js} +2 -2
  61. package/dist/chunk-M2UD3LPU.js +88 -0
  62. package/dist/{chunk-WOKKRCJ5.js → chunk-MBKQHGA7.js} +15 -14
  63. package/dist/{chunk-P7266HLM.js → chunk-MPNJLASA.js} +2 -2
  64. package/dist/{chunk-6GFRNVYR.js → chunk-MZGQNL3Y.js} +10 -10
  65. package/dist/{chunk-QZVW3AAK.js → chunk-N5FLEO7W.js} +33 -31
  66. package/dist/{chunk-3JNKMP3B.js → chunk-O4NWQT6I.js} +4 -4
  67. package/dist/{chunk-PFNSLWU5.js → chunk-OHKJ7PQF.js} +33 -31
  68. package/dist/{chunk-VRZP76YM.js → chunk-P63GXC37.js} +4 -1
  69. package/dist/{chunk-XTMJ7XDT.js → chunk-POMLXPMY.js} +1 -1
  70. package/dist/{chunk-QQNQ7SJW.js → chunk-QBFBRV2B.js} +10 -4
  71. package/dist/{chunk-3UUPUE3O.js → chunk-R3T7CLQ2.js} +84 -60
  72. package/dist/{chunk-23WOO7TX.js → chunk-R7YOCSNV.js} +10 -10
  73. package/dist/{chunk-2CD5RS6M.js → chunk-RKBXZN3A.js} +23 -2
  74. package/dist/{chunk-OG5UUPN7.js → chunk-RKKOYPRA.js} +14 -14
  75. package/dist/{chunk-52FUZI6K.js → chunk-RMLC67LF.js} +1 -1
  76. package/dist/{chunk-XMSXXAHW.js → chunk-ROYSX2S5.js} +2 -2
  77. package/dist/{chunk-PV6LR5P5.js → chunk-RVK72CZX.js} +9605 -14776
  78. package/dist/{chunk-FLKWCIZH.js → chunk-S2HEXIWI.js} +5 -5
  79. package/dist/{chunk-UZ3TUTRV.js → chunk-S67KYG37.js} +1 -1
  80. package/dist/{chunk-JWA5I3SR.js → chunk-SD4I7ZO4.js} +16 -18
  81. package/dist/{chunk-Z527N6YQ.js → chunk-SKAMFC7A.js} +12 -12
  82. package/dist/{chunk-MQLUAEOC.js → chunk-SRYEJNES.js} +2 -2
  83. package/dist/{chunk-BRGDSBJE.js → chunk-SYE6T2EB.js} +12 -12
  84. package/dist/{chunk-GXW6K4YJ.js → chunk-T57LYLZT.js} +1 -1
  85. package/dist/{chunk-ZXYDQ3TZ.js → chunk-T7FEBKXN.js} +48 -15
  86. package/dist/{chunk-IXXDFY73.js → chunk-TIQ2PKDW.js} +3 -3
  87. package/dist/{chunk-QILIYSJF.js → chunk-TLX72JTG.js} +1 -1
  88. package/dist/{chunk-TYM6OUIT.js → chunk-TOY6QKOH.js} +12 -12
  89. package/dist/{chunk-IZSHAIJ7.js → chunk-TRETHTQO.js} +12 -12
  90. package/dist/{chunk-3VPVG5SU.js → chunk-U44DJXQP.js} +10 -6
  91. package/dist/{chunk-PNQDPDRC.js → chunk-UA7ZXYDR.js} +1 -1
  92. package/dist/{chunk-OXXKQCSV.js → chunk-UBOQLVT2.js} +4 -4
  93. package/dist/{chunk-QGQ5CCCG.js → chunk-UMS2MFPZ.js} +2 -2
  94. package/dist/{chunk-QHJ6DQ2R.js → chunk-US7ECLD7.js} +55 -13
  95. package/dist/{chunk-EETY4LF7.js → chunk-UZ2XCLYS.js} +8 -8
  96. package/dist/chunk-V35JG4JB.js +2667 -0
  97. package/dist/{chunk-N3X6Q4YT.js → chunk-VOOAPROD.js} +3 -3
  98. package/dist/{chunk-SAEFPSB6.js → chunk-W66O5E25.js} +21 -11
  99. package/dist/{chunk-KBG47GHP.js → chunk-WMYE35PX.js} +15 -11
  100. package/dist/{chunk-5NGR67U5.js → chunk-XBJVAEEO.js} +32 -5
  101. package/dist/chunk-XHJBS436.js +4217 -0
  102. package/dist/{chunk-GBPP5P7X.js → chunk-XTBDCR27.js} +4 -4
  103. package/dist/{chunk-4UNXBNE2.js → chunk-XTFDJI3T.js} +11 -11
  104. package/dist/{chunk-UXGETRP4.js → chunk-XYURS6EN.js} +13 -12
  105. package/dist/{chunk-RPLINGVB.js → chunk-Y6GZHWSJ.js} +6 -6
  106. package/dist/{chunk-2ZGIZPDG.js → chunk-YCWYO6TQ.js} +41 -38
  107. package/dist/{chunk-66SXUJCA.js → chunk-YH5MVY2U.js} +10 -8
  108. package/dist/{chunk-AOHZU2BM.js → chunk-YNE3RP52.js} +1 -1
  109. package/dist/{chunk-GOYRYZ2R.js → chunk-ZHH37KZL.js} +72 -20
  110. package/dist/{chunk-ZGDGW2V3.js → chunk-ZIVJBOO3.js} +2 -2
  111. package/dist/{chunk-GHB6AYCP.js → chunk-ZVUL7QYY.js} +29 -28
  112. package/dist/index.js +11559 -6213
  113. package/dist/mcp/analyze-cli.js +10 -9
  114. package/dist/mcp/cli.js +41 -38
  115. package/dist/mcp/server.js +1142 -495
  116. package/dist/mcp/worker.js +61 -57
  117. package/dist/probes/installer.js +19 -19
  118. package/dist/workers/project-snapshot-persist-worker.js +28 -9
  119. package/dist/workers/review-graph-persist-worker.js +1 -1
  120. package/docs/configuration.md +11 -0
  121. package/docs/engineering-principles.md +32 -0
  122. package/docs/lsp-capability-matrix.md +1 -1
  123. package/docs/lsp-idle-eviction.md +15 -26
  124. package/docs/pi-lens-fixer.md +117 -13
  125. package/docs/pi-lens-merge-policy.md +109 -11
  126. package/docs/pi-lens-monitor.md +8 -2
  127. package/docs/pi-lens-retro.md +4 -1
  128. package/docs/pi-lens-reviewer.md +114 -3
  129. package/docs/pi-lens-subagent.md +27 -14
  130. package/docs/release-qa-baseline.md +3 -1
  131. package/docs/servercapabilities.md +9 -19
  132. package/package.json +7 -5
  133. package/scripts/download-grammars.js +40 -7
  134. package/scripts/lib/latency-log-phases.mjs +95 -0
  135. package/scripts/rpc-load-check.mjs +570 -81
  136. package/dist/chunk-3WA4HP4I.js +0 -55
  137. package/dist/chunk-466Z26MG.js +0 -30
  138. package/dist/chunk-5JM2524W.js +0 -84
  139. package/dist/chunk-CO2G5PHH.js +0 -84
  140. package/dist/chunk-LOP6GRPH.js +0 -31
  141. package/dist/chunk-UOPVJQ4N.js +0 -103
  142. package/dist/chunk-XPZPXCBV.js +0 -28
  143. /package/dist/{chunk-FC4RP4I2.js → chunk-6HWVI6MI.js} +0 -0
@@ -2,79 +2,83 @@
2
2
  import { createRequire as __pilensCreateRequire } from "node:module"; const require = __pilensCreateRequire(import.meta.url);
3
3
  import {
4
4
  analyzeFile
5
- } from "../chunk-ODBNXWT4.js";
6
- import "../chunk-OXXKQCSV.js";
7
- import "../chunk-EJIUXPBX.js";
8
- import "../chunk-FC4RP4I2.js";
9
- import "../chunk-66SXUJCA.js";
10
- import "../chunk-VCHXCQFG.js";
11
- import "../chunk-IXXDFY73.js";
12
- import "../chunk-MRXKYPCS.js";
13
- import "../chunk-2CD5RS6M.js";
14
- import "../chunk-UZ3TUTRV.js";
15
- import "../chunk-EETY4LF7.js";
16
- import "../chunk-GOYRYZ2R.js";
17
- import "../chunk-RPLINGVB.js";
18
- import "../chunk-XCJN5IJU.js";
19
- import "../chunk-76MWC2K6.js";
20
- import "../chunk-GXW6K4YJ.js";
21
- import "../chunk-2B73ZPOK.js";
22
- import "../chunk-XMSXXAHW.js";
23
- import "../chunk-7DYBLXYA.js";
24
- import "../chunk-PV6LR5P5.js";
5
+ } from "../chunk-CT4MN5IU.js";
6
+ import "../chunk-UBOQLVT2.js";
7
+ import "../chunk-5OFV52AM.js";
8
+ import "../chunk-RKBXZN3A.js";
9
+ import "../chunk-6HWVI6MI.js";
10
+ import "../chunk-S67KYG37.js";
11
+ import "../chunk-KN35K52N.js";
12
+ import "../chunk-ROYSX2S5.js";
13
+ import "../chunk-YH5MVY2U.js";
14
+ import "../chunk-ED36RF6Y.js";
15
+ import "../chunk-BWD5EPKO.js";
16
+ import "../chunk-TIQ2PKDW.js";
17
+ import "../chunk-EBC36LNN.js";
18
+ import "../chunk-HYGZEMX6.js";
19
+ import "../chunk-UZ2XCLYS.js";
20
+ import "../chunk-ZHH37KZL.js";
21
+ import "../chunk-Y6GZHWSJ.js";
22
+ import "../chunk-IYNJ6YPZ.js";
23
+ import "../chunk-EUXZEFTV.js";
24
+ import "../chunk-T57LYLZT.js";
25
+ import "../chunk-XHJBS436.js";
25
26
  import "../chunk-3MED75HT.js";
26
- import "../chunk-CO2G5PHH.js";
27
- import "../chunk-IBGIOBEE.js";
28
- import "../chunk-PNQDPDRC.js";
27
+ import "../chunk-RVK72CZX.js";
28
+ import "../chunk-IWDLU6K3.js";
29
29
  import "../chunk-XIMYZ3JA.js";
30
- import "../chunk-ESBQHFMJ.js";
31
- import "../chunk-34WSVGEM.js";
32
- import "../chunk-VWCXW2E7.js";
33
- import "../chunk-ZXYDQ3TZ.js";
30
+ import "../chunk-3DA2JDOL.js";
31
+ import "../chunk-HWCCCQNX.js";
32
+ import "../chunk-CUHSHSB6.js";
33
+ import "../chunk-UA7ZXYDR.js";
34
+ import "../chunk-T7FEBKXN.js";
35
+ import "../chunk-SRYEJNES.js";
34
36
  import "../chunk-5W3R6LFT.js";
35
- import "../chunk-QHJ6DQ2R.js";
37
+ import "../chunk-V35JG4JB.js";
38
+ import "../chunk-XTBDCR27.js";
39
+ import "../chunk-MPNJLASA.js";
40
+ import "../chunk-US7ECLD7.js";
36
41
  import "../chunk-Z54ZSOBZ.js";
37
- import "../chunk-724UL2TJ.js";
38
- import "../chunk-QOZXJ4NA.js";
39
- import "../chunk-GBPP5P7X.js";
40
- import "../chunk-P7266HLM.js";
41
- import "../chunk-MQLUAEOC.js";
42
- import "../chunk-52FUZI6K.js";
43
- import "../chunk-SAEFPSB6.js";
44
- import "../chunk-GAEGEPOC.js";
42
+ import "../chunk-GXFV6MYU.js";
43
+ import "../chunk-FM4RDMKT.js";
44
+ import "../chunk-RMLC67LF.js";
45
+ import "../chunk-W66O5E25.js";
46
+ import "../chunk-7HXE67BN.js";
45
47
  import "../chunk-Z3CCVDUB.js";
46
- import "../chunk-L3BLXZG7.js";
48
+ import "../chunk-KAPJINKN.js";
49
+ import "../chunk-AGSS7277.js";
47
50
  import "../chunk-2BEMIWEW.js";
48
- import "../chunk-66VMUL7Q.js";
49
- import "../chunk-Q7DUK4LX.js";
50
- import "../chunk-AW7R2GWG.js";
51
- import "../chunk-RWWYJ623.js";
52
- import "../chunk-VRZP76YM.js";
53
- import "../chunk-QOBUHWHX.js";
54
- import "../chunk-QILIYSJF.js";
55
- import "../chunk-6C7JWPCP.js";
56
- import "../chunk-5NGR67U5.js";
57
- import "../chunk-C34ZXZ7O.js";
51
+ import "../chunk-IJYGIDF4.js";
52
+ import "../chunk-GAQ7OQG6.js";
53
+ import "../chunk-DEL2U6CM.js";
54
+ import "../chunk-5VSF4UUQ.js";
55
+ import "../chunk-3KAZU3VR.js";
56
+ import "../chunk-P63GXC37.js";
57
+ import "../chunk-HRNHZA3N.js";
58
+ import "../chunk-TLX72JTG.js";
59
+ import "../chunk-DGUKIYSI.js";
60
+ import "../chunk-XBJVAEEO.js";
61
+ import "../chunk-KUA4OJ5Y.js";
58
62
  import "../chunk-BW5KFIQC.js";
59
63
  import "../chunk-QYF5S6P6.js";
60
- import "../chunk-PHDMFAKM.js";
61
- import "../chunk-2BBLFX75.js";
62
- import "../chunk-XVF2VVXP.js";
64
+ import "../chunk-DUO2TU77.js";
65
+ import "../chunk-J6MPGQTS.js";
66
+ import "../chunk-5HSRT26B.js";
63
67
  import {
64
68
  setProjectTrustState
65
- } from "../chunk-ZANLILYD.js";
69
+ } from "../chunk-M2IFVFWU.js";
66
70
  import "../chunk-2EECBNC5.js";
67
- import "../chunk-JAU72XNY.js";
71
+ import "../chunk-J64T55EI.js";
68
72
  import {
69
73
  logExtension
70
- } from "../chunk-XTMJ7XDT.js";
74
+ } from "../chunk-POMLXPMY.js";
71
75
  import "../chunk-JMYYOTQG.js";
72
- import "../chunk-QQNQ7SJW.js";
73
- import "../chunk-AOHZU2BM.js";
76
+ import "../chunk-QBFBRV2B.js";
77
+ import "../chunk-YNE3RP52.js";
74
78
  import "../chunk-LJDODBBZ.js";
75
- import "../chunk-CHOW74WE.js";
79
+ import "../chunk-CYRKQRPM.js";
76
80
  import "../chunk-ABBOA7UD.js";
77
- import "../chunk-N2H3ZPZY.js";
81
+ import "../chunk-3RKP3FMS.js";
78
82
  import "../chunk-NXL4FFQQ.js";
79
83
 
80
84
  // dist/mcp/worker.js
@@ -53,31 +53,31 @@ import {
53
53
  verifyNpmPackageEntry,
54
54
  verifyToolBinary,
55
55
  wasLastResolveTransient
56
- } from "../chunk-66VMUL7Q.js";
57
- import "../chunk-Q7DUK4LX.js";
58
- import "../chunk-AW7R2GWG.js";
59
- import "../chunk-RWWYJ623.js";
60
- import "../chunk-VRZP76YM.js";
61
- import "../chunk-QOBUHWHX.js";
62
- import "../chunk-QILIYSJF.js";
63
- import "../chunk-6C7JWPCP.js";
64
- import "../chunk-5NGR67U5.js";
65
- import "../chunk-C34ZXZ7O.js";
56
+ } from "../chunk-GAQ7OQG6.js";
57
+ import "../chunk-DEL2U6CM.js";
58
+ import "../chunk-5VSF4UUQ.js";
59
+ import "../chunk-3KAZU3VR.js";
60
+ import "../chunk-P63GXC37.js";
61
+ import "../chunk-HRNHZA3N.js";
62
+ import "../chunk-TLX72JTG.js";
63
+ import "../chunk-DGUKIYSI.js";
64
+ import "../chunk-XBJVAEEO.js";
65
+ import "../chunk-KUA4OJ5Y.js";
66
66
  import "../chunk-BW5KFIQC.js";
67
67
  import "../chunk-QYF5S6P6.js";
68
- import "../chunk-PHDMFAKM.js";
69
- import "../chunk-2BBLFX75.js";
70
- import "../chunk-XVF2VVXP.js";
71
- import "../chunk-ZANLILYD.js";
68
+ import "../chunk-DUO2TU77.js";
69
+ import "../chunk-J6MPGQTS.js";
70
+ import "../chunk-5HSRT26B.js";
71
+ import "../chunk-M2IFVFWU.js";
72
72
  import "../chunk-2EECBNC5.js";
73
- import "../chunk-JAU72XNY.js";
74
- import "../chunk-XTMJ7XDT.js";
73
+ import "../chunk-J64T55EI.js";
74
+ import "../chunk-POMLXPMY.js";
75
75
  import "../chunk-JMYYOTQG.js";
76
- import "../chunk-QQNQ7SJW.js";
77
- import "../chunk-AOHZU2BM.js";
76
+ import "../chunk-QBFBRV2B.js";
77
+ import "../chunk-YNE3RP52.js";
78
78
  import "../chunk-LJDODBBZ.js";
79
79
  import "../chunk-ABBOA7UD.js";
80
- import "../chunk-N2H3ZPZY.js";
80
+ import "../chunk-3RKP3FMS.js";
81
81
  import "../chunk-NXL4FFQQ.js";
82
82
  export {
83
83
  GITHUB_TOOLS,
@@ -4,19 +4,38 @@ import {
4
4
  } from "../chunk-2BEMIWEW.js";
5
5
  import {
6
6
  serveGzipStageWorker
7
- } from "../chunk-UOPVJQ4N.js";
7
+ } from "../chunk-IJYGIDF4.js";
8
8
  import "../chunk-LJDODBBZ.js";
9
9
  import "../chunk-NXL4FFQQ.js";
10
10
 
11
11
  // dist/clients/project-snapshot-persist-worker.js
12
+ import { parentPort } from "node:worker_threads";
12
13
  function semanticSnapshotFingerprint(request, body) {
13
14
  return fingerprintProjectSnapshotJson(body, request.generatedAt);
14
15
  }
15
- serveGzipStageWorker((request) => ({
16
- id: request.id,
17
- generation: request.generation,
18
- stagePath: request.stagePath
19
- }), {
20
- semanticFingerprint: semanticSnapshotFingerprint,
21
- skipIfFingerprints: (request) => request.priorFingerprints
22
- });
16
+ function serveForcedFailure() {
17
+ parentPort?.on("message", (message) => {
18
+ try {
19
+ if (typeof message !== "object" || message === null || !("id" in message) || !("generation" in message))
20
+ return;
21
+ parentPort?.postMessage({
22
+ id: message.id,
23
+ generation: message.generation,
24
+ error: "forced snapshot worker failure (PI_LENS_TEST_SNAPSHOT_WORKER_FAIL)"
25
+ });
26
+ } catch {
27
+ }
28
+ });
29
+ }
30
+ if (process.env.PI_LENS_TEST_SNAPSHOT_WORKER_FAIL === "no-stage-path") {
31
+ serveForcedFailure();
32
+ } else {
33
+ serveGzipStageWorker((request) => ({
34
+ id: request.id,
35
+ generation: request.generation,
36
+ stagePath: request.stagePath
37
+ }), {
38
+ semanticFingerprint: semanticSnapshotFingerprint,
39
+ skipIfFingerprints: (request) => request.priorFingerprints
40
+ });
41
+ }
@@ -1,7 +1,7 @@
1
1
  import { createRequire as __pilensCreateRequire } from "node:module"; const require = __pilensCreateRequire(import.meta.url);
2
2
  import {
3
3
  serveGzipStageWorker
4
- } from "../chunk-UOPVJQ4N.js";
4
+ } from "../chunk-IJYGIDF4.js";
5
5
  import "../chunk-LJDODBBZ.js";
6
6
  import "../chunk-NXL4FFQQ.js";
7
7
 
@@ -221,6 +221,17 @@ once no matter which spelling each file uses. Migrating does not change the
221
221
  answer, staying un-migrated is not a way around the denial, and a half-migrated
222
222
  pair of files merges rather than one clobbering the other.
223
223
 
224
+ Denying a file's **language server** (its primary, such as `json` or
225
+ `typescript`) leaves that file unchecked for the language itself. Every
226
+ diagnostics route then reports a file with no findings as **unconfirmed**, not
227
+ clean, with a "Primary LSP (…): not available" note that says it is disabled in
228
+ config. On write-time checks the note appears once per server per session, and
229
+ a later write of such a file shows a one-line "unconfirmed" status instead of
230
+ "clean". When the denied server has a fallback (`deno` for `typescript`) that
231
+ cannot start either, the note says so. Cross-file (cascade) checks name the
232
+ server once per session. Auxiliary scanners such as `typos` still run and
233
+ report their own findings.
234
+
224
235
  Two rules make the rest of the table unambiguous:
225
236
 
226
237
  - **The search stops at `$HOME`.** pi-lens never reads a config file in your
@@ -55,6 +55,14 @@ event per session, never one per occurrence in a loop; repeated degradations go
55
55
  through a once-only or counted record. A discriminator nothing can observe is a
56
56
  vacuous guard and does not ship.
57
57
 
58
+ **Gates fail closed on what they do not recognize.** When code decides a
59
+ verdict (a trust check, a lint, a census, a body check), it admits a short list
60
+ of known-good forms and refuses everything else. It never enumerates known-bad
61
+ spellings. Each review of an enumerated gate finds the next spelling it missed;
62
+ one gate took sixteen rounds that way. Once a second round finds a new
63
+ spelling, stop patching and redesign. Screen: can an input nobody planned for
64
+ pass the gate?
65
+
58
66
  **Blast radius as a call-tree diff.** For any change to a shared seam, list the
59
67
  changed symbol, its callers above, its callees below, with plus and minus on the
60
68
  lines that moved. Prose keeps missing callers; a tree the reviewer can grep does
@@ -118,6 +126,12 @@ thing is required, a registry entry, a baseline row and lane membership. A
118
126
  mocked seam is not a real spawn and gets an exemption with its reason, never an
119
127
  admission.
120
128
 
129
+ **A new ratchet sweeps the open changes before it lands.** A governance test
130
+ that rejects a new pattern fails every open change that already adds one, and
131
+ none of them conflict textually, so nothing warns. Before merging a ratchet,
132
+ run it against each open change merged with the base, and fix or notify each
133
+ one it fails. Screen: which open change would fail this test after the merge?
134
+
121
135
  **Run targeted files while iterating; the full suite runs once.** Build before
122
136
  testing when tests execute compiled output, and rebuild between mutations. A red
123
137
  is environmental only when the same test is red on the base branch in the same
@@ -156,6 +170,13 @@ literal, a changelog line) are applied by the orchestrator as trailing commits
156
170
  and never re-verified; anything that changes what code means goes through a
157
171
  round, however small it looks.
158
172
 
173
+ **A round does what its brief prescribes, and flags anything more.** A worker
174
+ that changes something outside its brief lists that change as scope growth at
175
+ the top of its hand-off; one round widened a shared detector while asked to add
176
+ a single admission. Routing then re-classifies the round, because a silent extra
177
+ mechanism turns a merge-on-green round into one that needs a verify. Screen:
178
+ does the hand-off name every change the brief did not ask for?
179
+
159
180
  **Merge gate.** Merge only when the verdict is merge-ready, every gating check
160
181
  genuinely executed and passed on the exact head, and every failing check was
161
182
  read and judged. Absent is not green. Read exit codes directly, never after a
@@ -170,11 +191,22 @@ at the worktree root; the orchestrator commits. Implementation and review are
170
191
  never mixed in one delegation. Scope changes are mirrored on the tracking issue
171
192
  before they are sent, so the worker can verify them.
172
193
 
194
+ **Work in progress survives a cut.** A worker with Git authority commits at
195
+ each green step; one without it leaves its tree and names it in the hand-off.
196
+ A backend outage, a quota stop or a crash then costs one step, not the round,
197
+ and the next round ports the tree instead of redoing it. Screen: how much is
198
+ lost if this process dies now?
199
+
173
200
  **No manufactured work.** An idle worker is cheaper than fake work. Delegate
174
201
  when a real producer, consumer, probe or diagnosis is unresolved, never to keep
175
202
  a pool busy. The same applies to status artifacts: they exist for coordination
176
203
  someone consumes.
177
204
 
205
+ **A step done by hand twice becomes a script with a test.** That includes
206
+ cleanup, triage and status upkeep. The script surfaces unfinished work and
207
+ never hides it: stalled work is restarted, not unassigned, archived or deleted.
208
+ Screen: is this the second time this step was done by hand?
209
+
178
210
  ## 4. Honesty and records
179
211
 
180
212
  **Closes versus refs follows delivery, not optimism.** Close only when every
@@ -283,5 +283,5 @@ date-based `direct` expiry (#3401) and consecutive-night `idle-eviction`
283
283
  counts (#3989). Regenerated every run; never a measurement.
284
284
 
285
285
  ```json
286
- {"first-publish":{"dockerfile":{"firstMissed":"2026-10-10"},"prisma":{"firstMissed":"2026-10-10"},"python":{"firstMissed":"2026-10-10"},"shell":{"firstMissed":"2026-10-10"},"terraform":{"pendingFirstPublish":"direct","runs":1},"yaml":{"firstMissed":"2026-10-10"}}}
286
+ {"first-publish":{"typescript":{"pendingFirstPublish":"direct","runs":1}}}
287
287
  ```
@@ -15,7 +15,7 @@ respawned server reports every finding it reported before eviction; the
15
15
  content-bound coverage check the auxiliary freeze depends on. A server that
16
16
  cannot demonstrate it is never proposed for eviction on perf grounds alone.
17
17
 
18
- _Last generated: 2026-10-10 on linux; 48 registry servers: 8 eligible, 0 vetoed, 16 inconclusive, 24 unavailable (0 not reached: budget)._
18
+ _Last generated: 2026-10-18 on linux; 49 registry servers: 17 eligible, 0 vetoed, 18 inconclusive, 14 unavailable (0 not reached: budget)._
19
19
 
20
20
  ## Per-server rows
21
21
 
@@ -36,69 +36,58 @@ step log only, because it flaps.
36
36
  | server | role | declared | result | reason | respawn | coverage |
37
37
  |---|---|---|---|---|---|---|
38
38
  | ast-grep | auxiliary | unmeasured | inconclusive | not-evicted | not-evicted | n/a |
39
- | bash | primary | transparent | unavailable | server-not-started | n/a | n/a |
39
+ | bash | primary | transparent | eligible | · | ok | preserved |
40
40
  | clojure | primary | transparent | eligible | · | ok | preserved |
41
41
  | cmake | primary | unmeasured | inconclusive | no-baseline | n/a | unproven |
42
42
  | cpp | primary | transparent | eligible | · | ok | preserved |
43
43
  | csharp | primary | unmeasured | unavailable | tool-unavailable | n/a | n/a |
44
- | css | primary | transparent | unavailable | server-not-started | n/a | n/a |
44
+ | css | primary | transparent | eligible | · | ok | preserved |
45
45
  | cue | primary | unmeasured | inconclusive | not-evicted | not-evicted | n/a |
46
46
  | dart | primary | unmeasured | unavailable | tool-unavailable | n/a | n/a |
47
47
  | deno | primary | transparent | eligible | · | ok | preserved |
48
- | docker | primary | transparent | unavailable | server-not-started | n/a | n/a |
48
+ | docker | primary | transparent | eligible | · | ok | preserved |
49
49
  | docker-official | primary | unmeasured | inconclusive | not-evicted | not-evicted | n/a |
50
50
  | elixir | primary | unmeasured | unavailable | tool-unavailable | n/a | n/a |
51
- | expert | primary | unmeasured | inconclusive | not-evicted | not-evicted | n/a |
52
- | fish | primary | transparent | unavailable | server-not-started | n/a | n/a |
51
+ | expert | primary | unmeasured | inconclusive | no-baseline | n/a | unproven |
52
+ | fish | primary | transparent | eligible | · | ok | preserved |
53
53
  | fsharp | primary | unmeasured | unavailable | tool-unavailable | n/a | n/a |
54
54
  | gleam | primary | unmeasured | inconclusive | not-evicted | not-evicted | n/a |
55
55
  | go | primary | unmeasured | unavailable | tool-unavailable | n/a | n/a |
56
56
  | haskell | primary | unmeasured | unavailable | tool-unavailable | n/a | n/a |
57
- | html | primary | transparent | unavailable | server-not-started | n/a | n/a |
57
+ | html | primary | transparent | eligible | · | ok | preserved |
58
58
  | java | primary | unmeasured | unavailable | tool-unavailable | n/a | n/a |
59
59
  | json | primary | transparent | eligible | · | ok | preserved |
60
60
  | kotlin | primary | unmeasured | inconclusive | not-evicted | not-evicted | n/a |
61
+ | lean | primary | unmeasured | unavailable | server-not-started | n/a | n/a |
61
62
  | lua | primary | unmeasured | inconclusive | not-evicted | not-evicted | n/a |
62
63
  | marksman | primary | transparent | eligible | · | ok | preserved |
63
64
  | nix | primary | unmeasured | unavailable | tool-unavailable | n/a | n/a |
64
65
  | ocaml | primary | unmeasured | unavailable | tool-unavailable | n/a | n/a |
65
66
  | omnisharp | primary | unmeasured | unavailable | no-fixture | n/a | n/a |
66
67
  | opengrep | auxiliary | transparent | eligible | · | ok | preserved |
67
- | php | primary | transparent | unavailable | server-not-started | n/a | n/a |
68
+ | php | primary | transparent | eligible | · | ok | preserved |
68
69
  | powershell | primary | unmeasured | inconclusive | not-evicted | not-evicted | n/a |
69
- | prisma | primary | transparent | unavailable | server-not-started | n/a | n/a |
70
- | python | primary | transparent | unavailable | server-not-started | n/a | n/a |
70
+ | prisma | primary | transparent | eligible | · | ok | preserved |
71
+ | python | primary | transparent | eligible | · | ok | preserved |
71
72
  | python-jedi | primary | transparent | eligible | · | ok | preserved |
72
73
  | ruby | primary | unmeasured | unavailable | tool-unavailable | n/a | n/a |
73
74
  | rust | primary | unmeasured | inconclusive | not-evicted | not-evicted | n/a |
74
75
  | shuck | primary | unmeasured | unavailable | server-not-started | n/a | n/a |
75
- | svelte | primary | unmeasured | unavailable | server-not-started | n/a | n/a |
76
+ | svelte | primary | unmeasured | inconclusive | not-evicted | not-evicted | n/a |
76
77
  | swift | primary | unmeasured | unavailable | tool-unavailable | n/a | n/a |
77
78
  | terraform | primary | unmeasured | inconclusive | not-evicted | not-evicted | n/a |
78
79
  | tinymist | primary | unmeasured | inconclusive | not-evicted | not-evicted | n/a |
79
80
  | toml | primary | unmeasured | inconclusive | not-evicted | not-evicted | n/a |
80
81
  | typescript | primary | transparent | eligible | · | ok | preserved |
81
82
  | typos | auxiliary | unmeasured | inconclusive | not-evicted | not-evicted | n/a |
82
- | vue | primary | unmeasured | unavailable | server-not-started | n/a | n/a |
83
- | yaml | primary | transparent | unavailable | server-not-started | n/a | n/a |
83
+ | vue | primary | unmeasured | inconclusive | no-baseline | n/a | unproven |
84
+ | yaml | primary | transparent | eligible | · | ok | preserved |
84
85
  | zig | primary | unmeasured | inconclusive | not-evicted | not-evicted | n/a |
85
86
  | zizmor | auxiliary | unmeasured | inconclusive | not-evicted | not-evicted | n/a |
86
87
 
87
88
  ## Declared versus measured
88
89
 
89
- `drift` means the registry evicts a server the measurement vetoes. A `proposal`
90
- or `info` line is for a maintainer to act on in a follow-up; this run changed
91
- nothing.
92
-
93
- - **bash** [info] declared transparent with no evidence this run: unavailable (server-not-started)
94
- - **css** [info] declared transparent with no evidence this run: unavailable (server-not-started)
95
- - **docker** [info] declared transparent with no evidence this run: unavailable (server-not-started)
96
- - **fish** [info] declared transparent with no evidence this run: unavailable (server-not-started)
97
- - **html** [info] declared transparent with no evidence this run: unavailable (server-not-started)
98
- - **php** [info] declared transparent with no evidence this run: unavailable (server-not-started)
99
- - **prisma** [info] declared transparent with no evidence this run: unavailable (server-not-started)
100
- - **python** [info] declared transparent with no evidence this run: unavailable (server-not-started)
101
- - **yaml** [info] declared transparent with no evidence this run: unavailable (server-not-started)
90
+ No divergence between declared policy and this run's measurement.
102
91
 
103
92
  ## Reasons
104
93
 
@@ -32,12 +32,22 @@ exports change.
32
32
  - When a gate becomes fail-closed, sweep every construction site that reaches
33
33
  it, test doubles included, and prove the sweep with the behaviour suites
34
34
  (#3622).
35
+ - A new module, or a new use of a detection or caching primitive (stat,
36
+ mtime, inode, content or line hash, directory walk, install stamp, sequence
37
+ counter), lists the existing modules that use the same primitive for the
38
+ same purpose. Find them by searching for the primitive, such as
39
+ `rg -l 'statSync|mtimeMs|createHash|readdir|projectSeq' clients`, never by
40
+ your own seam's call names. The consolidation verdict cites that list
41
+ (#4536).
35
42
  - A change on a lifecycle, timing, or identity seam extends or adds a TLA+
36
43
  model in this step (#3802; the rule itself is in `AGENTS.md`). Find the
37
44
  family in `formal/coverage-map.json`, write the invariant the change
38
45
  preserves or tightens, and show the violating config red on the pre-fix model
39
46
  and green after, or carry `TLA+ unaffected: <family> — <reason>` in the PR
40
- body. A row is any-of (one listed family's model move or declaration
47
+ body. The unaffected line is wrong when the change alters a behaviour an
48
+ as-built config constant encodes: flip that constant, its configs and its
49
+ `formal/switch-pins.json` value instead, and keep its pin test red on the old
50
+ behaviour (#4387, #4393). A row is any-of (one listed family's model move or declaration
41
51
  satisfies it); a row of 4+ families only prints a note, except `index.ts`,
42
52
  whose row gates on the lifecycle hook handler a hunk lands in (#3878). The
43
53
  map owner is the lane that adds a `formal/<family>/`: it adds the family and
@@ -60,12 +70,13 @@ exports change.
60
70
  transcript quoted in `Test assessment`. A redundant test that still guards
61
71
  goes only with the named survivor that covers it.
62
72
  - Apply folded review follow-ups per `docs/pi-lens-subagent.md`
63
- "Follow-ups", and list filed residuals in one **Residuals** section of the
64
- PR body.
73
+ "Follow-ups", and list every gap left open in one **Accepted residuals** section of
74
+ the PR body, each named `p3` with its severity and live population (merge bar:
75
+ `docs/pi-lens-merge-policy.md` "Filing issues").
65
76
 
66
77
  ## Evidence
67
78
 
68
- - Use `scripts/mutate.mjs` for bounded hand mutations; it restores the target automatically, but only while the file still holds the mutated bytes. A leftover journal (`<file>.mutate-backup`) blocks the next run on that file; recover with `--restore`, which refuses and prints the three hashes when the file was edited since. If a crash left the file already at its original bytes, `--restore` also refuses; check the printed hashes and delete the journal by hand. `--restore` is crash recovery only: run against a live `mutate.mjs` run on the same file, it pulls that run's mutation, and that run then reports ERROR (exit 4).
79
+ - Use `scripts/mutate.mjs` for bounded hand mutations; it restores the target automatically, but only while the file still holds the mutated bytes. A leftover journal (`<file>.mutate-backup`) blocks the next run on that file; recover with `--restore`, which refuses and prints the three hashes when the file was edited since. If a crash left the file already at its original bytes, `--restore` also refuses; check the printed hashes and delete the journal by hand. `--restore` is crash recovery only: run against a live `mutate.mjs` run on the same file, it pulls that run's mutation, and that run then reports ERROR (exit 4). The transcript's status line is `SURVIVED` (tests passed), `RED` (a test run failed), `REFUSED` (the edit was not applied), `BROKEN` (the build failed) or `ERROR` (no verdict); only `RED` proves a test catches the mutation (#4510).
69
80
 
70
81
  - Witness rule (ADR 0007): #1605 owns the witness lanes, and their fixtures
71
82
  live under `tests/fixtures/witness/<slice>/`.
@@ -81,19 +92,65 @@ exports change.
81
92
  from the pre-fix SHA (`git checkout <sha> -- clients/…`), never `tests`
82
93
  (#3166) and never uncommitted work, and run `git status` after any bulk
83
94
  restore. Without Git authority, use a saved patch.
84
- - Hand-mutate only the NEW guard, branch, filter, or cap the PR is about, both
85
- directions, one row per direction, and quote the compile-valid red. The new
86
- test is the only red under at least one mutation. This is the whole PR
87
- mutation layer (AGENTS.md, #4005): there is no per-PR Stryker job, comment, or
95
+ - Hand-mutate only the NEW guard, branch, filter, or cap the PR is about, and
96
+ the behaviour behind each relaxed assertion (next bullet), both directions,
97
+ one row per direction, and quote the compile-valid red. The new test is the
98
+ only red under at least one mutation. These rows are the whole PR mutation
99
+ layer (AGENTS.md, #4005): there is no per-PR Stryker job, comment, or
88
100
  `MUTATION` line, and no survivor triage. The nightly Stryker report on master
89
101
  is exploratory and is not read here. Use bounded hand probes locally; never
90
102
  run a full Stryker campaign in a worktree.
91
- - Every record the `Observability` section names is asserted by a test in the
92
- diff and quoted in the body (#2642, #2647, #2649, #2654).
103
+ - A PR that relaxes or rewrites an existing assertion (a changed meaning makes
104
+ the old expectation wrong) mutates the behaviour that test is named for. Quote
105
+ the red on the new assertion. A rewrite that stays green with that
106
+ behaviour broken is a deleted test (#4390: the eviction smoke's rewrite
107
+ passed with root re-registration broken).
108
+ - A PR that adds or changes a detector (a ratchet, census or source scan)
109
+ writes STATE_TABLE.md before the first edit: each form the detector
110
+ admits, and the statement that every other form refuses. It then plants
111
+ three spellings its author did not design for: an alias, an indirect
112
+ call and a value reached through another binding. Quote each result. A
113
+ plant that stays green is fixed. A stated residual names an admitted
114
+ form that is too broad, never an unread spelling (AGENTS.md shape 69).
115
+ - The manual per-test campaign (`stryker-pertest.yml`, #4365) lists tests that
116
+ cover mutated code but kill no mutant. It is a triage input, not a verdict:
117
+ a candidate that guards another seam or a named recurrence stays, and a
118
+ deletion it proposes lands only with the covering test named and the
119
+ reviewer's agreement test by test.
120
+ - Every record the `Observability` section names is asserted by a test under
121
+ `tests/` (any collected test, not only a file this PR touches) and quoted in
122
+ the body (#2642, #2647, #2649, #2654). The body lint refuses a named record
123
+ literal no collected test reads back (`scripts/check-pr-body.mjs`, #4597), so
124
+ a record the section names without a read-back cannot merge; `none: <reason>`
125
+ still answers a diff with no record. A mention counts whatever its spelling:
126
+ a backticked span, a bare word and a link label are all read against the
127
+ record values the runtime emits, so prose that reuses a record's own name is
128
+ a mention too.
129
+ - The read-back is the record's full name in an assertion, a comparison, a
130
+ `case` label or a record-keyed row inside a declaration vitest collects and
131
+ runs, or inside a module-scope helper that one of them calls. A title, a
132
+ `console.log`, a skipped test and an excluded tree do not count, and a record
133
+ named only through a table imported from another file does not either, so
134
+ inline the name in the assertion. The declaration may be spelled with a
135
+ computed modifier or bracket link (`it["only"]`) and through an alias
136
+ (`const t = it`); a form the lint cannot classify (`it[modifier]`, a
137
+ destructured alias) makes its whole file contribute nothing, so write the
138
+ declaration the plain way. Naming a record that predates the PR owes the same
139
+ test.
93
140
  - Every behavioural sentence (a docstring invariant, a memo, a registry
94
141
  justification) maps to a test or a probe, or is deleted (#2643, #2654).
95
142
  - Name where new state sits in its ladder and what happens to it at
96
143
  `session_start` (#2649, #2654).
144
+ - A change on a hot path (dispatch, `session_start`, LSP diagnostics, a cache
145
+ lookup, turn-end) quotes a before/after timing from the real path in the
146
+ body, with the command, at a small and a large input; per-input work on a
147
+ tool path also needs a size or time bound. Name and justify any regression
148
+ (#4344 r1; #4368 r1). The `Hot-path latency (advisory)` lane (#4363) times
149
+ `session_start`, `dispatchLint` and the diagnostics-cache replay on the head
150
+ and its first parent (`HEAD^1`); read its job summary, and time by hand any
151
+ hot path it does not cover (a language server's time, a dependency bump).
152
+ Green means no regression of 1.25x and 5 ms; a "slower, below threshold" row
153
+ is still a number to read.
97
154
  - A measured constant names its measuring command and keeps raw output as a
98
155
  tracked artifact pinned by a test (#3648). Run `git check-ignore -v` on every
99
156
  new artifact path before citing it (#3648).
@@ -114,10 +171,31 @@ exports change.
114
171
  `node_modules`.
115
172
  - Run release-QA end to end when a release-QA row changes.
116
173
  - Run the reviewer's standing probes (`docs/pi-lens-reviewer.md`) that the diff
117
- can trip, and quote their output in the PR body.
174
+ can trip, and quote their output in the PR body. The `verify-pi-lens` skill
175
+ (`.claude/skills/verify-pi-lens/SKILL.md`) holds the real-path recipes.
118
176
 
119
177
  ## Fix rounds
120
178
 
179
+ - When bot threads or code-scanning alerts exist, read and dispose of each
180
+ one. A missing bot review is never evidence of anything: it never blocks the
181
+ round and never becomes something to wait for. The threads, every page:
182
+ `gh api graphql --paginate -f query='query($owner:String!,$name:String!,$number:Int!,$endCursor:String){repository(owner:$owner,name:$name){pullRequest(number:$number){reviewThreads(first:100,after:$endCursor){pageInfo{hasNextPage endCursor} nodes{id isResolved isOutdated path line comments(first:1){nodes{author{login} body}}}}}}}' -F owner="$(gh repo view --json owner --jq .owner.login)" -F name="$(gh repo view --json name --jq .name)" -F number=<N> --jq '.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved == false) | "\(.id) \(.path):\(.line) \(.comments.nodes[0].author.login): \(.comments.nodes[0].body)"'`,
183
+ and the alerts:
184
+ `gh api --paginate "repos/{owner}/{repo}/code-scanning/alerts?ref=refs/heads/<branch>&state=open" --jq '.[] | "\(.number) \(.rule.id) \(.most_recent_instance.location.path):\(.most_recent_instance.location.start_line)"'`.
185
+ Both commands follow their cursor to the last page, so the list is clear
186
+ only after that page prints: one page is never read as the whole list.
187
+ An alert on another branch is not this round's; read the head you pushed.
188
+ Before the hand-back, reply to and resolve every thread this round fixes, and
189
+ name every other open thread and alert in the PR body with its disposition.
190
+ A round that finds none says so in one line and moves on. Where the brief
191
+ withholds GitHub writes
192
+ (`docs/pi-lens-subagent.md`),
193
+ put the same list in the body and the hand-back for the orchestrator to post
194
+ and resolve. The disposition words and the merge condition are
195
+ `docs/pi-lens-merge-policy.md` "The loop, per PR" step 1 and step 4
196
+ (`:34`, `:81`); this contract does not restate them. Recurrence: #4566 and
197
+ #4575 (2026-10-17), where the threads outlived the rounds that should have
198
+ read them.
121
199
  - On `FIX ROUND` findings, reproduce each finding before fixing it, add a
122
200
  red-first test for every behavioural fix, rebuild, re-run the targeted suites
123
201
  plus anything the findings touched, and push the same branch. If the PR reads
@@ -137,6 +215,9 @@ exports change.
137
215
  - Spot-check one prior round's mutation on the new head (#2583).
138
216
  - Re-read the changelog fragment for any claim the round retracts (#3155), then
139
217
  add an honest review-round section to the PR body.
218
+ - Lint the PR body (`--lint-local`) before the first push of a round, not only
219
+ at hand-back: `PR body (advisory)` re-reads the same body on every push, so
220
+ an unfixed finding reds each one.
140
221
 
141
222
  ## Before the report
142
223
 
@@ -160,8 +241,17 @@ rigor (#2599).
160
241
  GitHub ignores it in a title.
161
242
  - Run `node scripts/check-pr-body.mjs --lint-local PR_BODY.md` and
162
243
  `node scripts/check-changelog-fragments.mjs` before the hand-back; both
163
- must pass, and the hand-back quotes them. A red `PR body (advisory)` check is
164
- a fix-before-review item. A changelog fragment is `---` /
244
+ must pass, and the hand-back quotes them. The body lint reads the closed
245
+ issue's labels through `gh api`; a `::warning::` that the Detection check
246
+ was skipped means gh failed, so the section was not required, not that it
247
+ passed (#4288). A non-bug PR either deletes the `## Detection` section or
248
+ fills the three lines, and a `Shape:` or `Enforcer:` line it does write is
249
+ validated too; free text in between reds the local preflight. A bug-closing
250
+ PR that answers a layer, the `Gap: none:` reason, the `Shape: new:` name or
251
+ the `Enforcer: none:` reason with `unknown`, `n/a`, `tbd` or `?` is refused
252
+ (#4519); the layer words are `DETECTION_LAYERS`, `nightly:<workflow>`
253
+ included. A red `PR body (advisory)` check is a fix-before-review item.
254
+ A changelog fragment is `---` /
165
255
  `section: <Added|Changed|Deprecated|Removed|Fixed|Security>` /
166
256
  `audience: <user|internal>` / `---` / blank / one `- ` bullet. New
167
257
  `audience: user` fragments must begin with a bold lead of at most 100
@@ -172,6 +262,20 @@ rigor (#2599).
172
262
  hit); `internal` is CI, tests, `formal/`, contributor docs, orchestration, and
173
263
  refactors with no observable change. `npm run changelog:check` checks the
174
264
  rollup, not fragments (#2456).
265
+ - A bug fix's detection lesson names the defect SHAPE and its tree-wide
266
+ enforcer, not only a test for the instance: the `Shape:` line names the
267
+ `AGENTS.md` "Recurring defect shapes" number (or `new: <name>`), and the
268
+ `Enforcer:` line a test on base that scans the whole population, an issue to
269
+ build one, or `none: judgment-only, <reason>`. With `Shape: #N` a path
270
+ enforcer must be one catalog entry N names. The fix's own one-bug test is its
271
+ regression test, not the closed gap. The body lint enforces both lines
272
+ (#4439).
273
+ - A `Shape: new: <name>` is a proposal, not a catalog entry: write the
274
+ proposed shape text in the PR body under "Proposed contract change
275
+ (maintainer decision)" and never edit `AGENTS.md`. Catalog shapes and rule
276
+ changes are the maintainer's decision; the orchestrator lands an approved
277
+ one in its own change (#4577's fixer added shape 74 unasked, and the
278
+ governance suites redded).
175
279
  - Every code fact in the PR body is a `` `path:line` `` citation the check
176
280
  verifies: the file must be in the committed tree (never an untracked or
177
281
  git-ignored path, which CI cannot read), and a fenced quote after it must