pi-lens 3.8.72 → 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 (210) hide show
  1. package/CHANGELOG.md +1162 -0
  2. package/README.md +45 -28
  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/deps/js-yaml.js +1 -1
  12. package/dist/clients/diagnostic-dispositions.js +22 -2
  13. package/dist/clients/dispatch/auxiliary-lsp.js +34 -0
  14. package/dist/clients/dispatch/dispatcher.js +16 -1
  15. package/dist/clients/dispatch/fact-store.js +11 -0
  16. package/dist/clients/dispatch/facts/function-facts.js +14 -0
  17. package/dist/clients/dispatch/facts/import-facts.js +100 -101
  18. package/dist/clients/dispatch/facts/tree-sitter-facts.js +5 -8
  19. package/dist/clients/dispatch/facts/try-catch-facts.js +13 -0
  20. package/dist/clients/dispatch/integration.js +94 -15
  21. package/dist/clients/dispatch/rules/async-noise.js +1 -0
  22. package/dist/clients/dispatch/rules/unsafe-boundary.js +7 -0
  23. package/dist/clients/dispatch/runners/ast-grep-napi.js +84 -8
  24. package/dist/clients/dispatch/runners/psscriptanalyzer.js +10 -1
  25. package/dist/clients/dispatch/runners/tree-sitter.js +153 -166
  26. package/dist/clients/dispatch/runners/utils/runner-helpers.js +17 -18
  27. package/dist/clients/feature-hints.js +53 -7
  28. package/dist/clients/file-kinds.js +86 -31
  29. package/dist/clients/fix-worklog.js +14 -2
  30. package/dist/clients/generated-artifacts.js +21 -0
  31. package/dist/clients/gzip-stage-write.js +84 -0
  32. package/dist/clients/installer/index.js +358 -129
  33. package/dist/clients/instance-registry.js +12 -1
  34. package/dist/clients/knip-client.js +64 -1
  35. package/dist/clients/language-profile.js +61 -72
  36. package/dist/clients/latency-logger.js +6 -16
  37. package/dist/clients/lens-config.js +128 -165
  38. package/dist/clients/lens-engine.js +39 -5
  39. package/dist/clients/lens-flag-registry.js +285 -0
  40. package/dist/clients/lens-map.js +2 -7
  41. package/dist/clients/log-cleanup.js +6 -17
  42. package/dist/clients/lsp/cascade-tier.js +22 -2
  43. package/dist/clients/lsp/client.js +74 -10
  44. package/dist/clients/lsp/config.js +62 -9
  45. package/dist/clients/lsp/index.js +113 -31
  46. package/dist/clients/lsp/language.js +4 -1
  47. package/dist/clients/lsp/launch.js +8 -6
  48. package/dist/clients/lsp/server.js +84 -29
  49. package/dist/clients/lsp-document-symbols.js +112 -0
  50. package/dist/clients/mcp/analyze.js +5 -4
  51. package/dist/clients/mcp/review.js +36 -3
  52. package/dist/clients/module-report.js +100 -80
  53. package/dist/clients/ndjson-logger.js +51 -21
  54. package/dist/clients/path-keyed-map.js +76 -0
  55. package/dist/clients/path-utils.js +35 -0
  56. package/dist/clients/performance-report.js +327 -0
  57. package/dist/clients/pipeline.js +7 -0
  58. package/dist/clients/project-changes.js +77 -4
  59. package/dist/clients/project-diagnostics/extractors.js +26 -115
  60. package/dist/clients/project-diagnostics/fresh-fetch.js +149 -19
  61. package/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +109 -0
  62. package/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +37 -5
  63. package/dist/clients/project-diagnostics/scanner.js +247 -127
  64. package/dist/clients/project-lens-config.js +53 -26
  65. package/dist/clients/project-report.js +39 -14
  66. package/dist/clients/project-scale.js +8 -10
  67. package/dist/clients/project-snapshot-persist-worker.js +6 -0
  68. package/dist/clients/project-snapshot.js +611 -7
  69. package/dist/clients/quiet-window.js +9 -1
  70. package/dist/clients/read-expansion.js +22 -12
  71. package/dist/clients/read-guard.js +78 -0
  72. package/dist/clients/redact/secrets.js +449 -0
  73. package/dist/clients/resource-sampler.js +133 -14
  74. package/dist/clients/reverse-deps.js +48 -0
  75. package/dist/clients/review-graph/builder.js +1522 -179
  76. package/dist/clients/review-graph/persist-worker.js +10 -0
  77. package/dist/clients/review-graph/query.js +5 -0
  78. package/dist/clients/review-graph/shared-extraction-ir.js +90 -0
  79. package/dist/clients/review-graph-logger.js +24 -0
  80. package/dist/clients/runtime-agent-end.js +4 -2
  81. package/dist/clients/runtime-coordinator.js +2 -2
  82. package/dist/clients/runtime-session.js +433 -59
  83. package/dist/clients/runtime-tool-call.js +46 -14
  84. package/dist/clients/runtime-turn.js +97 -26
  85. package/dist/clients/safe-spawn.js +102 -15
  86. package/dist/clients/session-start-observability.js +15 -0
  87. package/dist/clients/session-state-store.js +2 -1
  88. package/dist/clients/sessionstart-logger.js +18 -0
  89. package/dist/clients/sg-runner.js +48 -33
  90. package/dist/clients/source-filter.js +49 -25
  91. package/dist/clients/startup-marker.js +6 -0
  92. package/dist/clients/startup-timing.js +9 -0
  93. package/dist/clients/todo-scanner.js +30 -3
  94. package/dist/clients/tree-sitter-cache.js +147 -139
  95. package/dist/clients/tree-sitter-client.js +1340 -67
  96. package/dist/clients/tree-sitter-logger.js +43 -1
  97. package/dist/clients/tree-sitter-query-loader.js +110 -11
  98. package/dist/clients/tree-sitter-shared.js +43 -19
  99. package/dist/clients/tree-sitter-symbol-extractor.js +71 -4
  100. package/dist/clients/widget-state.js +50 -14
  101. package/dist/clients/word-index-logger.js +44 -0
  102. package/dist/clients/word-index.js +252 -35
  103. package/dist/clients/workspace-topology.js +11 -1
  104. package/dist/index.js +45976 -39387
  105. package/dist/mcp/analyze-cli.js +3 -0
  106. package/dist/mcp/cli.js +110 -0
  107. package/dist/mcp/server.js +63 -8
  108. package/dist/tools/lens-diagnostics.js +47 -5
  109. package/dist/tools/lsp-diagnostics.js +126 -9
  110. package/dist/tools/lsp-navigation.js +5 -28
  111. package/dist/tools/symbol-search.js +21 -2
  112. package/docs/agent-guide.md +247 -0
  113. package/docs/astplayground.md +3 -4
  114. package/docs/audit1.md +5 -5
  115. package/docs/environment-variables.md +153 -11
  116. package/docs/features.md +4 -3
  117. package/docs/globalconfig.md +56 -4
  118. package/docs/language-coverage.md +2 -2
  119. package/docs/mcp.md +17 -16
  120. package/docs/servercapabilities.md +5 -2
  121. package/docs/settings.md +181 -0
  122. package/docs/tools.md +14 -0
  123. package/docs/tree-sitter_rules_catalog.md +11 -31
  124. package/docs/usage.md +13 -0
  125. package/package.json +139 -139
  126. package/rules/ast-grep-rules/rule-tests/hardcoded-url-js-test.yml +1 -0
  127. package/rules/ast-grep-rules/rule-tests/hardcoded-url-test.yml +2 -0
  128. package/rules/ast-grep-rules/rule-tests/no-case-declarations-js-test.yml +10 -0
  129. package/rules/ast-grep-rules/rule-tests/no-case-declarations-test.yml +10 -0
  130. package/rules/ast-grep-rules/rule-tests/no-javascript-url-js-test.yml +10 -0
  131. package/rules/ast-grep-rules/rule-tests/no-javascript-url-test.yml +10 -0
  132. package/rules/ast-grep-rules/rule-tests/strict-equality-js-test.yml +3 -0
  133. package/rules/ast-grep-rules/rule-tests/strict-equality-test.yml +3 -0
  134. package/rules/ast-grep-rules/rule-tests/strict-inequality-js-test.yml +3 -0
  135. package/rules/ast-grep-rules/rule-tests/strict-inequality-test.yml +4 -0
  136. package/rules/ast-grep-rules/rules/hardcoded-url-js.yml +22 -4
  137. package/rules/ast-grep-rules/rules/hardcoded-url.yml +22 -4
  138. package/rules/ast-grep-rules/rules/no-case-declarations-js.yml +4 -3
  139. package/rules/ast-grep-rules/rules/no-case-declarations.yml +4 -3
  140. package/rules/ast-grep-rules/rules/no-console-except-error-js.yml +10 -0
  141. package/rules/ast-grep-rules/rules/no-console-except-error.yml +10 -0
  142. package/rules/ast-grep-rules/rules/no-javascript-url-js.yml +42 -6
  143. package/rules/ast-grep-rules/rules/no-javascript-url.yml +42 -6
  144. package/rules/ast-grep-rules/rules/strict-equality-js.yml +14 -1
  145. package/rules/ast-grep-rules/rules/strict-equality.yml +14 -1
  146. package/rules/ast-grep-rules/rules/strict-inequality-js.yml +14 -1
  147. package/rules/ast-grep-rules/rules/strict-inequality.yml +14 -1
  148. package/rules/tree-sitter-queries/cpp/no-memset-sensitive-data.yml +9 -3
  149. package/rules/tree-sitter-queries/cpp/no-scoped-lock-without-args.yml +6 -6
  150. package/rules/tree-sitter-queries/css/calc-spacing.yml +8 -6
  151. package/rules/tree-sitter-queries/go/go-mutex-copy.yml +21 -18
  152. package/rules/tree-sitter-queries/go/go-shared-map-write-goroutine.yml +3 -3
  153. package/rules/tree-sitter-queries/java/infinite-loop-java.yml +3 -3
  154. package/rules/tree-sitter-queries/java/junit-call-super.yml +6 -4
  155. package/rules/tree-sitter-queries/java/no-double-checked-locking.yml +10 -8
  156. package/rules/tree-sitter-queries/java/no-field-shadowing.yml +1 -1
  157. package/rules/tree-sitter-queries/java/prepared-statement-valid-indices.yml +2 -3
  158. package/rules/tree-sitter-queries/java/resources-closed.yml +2 -2
  159. package/rules/tree-sitter-queries/java/springboot-default-package.yml +6 -3
  160. package/rules/tree-sitter-queries/java/switch-fall-through.yml +6 -4
  161. package/rules/tree-sitter-queries/java/switch-non-case-labels.yml +4 -3
  162. package/rules/tree-sitter-queries/java/tests-include-assertions.yml +6 -5
  163. package/rules/tree-sitter-queries/javascript/switch-case-termination-js.yml +5 -8
  164. package/rules/tree-sitter-queries/javascript/switch-non-case-labels.yml +3 -2
  165. package/rules/tree-sitter-queries/kotlin/prepared-statement-indices.yml +9 -5
  166. package/rules/tree-sitter-queries/php/this-in-static-context.yml +5 -5
  167. package/rules/tree-sitter-queries/python/exit-signature-check.yml +2 -9
  168. package/rules/tree-sitter-queries/python/in-operator-unsupported.yml +3 -4
  169. package/rules/tree-sitter-queries/python/no-super-torchscript.yml +4 -8
  170. package/rules/tree-sitter-queries/python/notimplemented-boolean-context.yml +1 -5
  171. package/rules/tree-sitter-queries/python/python-empty-except.yml +1 -1
  172. package/rules/tree-sitter-queries/python/return-in-init.yml +0 -2
  173. package/rules/tree-sitter-queries/python/yield-return-outside-function.yml +0 -3
  174. package/rules/tree-sitter-queries/ruby/ruby-command-injection.yml +1 -12
  175. package/rules/tree-sitter-queries/ruby/ruby-eval.yml +1 -1
  176. package/rules/tree-sitter-queries/ruby/ruby-insecure-deserialization.yml +4 -4
  177. package/rules/tree-sitter-queries/ruby/ruby-insecure-random.yml +4 -11
  178. package/rules/tree-sitter-queries/ruby/ruby-open-struct.yml +1 -1
  179. package/rules/tree-sitter-queries/ruby/ruby-string-eval.yml +2 -3
  180. package/rules/tree-sitter-queries/ruby/ruby-weak-hash.yml +4 -4
  181. package/rules/tree-sitter-queries/rust/rust-lock-held-across-await.yml +4 -3
  182. package/rules/tree-sitter-queries/typescript/console-statement.yml +11 -0
  183. package/rules/tree-sitter-queries/typescript/duplicate-function-arg.yml +4 -9
  184. package/rules/tree-sitter-queries/typescript/empty-switch-case.yml +13 -9
  185. package/rules/tree-sitter-queries/typescript/infinite-loop.yml +2 -2
  186. package/rules/tree-sitter-queries/typescript/mixed-async-styles.yml +10 -1
  187. package/rules/tree-sitter-queries/typescript/no-equality-in-for-condition.yml +1 -0
  188. package/rules/tree-sitter-queries/typescript/no-jump-in-finally.yml +1 -0
  189. package/rules/tree-sitter-queries/typescript/switch-case-termination.yml +5 -8
  190. package/rules/tree-sitter-queries/typescript/ts-insecure-random.yml +7 -10
  191. package/rules/tree-sitter-queries/typescript/unsafe-regex.yml +4 -2
  192. package/rules/typos/_typos.toml +17 -0
  193. package/skills/pi-lens-write-tree-sitter-rule/SKILL.md +5 -0
  194. package/dist/clients/config-enabled-shape.js +0 -39
  195. package/docs/env_variables.md +0 -10
  196. package/rules/tree-sitter-queries/abap-disabled/delete-where.yml +0 -46
  197. package/rules/tree-sitter-queries/cpp/no-confused-move-forward.yml +0 -59
  198. package/rules/tree-sitter-queries/cpp/noexcept-functions.yml +0 -58
  199. package/rules/tree-sitter-queries/java/mockito-initialized.yml +0 -66
  200. package/rules/tree-sitter-queries/java/no-wait-notify-on-thread.yml +0 -58
  201. package/rules/tree-sitter-queries/java/short-circuit-logic.yml +0 -57
  202. package/rules/tree-sitter-queries/java/spring-session-attributes-setcomplete.yml +0 -75
  203. package/rules/tree-sitter-queries/plsql-disabled/delete-update-where.yml +0 -56
  204. package/rules/tree-sitter-queries/plsql-disabled/end-loop-semicolon.yml +0 -51
  205. package/rules/tree-sitter-queries/plsql-disabled/fetch-bulk-collect-limit.yml +0 -47
  206. package/rules/tree-sitter-queries/plsql-disabled/forallsave-exceptions.yml +0 -51
  207. package/rules/tree-sitter-queries/plsql-disabled/nchar-nvarchar2-bytes.yml +0 -54
  208. package/rules/tree-sitter-queries/plsql-disabled/not-null-initialization.yml +0 -59
  209. package/rules/tree-sitter-queries/plsql-disabled/raise-application-error-codes.yml +0 -50
  210. 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
