pi-lens 3.8.73 → 3.8.74

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 (205) hide show
  1. package/CHANGELOG.md +1116 -0
  2. package/README.md +37 -21
  3. package/dist/clients/atomic-write.js +3 -3
  4. package/dist/clients/bus-events-logger.js +5 -1
  5. package/dist/clients/cache/rule-cache.js +43 -2
  6. package/dist/clients/cache-observability.js +211 -0
  7. package/dist/clients/call-graph.js +20 -0
  8. package/dist/clients/cascade-logger.js +5 -1
  9. package/dist/clients/complexity-client.js +4 -7
  10. package/dist/clients/dependency-checker.js +151 -20
  11. package/dist/clients/diagnostic-dispositions.js +22 -2
  12. package/dist/clients/dispatch/auxiliary-lsp.js +34 -0
  13. package/dist/clients/dispatch/dispatcher.js +16 -1
  14. package/dist/clients/dispatch/fact-store.js +11 -0
  15. package/dist/clients/dispatch/facts/function-facts.js +14 -0
  16. package/dist/clients/dispatch/facts/import-facts.js +100 -101
  17. package/dist/clients/dispatch/facts/tree-sitter-facts.js +5 -8
  18. package/dist/clients/dispatch/facts/try-catch-facts.js +13 -0
  19. package/dist/clients/dispatch/integration.js +94 -15
  20. package/dist/clients/dispatch/rules/async-noise.js +1 -0
  21. package/dist/clients/dispatch/rules/unsafe-boundary.js +7 -0
  22. package/dist/clients/dispatch/runners/ast-grep-napi.js +24 -1
  23. package/dist/clients/dispatch/runners/psscriptanalyzer.js +10 -1
  24. package/dist/clients/dispatch/runners/tree-sitter.js +153 -166
  25. package/dist/clients/dispatch/runners/utils/runner-helpers.js +17 -18
  26. package/dist/clients/feature-hints.js +53 -7
  27. package/dist/clients/file-kinds.js +86 -31
  28. package/dist/clients/fix-worklog.js +14 -2
  29. package/dist/clients/generated-artifacts.js +21 -0
  30. package/dist/clients/gzip-stage-write.js +84 -0
  31. package/dist/clients/installer/index.js +358 -129
  32. package/dist/clients/knip-client.js +64 -1
  33. package/dist/clients/language-profile.js +61 -72
  34. package/dist/clients/latency-logger.js +6 -16
  35. package/dist/clients/lens-config.js +128 -165
  36. package/dist/clients/lens-engine.js +39 -5
  37. package/dist/clients/lens-flag-registry.js +285 -0
  38. package/dist/clients/lens-map.js +2 -7
  39. package/dist/clients/log-cleanup.js +6 -17
  40. package/dist/clients/lsp/cascade-tier.js +22 -2
  41. package/dist/clients/lsp/client.js +74 -10
  42. package/dist/clients/lsp/config.js +62 -9
  43. package/dist/clients/lsp/index.js +34 -24
  44. package/dist/clients/lsp/language.js +4 -1
  45. package/dist/clients/lsp/launch.js +8 -6
  46. package/dist/clients/lsp/server.js +84 -29
  47. package/dist/clients/lsp-document-symbols.js +112 -0
  48. package/dist/clients/mcp/analyze.js +5 -4
  49. package/dist/clients/mcp/review.js +36 -3
  50. package/dist/clients/module-report.js +100 -80
  51. package/dist/clients/ndjson-logger.js +51 -21
  52. package/dist/clients/path-keyed-map.js +76 -0
  53. package/dist/clients/path-utils.js +35 -0
  54. package/dist/clients/performance-report.js +327 -0
  55. package/dist/clients/pipeline.js +7 -0
  56. package/dist/clients/project-changes.js +77 -4
  57. package/dist/clients/project-diagnostics/extractors.js +26 -115
  58. package/dist/clients/project-diagnostics/fresh-fetch.js +149 -19
  59. package/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +109 -0
  60. package/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +37 -5
  61. package/dist/clients/project-diagnostics/scanner.js +247 -127
  62. package/dist/clients/project-lens-config.js +53 -26
  63. package/dist/clients/project-report.js +39 -14
  64. package/dist/clients/project-scale.js +8 -10
  65. package/dist/clients/project-snapshot-persist-worker.js +6 -0
  66. package/dist/clients/project-snapshot.js +611 -7
  67. package/dist/clients/quiet-window.js +9 -1
  68. package/dist/clients/read-expansion.js +22 -12
  69. package/dist/clients/read-guard.js +78 -0
  70. package/dist/clients/redact/secrets.js +449 -0
  71. package/dist/clients/resource-sampler.js +133 -14
  72. package/dist/clients/reverse-deps.js +48 -0
  73. package/dist/clients/review-graph/builder.js +1522 -179
  74. package/dist/clients/review-graph/persist-worker.js +10 -0
  75. package/dist/clients/review-graph/query.js +5 -0
  76. package/dist/clients/review-graph/shared-extraction-ir.js +90 -0
  77. package/dist/clients/review-graph-logger.js +24 -0
  78. package/dist/clients/runtime-agent-end.js +4 -2
  79. package/dist/clients/runtime-coordinator.js +2 -2
  80. package/dist/clients/runtime-session.js +433 -59
  81. package/dist/clients/runtime-tool-call.js +46 -14
  82. package/dist/clients/runtime-turn.js +97 -26
  83. package/dist/clients/safe-spawn.js +102 -15
  84. package/dist/clients/session-start-observability.js +15 -0
  85. package/dist/clients/session-state-store.js +2 -1
  86. package/dist/clients/sessionstart-logger.js +18 -0
  87. package/dist/clients/sg-runner.js +48 -33
  88. package/dist/clients/source-filter.js +49 -25
  89. package/dist/clients/startup-marker.js +6 -0
  90. package/dist/clients/startup-timing.js +9 -0
  91. package/dist/clients/todo-scanner.js +30 -3
  92. package/dist/clients/tree-sitter-cache.js +147 -139
  93. package/dist/clients/tree-sitter-client.js +1340 -67
  94. package/dist/clients/tree-sitter-logger.js +43 -1
  95. package/dist/clients/tree-sitter-query-loader.js +110 -11
  96. package/dist/clients/tree-sitter-shared.js +43 -19
  97. package/dist/clients/tree-sitter-symbol-extractor.js +71 -4
  98. package/dist/clients/widget-state.js +50 -14
  99. package/dist/clients/word-index-logger.js +44 -0
  100. package/dist/clients/word-index.js +252 -35
  101. package/dist/clients/workspace-topology.js +11 -1
  102. package/dist/index.js +45544 -39255
  103. package/dist/mcp/analyze-cli.js +3 -0
  104. package/dist/mcp/cli.js +110 -0
  105. package/dist/mcp/server.js +63 -8
  106. package/dist/tools/lens-diagnostics.js +47 -5
  107. package/dist/tools/lsp-navigation.js +5 -28
  108. package/dist/tools/symbol-search.js +21 -2
  109. package/docs/agent-guide.md +247 -0
  110. package/docs/astplayground.md +3 -4
  111. package/docs/audit1.md +5 -5
  112. package/docs/environment-variables.md +153 -11
  113. package/docs/features.md +4 -3
  114. package/docs/globalconfig.md +56 -4
  115. package/docs/language-coverage.md +2 -2
  116. package/docs/mcp.md +17 -16
  117. package/docs/servercapabilities.md +5 -2
  118. package/docs/settings.md +181 -0
  119. package/docs/tools.md +14 -0
  120. package/docs/tree-sitter_rules_catalog.md +11 -31
  121. package/docs/usage.md +13 -0
  122. package/package.json +139 -138
  123. package/rules/ast-grep-rules/rule-tests/hardcoded-url-js-test.yml +1 -0
  124. package/rules/ast-grep-rules/rule-tests/hardcoded-url-test.yml +2 -0
  125. package/rules/ast-grep-rules/rule-tests/no-case-declarations-js-test.yml +10 -0
  126. package/rules/ast-grep-rules/rule-tests/no-case-declarations-test.yml +10 -0
  127. package/rules/ast-grep-rules/rule-tests/no-javascript-url-js-test.yml +10 -0
  128. package/rules/ast-grep-rules/rule-tests/no-javascript-url-test.yml +10 -0
  129. package/rules/ast-grep-rules/rule-tests/strict-equality-js-test.yml +3 -0
  130. package/rules/ast-grep-rules/rule-tests/strict-equality-test.yml +3 -0
  131. package/rules/ast-grep-rules/rule-tests/strict-inequality-js-test.yml +3 -0
  132. package/rules/ast-grep-rules/rule-tests/strict-inequality-test.yml +4 -0
  133. package/rules/ast-grep-rules/rules/hardcoded-url-js.yml +22 -4
  134. package/rules/ast-grep-rules/rules/hardcoded-url.yml +22 -4
  135. package/rules/ast-grep-rules/rules/no-case-declarations-js.yml +4 -3
  136. package/rules/ast-grep-rules/rules/no-case-declarations.yml +4 -3
  137. package/rules/ast-grep-rules/rules/no-console-except-error-js.yml +10 -0
  138. package/rules/ast-grep-rules/rules/no-console-except-error.yml +10 -0
  139. package/rules/ast-grep-rules/rules/no-javascript-url-js.yml +42 -6
  140. package/rules/ast-grep-rules/rules/no-javascript-url.yml +42 -6
  141. package/rules/ast-grep-rules/rules/strict-equality-js.yml +14 -1
  142. package/rules/ast-grep-rules/rules/strict-equality.yml +14 -1
  143. package/rules/ast-grep-rules/rules/strict-inequality-js.yml +14 -1
  144. package/rules/ast-grep-rules/rules/strict-inequality.yml +14 -1
  145. package/rules/tree-sitter-queries/cpp/no-memset-sensitive-data.yml +9 -3
  146. package/rules/tree-sitter-queries/cpp/no-scoped-lock-without-args.yml +6 -6
  147. package/rules/tree-sitter-queries/css/calc-spacing.yml +8 -6
  148. package/rules/tree-sitter-queries/go/go-mutex-copy.yml +21 -18
  149. package/rules/tree-sitter-queries/go/go-shared-map-write-goroutine.yml +3 -3
  150. package/rules/tree-sitter-queries/java/infinite-loop-java.yml +3 -3
  151. package/rules/tree-sitter-queries/java/junit-call-super.yml +6 -4
  152. package/rules/tree-sitter-queries/java/no-double-checked-locking.yml +10 -8
  153. package/rules/tree-sitter-queries/java/no-field-shadowing.yml +1 -1
  154. package/rules/tree-sitter-queries/java/prepared-statement-valid-indices.yml +2 -3
  155. package/rules/tree-sitter-queries/java/resources-closed.yml +2 -2
  156. package/rules/tree-sitter-queries/java/springboot-default-package.yml +6 -3
  157. package/rules/tree-sitter-queries/java/switch-fall-through.yml +6 -4
  158. package/rules/tree-sitter-queries/java/switch-non-case-labels.yml +4 -3
  159. package/rules/tree-sitter-queries/java/tests-include-assertions.yml +6 -5
  160. package/rules/tree-sitter-queries/javascript/switch-case-termination-js.yml +5 -8
  161. package/rules/tree-sitter-queries/javascript/switch-non-case-labels.yml +3 -2
  162. package/rules/tree-sitter-queries/kotlin/prepared-statement-indices.yml +9 -5
  163. package/rules/tree-sitter-queries/php/this-in-static-context.yml +5 -5
  164. package/rules/tree-sitter-queries/python/exit-signature-check.yml +2 -9
  165. package/rules/tree-sitter-queries/python/in-operator-unsupported.yml +3 -4
  166. package/rules/tree-sitter-queries/python/no-super-torchscript.yml +4 -8
  167. package/rules/tree-sitter-queries/python/notimplemented-boolean-context.yml +1 -5
  168. package/rules/tree-sitter-queries/python/python-empty-except.yml +1 -1
  169. package/rules/tree-sitter-queries/python/return-in-init.yml +0 -2
  170. package/rules/tree-sitter-queries/python/yield-return-outside-function.yml +0 -3
  171. package/rules/tree-sitter-queries/ruby/ruby-command-injection.yml +1 -12
  172. package/rules/tree-sitter-queries/ruby/ruby-eval.yml +1 -1
  173. package/rules/tree-sitter-queries/ruby/ruby-insecure-deserialization.yml +4 -4
  174. package/rules/tree-sitter-queries/ruby/ruby-insecure-random.yml +4 -11
  175. package/rules/tree-sitter-queries/ruby/ruby-open-struct.yml +1 -1
  176. package/rules/tree-sitter-queries/ruby/ruby-string-eval.yml +2 -3
  177. package/rules/tree-sitter-queries/ruby/ruby-weak-hash.yml +4 -4
  178. package/rules/tree-sitter-queries/rust/rust-lock-held-across-await.yml +4 -3
  179. package/rules/tree-sitter-queries/typescript/console-statement.yml +11 -0
  180. package/rules/tree-sitter-queries/typescript/duplicate-function-arg.yml +4 -9
  181. package/rules/tree-sitter-queries/typescript/empty-switch-case.yml +13 -9
  182. package/rules/tree-sitter-queries/typescript/infinite-loop.yml +2 -2
  183. package/rules/tree-sitter-queries/typescript/mixed-async-styles.yml +10 -1
  184. package/rules/tree-sitter-queries/typescript/switch-case-termination.yml +5 -8
  185. package/rules/tree-sitter-queries/typescript/ts-insecure-random.yml +7 -10
  186. package/rules/tree-sitter-queries/typescript/unsafe-regex.yml +4 -2
  187. package/rules/typos/_typos.toml +17 -0
  188. package/skills/pi-lens-write-tree-sitter-rule/SKILL.md +5 -0
  189. package/dist/clients/config-enabled-shape.js +0 -39
  190. package/docs/env_variables.md +0 -10
  191. package/rules/tree-sitter-queries/abap-disabled/delete-where.yml +0 -46
  192. package/rules/tree-sitter-queries/cpp/no-confused-move-forward.yml +0 -59
  193. package/rules/tree-sitter-queries/cpp/noexcept-functions.yml +0 -58
  194. package/rules/tree-sitter-queries/java/mockito-initialized.yml +0 -66
  195. package/rules/tree-sitter-queries/java/no-wait-notify-on-thread.yml +0 -58
  196. package/rules/tree-sitter-queries/java/short-circuit-logic.yml +0 -57
  197. package/rules/tree-sitter-queries/java/spring-session-attributes-setcomplete.yml +0 -75
  198. package/rules/tree-sitter-queries/plsql-disabled/delete-update-where.yml +0 -56
  199. package/rules/tree-sitter-queries/plsql-disabled/end-loop-semicolon.yml +0 -51
  200. package/rules/tree-sitter-queries/plsql-disabled/fetch-bulk-collect-limit.yml +0 -47
  201. package/rules/tree-sitter-queries/plsql-disabled/forallsave-exceptions.yml +0 -51
  202. package/rules/tree-sitter-queries/plsql-disabled/nchar-nvarchar2-bytes.yml +0 -54
  203. package/rules/tree-sitter-queries/plsql-disabled/not-null-initialization.yml +0 -59
  204. package/rules/tree-sitter-queries/plsql-disabled/raise-application-error-codes.yml +0 -50
  205. package/rules/tree-sitter-queries/typescript-disabled/ts-path-traversal.yml +0 -71
