pi-lens 3.8.68 → 3.8.70

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 (168) hide show
  1. package/CHANGELOG.md +114 -0
  2. package/README.md +53 -34
  3. package/dist/clients/agent-nudge.js +262 -0
  4. package/dist/clients/biome-client.js +2 -2
  5. package/dist/clients/bootstrap.js +3 -1
  6. package/dist/clients/bus-events-logger.js +51 -0
  7. package/dist/clients/bus-publish.js +153 -0
  8. package/dist/clients/code-quality-warnings.js +27 -0
  9. package/dist/clients/dead-code-client.js +5 -23
  10. package/dist/clients/diagnostic-logger.js +2 -2
  11. package/dist/clients/diagnostics-publish.js +216 -0
  12. package/dist/clients/dispatch/auxiliary-lsp.js +62 -6
  13. package/dist/clients/dispatch/dispatcher.js +2 -0
  14. package/dist/clients/dispatch/facts/function-facts.js +83 -0
  15. package/dist/clients/dispatch/integration.js +153 -2
  16. package/dist/clients/dispatch/rules/async-noise.js +1 -1
  17. package/dist/clients/dispatch/rules/async-unnecessary-wrapper.js +1 -1
  18. package/dist/clients/dispatch/rules/cors-wildcard.js +1 -1
  19. package/dist/clients/dispatch/rules/error-obscuring.js +2 -1
  20. package/dist/clients/dispatch/rules/error-swallowing.js +2 -1
  21. package/dist/clients/dispatch/rules/framework-call-noise.js +67 -0
  22. package/dist/clients/dispatch/rules/high-complexity.js +12 -1
  23. package/dist/clients/dispatch/rules/high-fan-out.js +14 -3
  24. package/dist/clients/dispatch/rules/high-import-coupling.js +1 -1
  25. package/dist/clients/dispatch/rules/missing-error-propagation.js +1 -1
  26. package/dist/clients/dispatch/rules/no-commented-credentials.js +1 -1
  27. package/dist/clients/dispatch/rules/pass-through-wrappers.js +1 -1
  28. package/dist/clients/dispatch/rules/placeholder-comments.js +1 -1
  29. package/dist/clients/dispatch/rules/unsafe-boundary.js +1 -1
  30. package/dist/clients/dispatch/runners/ast-grep-napi.js +133 -31
  31. package/dist/clients/dispatch/runners/lsp.js +22 -6
  32. package/dist/clients/dispatch/runners/yaml-rule-parser.js +68 -19
  33. package/dist/clients/file-utils.js +20 -7
  34. package/dist/clients/gitleaks-client.js +40 -5
  35. package/dist/clients/installer/index.js +155 -7
  36. package/dist/clients/instance-reaper.js +343 -18
  37. package/dist/clients/instance-registry.js +71 -2
  38. package/dist/clients/jscpd-client.js +2 -2
  39. package/dist/clients/knip-client.js +35 -38
  40. package/dist/clients/language-profile.js +7 -11
  41. package/dist/clients/lens-config.js +17 -0
  42. package/dist/clients/lens-engine.js +56 -10
  43. package/dist/clients/log-cleanup.js +61 -19
  44. package/dist/clients/lsp/cascade-tier.js +266 -0
  45. package/dist/clients/lsp/client.js +57 -8
  46. package/dist/clients/lsp/config.js +27 -1
  47. package/dist/clients/lsp/index.js +550 -59
  48. package/dist/clients/lsp/server-strategies.js +19 -0
  49. package/dist/clients/lsp/server.js +174 -32
  50. package/dist/clients/lsp-budget.js +148 -0
  51. package/dist/clients/mcp/analyze.js +110 -1
  52. package/dist/clients/mcp/session.js +1 -0
  53. package/dist/clients/middle-man-analysis.js +260 -0
  54. package/dist/clients/module-report.js +333 -26
  55. package/dist/clients/ndjson-logger.js +24 -0
  56. package/dist/clients/opengrep-client.js +217 -0
  57. package/dist/clients/path-utils.js +65 -0
  58. package/dist/clients/persist-debounce.js +63 -0
  59. package/dist/clients/pipeline.js +83 -3
  60. package/dist/clients/project-diagnostics/extractors.js +50 -4
  61. package/dist/clients/project-diagnostics/fresh-fetch.js +275 -0
  62. package/dist/clients/project-diagnostics/runner-adapters/opengrep.js +40 -0
  63. package/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +46 -0
  64. package/dist/clients/project-snapshot.js +7 -2
  65. package/dist/clients/quiet-window.js +223 -0
  66. package/dist/clients/read-expansion.js +2 -3
  67. package/dist/clients/read-guard-tool-lines.js +10 -0
  68. package/dist/clients/recent-touches.js +233 -0
  69. package/dist/clients/resource-sampler.js +254 -0
  70. package/dist/clients/review-graph/builder.js +265 -11
  71. package/dist/clients/review-graph/symbol-id.js +57 -0
  72. package/dist/clients/runtime-agent-end.js +51 -1
  73. package/dist/clients/runtime-coordinator.js +21 -0
  74. package/dist/clients/runtime-session.js +106 -47
  75. package/dist/clients/runtime-tool-result.js +46 -0
  76. package/dist/clients/runtime-turn.js +53 -11
  77. package/dist/clients/safe-spawn.js +48 -2
  78. package/dist/clients/sgconfig.js +230 -52
  79. package/dist/clients/source-filter.js +56 -34
  80. package/dist/clients/source-walker.js +66 -0
  81. package/dist/clients/startup-scan.js +37 -42
  82. package/dist/clients/subagent-mode.js +54 -20
  83. package/dist/clients/symbol-containment.js +24 -0
  84. package/dist/clients/test-runner-client.js +544 -29
  85. package/dist/clients/tree-sitter-symbol-extractor.js +115 -0
  86. package/dist/clients/tui-fit.js +54 -0
  87. package/dist/clients/turn-summary-render.js +72 -0
  88. package/dist/clients/turn-summary.js +132 -0
  89. package/dist/clients/widget-state.js +88 -31
  90. package/dist/clients/word-index.js +296 -1
  91. package/dist/clients/write-ordering-guard.js +58 -0
  92. package/dist/clients/zizmor-config.js +27 -0
  93. package/dist/index.js +20060 -14142
  94. package/dist/mcp/build-staleness.js +123 -0
  95. package/dist/mcp/server.js +470 -60
  96. package/dist/tools/activate-tools.js +72 -0
  97. package/dist/tools/ast-dump.js +2 -8
  98. package/dist/tools/ast-grep-search.js +1 -1
  99. package/dist/tools/lens-diagnostics.js +317 -27
  100. package/dist/tools/lsp-diagnostics.js +617 -64
  101. package/dist/tools/module-report.js +32 -16
  102. package/dist/tools/symbol-search.js +110 -0
  103. package/docs/agent-tooling-improvement-plan.md +660 -0
  104. package/docs/agent-tools.md +129 -0
  105. package/docs/ast-grep_rules_catalog.md +564 -0
  106. package/docs/astplayground.md +178 -0
  107. package/docs/audit1.md +177 -0
  108. package/docs/custom-rules.md +231 -0
  109. package/docs/dependencies.md +54 -0
  110. package/docs/env_variables.md +10 -0
  111. package/docs/environment-variables.md +67 -0
  112. package/docs/fable.md +139 -0
  113. package/docs/features.md +338 -0
  114. package/docs/globalconfig.md +79 -0
  115. package/docs/inspiration_gograph.md +601 -0
  116. package/docs/inspiration_neovim.md +1795 -0
  117. package/docs/inspiration_neovim2.md +402 -0
  118. package/docs/inspiration_tree_sitter_rules.md +369 -0
  119. package/docs/inspiration_tree_sitter_rules_investigation.md +328 -0
  120. package/docs/inspiration_tree_sitter_rules_round2.md +470 -0
  121. package/docs/language-coverage.md +48 -0
  122. package/docs/lsp-bridge-analysis.md +444 -0
  123. package/docs/lsp-capability-matrix.md +157 -0
  124. package/docs/lsp-latency-benchmark.md +169 -0
  125. package/docs/mcp.md +438 -0
  126. package/docs/module-report-read-symbol.md +258 -0
  127. package/docs/newinspiration.md +338 -0
  128. package/docs/servercapabilities.md +118 -0
  129. package/docs/subagent-compat.md +281 -0
  130. package/docs/tools.md +13 -0
  131. package/docs/tools_improvement2.md +345 -0
  132. package/docs/tree-sitter_rules_catalog.md +296 -0
  133. package/docs/usage.md +129 -0
  134. package/docs/word-index.md +150 -0
  135. package/grammars/tree-sitter-c.wasm +0 -0
  136. package/grammars/tree-sitter-c_sharp.wasm +0 -0
  137. package/grammars/tree-sitter-cpp.wasm +0 -0
  138. package/grammars/tree-sitter-dart.wasm +0 -0
  139. package/grammars/tree-sitter-elixir.wasm +0 -0
  140. package/grammars/tree-sitter-kotlin.wasm +0 -0
  141. package/grammars/tree-sitter-lua.wasm +0 -0
  142. package/grammars/tree-sitter-ocaml.wasm +0 -0
  143. package/grammars/tree-sitter-php.wasm +0 -0
  144. package/grammars/tree-sitter-ruby.wasm +0 -0
  145. package/grammars/tree-sitter-swift.wasm +0 -0
  146. package/grammars/tree-sitter-toml.wasm +0 -0
  147. package/grammars/tree-sitter-vue.wasm +0 -0
  148. package/grammars/tree-sitter-zig.wasm +0 -0
  149. package/package.json +5 -1
  150. package/rules/ast-grep-rules/rule-tests/hardcoded-url-js-test.yml +1 -0
  151. package/rules/ast-grep-rules/rule-tests/no-typeof-undefined-js-test.yml +8 -0
  152. package/rules/ast-grep-rules/rule-tests/no-typeof-undefined-test.yml +8 -0
  153. package/rules/ast-grep-rules/rules/array-callback-return-js.yml +1 -1
  154. package/rules/ast-grep-rules/rules/hardcoded-url-js.yml +1 -1
  155. package/rules/ast-grep-rules/rules/jwt-no-verify-js.yml +1 -1
  156. package/rules/ast-grep-rules/rules/no-extra-boolean-cast-js.yml +1 -1
  157. package/rules/ast-grep-rules/rules/no-implied-eval-js.yml +1 -1
  158. package/rules/ast-grep-rules/rules/no-javascript-url-js.yml +1 -1
  159. package/rules/ast-grep-rules/rules/no-sql-in-code-js.yml +1 -1
  160. package/rules/ast-grep-rules/rules/no-typeof-undefined-js.yml +9 -7
  161. package/rules/ast-grep-rules/rules/no-typeof-undefined.yml +9 -7
  162. package/rules/ast-grep-rules/rules/weak-rsa-key-js.yml +1 -1
  163. package/skills/{ast-grep → pi-lens-ast-grep}/SKILL.md +4 -2
  164. package/skills/{lsp-navigation → pi-lens-lsp-navigation}/SKILL.md +1 -1
  165. package/skills/{write-ast-grep-rule → pi-lens-write-ast-grep-rule}/SKILL.md +1 -1
  166. package/skills/{write-tree-sitter-rule → pi-lens-write-tree-sitter-rule}/SKILL.md +1 -1
  167. package/dist/clients/production-readiness.js +0 -493
  168. package/dist/clients/tree-sitter-fixer.js +0 -127