@@ -79,52 +86,61 @@ Thanks goes to these wonderful people:
79
86
  <!-- prettier-ignore-start -->
80
87
  <!-- markdownlint-disable -->
81
88
  <table>
82
- <tbody>
83
- <tr>
89
+ <tbody>
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
- </tr><br />
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
- </tr><br />
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
- </tr><br />
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
- </tr><br />
125
+ </tr>
119
126
  <tr>
120
- <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>
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>
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>
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>
121
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>
122
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>
123
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>
124
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>
134
+ </tr>
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>
125
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>
126
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>
127
- </tr><br />
143
+ </tr>
128
144
  <tr>
129
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>
130
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>
@@ -133,7 +149,7 @@ Thanks goes to these wonderful people:
133
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>
134
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>
135
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
- </tr><br />
152
+ </tr>
137
153
  <tr>
138
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>
139
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>
@@ -142,7 +158,7 @@ Thanks goes to these wonderful people:
142
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>
143
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>
144
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
- </tr><br />
161
+ </tr>
146
162
  <tr>
147
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>
148
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>
@@ -151,7 +167,7 @@ Thanks goes to these wonderful people:
151
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>
152
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>
153
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
- </tr><br />
170
+ </tr>
155
171
  <tr>
156
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>
157
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>
@@ -160,7 +176,7 @@ Thanks goes to these wonderful people:
160
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>
161
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>
162
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
- </tr><br />
179
+ </tr>
164
180
  <tr>