package/README.md CHANGED
@@ -6,6 +6,10 @@
6
6
 
7
7
  pi-lens gives AI coding agents fast, language-aware feedback while they write/edit.
8
8
 
9
+ > **Working in this project as an AI agent?** Read the [agent guide](docs/agent-guide.md)
10
+ > for how pi-lens surfaces diagnostics (honesty labels, blockers, read-before-edit)
11
+ > and how to respond.
12
+
9
13
  ## What It Does
10
14
 
11
15
  - LSP diagnostics and navigation across supported languages
@@ -47,6 +51,7 @@ pi install git:github.com/apmantza/pi-lens
47
51
 
48
52
  ## Documentation
49
53
 
54
+ - [Agent guide](docs/agent-guide.md) — how an AI agent should consume and respond to pi-lens
50
55
  - [Agent tools](docs/agent-tools.md) — pi tool names, scopes, and arguments
51
56
  - [Usage guide](docs/usage.md) — lifecycle, tool behavior, MCP notes, and
52
57
  troubleshooting
@@ -56,9 +61,11 @@ pi install git:github.com/apmantza/pi-lens
56
61
  - [Tools and commands](docs/tools.md) — runtime flags and slash commands
57
62
  - [Diagnostic dispositions](docs/dispositions.md) — triage: false-positive,