package/README.md CHANGED
@@ -16,6 +16,8 @@ pi-lens gives AI coding agents fast, language-aware feedback while they write/ed
16
16
  - Agent-facing tools for LSP navigation/diagnostics, AST search/replace,
17
17
  diagnostics state, and project intelligence
18
18
  - Review-graph intelligence for supported languages via bundled tree-sitter WASMs
19
+ - Ranked identifier search (`symbol_search`) over an always-warm word index,
20
+ feeding the discovery funnel (symbol_search → module_report → read_symbol)
19
21
  - Read-guard and edit-autopatch support to reduce bad edits
20
22
  - Background security/dependency scans for opted-in projects
21
23
 
@@ -37,6 +39,8 @@ pi install git:github.com/apmantza/pi-lens
37
39
  - [Usage guide](docs/usage.md) — lifecycle, tool behavior, MCP notes, and
38
40
  troubleshooting
39
41
  - [Features](docs/features.md) — detailed feature reference
42
+ - [Word index](docs/word-index.md) — identifier search (`symbol_search`) and
43
+ the discovery funnel
40
44
  - [Tools and commands](docs/tools.md) — runtime flags and slash commands
41
45
  - [Configuration](docs/globalconfig.md) — global and project config files
42
46
  - [Environment variables](docs/env_variables.md) — common env vars and full
@@ -63,94 +67,109 @@ Thanks goes to these wonderful people:
63
67
  <table>
64
68
  <tbody>
65
69
  <tr>
66
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/wastedC"><img src="https://avatars.githubusercontent.com/u/917574?v=4" width="100px;" alt=""/><br /><sub><b>wastedC</b></sub></a><br /><a href="#code-wastedC" title="Code">💻</a> <a href="#ideas-wastedC" title="Ideas & Planning">🤔</a> <a href="#maintenance-wastedC" title="Maintenance">🚧</a> <a href="#review-wastedC" title="Reviewed Pull Requests">👀</a></td>
67
70
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/apmantza"><img src="https://avatars.githubusercontent.com/u/247365598?v=4" width="100px;" alt=""/><br /><sub><b>Apostolos Mantzaris</b></sub></a><br /><a href="#code-apmantza" title="Code">💻</a> <a href="#doc-apmantza" title="Documentation">📖</a> <a href="#ideas-apmantza" title="Ideas & Planning">🤔</a> <a href="#maintenance-apmantza" title="Maintenance">🚧</a> <a href="#review-apmantza" title="Reviewed Pull Requests">👀</a></td>
68
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/apps/dependabot"><img src="https://avatars.githubusercontent.com/in/29110?v=4" width="100px;" alt=""/><br /><sub><b>Dependabot</b></sub></a><br /><a href="#maintenance-dependabot[bot]" title="Maintenance">🚧</a></td>
69
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/silvanshade"><img src="https://avatars.githubusercontent.com/u/11022302?v=4" width="100px;" alt=""/><br /><sub><b>silvanshade</b></sub></a><br /><a href="#code-silvanshade" title="Code">💻</a></td>
70
71
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/anh-chu"><img src="https://avatars.githubusercontent.com/u/34973633?v=4" width="100px;" alt=""/><br /><sub><b>Anh Chu</b></sub></a><br /><a href="#code-anh-chu" title="Code">💻</a></td>
71
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/Istar-Eldritch"><img src="https://avatars.githubusercontent.com/u/3746468?v=4" width="100px;" alt=""/><br /><sub><b>Ruben Paz</b></sub></a><br /><a href="#code-Istar-Eldritch" title="Code">💻</a> <a href="#bug-Istar-Eldritch" title="Bug reports">🐛</a></td>
72
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/loss-and-quick"><img src="https://avatars.githubusercontent.com/u/39405619?v=4" width="100px;" alt=""/><br /><sub><b>minicx</b></sub></a><br /><a href="#code-loss-and-quick" title="Code">💻</a></td>
73
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/silvanshade"><img src="https://avatars.githubusercontent.com/u/11022302?v=4" width="100px;" alt=""/><br /><sub><b>silvanshade</b></sub></a><br /><a href="#code-silvanshade" title="Code">💻</a></td>
72
74
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/tifandotme"><img src="https://avatars.githubusercontent.com/u/33323177?v=4" width="100px;" alt=""/><br /><sub><b>Tifan Dwi Avianto</b></sub></a><br /><a href="#code-tifandotme" title="Code">💻</a></td>
75
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/amit-gshe"><img src="https://avatars.githubusercontent.com/u/7383028?v=4" width="100px;" alt=""/><br /><sub><b>Amit</b></sub></a><br /><a href="#code-amit-gshe" title="Code">💻</a> <a href="#bug-amit-gshe" title="Bug reports">🐛</a></td>
76
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/fractiunate"><img src="https://avatars.githubusercontent.com/u/78024279?v=4" width="100px;" alt=""/><br /><sub><b>Fractiunate // David Rahäuser</b></sub></a><br /><a href="#code-fractiunate" title="Code">💻</a></td>
77
+ </tr><br />
78
+ <tr>
79
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/vkarasen"><img src="https://avatars.githubusercontent.com/u/7932536?v=4" width="100px;" alt=""/><br /><sub><b>Vitali Karasenko</b></sub></a><br /><a href="#code-vkarasen" title="Code">💻</a></td>
80
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/rfxlamia"><img src="https://avatars.githubusercontent.com/u/222023708?v=4" width="100px;" alt=""/><br /><sub><b>V</b></sub></a><br /><a href="#code-rfxlamia" title="Code">💻</a></td>
81
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/fabio-dee"><img src="https://avatars.githubusercontent.com/u/10808443?v=4" width="100px;" alt=""/><br /><sub><b>Fabio Dee</b></sub></a><br /><a href="#code-fabio-dee" title="Code">💻</a></td>
82
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/moj02090"><img src="https://avatars.githubusercontent.com/u/57255166?v=4" width="100px;" alt=""/><br /><sub><b>moj02090</b></sub></a><br /><a href="#code-moj02090" title="Code">💻</a></td>
83
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/mehalter"><img src="https://avatars.githubusercontent.com/u/1591837?v=4" width="100px;" alt=""/><br /><sub><b>Micah Halter</b></sub></a><br /><a href="#code-mehalter" title="Code">💻</a></td>
84
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/JulienEllie"><img src="https://avatars.githubusercontent.com/u/670518?v=4" width="100px;" alt=""/><br /><sub><b>Julien Ellie</b></sub></a><br /><a href="#code-JulienEllie" title="Code">💻</a></td>
85
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/Istar-Eldritch"><img src="https://avatars.githubusercontent.com/u/3746468?v=4" width="100px;" alt=""/><br /><sub><b>Ruben Paz</b></sub></a><br /><a href="#code-Istar-Eldritch" title="Code">💻</a> <a href="#bug-Istar-Eldritch" title="Bug reports">🐛</a></td>
73
86
  </tr><br />