165
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>
166
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>
@@ -169,7 +185,7 @@ Thanks goes to these wonderful people:
169
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>
170
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>
171
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
- </tr><br />
188
+ </tr>
173
189
  <tr>
174
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>
175
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>
@@ -178,30 +194,31 @@ Thanks goes to these wonderful people:
178
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>
179
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>
180
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
- </tr><br />
197
+ </tr>
182
198
  <tr>
183
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>
184
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>
185
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>
186
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>
187
- <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>
188
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>
189
- <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>
190
- </tr><br />
191
- <tr>
192
- <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>
193
- <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>
194
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>
195
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>
196
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>
197
209
  </tr>
198
- </tbody>
210
+ </tbody>
199
211
  </table>
200
212
 
201
-
202
213
  <!-- markdownlint-restore -->
203
214
  <!-- prettier-ignore-end -->
204
215
 
205
216
  <!-- ALL-CONTRIBUTORS-LIST:END -->
206
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
+
207
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
+ }
@@ -65,6 +65,26 @@ export function impact(graph, startKey, maxDepth = 3, minWeight = 0.1) {
65
65
  // Sort by depth then symbolKey for stable output
66
66
  return results.sort((a, b) => a.depth - b.depth || a.symbolKey.localeCompare(b.symbolKey));
67
67
  }