58
63
  suppress, defer, flagged-to-fix
64
+ - [Settings](docs/settings.md) — the configuration hub: defaults, env vars, CLI
65
+ flags, and global vs project config at a glance
59
66
  - [Configuration](docs/globalconfig.md) — global and project config files
60
- - [Environment variables](docs/env_variables.md) — common env vars and full
61
- reference link
67
+ - [Environment variables](docs/environment-variables.md) — common env vars and
68
+ full reference
62
69
  - [Language coverage](docs/language-coverage.md) — supported languages, runners,
63
70
  and formatters
64
71
  - [Dependencies](docs/dependencies.md) — auto-install policy and external tools
@@ -82,120 +89,123 @@ Thanks goes to these wonderful people:
82
89
  <tbody>
83
90
  <tr>
84
91
  <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>
92
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/AngriestBird"><img src="https://avatars.githubusercontent.com/u/12663269?v=4" width="100px;" alt=""/><br /><sub><b>Kenneth McCormick</b></sub></a><br /><a href="#bug-AngriestBird" title="Bug reports">🐛</a> <a href="#code-AngriestBird" title="Code">💻</a></td>
85
93
  <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>
86
94
  <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>
87
95
  <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>
88
96
  <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>
89
97
  <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>
90
- <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>
91
98
  </tr>