74
87
  <tr>
75
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/ricardoraposo"><img src="https://avatars.githubusercontent.com/u/50217712?v=4" width="100px;" alt=""/><br /><sub><b>Ricardo Raposo</b></sub></a><br /><a href="#code-ricardoraposo" title="Code">💻</a></td>
76
88
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/cjunxiang"><img src="https://avatars.githubusercontent.com/u/26619858?v=4" width="100px;" alt=""/><br /><sub><b>C.Junxiang</b></sub></a><br /><a href="#code-cjunxiang" title="Code">💻</a> <a href="#bug-cjunxiang" title="Bug reports">🐛</a></td>
77
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/chenxin-yan"><img src="https://avatars.githubusercontent.com/u/71162231?v=4" width="100px;" alt=""/><br /><sub><b>Chenxin Yan</b></sub></a><br /><a href="#code-chenxin-yan" title="Code">💻</a> <a href="#doc-chenxin-yan" title="Documentation">📖</a> <a href="#bug-chenxin-yan" title="Bug reports">🐛</a></td>
78
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/feoh"><img src="https://avatars.githubusercontent.com/u/330070?v=4" width="100px;" alt=""/><br /><sub><b>Chris Patti</b></sub></a><br /><a href="#code-feoh" title="Code">💻</a> <a href="#bug-feoh" title="Bug reports">🐛</a></td>
79
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/fractiunate"><img src="https://avatars.githubusercontent.com/u/78024279?v=4" width="100px;" alt=""/><br /><sub><b>Fractiunate // David Rahäuser</b></sub></a><br /><a href="#code-fractiunate" title="Code">💻</a></td>
80
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/bloodf"><img src="https://avatars.githubusercontent.com/u/1626923?v=4" width="100px;" alt=""/><br /><sub><b>Heitor Ramon Ribeiro</b></sub></a><br /><a href="#code-bloodf" title="Code">💻</a></td>
81
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/JayceFreeman"><img src="https://avatars.githubusercontent.com/u/92962110?v=4" width="100px;" alt=""/><br /><sub><b>JayceFreeman</b></sub></a><br /><a href="#code-JayceFreeman" title="Code">💻</a></td>
89
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/trvon"><img src="https://avatars.githubusercontent.com/u/6031322?v=4" width="100px;" alt=""/><br /><sub><b>Trevon</b></sub></a><br /><a href="#code-trvon" title="Code">💻</a></td>
90
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/cmptr"><img src="https://avatars.githubusercontent.com/u/32746889?v=4" width="100px;" alt=""/><br /><sub><b>Aaron Bell</b></sub></a><br /><a href="#code-cmptr" title="Code">💻</a> <a href="#bug-cmptr" title="Bug reports">🐛</a></td>
91
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/StartupBros"><img src="https://avatars.githubusercontent.com/u/16693591?v=4" width="100px;" alt=""/><br /><sub><b>Will Mitchell</b></sub></a><br /><a href="#code-StartupBros" title="Code">💻</a></td>
92
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/spyrosbazios"><img src="https://avatars.githubusercontent.com/u/37960233?v=4" width="100px;" alt=""/><br /><sub><b>spyrosbazios</b></sub></a><br /><a href="#code-spyrosbazios" title="Code">💻</a></td>
93
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/Roman-Galeev"><img src="https://avatars.githubusercontent.com/u/40388226?v=4" width="100px;" alt=""/><br /><sub><b>Roman Galeev</b></sub></a><br /><a href="#code-Roman-Galeev" title="Code">💻</a></td>
94
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/jerryfan"><img src="https://avatars.githubusercontent.com/u/2540814?v=4" width="100px;" alt=""/><br /><sub><b>jerryfan</b></sub></a><br /><a href="#code-jerryfan" title="Code">💻</a></td>
82
95
  </tr><br />
83
96
  <tr>