68
+ /**
69
+ * Splits a `SymbolKey` into its file and symbol-name parts. Two shapes exist
70
+ * (see `buildCallGraph` above): the normal `"filePath:symbolName"` form, and
71
+ * the file-level fallback `"file:filePath"` used when no enclosing symbol was
72
+ * found for a call site (module-level code). Splitting at the LAST colon
73
+ * (rather than a naive `split(":")`) means a Windows drive letter in
74
+ * `filePath` (`C:\...`) is never mistaken for the name/file separator; the
75
+ * `"file:"` fallback is matched as a literal prefix first so it isn't
76
+ * misparsed by the same last-colon rule.
77
+ */
78
+ export function parseSymbolKey(key) {
79
+ if (key.startsWith("file:")) {
80
+ return { filePath: key.slice("file:".length) };
81
+ }
82
+ const idx = key.lastIndexOf(":");
83
+ if (idx === -1)
84
+ return { filePath: key };
85
+ const symbolName = key.slice(idx + 1);
86
+ return { filePath: key.slice(0, idx), symbolName: symbolName || undefined };
87
+ }
68
88
  /**
69
89
  * Format an impact result set as a compact human-readable summary.
70
90
  * Example: "handleToolResult (WillBreak) → handleAgentEnd (MayBreak) → 3 Review callers"
@@ -2,9 +2,13 @@ import * as path from "node:path";
2
2
  import { isTestMode } from "./env-utils.js";
3
3
  import { getGlobalPiLensDir } from "./file-utils.js";
4
4
  import { createNdjsonLogger } from "./ndjson-logger.js";
5
+ import { getMaxLogSizeMB } from "./log-cleanup.js";
5
6
  const CASCADE_LOG_DIR = getGlobalPiLensDir();
6
7
  const CASCADE_LOG_FILE = path.join(CASCADE_LOG_DIR, "cascade.log");
7
- const writer = createNdjsonLogger({ filePath: CASCADE_LOG_FILE });
8
+ const writer = createNdjsonLogger({
9
+ filePath: CASCADE_LOG_FILE,
10
+ maxBytes: getMaxLogSizeMB() * 1024 * 1024,
11
+ });
8
12
  export function logCascade(entry) {
9
13
  if (isTestMode()) {
10
14
  return;