92
99
  <tr>
100
+ <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>
93
101
  <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>
94
102
  <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>
95
103
  <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>
96
104
  <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>
97
105
  <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>
98
106
  <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>
99
- <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>
100
107
  </tr>
101
108
  <tr>
109
+ <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>
102
110
  <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>
103
111
  <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>
104
112
  <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>
105
113
  <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>
106
114
  <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>
107
115
  <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>
108
- <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>
109
116
  </tr>
110
117
  <tr>
118
+ <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>
111
119
  <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>
112
120
  <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>
113
121
  <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>
114
122
  <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>
115
123
  <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>
116
124
  <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>
117
- <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>
118
125
  </tr>
119
126
  <tr>
127
+ <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>
120
128
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/shaochong-theoai"><img src="https://avatars.githubusercontent.com/u/305592060?v=4" width="100px;" alt=""/><br /><sub><b>shaochong-theoai</b></sub></a><br /><a href="#code-shaochong-theoai" title="Code">💻</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>
129
+ <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> <a href="#code-xuli500177" title="Code">💻</a></td>
122
130
  <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>
123
131
  <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>
124
132
  <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>
125
133
  <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>
126
- <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>
127
134
  </tr>
128
135
  <tr>
136
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/ymek"><img src="https://avatars.githubusercontent.com/u/300569?v=4" width="100px;" alt=""/><br /><sub><b>Myke Stubbs</b></sub></a><br /><a href="#code-ymek" title="Code">💻</a> <a href="#bug-ymek" title="Bug reports">🐛</a></td>
137
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/JSap0914"><img src="https://avatars.githubusercontent.com/u/116227558?v=4" width="100px;" alt=""/><br /><sub><b>JSap0914</b></sub></a><br /><a href="#code-JSap0914" title="Code">💻</a></td>
138
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/knthmn"><img src="https://avatars.githubusercontent.com/u/60438008?v=4" width="100px;" alt=""/><br /><sub><b>Kent Hou Man</b></sub></a><br /><a href="#code-knthmn" title="Code">💻</a> <a href="#bug-knthmn" title="Bug reports">🐛</a></td>
139
+ <td align="center" valign="top" width="14.28%"><a href="https://neumie.dev/"><img src="https://avatars.githubusercontent.com/u/23665482?v=4" width="100px;" alt=""/><br /><sub><b>Jakub Neumann</b></sub></a><br /><a href="#code-neumie" title="Code">💻</a></td>
140
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/cy7372"><img src="https://avatars.githubusercontent.com/u/142197117?v=4" width="100px;" alt=""/><br /><sub><b>CyYu</b></sub></a><br /><a href="#code-cy7372" title="Code">💻</a></td>
141
+ <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>
129
142
  <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>
143
+ </tr>
144
+ <tr>
130
145
  <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>
131
146
  <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>
132
147
  <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>
133
148
  <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>
134
149
  <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>
135
150
  <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>
151
+ <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>
136
152
  </tr>
137
153
  <tr>
138
- <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>
139
154
  <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>
140
155
  <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>
141
156
  <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>
142
157
  <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>
143
158
  <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>