84
97
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/shaDmx"><img src="https://avatars.githubusercontent.com/u/91132641?v=4" width="100px;" alt=""/><br /><sub><b>Max L.</b></sub></a><br /><a href="#code-shaDmx" title="Code">💻</a></td>
85
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/Roman-Galeev"><img src="https://avatars.githubusercontent.com/u/40388226?v=4" width="100px;" alt=""/><br /><sub><b>Roman Galeev</b></sub></a><br /><a href="#code-Roman-Galeev" title="Code">💻</a></td>
86
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/StartupBros"><img src="https://avatars.githubusercontent.com/u/16693591?v=4" width="100px;" alt=""/><br /><sub><b>Will Mitchell</b></sub></a><br /><a href="#code-StartupBros" title="Code">💻</a></td>
87
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/amit-gshe"><img src="https://avatars.githubusercontent.com/u/7383028?v=4" width="100px;" alt=""/><br /><sub><b>Amit</b></sub></a><br /><a href="#code-amit-gshe" title="Code">💻</a> <a href="#bug-amit-gshe" title="Bug reports">🐛</a></td>
98
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/radge"><img src="https://avatars.githubusercontent.com/u/129205?v=4" width="100px;" alt=""/><br /><sub><b>David Ryan</b></sub></a><br /><a href="#code-radge" title="Code">💻</a></td>
99
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/JayceFreeman"><img src="https://avatars.githubusercontent.com/u/92962110?v=4" width="100px;" alt=""/><br /><sub><b>JayceFreeman</b></sub></a><br /><a href="#code-JayceFreeman" title="Code">💻</a></td>
100
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/bloodf"><img src="https://avatars.githubusercontent.com/u/1626923?v=4" width="100px;" alt=""/><br /><sub><b>Heitor Ramon Ribeiro</b></sub></a><br /><a href="#code-bloodf" title="Code">💻</a></td>
101
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/feoh"><img src="https://avatars.githubusercontent.com/u/330070?v=4" width="100px;" alt=""/><br /><sub><b>Chris Patti</b></sub></a><br /><a href="#code-feoh" title="Code">💻</a> <a href="#bug-feoh" title="Bug reports">🐛</a></td>
102
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/chenxin-yan"><img src="https://avatars.githubusercontent.com/u/71162231?v=4" width="100px;" alt=""/><br /><sub><b>Chenxin Yan</b></sub></a><br /><a href="#code-chenxin-yan" title="Code">💻</a> <a href="#doc-chenxin-yan" title="Documentation">📖</a> <a href="#bug-chenxin-yan" title="Bug reports">🐛</a></td>
88
103
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/flowing-abyss"><img src="https://avatars.githubusercontent.com/u/98622217?v=4" width="100px;" alt=""/><br /><sub><b>flowing-abyss</b></sub></a><br /><a href="#code-flowing-abyss" title="Code">💻</a></td>
89
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/jerryfan"><img src="https://avatars.githubusercontent.com/u/2540814?v=4" width="100px;" alt=""/><br /><sub><b>jerryfan</b></sub></a><br /><a href="#code-jerryfan" title="Code">💻</a></td>
90
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/loss-and-quick"><img src="https://avatars.githubusercontent.com/u/39405619?v=4" width="100px;" alt=""/><br /><sub><b>minicx</b></sub></a><br /><a href="#code-loss-and-quick" title="Code">💻</a></td>
91
104
  </tr><br />
92
105
  <tr>
93
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/spyrosbazios"><img src="https://avatars.githubusercontent.com/u/37960233?v=4" width="100px;" alt=""/><br /><sub><b>spyrosbazios</b></sub></a><br /><a href="#code-spyrosbazios" title="Code">💻</a></td>
94
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/trvon"><img src="https://avatars.githubusercontent.com/u/6031322?v=4" width="100px;" alt=""/><br /><sub><b>Trevon</b></sub></a><br /><a href="#code-trvon" title="Code">💻</a></td>
106
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/xuli500177"><img src="https://avatars.githubusercontent.com/u/62830942?v=4" width="100px;" alt=""/><br /><sub><b>Xu Yili</b></sub></a><br /><a href="#bug-xuli500177" title="Bug reports">🐛</a></td>
107
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/ricardoraposo"><img src="https://avatars.githubusercontent.com/u/50217712?v=4" width="100px;" alt=""/><br /><sub><b>Ricardo Raposo</b></sub></a><br /><a href="#code-ricardoraposo" title="Code">💻</a></td>
108
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/jsmestad"><img src="https://avatars.githubusercontent.com/u/16150?v=4" width="100px;" alt=""/><br /><sub><b>Justin Smestad</b></sub></a><br /><a href="#code-jsmestad" title="Code">💻</a></td>
109
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/anasalsbey-glitch"><img src="https://avatars.githubusercontent.com/u/283609702?v=4" width="100px;" alt=""/><br /><sub><b>anasalsbey-glitch</b></sub></a><br /><a href="#code-anasalsbey-glitch" title="Code">💻</a> <a href="#bug-anasalsbey-glitch" title="Bug reports">🐛</a></td>
110
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/PeraSite"><img src="https://avatars.githubusercontent.com/u/19837403?v=4" width="100px;" alt=""/><br /><sub><b>정제훈</b></sub></a><br /><a href="#code-PeraSite" title="Code">💻</a></td>
95
111
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/carolitascl"><img src="https://avatars.githubusercontent.com/u/26188349?v=4" width="100px;" alt=""/><br /><sub><b>Carolina</b></sub></a><br /><a href="#bug-carolitascl" title="Bug reports">🐛</a></td>
96
112
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/leakedby"><img src="https://avatars.githubusercontent.com/u/4213260?v=4" width="100px;" alt=""/><br /><sub><b>LeakedBy</b></sub></a><br /><a href="#bug-leakedby" title="Bug reports">🐛</a></td>
113
+ </tr><br />
114
+ <tr>
97
115
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/mdbooth"><img src="https://avatars.githubusercontent.com/u/1318691?v=4" width="100px;" alt=""/><br /><sub><b>Matthew Booth</b></sub></a><br /><a href="#bug-mdbooth" title="Bug reports">🐛</a></td>
98
116
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/Wiedzmin89"><img src="https://avatars.githubusercontent.com/u/61706855?v=4" width="100px;" alt=""/><br /><sub><b>Wiedzmin89</b></sub></a><br /><a href="#ideas-Wiedzmin89" title="Ideas & Planning">🤔</a></td>
99
117
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/grodingo"><img src="https://avatars.githubusercontent.com/u/244184972?v=4" width="100px;" alt=""/><br /><sub><b>Virgile</b></sub></a><br /><a href="#bug-grodingo" title="Bug reports">🐛</a></td>
100
- </tr><br />
101
- <tr>
102
118
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/finnvyrn"><img src="https://avatars.githubusercontent.com/u/90801772?v=4" width="100px;" alt=""/><br /><sub><b>Finn</b></sub></a><br /><a href="#ideas-finnvyrn" title="Ideas & Planning">🤔</a></td>
103
119
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/ywh555hhh"><img src="https://avatars.githubusercontent.com/u/121592812?v=4" width="100px;" alt=""/><br /><sub><b>Wayne E</b></sub></a><br /><a href="#bug-ywh555hhh" title="Bug reports">🐛</a></td>
104
120
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/RimuruW"><img src="https://avatars.githubusercontent.com/u/59136309?v=4" width="100px;" alt=""/><br /><sub><b>RimuruW</b></sub></a><br /><a href="#ideas-RimuruW" title="Ideas & Planning">🤔</a></td>
105
121
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/Bjynt"><img src="https://avatars.githubusercontent.com/u/22177300?v=4" width="100px;" alt=""/><br /><sub><b>Bjynt</b></sub></a><br /><a href="#bug-Bjynt" title="Bug reports">🐛</a> <a href="#ideas-Bjynt" title="Ideas & Planning">🤔</a></td>
122
+ </tr><br />
123
+ <tr>
106
124
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/pvtri96"><img src="https://avatars.githubusercontent.com/u/28696888?v=4" width="100px;" alt=""/><br /><sub><b>Tri Van Pham</b></sub></a><br /><a href="#bug-pvtri96" title="Bug reports">🐛</a></td>
107
125
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/BingWuJ"><img src="https://avatars.githubusercontent.com/u/117666511?v=4" width="100px;" alt=""/><br /><sub><b>BingWuJ</b></sub></a><br /><a href="#bug-BingWuJ" title="Bug reports">🐛</a></td>
108
126
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/tackleberrybey"><img src="https://avatars.githubusercontent.com/u/156954032?v=4" width="100px;" alt=""/><br /><sub><b>tackleberrybey</b></sub></a><br /><a href="#bug-tackleberrybey" title="Bug reports">🐛</a></td>
109
- </tr><br />
110
- <tr>
111
127
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/TaterDoge"><img src="https://avatars.githubusercontent.com/u/121467933?v=4" width="100px;" alt=""/><br /><sub><b>Mariann Abshire</b></sub></a><br /><a href="#bug-TaterDoge" title="Bug reports">🐛</a></td>
112
128
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/ProbabilityEngineer"><img src="https://avatars.githubusercontent.com/u/38498804?v=4" width="100px;" alt=""/><br /><sub><b>ProbabilityEngineer</b></sub></a><br /><a href="#ideas-ProbabilityEngineer" title="Ideas & Planning">🤔</a></td>
113
129
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/LeonardoRick"><img src="https://avatars.githubusercontent.com/u/17517057?v=4" width="100px;" alt=""/><br /><sub><b>Leonardo Rick</b></sub></a><br /><a href="#bug-LeonardoRick" title="Bug reports">🐛</a> <a href="#ideas-LeonardoRick" title="Ideas & Planning">🤔</a></td>
114
130
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/kenbanks-peng"><img src="https://avatars.githubusercontent.com/u/26904200?v=4" width="100px;" alt=""/><br /><sub><b>Ken Banks</b></sub></a><br /><a href="#bug-kenbanks-peng" title="Bug reports">🐛</a></td>
131
+ </tr><br />
132
+ <tr>
115
133
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/rstacruz"><img src="https://avatars.githubusercontent.com/u/74385?v=4" width="100px;" alt=""/><br /><sub><b>Rico Sta. Cruz</b></sub></a><br /><a href="#ideas-rstacruz" title="Ideas & Planning">🤔</a></td>
116
134
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/acidnik"><img src="https://avatars.githubusercontent.com/u/1227955?v=4" width="100px;" alt=""/><br /><sub><b>Nikita Bilous</b></sub></a><br /><a href="#bug-acidnik" title="Bug reports">🐛</a></td>
117
135
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/av1155"><img src="https://avatars.githubusercontent.com/u/117413846?v=4" width="100px;" alt=""/><br /><sub><b>Andrea Arturo Venti Fuentes</b></sub></a><br /><a href="#bug-av1155" title="Bug reports">🐛</a></td>
118
- </tr><br />
119
- <tr>
120
136
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/dacec354"><img src="https://avatars.githubusercontent.com/u/90093629?v=4" width="100px;" alt=""/><br /><sub><b>dacec354</b></sub></a><br /><a href="#bug-dacec354" title="Bug reports">🐛</a></td>
121
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/xuli500177"><img src="https://avatars.githubusercontent.com/u/62830942?v=4" width="100px;" alt=""/><br /><sub><b>Xu Yili</b></sub></a><br /><a href="#bug-xuli500177" title="Bug reports">🐛</a></td>
122
137
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/nodnarbnitram"><img src="https://avatars.githubusercontent.com/u/44812862?v=4" width="100px;" alt=""/><br /><sub><b>Brandon Martin</b></sub></a><br /><a href="#bug-nodnarbnitram" title="Bug reports">🐛</a></td>
123
138
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/alpertarhan"><img src="https://avatars.githubusercontent.com/u/50966980?v=4" width="100px;" alt=""/><br /><sub><b>Alper Tarhan</b></sub></a><br /><a href="#bug-alpertarhan" title="Bug reports">🐛</a></td>
124
139
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/asolopovas"><img src="https://avatars.githubusercontent.com/u/6893216?v=4" width="100px;" alt=""/><br /><sub><b>Andrius Solopovas</b></sub></a><br /><a href="#bug-asolopovas" title="Bug reports">🐛</a></td>
125
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/GrahamJenkins"><img src="https://avatars.githubusercontent.com/u/6607975?v=4" width="100px;" alt=""/><br /><sub><b>Graham Jenkins</b></sub></a><br /><a href="#bug-GrahamJenkins" title="Bug reports">🐛</a></td>
126
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/wings1848"><img src="https://avatars.githubusercontent.com/u/120104016?v=4" width="100px;" alt=""/><br /><sub><b>Wings Butterfly</b></sub></a><br /><a href="#bug-wings1848" title="Bug reports">🐛</a></td>
127
140
  </tr><br />
128
141
  <tr>
142
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/GrahamJenkins"><img src="https://avatars.githubusercontent.com/u/6607975?v=4" width="100px;" alt=""/><br /><sub><b>Graham Jenkins</b></sub></a><br /><a href="#bug-GrahamJenkins" title="Bug reports">🐛</a></td>
143
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/wings1848"><img src="https://avatars.githubusercontent.com/u/120104016?v=4" width="100px;" alt=""/><br /><sub><b>Wings Butterfly</b></sub></a><br /><a href="#bug-wings1848" title="Bug reports">🐛</a></td>
129
144
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/JohannesKlauss"><img src="https://avatars.githubusercontent.com/u/6214415?v=4" width="100px;" alt=""/><br /><sub><b>Johannes Klauss</b></sub></a><br /><a href="#ideas-JohannesKlauss" title="Ideas & Planning">🤔</a></td>
130
145
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/tan-yong-sheng"><img src="https://avatars.githubusercontent.com/u/64836390?v=4" width="100px;" alt=""/><br /><sub><b>Tan Yong Sheng</b></sub></a><br /><a href="#ideas-tan-yong-sheng" title="Ideas & Planning">🤔</a></td>
131
146
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/fmatray"><img src="https://avatars.githubusercontent.com/u/8267716?v=4" width="100px;" alt=""/><br /><sub><b>Frédéric</b></sub></a><br /><a href="#bug-fmatray" title="Bug reports">🐛</a></td>
132
147
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/fuentesjr"><img src="https://avatars.githubusercontent.com/u/9240?v=4" width="100px;" alt=""/><br /><sub><b>Salvador Fuentes Jr</b></sub></a><br /><a href="#bug-fuentesjr" title="Bug reports">🐛</a></td>
133
148
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/Viterkim"><img src="https://avatars.githubusercontent.com/u/17838985?v=4" width="100px;" alt=""/><br /><sub><b>Viktor</b></sub></a><br /><a href="#bug-Viterkim" title="Bug reports">🐛</a></td>
134
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/ortonomy"><img src="https://avatars.githubusercontent.com/u/6688676?v=4" width="100px;" alt=""/><br /><sub><b>Gregory Orton</b></sub></a><br /><a href="#bug-ortonomy" title="Bug reports">🐛</a></td>
135
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/jimallen"><img src="https://avatars.githubusercontent.com/u/868773?v=4" width="100px;" alt=""/><br /><sub><b>Jim Allen</b></sub></a><br /><a href="#bug-jimallen" title="Bug reports">🐛</a></td>
136
149
  </tr><br />
137
150
  <tr>
151
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/ortonomy"><img src="https://avatars.githubusercontent.com/u/6688676?v=4" width="100px;" alt=""/><br /><sub><b>Gregory Orton</b></sub></a><br /><a href="#bug-ortonomy" title="Bug reports">🐛</a></td>
152
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/jimallen"><img src="https://avatars.githubusercontent.com/u/868773?v=4" width="100px;" alt=""/><br /><sub><b>Jim Allen</b></sub></a><br /><a href="#bug-jimallen" title="Bug reports">🐛</a></td>
138
153
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/quantfiction"><img src="https://avatars.githubusercontent.com/u/49965454?v=4" width="100px;" alt=""/><br /><sub><b>quantfiction</b></sub></a><br /><a href="#bug-quantfiction" title="Bug reports">🐛</a></td>
139
154
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/Artawower"><img src="https://avatars.githubusercontent.com/u/16963833?v=4" width="100px;" alt=""/><br /><sub><b>Art</b></sub></a><br /><a href="#bug-Artawower" title="Bug reports">🐛</a></td>
140
155
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/chozandrias76"><img src="https://avatars.githubusercontent.com/u/2087677?v=4" width="100px;" alt=""/><br /><sub><b>Colin Swenson-Healey</b></sub></a><br /><a href="#bug-chozandrias76" title="Bug reports">🐛</a></td>
141
156
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/JustlyAI"><img src="https://avatars.githubusercontent.com/u/12634140?v=4" width="100px;" alt=""/><br /><sub><b>Laurent Wiesel</b></sub></a><br /><a href="#bug-JustlyAI" title="Bug reports">🐛</a></td>
142
157
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/stark-bit"><img src="https://avatars.githubusercontent.com/u/44064758?v=4" width="100px;" alt=""/><br /><sub><b>Rei Starks</b></sub></a><br /><a href="#bug-stark-bit" title="Bug reports">🐛</a></td>
143
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/robertoecf"><img src="https://avatars.githubusercontent.com/u/54923863?v=4" width="100px;" alt=""/><br /><sub><b>Roberto Freitas</b></sub></a><br /><a href="#bug-robertoecf" title="Bug reports">🐛</a></td>
144
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/tmttodd"><img src="https://avatars.githubusercontent.com/u/160077416?v=4" width="100px;" alt=""/><br /><sub><b>tmttodd</b></sub></a><br /><a href="#bug-tmttodd" title="Bug reports">🐛</a></td>
145
158
  </tr><br />