144
159
  <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>
160
+ <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>
145
161
  </tr>
146
162
  <tr>
147
- <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>
148
163
  <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>
149
164
  <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>
150
165
  <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>
151
166
  <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>
152
167
  <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>
153
168
  <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>
169
+ <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>
154
170
  </tr>
155
171
  <tr>
156
- <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>
157
172
  <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>
158
173
  <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>
159
174
  <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>
160
175
  <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>
161
176
  <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>
162
177
  <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>
178
+ <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>
163
179
  </tr>
164
180
  <tr>
165
- <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>
166
181
  <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>
167
182
  <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>
168
183
  <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>
169
184
  <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>
170
185
  <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>
171
186
  <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>
187
+ <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>
172
188
  </tr>
173
189
  <tr>
174
- <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>
175
190
  <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>
176
191
  <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>
177
192
  <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>
178
193
  <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>
179
194
  <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>
180
195
  <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>
196
+ <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>
181
197
  </tr>
182
198
  <tr>
183
- <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>
184
199
  <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>
185
200
  <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>
186
201
  <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>
187
202
  <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>
188
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/ymek"><img src="https://avatars.githubusercontent.com/u/300569?v=4" width="100px;" alt=""/><br /><sub><b>Myke Stubbs</b></sub></a><br /><a href="#code-ymek" title="Code">💻</a> <a href="#bug-ymek" title="Bug reports">🐛</a></td>
189
203
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/black-snow"><img src="https://avatars.githubusercontent.com/u/579733?v=4" width="100px;" alt=""/><br /><sub><b>Ronald</b></sub></a><br /><a href="#bug-black-snow" title="Bug reports">🐛</a></td>
190
- </tr>
191
- <tr>
192
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/JSap0914"><img src="https://avatars.githubusercontent.com/u/116227558?v=4" width="100px;" alt=""/><br /><sub><b>JSap0914</b></sub></a><br /><a href="#code-JSap0914" title="Code">💻</a></td>
193
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/AngriestBird"><img src="https://avatars.githubusercontent.com/u/12663269?v=4" width="100px;" alt=""/><br /><sub><b>Kenneth McCormick</b></sub></a><br /><a href="#bug-AngriestBird" title="Bug reports">🐛</a></td>
194
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/knthmn"><img src="https://avatars.githubusercontent.com/u/60438008?v=4" width="100px;" alt=""/><br /><sub><b>Kent Hou Man</b></sub></a><br /><a href="#code-knthmn" title="Code">💻</a> <a href="#bug-knthmn" title="Bug reports">🐛</a></td>
195
204
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/aaronkyriesenbach"><img src="https://avatars.githubusercontent.com/u/12665860?v=4" width="100px;" alt=""/><br /><sub><b>Aaron Ky-Riesenbach</b></sub></a><br /><a href="#bug-aaronkyriesenbach" title="Bug reports">🐛</a></td>
196
205
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/LovelyLoong"><img src="https://avatars.githubusercontent.com/u/54889641?v=4" width="100px;" alt=""/><br /><sub><b>可爱的龙</b></sub></a><br /><a href="#bug-LovelyLoong" title="Bug reports">🐛</a></td>
206
+ </tr>
207
+ <tr>
197
208
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/pppobear"><img src="https://avatars.githubusercontent.com/u/21952175?v=4" width="100px;" alt=""/><br /><sub><b>pppobear</b></sub></a><br /><a href="#bug-pppobear" title="Bug reports">🐛</a></td>
198
- <td align="center" valign="top" width="14.28%"><a href="https://neumie.dev/"><img src="https://avatars.githubusercontent.com/u/23665482?v=4" width="100px;" alt=""/><br /><sub><b>Jakub Neumann</b></sub></a><br /><a href="#code-neumie" title="Code">💻</a></td>
199
209
  </tr>
200
210
  </tbody>
201
211
  </table>
@@ -205,4 +215,10 @@ Thanks goes to these wonderful people:
205
215
 
206
216
  <!-- ALL-CONTRIBUTORS-LIST:END -->
207
217
 
218
+ The following commit identities also appear in `git shortlog -sne HEAD` but are
219
+ not represented in the generated table above: Anas Alsbei, Claude,
220
+ Christopher Patti,
221
+ dependabot[bot], Fabio-D, github-actions[bot], JSup, Kenny McCormick,
222
+ Max Lupus, Moritz Hofmann, ricardo, and root.
223
+
208
224
  If you land a pull request or report an issue that gets fixed, we'll add you here.
@@ -30,7 +30,7 @@
30
30
  */
31
31
  import * as fs from "node:fs";