146
159
  <tr>
160
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/robertoecf"><img src="https://avatars.githubusercontent.com/u/54923863?v=4" width="100px;" alt=""/><br /><sub><b>Roberto Freitas</b></sub></a><br /><a href="#bug-robertoecf" title="Bug reports">🐛</a></td>
161
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/tmttodd"><img src="https://avatars.githubusercontent.com/u/160077416?v=4" width="100px;" alt=""/><br /><sub><b>tmttodd</b></sub></a><br /><a href="#bug-tmttodd" title="Bug reports">🐛</a></td>
147
162
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/smaharnav"><img src="https://avatars.githubusercontent.com/u/263328627?v=4" width="100px;" alt=""/><br /><sub><b>smaharnav</b></sub></a><br /><a href="#bug-smaharnav" title="Bug reports">🐛</a></td>
148
163
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/MzaGuille"><img src="https://avatars.githubusercontent.com/u/151482698?v=4" width="100px;" alt=""/><br /><sub><b>Mza.Guille</b></sub></a><br /><a href="#bug-MzaGuille" title="Bug reports">🐛</a></td>
149
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/cmptr"><img src="https://avatars.githubusercontent.com/u/32746889?v=4" width="100px;" alt=""/><br /><sub><b>Aaron Bell</b></sub></a><br /><a href="#code-cmptr" title="Code">💻</a> <a href="#bug-cmptr" title="Bug reports">🐛</a></td>
150
164
  <td align="center" valign="top" width="14.28%"><a href="http://jasonrimmer.com/"><img src="https://avatars.githubusercontent.com/u/629?v=4" width="100px;" alt=""/><br /><sub><b>Jason Rimmer</b></sub></a><br /><a href="#bug-jrimmer" title="Bug reports">🐛</a></td>
151
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/moj02090"><img src="https://avatars.githubusercontent.com/u/57255166?v=4" width="100px;" alt=""/><br /><sub><b>moj02090</b></sub></a><br /><a href="#code-moj02090" title="Code">💻</a></td>
152
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/vkarasen"><img src="https://avatars.githubusercontent.com/u/7932536?v=4" width="100px;" alt=""/><br /><sub><b>Vitali Karasenko</b></sub></a><br /><a href="#code-vkarasen" title="Code">💻</a></td>
153
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/mehalter"><img src="https://avatars.githubusercontent.com/u/1591837?v=4" width="100px;" alt=""/><br /><sub><b>Micah Halter</b></sub></a><br /><a href="#code-mehalter" title="Code">💻</a></td>
165
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/ArthurHeymans"><img src="https://avatars.githubusercontent.com/u/15137817?v=4" width="100px;" alt=""/><br /><sub><b>Arthur Heymans</b></sub></a><br /><a href="#ideas-ArthurHeymans" title="Ideas & Planning">🤔</a></td>
166
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/Blue-B"><img src="https://avatars.githubusercontent.com/u/55532956?v=4" width="100px;" alt=""/><br /><sub><b>Blue-B</b></sub></a><br /><a href="#bug-Blue-B" title="Bug reports">🐛</a></td>
167
+ </tr><br />
168
+ <tr>
169
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/bisko"><img src="https://avatars.githubusercontent.com/u/184938?v=4" width="100px;" alt=""/><br /><sub><b>Biser Perchinkov</b></sub></a><br /><a href="#bug-bisko" title="Bug reports">🐛</a></td>
170
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/xificurC"><img src="https://avatars.githubusercontent.com/u/4800719?v=4" width="100px;" alt=""/><br /><sub><b>Peter Nagy</b></sub></a><br /><a href="#bug-xificurC" title="Bug reports">🐛</a></td>
171
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/orest-tokovenko-block"><img src="https://avatars.githubusercontent.com/u/190554467?v=4" width="100px;" alt=""/><br /><sub><b>Orest Tokovenko</b></sub></a><br /><a href="#bug-orest-tokovenko-block" title="Bug reports">🐛</a></td>
172
+ <td align="center" valign="top" width="14.28%"><a href="https://eisterman.dev/"><img src="https://avatars.githubusercontent.com/u/3028953?v=4" width="100px;" alt=""/><br /><sub><b>Federico Pasqua</b></sub></a><br /><a href="#bug-eisterman" title="Bug reports">🐛</a></td>
154
173
  </tr>
155
174
  </tbody>
156
175
  </table>
@@ -0,0 +1,262 @@
1
+ import { logLatency } from "./latency-logger.js";
2
+ import { normalizeMapKey } from "./path-utils.js";
3
+ const BUS_FILES_TOUCHED_EVENT = "pilens:files:touched";
4
+ const MAX_NAMES_SHOWN = 5;
5
+ // Module-level accumulator: one process/session, so a plain map keyed via
6
+ // normalizeMapKey (house style — every map/set key in this module MUST go
7
+ // through it; a hand-rolled replace() is the exact trap that cost two red CI
8
+ // rounds on #458's reconcile tests, PR #491) is sufficient. Cleared ONLY
9
+ // inside consumeAgentNudge() (i.e. only at actual injection into a `context`
10
+ // call) — deliberately NOT tied to turn_start/agent_end/agent_settled, so
11
+ // entries accumulated during run A's turn_end survive until run B's first
12
+ // `context` call in the same session (the cross-run `git status` case).
13
+ const _touched = new Map();
14
+ // Count of bus-reported paths dropped by the relevance filter (file never
15
+ // read/edited this session) since the last consume — drained alongside the
16
+ // accumulator so the `agent_nudge` phase can report how much the filter
17
+ // actually suppresses, which is the metric that validates (or indicts) the
18
+ // "only nudge for files the session saw" rule.
19
+ let _relevanceFilteredCount = 0;
20
+ /** Test-only: clear accumulator state between test files/cases. */
21
+ export function _resetAgentNudgeForTests() {
22
+ _touched.clear();
23
+ _relevanceFilteredCount = 0;
24
+ _enabledCache = undefined;
25
+ }
26
+ // --- Kill switch (lazy, memoized — house style per clients/quiet-window.ts) ---
27
+ let _enabledCache;
28
+ /** `PI_LENS_AGENT_NUDGE=0` disables accumulation and injection outright. */
29
+ export function isAgentNudgeEnabled() {
30
+ if (_enabledCache === undefined) {
31
+ _enabledCache = process.env.PI_LENS_AGENT_NUDGE !== "0";
32
+ }
33
+ return _enabledCache;
34
+ }
35
+ function isValidPayload(data) {
36
+ if (!data || typeof data !== "object")
37
+ return false;
38
+ const p = data;
39
+ return (p.v === 1 &&
40
+ p.source === "pi-lens" &&
41
+ (p.reason === "autofix" || p.reason === "format") &&
42
+ Array.isArray(p.paths));
43
+ }
44
+ /**
45
+ * Record a `pilens:files:touched` event into the accumulator, filtered to
46
+ * files the session has actually read or edited (read-guard is the source of
47
+ * truth — `getReadHistory`/`getEditHistory` both key internally via
48
+ * `normalizeFilePath`, so either separator form on the incoming bus payload
49
+ * or the guard's stored records resolves to the same record regardless of
50
+ * which form was recorded first).
51
+ */
52
+ function recordTouchedEvent(payload, getReadGuard) {
53
+ const readGuard = getReadGuard();
54
+ if (!readGuard)
55
+ return;
56
+ for (const rawPath of payload.paths) {
57
+ const isRelevant = readGuard.getReadHistory(rawPath).length > 0 ||
58
+ readGuard.getEditHistory(rawPath).length > 0;
59
+ if (!isRelevant) {
60
+ _relevanceFilteredCount++;
61
+ continue;
62
+ }
63
+ const mapKey = normalizeMapKey(rawPath);
64
+ const existing = _touched.get(mapKey);
65
+ if (existing) {
66
+ existing.reasons.add(payload.reason);
67
+ // "local" is sticky — see AccumulatedFileOrigin doc. A file already
68
+ // recorded via the cross-process feed, now also reported by this
69
+ // session's own bus, upgrades to "local".
70
+ existing.origin = "local";
71
+ }
72
+ else {
73
+ _touched.set(mapKey, {
74
+ displayPath: rawPath,
75
+ reasons: new Set([payload.reason]),
76
+ origin: "local",
77
+ });
78
+ }
79
+ }
80
+ }
81
+ /**
82
+ * Feed entries read from the cross-process `recent-touches.json` record
83
+ * (#492, clients/recent-touches.ts) into the SAME accumulator #485 uses for
84
+ * in-process bus events — one accumulator, one `consumeAgentNudge` call, one
85
+ * batched context message regardless of how many files came from which
86
+ * channel.
87
+ *
88
+ * Relevance filtering differs by call site (#492 point 6) and is therefore
89
+ * the CALLER's responsibility, not this function's:
90
+ * - parent at turn_start: read-guard history FIRST (a parent usually has
91
+ * one), falling back to recency-only for files it hasn't read (a parent
92
+ * about to `git commit` needs attribution even for unread files);
93
+ * - child at session_start: recency + file-existence only (no read
94
+ * history exists yet this early).
95
+ * `readCrossProcessTouchesForTurnStart` / `...ForSessionStart` in
96
+ * recent-touches.ts already apply their own recency/existence/self-pid
97
+ * filtering before entries reach here — this function does not re-derive
98
+ * relevance, it only merges into the accumulator and marks provenance.
99
+ *
100
+ * Self-exclusion (an entry this process itself published never nudges
101
+ * itself) and path+ts dedup across repeated reads of the record are both
102
+ * handled upstream in recent-touches.ts (pid filter + last-consumed cursor),
103
+ * so entries reaching this function are already the "new, foreign" set.
104
+ */
105
+ export function recordCrossProcessTouches(entries) {
106
+ if (!isAgentNudgeEnabled())
107
+ return;
108
+ for (const entry of entries) {
109
+ const mapKey = normalizeMapKey(entry.path);
110
+ const existing = _touched.get(mapKey);
111
+ if (existing) {
112
+ existing.reasons.add(entry.reason);
113
+ // "local" is sticky (see AccumulatedFileOrigin) — never downgrade an
114
+ // already-local entry back to cross-process.
115
+ }
116
+ else {
117
+ _touched.set(mapKey, {
118
+ displayPath: entry.path,
119
+ reasons: new Set([entry.reason]),
120
+ origin: "cross-process",
121
+ });
122
+ }
123
+ }
124
+ }
125
+ /**
126
+ * Subscribe to `pilens:files:touched` on pi's shared event bus. Called once
127
+ * at extension factory time from index.ts, mirroring `wireBusEmitter`'s
128
+ * placement (clients/bus-publish.ts). No-ops silently when `pi.events` or
129
+ * `.on` is unavailable (older pi host) — never throws.
130
+ */
131
+ export function wireAgentNudgeSubscriber(args) {
132
+ const { events, getReadGuard, dbg } = args;
133
+ if (!events?.on)
134
+ return;
135
+ try {
136
+ events.on(BUS_FILES_TOUCHED_EVENT, (data) => {
137
+ if (!isAgentNudgeEnabled())
138
+ return;
139
+ if (!isValidPayload(data))
140
+ return;
141
+ try {
142
+ recordTouchedEvent(data, getReadGuard);
143
+ }
144
+ catch (err) {
145
+ dbg?.(`agent-nudge: failed to record touched event: ${err}`);
146
+ }
147
+ });
148
+ }
149
+ catch (err) {
150
+ dbg?.(`agent-nudge: subscribe failed (older pi host?): ${err}`);
151
+ }
152
+ }
153
+ /**
154
+ * Consume the accumulated touched-file set and produce (at most) one context
155
+ * message, e.g.:
156
+ * "pi-lens: 2 file(s) were autoformatted after your last turn: a.ts, b.ts —
157
+ * working-tree changes to these are expected; re-read before editing."
158
+ * The provenance framing ("pi-lens ... expected") matters: the primary pain
159
+ * case is an agent running `git status` (often at the START of a brand-new
160
+ * run/session, not just mid-run) and burning turns investigating diffs it
161
+ * did not knowingly make. Naming pi-lens as the source lets the agent act
162
+ * (re-read, proceed) instead of investigating.
163
+ *
164
+ * #492: attribution is three-way by the batch's origin mix (see
165
+ * AccumulatedFileOrigin) — still ONE message total (never split local vs.
166
+ * cross-process into two separate injections; the agent gets one coherent
167
+ * picture of everything unexplained in the working tree), but the wording
168
+ * must never assign a LOCAL file to another instance:
169
+ * - all local → "after your last turn" (the original #485 wording,
170
+ * unchanged — verified by the pre-existing #485
171
+ * tests);
172
+ * - all cross-process → "by another pi-lens instance (e.g. a subagent's)";
173
+ * - mixed → "after your last turn (N of them by another
174
+ * pi-lens instance)" — the base framing stays local
175
+ * and the cross-process portion is counted out
176
+ * precisely, so no local file is ever misattributed
177
+ * to another instance.
178
+ *
179
+ * Clears the accumulator ONLY here, on actual injection — never on
180
+ * agent_end/agent_settled/turn_start. Files formatted at the last turn_end of
181
+ * a PREVIOUS run must still nudge at the first turn of the NEXT run in the
182
+ * same session: this function is invoked from the `context` extension event
183
+ * (index.ts), which fires before every provider/LLM call — including the
184
+ * very first call of a fresh `agent_start` — so the accumulator surviving
185
+ * across run boundaries is exactly what makes that cross-run delivery work.
186
+ * Empty accumulator ⇒ returns undefined ⇒ zero bytes injected.
187
+ */
188
+ export function consumeAgentNudge(dbg) {
189
+ const entries = Array.from(_touched.values());
190
+ _touched.clear();
191
+ const filesFiltered = _relevanceFilteredCount;
192
+ _relevanceFilteredCount = 0;
193
+ if (!isAgentNudgeEnabled())
194
+ return undefined;
195
+ if (entries.length === 0)
196
+ return undefined;
197
+ try {
198
+ const filesTotal = entries.length;
199
+ const shown = entries.slice(0, MAX_NAMES_SHOWN);
200
+ const remaining = filesTotal - shown.length;
201
+ // Determine a single verb covering every reason seen across all
202
+ // accumulated files (not just the shown subset) — most turns will have
203
+ // a single uniform reason, so keep that common case terse; a mix of
204
+ // autofix + format across the batch falls back to a combined verb.
205
+ const allReasons = new Set();
206
+ for (const e of entries) {
207
+ for (const r of e.reasons)
208
+ allReasons.add(r);
209
+ }
210
+ const verbLabel = allReasons.size > 1
211
+ ? "autofixed/reformatted"
212
+ : allReasons.has("format")
213
+ ? "reformatted"
214
+ : "autofixed";
215
+ const names = shown.map((e) => e.displayPath);
216
+ const nameList = remaining > 0
217
+ ? `${names.join(", ")}, and ${remaining} more`
218
+ : names.join(", ");
219
+ const crossProcessCount = entries.filter((e) => e.origin === "cross-process").length;
220
+ const localCount = filesTotal - crossProcessCount;
221
+ // Three-way attribution (see the function doc): never assign a local
222
+ // file to another instance — a mixed batch keeps the local base framing
223
+ // and calls out the cross-process portion by exact count.
224
+ const attribution = localCount === 0
225
+ ? "by another pi-lens instance (e.g. a subagent's)"
226
+ : crossProcessCount === 0
227
+ ? "after your last turn"
228
+ : `after your last turn (${crossProcessCount} of them by another pi-lens instance)`;
229
+ const message = `pi-lens: ${filesTotal} file(s) were ${verbLabel} ${attribution}: ${nameList} — working-tree changes to these are expected; re-read before editing.`;
230
+ logLatency({
231
+ type: "phase",
232
+ filePath: "<pi-lens>",
233
+ phase: "agent_nudge",
234
+ durationMs: 0,
235
+ metadata: {
236
+ filesTotal,
237
+ filesShown: shown.length,
238
+ // Relevance-filter drops since the last consume (files the session
239
+ // never read/edited) — NOT the display overflow, which is
240
+ // filesTotal - filesShown.
241
+ filesFiltered,
242
+ reasonMix: Array.from(allReasons),
243
+ // #492: origin mix so cross-process pickup rate is observable
244
+ // alongside the existing relevance-filter metric.
245
+ originLocal: localCount,
246
+ originCrossProcess: crossProcessCount,
247
+ },
248
+ });
249
+ return {
250
+ messages: [
251
+ {
252
+ role: "user",
253
+ content: `[pi-lens automated context — not a user request] ${message}`,
254
+ },
255
+ ],
256
+ };
257
+ }
258
+ catch (err) {
259
+ dbg?.(`agent-nudge: consume failed: ${err}`);
260
+ return undefined;
261
+ }
262
+ }
@@ -8,9 +8,9 @@
8
8
  * Docs: https://biomejs.dev/