32
32
  /**
33
- * Synchronous atomic write: `fs.writeFileSync` to `${targetPath}.tmp-${pid}`,
33
+ * Synchronous atomic write of text or binary data to `${targetPath}.tmp-${pid}`,
34
34
  * then `fs.renameSync` over `targetPath`. On any failure (from either step),
35
35
  * attempts a best-effort `fs.rmSync(tmp, { force: true })` cleanup, then
36
36
  * either swallows (default) or rethrows per `options.bestEffort`.
@@ -43,7 +43,7 @@ export function writeFileAtomic(targetPath, data, options) {
43
43
  const bestEffort = options?.bestEffort ?? true;
44
44
  const tmpPath = `${targetPath}.tmp-${process.pid}`;
45
45
  try {
46
- fs.writeFileSync(tmpPath, data, "utf-8");
46
+ fs.writeFileSync(tmpPath, data, typeof data === "string" ? "utf-8" : undefined);
47
47
  fs.renameSync(tmpPath, targetPath);
48
48
  }
49
49
  catch (err) {
@@ -67,7 +67,7 @@ export async function writeFileAtomicAsync(targetPath, data, options) {
67
67
  const bestEffort = options?.bestEffort ?? true;
68
68
  const tmpPath = `${targetPath}.tmp-${process.pid}`;
69
69
  try {
70
- await fs.promises.writeFile(tmpPath, data, "utf-8");
70
+ await fs.promises.writeFile(tmpPath, data, typeof data === "string" ? "utf-8" : undefined);
71
71
  await fs.promises.rename(tmpPath, targetPath);
72
72
  }
73
73
  catch (err) {
@@ -36,8 +36,12 @@ import * as path from "node:path";
36
36
  import { isTestMode } from "./env-utils.js";
37
37
  import { getGlobalPiLensDir } from "./file-utils.js";
38
38
  import { createNdjsonLogger } from "./ndjson-logger.js";
39
+ import { getMaxLogSizeMB } from "./log-cleanup.js";
39
40
  const BUS_EVENTS_LOG_FILE = path.join(getGlobalPiLensDir(), "bus-events.log");
40
- const writer = createNdjsonLogger({ filePath: BUS_EVENTS_LOG_FILE });
41
+ const writer = createNdjsonLogger({
42
+ filePath: BUS_EVENTS_LOG_FILE,
43
+ maxBytes: getMaxLogSizeMB() * 1024 * 1024,
44
+ });
41
45
  export function logBusEvent(entry) {
42
46
  if (isTestMode()) {
43
47
  return;
@@ -9,11 +9,24 @@ import * as fs from "node:fs";
9
9
  import * as path from "node:path";
10
10
  import { getProjectDataDir } from "../file-utils.js";
11
11
  import { readJsonCache } from "../json-cache-read.js";
12
- const CACHE_VERSION = "v3";
12
+ // v4: cache skip_test_files + fix_action — v3 entries silently dropped them,
13
+ // and ruleHash (rule-file mtimes) never invalidates on a code-only fix.
14
+ // v5 (#675): rule SELECTION changed in code — javascript no longer inherits the
15
+ // typescript rule set and tsx now does. A v4 entry holds the old merge, and
16
+ // queries now compile against the file's parse language, so replaying it would
17
+ // fire typescript rules on javascript trees for real this time.
18
+ // v6 (#878): the ruleHash FINGERPRINT now covers the full effective rule set —
19
+ // inherited rule-source directories (tsx also runs typescript rules), not just
20
+ // the language's own. A v5 tsx entry was hashed over tsx files only, so a
21
+ // typescript-rule edit never invalidated it; v5 entries self-miss anyway (the
22
+ // hash input set changed), the bump just makes the semantics break explicit.
23
+ export const CACHE_VERSION = "v6";
13
24
  export class RuleCache {
14
25
  cacheFile;
15
26
  cacheDir;
27
+ language;
16
28
  constructor(language, rootDir = process.cwd()) {
29
+ this.language = language;
17
30
  this.cacheDir = path.join(getProjectDataDir(rootDir), "cache");
18
31
  this.cacheFile = path.join(this.cacheDir, `${language}-rules-${CACHE_VERSION}.json`);
19
32
  }
@@ -40,7 +53,8 @@ export class RuleCache {
40
53
  const currentHash = this.computeRuleHash(ruleFiles);
41
54
  const cached = readJsonCache(this.cacheFile, (parsed) => {
42
55
  const entry = parsed;
43
- if (entry.version !== CACHE_VERSION || entry.ruleHash !== currentHash) {
56
+ if (entry.version !== CACHE_VERSION ||
57
+ entry.ruleHash !== currentHash) {
44
58
  return undefined; // Cache invalid
45
59
  }
46
60
  return entry;
@@ -61,11 +75,38 @@ export class RuleCache {
61
75
  queries,
62
76
  };
63
77
  fs.writeFileSync(this.cacheFile, JSON.stringify(entry, null, 2));
78
+ this.pruneStaleVersions();
64
79
  }
65
80
  catch {
66
81
  // Cache write failure is non-fatal
67
82
  }
68
83
  }
84
+ // Orphaned `<language>-rules-v<N>.json` files from a prior CACHE_VERSION
85
+ // (e.g. v3 entries left behind by the #448 v3→v4 bump) never got cleaned up
86
+ // on their own — nothing ever read or removed them again once the version
87
+ // bumped. Delete every sibling for this language that isn't the current file.
88
+ pruneStaleVersions() {
89
+ const currentName = path.basename(this.cacheFile);
90
+ const pattern = new RegExp(`^${this.language}-rules-v\\d+\\.json$`);
91
+ let dirents;
92
+ try {
93
+ dirents = fs.readdirSync(this.cacheDir);
94
+ }
95
+ catch {
96
+ return;
97
+ }
98
+ for (const name of dirents) {
99
+ if (name === currentName || !pattern.test(name))
100
+ continue;
101
+ try {
102
+ fs.unlinkSync(path.join(this.cacheDir, name));
103
+ }
104
+ catch {
105
+ // Best-effort; ENOENT (already gone) or any other removal failure
106
+ // shouldn't undo the write that already succeeded above.
107
+ }
108
+ }
109
+ }
69
110
  clear() {
70
111
  try {
71
112
  if (fs.existsSync(this.cacheFile)) {
@@ -0,0 +1,211 @@
1
+ /**
2
+ * Prompt-cache observability (#1018).
3
+ *
4
+ * Two provider-independent signals, both sinking into ~/.pi-lens/latency.log
5
+ * via {@link logLatency} (matching the neighboring `type: "phase"` records):
6
+ *
7
+ * 1. Response-side cache usage (`cache_usage`) — on each assistant
8
+ * `message_end`, record the provider-reported token/cost breakdown so a
9
+ * session's actual cacheRead/cacheWrite behavior is queryable after the
10
+ * fact. This is what the provider DID, not what we hoped it would do.
11
+ *
12
+ * 2. Request-side prefix stability (`cache_prefix_break`) — a content hash of
13
+ * `messages[0]` observed on every `context` call. After #1016 the first
14
+ * message must stay byte-stable across a whole session; a logged CHANGE
15
+ * flags that something (pi-lens or otherwise) broke the cache prefix, which
16
+ * would silently invalidate the entire prompt-cache on every prefix-caching
17
+ * provider. Pure observation — it never alters context injection.
18
+ *
19
+ * Both paths are defensive: `usage` (and its fields) may be absent on older
20
+ * hosts or non-assistant messages, so every access is guarded and the handlers
21
+ * never throw — on error they `dbg(...)` and no-op, like the other index.ts
22
+ * event handlers.
23
+ */
24
+ import { createHash } from "node:crypto";
25
+ import { logLatency } from "./latency-logger.js";
26
+ /**
27
+ * Part 1 — log one `cache_usage` record for an assistant `message_end` that
28
+ * carries a `usage`. Provider/model come straight off the message itself
29
+ * (`AssistantMessage.provider` / `.model` in pi-ai) — no dependency on the
30
+ * runtime telemetry identity. Skips silently (no record) when the message is
31
+ * not an assistant message or has no usage; never logs a zeros-only record for
32
+ * a message that simply lacks usage.
33
+ */
34
+ export function logCacheUsage(message, dbg) {
35
+ try {
36
+ if (!message || typeof message !== "object")
37
+ return;
38
+ const msg = message;
39
+ // Only assistant messages carry LLM usage; tool-result / user messages
40
+ // (and unknown custom AgentMessage variants) are skipped.
41
+ if (msg.role !== "assistant")
42
+ return;
43
+ const usage = msg.usage;
44
+ if (!usage || typeof usage !== "object")
45
+ return;
46
+ const u = usage;
47
+ logLatency({
48
+ type: "phase",
49
+ filePath: "<pi-lens>",
50
+ phase: "cache_usage",
51
+ durationMs: 0,
52
+ metadata: {
53
+ provider: typeof msg.provider === "string" ? msg.provider : undefined,
54
+ model: typeof msg.model === "string" ? msg.model : undefined,
55
+ cacheRead: u.cacheRead,
56
+ cacheWrite: u.cacheWrite,
57
+ input: u.input,
58
+ output: u.output,
59
+ // `Usage.cost` is a breakdown object; the total is the headline number.
60
+ cost: u.cost?.total,
61
+ },
62
+ });
63
+ }
64
+ catch (err) {
65
+ dbg?.(`cache-usage: failed to log message_end usage: ${err}`);
66
+ }
67
+ }
68
+ /**
69
+ * Content hash of the first transcript message. Stable for identical content
70
+ * (role + content are serialized in a fixed order), so a changing hash means
71
+ * `messages[0]` actually changed byte-for-byte.
72
+ */
73
+ function hashFirstMessage(first) {
74
+ const serialized = JSON.stringify({ role: first.role, content: first.content });
75
+ return createHash("sha256").update(serialized).digest("hex");
76
+ }
77
+ /**
78
+ * Per-session baseline hash of `messages[0]`, keyed by the STABLE pi session id
79
+ * (`ctx.sessionManager.getSessionId()`). A single module-scoped var was wrong:
80
+ * in ONE process, multiple logical conversations touch this module, and they
81
+ * must not share a baseline —
82
+ * - new / fork / a concurrent in-process subagent (#473) is a DIFFERENT id →
83
+ * its OWN independent baseline, never compared against another id's (else a
84
+ * benign session boundary logs a spurious `cache_prefix_break`, and a
85
+ * concurrent subagent + its parent stomp each other's baseline, each
86
+ * emitting a false positive — fatal for a signal whose value is trust);
87
+ * - an IN-PROCESS reload / resume reuses the SAME id → the baseline is still in
88
+ * this map, so a genuine break is caught (no blinding reset). NOTE: a full
89
+ * process restart (quit → `pi --session <id>`) starts a NEW process with an
90
+ * empty map, so the first post-restart `context` re-anchors a fresh baseline
91
+ * rather than comparing across the restart. That's acceptable here — this is a
92
+ * pure observability signal (at worst a missed `cache_prefix_break` log on the
93
+ * first post-restart turn, never a user-facing action) — unlike genuine
94
+ * session state (read guard #1041, widget #190) which IS rehydrated from the
95
+ * sidecar.
96
+ *
97
+ * Bounded as an insertion-ordered LRU (evict oldest-inserted past the cap) so a
98
+ * long-lived process cycling through many sessions can't grow this unbounded.
99
+ */
100
+ const MAX_TRACKED_SESSIONS = 32;
101
+ const prefixHashBySession = new Map();
102
+ /**
103
+ * Bucket key used when no session id is available (undefined/empty). Degrades
104
+ * gracefully to the old single-var semantics — one shared baseline — rather than
105
+ * throwing or dropping the signal.
106
+ */
107
+ const NO_SESSION_KEY = "<no-session>";
108
+ /**
109
+ * Store `hash` for `key`, refreshing its LRU recency (re-insert moves it to the
110
+ * newest position since `Map` preserves insertion order) and evicting the
111
+ * oldest-inserted entries once the cap is exceeded.
112
+ */
113
+ function recordSessionHash(key, hash) {
114
+ prefixHashBySession.delete(key);
115
+ prefixHashBySession.set(key, hash);
116
+ while (prefixHashBySession.size > MAX_TRACKED_SESSIONS) {
117
+ const oldest = prefixHashBySession.keys().next().value;
118
+ if (oldest === undefined)
119
+ break;
120
+ prefixHashBySession.delete(oldest);
121
+ }
122
+ }
123
+ /**
124
+ * Part 2 — observe `messages[0]` stability turn-over-turn, keyed by session id.
125
+ * Logs a baseline the first time it sees a non-empty transcript FOR A GIVEN
126
+ * session id, then logs a `cache_prefix_break` whenever that same session's
127
+ * first-message hash changes. Pure observation: it never inspects or mutates
128
+ * anything but its own per-session hash map, and never throws.
129
+ *
130
+ * `sessionId` should be pi's STABLE id (`ctx.sessionManager.getSessionId()`),
131
+ * which uniquely identifies the CURRENTLY-firing session: a concurrent
132
+ * in-process subagent runs its own `AgentSession`/`sessionManager`, so its
133
+ * `context` calls carry a DIFFERENT id than the parent's and get their own
134
+ * baseline. (`runtime.telemetrySessionId` cannot be used here: per the #473
135
+ * guard a concurrent secondary skips `updateRuntimeIdentityFromEvent`, so that
136
+ * process-global singleton stays pinned to the PARENT — it would collapse
137
+ * parent and subagent onto one baseline.) Residual limitation: if the host ever
138
+ * does NOT supply a stable id, all such sessions collapse onto `NO_SESSION_KEY`
139
+ * and behave like the old single-var (a concurrent subagent could then still
140
+ * cross-contaminate) — accepted as graceful degradation, not silently perfect.
141
+ *
142
+ * Does nothing on an empty transcript (nothing to anchor a prefix to yet).
143
+ */
144
+ export function observeCachePrefix(messages, turnIndex, sessionId, sessionRole, dbg) {
145
+ try {
146
+ if (!messages || messages.length === 0)
147
+ return;
148
+ const first = messages[0];
149
+ if (!first || typeof first !== "object")
150
+ return;
151
+ const key = sessionId?.trim() ? sessionId.trim() : NO_SESSION_KEY;
152
+ const currentHash = hashFirstMessage(first);
153
+ const previousHash = prefixHashBySession.get(key);
154
+ if (previousHash === undefined) {
155
+ // Baseline: record this session's starting prefix so a later break has a
156
+ // reference point in the log. `previousHash: null` marks the baseline.
157
+ recordSessionHash(key, currentHash);
158
+ logLatency({
159
+ type: "phase",
160
+ filePath: "<pi-lens>",
161
+ phase: "cache_prefix_break",
162
+ durationMs: 0,
163
+ metadata: {
164
+ turnIndex,
165
+ previousHash: null,
166
+ currentHash,
167
+ baseline: true,
168
+ sessionId: key,
169
+ sessionRole,
170
+ },
171
+ });
172
+ return;
173
+ }
174
+ if (currentHash !== previousHash) {
175
+ logLatency({
176
+ type: "phase",
177
+ filePath: "<pi-lens>",
178
+ phase: "cache_prefix_break",
179
+ durationMs: 0,
180
+ metadata: {
181
+ turnIndex,
182
+ previousHash,
183
+ currentHash,
184
+ sessionId: key,
185
+ sessionRole,
186
+ },
187
+ });
188
+ }
189
+ // Refresh recency (and update the stored hash after a break) so an active
190
+ // session stays warm in the LRU and isn't evicted while still in use.
191
+ recordSessionHash(key, currentHash);
192
+ }
193
+ catch (err) {
194
+ dbg?.(`cache-prefix: failed to observe messages[0]: ${err}`);
195
+ }
196
+ }
197
+ /**
198
+ * Drop a single session's prefix baseline. Called from the `session_shutdown`
199
+ * handler (primary path only — the concurrent-secondary guard there returns
200
+ * first, and the LRU cap backstops any secondary entry left behind) so an ended
201
+ * conversation's entry is reclaimed promptly rather than only when the LRU
202
+ * evicts it. Idempotent and never throws.
203
+ */
204
+ export function clearCachePrefixSession(sessionId) {
205
+ const key = sessionId?.trim() ? sessionId.trim() : NO_SESSION_KEY;
206
+ prefixHashBySession.delete(key);
207
+ }
208
+ /** Clear all per-session prefix hashes. For tests / session boundaries. */
209
+ export function resetCachePrefixObservation() {
210
+ prefixHashBySession.clear();
211
+ }