9
9
  */
10
10
  import * as fs from "node:fs";
11
- import * as os from "node:os";
12
11
  import * as path from "node:path";
13
12
  import { isFileKind } from "./file-kinds.js";
13
+ import { getGlobalPiLensDir } from "./file-utils.js";
14
14
  import { findGlobalBinary } from "./package-manager.js";
15
15
  import { safeSpawnAsync } from "./safe-spawn.js";
16
16
  // --- Client ---
@@ -56,7 +56,7 @@ export class BiomeClient {
56
56
  // the tool is already installed but not in the project's node_modules.
57
57
  // On Windows prefer .cmd (native batch) over the sh wrapper — 2x faster.
58
58
  const isWin = process.platform === "win32";
59
- const piLensBin = path.join(os.homedir(), ".pi-lens", "tools", "node_modules", ".bin");
59
+ const piLensBin = path.join(getGlobalPiLensDir(), "tools", "node_modules", ".bin");
60
60
  const candidates = isWin
61
61
  ? [
62
62
  path.join(resolveCwd, "node_modules", ".bin", "biome.cmd"),
@@ -63,7 +63,7 @@ export function loadBootstrapClients() {
63
63
  return [];
64
64
  }
65
65
  }
66
- const [ruffClient, biomeClient, knipClient, todoScanner, jscpdClient, depChecker, testRunnerClient, metricsClient, complexityClient, goClient, govulncheckClient, gitleaksClient, trivyClient, rustClient, agentBehaviorClient, deadCodeClients,] = await Promise.all([
66
+ const [ruffClient, biomeClient, knipClient, todoScanner, jscpdClient, depChecker, testRunnerClient, metricsClient, complexityClient, goClient, govulncheckClient, gitleaksClient, trivyClient, opengrepClient, rustClient, agentBehaviorClient, deadCodeClients,] = await Promise.all([
67
67
  load("ruff", async () => new (await import("./ruff-client.js")).RuffClient()),
68
68
  load("biome", async () => new (await import("./biome-client.js")).BiomeClient()),
69
69
  load("knip", async () => new (await import("./knip-client.js")).KnipClient()),
@@ -77,6 +77,7 @@ export function loadBootstrapClients() {
77
77
  load("govulncheck", async () => new (await import("./govulncheck-client.js")).GovulncheckClient()),
78
78
  load("gitleaks", async () => new (await import("./gitleaks-client.js")).GitleaksClient()),
79
79
  load("trivy", async () => new (await import("./trivy-client.js")).TrivyClient()),
80
+ load("opengrep", async () => new (await import("./opengrep-client.js")).OpengrepClient()),
80
81
  load("rust", async () => new (await import("./rust-client.js")).RustClient()),
81
82
  load("agent-behavior", async () => new (await import("./agent-behavior-client.js")).AgentBehaviorClient()),
82
83
  loadList("dead-code", async () => (await import("./dead-code-client.js")).getDeadCodeClients()),
@@ -97,6 +98,7 @@ export function loadBootstrapClients() {
97
98
  govulncheckClient,
98
99
  gitleaksClient,
99
100
  trivyClient,
101
+ opengrepClient,
100
102
  rustClient,
101
103
  agentBehaviorClient,
102
104
  deadCodeClients,
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Persistent NDJSON trace of `pi.events` bus publish attempts
3
+ * (`pilens:files:touched` #482 / `pilens:diagnostics` #502).
4
+ *
5
+ * Both producers (clients/bus-publish.ts, clients/diagnostics-publish.ts) are
6
+ * fire-and-forget: on failure or on a structural no-op (never wired, kill
7
+ * switch off) they only invoke an optional `dbg` callback, which varies by
8
+ * host and is a documented no-op in the MCP host (clients/mcp/session.ts's
9
+ * `dbg: noop`). That leaves bus-publish outcomes invisible in exactly the
10
+ * context where they matter most — same failure shape as the #544 MCP
11
+ * session_start incident this repo already fixed once.
12
+ *
13
+ * This module gives bus events the same durable trace every other pi-lens
14
+ * subsystem already has (latency.log, cascade.log, read-guard.log, ...) —
15
+ * see clients/latency-logger.ts for the house pattern this mirrors exactly:
16
+ * one shared `createNdjsonLogger` writer, `isTestMode()` no-op guard,
17
+ * `getBusEventsLogPath()` for testability.
18
+ *
19
+ * Logging volume: `emitted` and `emit_failed` are logged on every call —
20
+ * they're the two outcomes an operator actually needs a per-event trace for.
21
+ * `skipped_unwired` and `skipped_disabled` are process-lifetime-static facts
22
+ * (wiring happens once at extension factory time; the kill switch is an env
23
+ * var read once at startup) — logging them on every publish attempt would
24
+ * spam one identical line per write batch for an entire session with zero
25
+ * new information after the first. Both are gated log-once-per-process, the
26
+ * same `hasLoggedFailure` shape the producers already use for emit_failed.
27
+ * The empty-batch branch (`paths.length === 0` / `files.length === 0`) is
28
+ * NOT logged at all: every call site already guards against invoking these
29
+ * functions with nothing to report (see clients/pipeline.ts,
30
+ * clients/runtime-agent-end.ts), so it's a normal, frequent no-op rather
31
+ * than a real event worth a log line.
32
+ */
33
+ import * as path from "node:path";
34
+ import { isTestMode } from "./env-utils.js";
35
+ import { getGlobalPiLensDir } from "./file-utils.js";
36
+ import { createNdjsonLogger } from "./ndjson-logger.js";
37
+ const BUS_EVENTS_LOG_FILE = path.join(getGlobalPiLensDir(), "bus-events.log");
38
+ const writer = createNdjsonLogger({ filePath: BUS_EVENTS_LOG_FILE });
39
+ export function logBusEvent(entry) {
40
+ if (isTestMode()) {
41
+ return;
42
+ }
43
+ writer.log({ ts: new Date().toISOString(), ...entry });
44
+ }
45
+ export function getBusEventsLogPath() {
46
+ return BUS_EVENTS_LOG_FILE;
47
+ }
48
+ /** Resolve once all enqueued bus-event writes are on disk (tests/shutdown). */
49
+ export function flushBusEventsLog() {
50
+ return writer.flush();
51
+ }