pi-usereq 0.4.0

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 (361) hide show
  1. package/.g.conf +20 -0
  2. package/.github/workflows/release-npm.yml +131 -0
  3. package/.gitignore +270 -0
  4. package/CHANGELOG.md +221 -0
  5. package/LICENSE +674 -0
  6. package/README.md +260 -0
  7. package/TODO.md +20 -0
  8. package/docs/pi.dev/agent-document-manifest.json +6399 -0
  9. package/docs/pi.dev/coding-agent-docs/compaction.md +394 -0
  10. package/docs/pi.dev/coding-agent-docs/custom-provider.md +596 -0
  11. package/docs/pi.dev/coding-agent-docs/development.md +71 -0
  12. package/docs/pi.dev/coding-agent-docs/extensions.md +2262 -0
  13. package/docs/pi.dev/coding-agent-docs/images/doom-extension.png +0 -0
  14. package/docs/pi.dev/coding-agent-docs/images/exy.png +0 -0
  15. package/docs/pi.dev/coding-agent-docs/images/interactive-mode.png +0 -0
  16. package/docs/pi.dev/coding-agent-docs/images/tree-view.png +0 -0
  17. package/docs/pi.dev/coding-agent-docs/json.md +82 -0
  18. package/docs/pi.dev/coding-agent-docs/keybindings.md +175 -0
  19. package/docs/pi.dev/coding-agent-docs/models.md +392 -0
  20. package/docs/pi.dev/coding-agent-docs/packages.md +218 -0
  21. package/docs/pi.dev/coding-agent-docs/prompt-templates.md +67 -0
  22. package/docs/pi.dev/coding-agent-docs/providers.md +195 -0
  23. package/docs/pi.dev/coding-agent-docs/rpc.md +1377 -0
  24. package/docs/pi.dev/coding-agent-docs/sdk.md +1124 -0
  25. package/docs/pi.dev/coding-agent-docs/session.md +412 -0
  26. package/docs/pi.dev/coding-agent-docs/settings.md +247 -0
  27. package/docs/pi.dev/coding-agent-docs/shell-aliases.md +13 -0
  28. package/docs/pi.dev/coding-agent-docs/skills.md +232 -0
  29. package/docs/pi.dev/coding-agent-docs/terminal-setup.md +106 -0
  30. package/docs/pi.dev/coding-agent-docs/termux.md +127 -0
  31. package/docs/pi.dev/coding-agent-docs/themes.md +295 -0
  32. package/docs/pi.dev/coding-agent-docs/tmux.md +61 -0
  33. package/docs/pi.dev/coding-agent-docs/tree.md +231 -0
  34. package/docs/pi.dev/coding-agent-docs/tui.md +887 -0
  35. package/docs/pi.dev/coding-agent-docs/windows.md +17 -0
  36. package/docs/pi.dev/mom-docs/artifacts-server.md +475 -0
  37. package/docs/pi.dev/mom-docs/events.md +307 -0
  38. package/docs/pi.dev/mom-docs/new.md +970 -0
  39. package/docs/pi.dev/mom-docs/sandbox.md +153 -0
  40. package/docs/pi.dev/mom-docs/slack-bot-minimal-guide.md +399 -0
  41. package/docs/pi.dev/mom-docs/v86.md +319 -0
  42. package/docs/pi.dev/pods-docs/gml-4.5.md +189 -0
  43. package/docs/pi.dev/pods-docs/gpt-oss.md +233 -0
  44. package/docs/pi.dev/pods-docs/implementation-plan.md +183 -0
  45. package/docs/pi.dev/pods-docs/kimi-k2.md +197 -0
  46. package/docs/pi.dev/pods-docs/models.md +116 -0
  47. package/docs/pi.dev/pods-docs/plan.md +166 -0
  48. package/docs/pi.dev/pods-docs/qwen3-coder.md +132 -0
  49. package/images/flowchart-bw.png +0 -0
  50. package/images/flowchart-bw.svg +102 -0
  51. package/images/flowchart.md +100 -0
  52. package/images/flowchart.png +0 -0
  53. package/images/flowchart.svg +3 -0
  54. package/package.json +46 -0
  55. package/req/docs/REFERENCES.md +4554 -0
  56. package/req/docs/REQUIREMENTS.md +475 -0
  57. package/req/docs/WORKFLOW.md +1059 -0
  58. package/scripts/debug-extension.ts +497 -0
  59. package/scripts/lib/extension-debug-harness.ts +450 -0
  60. package/scripts/lib/recording-extension-api.ts +786 -0
  61. package/scripts/lib/sdk-smoke.ts +503 -0
  62. package/scripts/pi-usereq-debug.sh +330 -0
  63. package/scripts/tool-args-to-params.ts +208 -0
  64. package/src/cli.ts +349 -0
  65. package/src/core/agent-tool-json.ts +621 -0
  66. package/src/core/compress-files.ts +72 -0
  67. package/src/core/compress-payload.ts +648 -0
  68. package/src/core/compress.ts +464 -0
  69. package/src/core/config.ts +272 -0
  70. package/src/core/doxygen-parser.ts +318 -0
  71. package/src/core/errors.ts +31 -0
  72. package/src/core/extension-status.ts +660 -0
  73. package/src/core/find-constructs.ts +319 -0
  74. package/src/core/find-payload.ts +915 -0
  75. package/src/core/generate-markdown.ts +120 -0
  76. package/src/core/path-context.ts +196 -0
  77. package/src/core/pi-notify.ts +430 -0
  78. package/src/core/pi-usereq-tools.ts +140 -0
  79. package/src/core/prompts.ts +184 -0
  80. package/src/core/reference-payload.ts +818 -0
  81. package/src/core/resources.ts +63 -0
  82. package/src/core/runtime-project-paths.ts +99 -0
  83. package/src/core/settings-menu.ts +233 -0
  84. package/src/core/source-analyzer.ts +1721 -0
  85. package/src/core/static-check.ts +674 -0
  86. package/src/core/token-counter.ts +729 -0
  87. package/src/core/tool-runner.ts +717 -0
  88. package/src/core/utils.ts +185 -0
  89. package/src/index.ts +2209 -0
  90. package/src/resources/guidelines/Google_C++_Style_Guide.md +3711 -0
  91. package/src/resources/guidelines/Google_Python_Style_Guide.md +3709 -0
  92. package/src/resources/prompts/analyze.md +130 -0
  93. package/src/resources/prompts/change.md +227 -0
  94. package/src/resources/prompts/check.md +139 -0
  95. package/src/resources/prompts/cover.md +219 -0
  96. package/src/resources/prompts/create.md +104 -0
  97. package/src/resources/prompts/fix.md +221 -0
  98. package/src/resources/prompts/flowchart.md +220 -0
  99. package/src/resources/prompts/implement.md +163 -0
  100. package/src/resources/prompts/new.md +226 -0
  101. package/src/resources/prompts/readme.md +182 -0
  102. package/src/resources/prompts/recreate.md +213 -0
  103. package/src/resources/prompts/refactor.md +213 -0
  104. package/src/resources/prompts/references.md +100 -0
  105. package/src/resources/prompts/renumber.md +119 -0
  106. package/src/resources/prompts/workflow.md +202 -0
  107. package/src/resources/prompts/write.md +99 -0
  108. package/src/resources/sounds/Machine-alert-beep-sound-effect.mp3 +0 -0
  109. package/src/resources/sounds/Soft-high-tech-notification-sound-effect.mp3 +0 -0
  110. package/src/resources/templates/Document_Source_Code_in_Doxygen_Style.md +130 -0
  111. package/src/resources/templates/HDT_Test_Authoring_Guide.md +318 -0
  112. package/src/resources/templates/Requirements_Template.md +78 -0
  113. package/tests/attended-results-scenarios.ts +758 -0
  114. package/tests/attended-results.test.ts +39 -0
  115. package/tests/cli-command-option-parity.test.ts +815 -0
  116. package/tests/debug-extension-harness.test.ts +463 -0
  117. package/tests/extension-registration.test.ts +2006 -0
  118. package/tests/fixtures/fixture_c.c +361 -0
  119. package/tests/fixtures/fixture_cpp.cpp +407 -0
  120. package/tests/fixtures/fixture_csharp.cs +411 -0
  121. package/tests/fixtures/fixture_elixir.ex +409 -0
  122. package/tests/fixtures/fixture_go.go +341 -0
  123. package/tests/fixtures/fixture_haskell.hs +250 -0
  124. package/tests/fixtures/fixture_java.java +430 -0
  125. package/tests/fixtures/fixture_javascript.js +383 -0
  126. package/tests/fixtures/fixture_kotlin.kt +451 -0
  127. package/tests/fixtures/fixture_lua.lua +276 -0
  128. package/tests/fixtures/fixture_perl.pl +310 -0
  129. package/tests/fixtures/fixture_php.php +433 -0
  130. package/tests/fixtures/fixture_python.py +502 -0
  131. package/tests/fixtures/fixture_ruby.rb +345 -0
  132. package/tests/fixtures/fixture_rust.rs +380 -0
  133. package/tests/fixtures/fixture_scala.scala +398 -0
  134. package/tests/fixtures/fixture_shell.sh +276 -0
  135. package/tests/fixtures/fixture_swift.swift +397 -0
  136. package/tests/fixtures/fixture_typescript.ts +434 -0
  137. package/tests/fixtures/fixture_zig.zig +295 -0
  138. package/tests/fixtures_attended_results/project/compress-line-numbers.json +5 -0
  139. package/tests/fixtures_attended_results/project/compress.json +5 -0
  140. package/tests/fixtures_attended_results/project/enable-static-check-invalid-command.json +5 -0
  141. package/tests/fixtures_attended_results/project/enable-static-check-valid.json +5 -0
  142. package/tests/fixtures_attended_results/project/files-static-check.json +5 -0
  143. package/tests/fixtures_attended_results/project/find-line-numbers.json +5 -0
  144. package/tests/fixtures_attended_results/project/find.json +5 -0
  145. package/tests/fixtures_attended_results/project/get-base-path.json +5 -0
  146. package/tests/fixtures_attended_results/project/git-check-clean.json +5 -0
  147. package/tests/fixtures_attended_results/project/git-check-dirty.json +5 -0
  148. package/tests/fixtures_attended_results/project/git-path.json +5 -0
  149. package/tests/fixtures_attended_results/project/git-wt-create-invalid.json +5 -0
  150. package/tests/fixtures_attended_results/project/git-wt-create-valid.json +5 -0
  151. package/tests/fixtures_attended_results/project/git-wt-delete-nonexistent.json +5 -0
  152. package/tests/fixtures_attended_results/project/git-wt-delete-valid.json +5 -0
  153. package/tests/fixtures_attended_results/project/git-wt-name.json +5 -0
  154. package/tests/fixtures_attended_results/project/references.json +5 -0
  155. package/tests/fixtures_attended_results/project/static-check.json +5 -0
  156. package/tests/fixtures_attended_results/project/tokens.json +5 -0
  157. package/tests/fixtures_attended_results/standalone/files-compress/fixture_c.c.json +5 -0
  158. package/tests/fixtures_attended_results/standalone/files-compress/fixture_cpp.cpp.json +5 -0
  159. package/tests/fixtures_attended_results/standalone/files-compress/fixture_csharp.cs.json +5 -0
  160. package/tests/fixtures_attended_results/standalone/files-compress/fixture_elixir.ex.json +5 -0
  161. package/tests/fixtures_attended_results/standalone/files-compress/fixture_go.go.json +5 -0
  162. package/tests/fixtures_attended_results/standalone/files-compress/fixture_haskell.hs.json +5 -0
  163. package/tests/fixtures_attended_results/standalone/files-compress/fixture_java.java.json +5 -0
  164. package/tests/fixtures_attended_results/standalone/files-compress/fixture_javascript.js.json +5 -0
  165. package/tests/fixtures_attended_results/standalone/files-compress/fixture_kotlin.kt.json +5 -0
  166. package/tests/fixtures_attended_results/standalone/files-compress/fixture_lua.lua.json +5 -0
  167. package/tests/fixtures_attended_results/standalone/files-compress/fixture_perl.pl.json +5 -0
  168. package/tests/fixtures_attended_results/standalone/files-compress/fixture_php.php.json +5 -0
  169. package/tests/fixtures_attended_results/standalone/files-compress/fixture_python.py.json +5 -0
  170. package/tests/fixtures_attended_results/standalone/files-compress/fixture_ruby.rb.json +5 -0
  171. package/tests/fixtures_attended_results/standalone/files-compress/fixture_rust.rs.json +5 -0
  172. package/tests/fixtures_attended_results/standalone/files-compress/fixture_scala.scala.json +5 -0
  173. package/tests/fixtures_attended_results/standalone/files-compress/fixture_shell.sh.json +5 -0
  174. package/tests/fixtures_attended_results/standalone/files-compress/fixture_swift.swift.json +5 -0
  175. package/tests/fixtures_attended_results/standalone/files-compress/fixture_typescript.ts.json +5 -0
  176. package/tests/fixtures_attended_results/standalone/files-compress/fixture_zig.zig.json +5 -0
  177. package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_c.c.json +5 -0
  178. package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_cpp.cpp.json +5 -0
  179. package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_csharp.cs.json +5 -0
  180. package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_elixir.ex.json +5 -0
  181. package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_go.go.json +5 -0
  182. package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_haskell.hs.json +5 -0
  183. package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_java.java.json +5 -0
  184. package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_javascript.js.json +5 -0
  185. package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_kotlin.kt.json +5 -0
  186. package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_lua.lua.json +5 -0
  187. package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_perl.pl.json +5 -0
  188. package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_php.php.json +5 -0
  189. package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_python.py.json +5 -0
  190. package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_ruby.rb.json +5 -0
  191. package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_rust.rs.json +5 -0
  192. package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_scala.scala.json +5 -0
  193. package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_shell.sh.json +5 -0
  194. package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_swift.swift.json +5 -0
  195. package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_typescript.ts.json +5 -0
  196. package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_zig.zig.json +5 -0
  197. package/tests/fixtures_attended_results/standalone/files-find/fixture_c.c.json +5 -0
  198. package/tests/fixtures_attended_results/standalone/files-find/fixture_cpp.cpp.json +5 -0
  199. package/tests/fixtures_attended_results/standalone/files-find/fixture_csharp.cs.json +5 -0
  200. package/tests/fixtures_attended_results/standalone/files-find/fixture_elixir.ex.json +5 -0
  201. package/tests/fixtures_attended_results/standalone/files-find/fixture_go.go.json +5 -0
  202. package/tests/fixtures_attended_results/standalone/files-find/fixture_haskell.hs.json +5 -0
  203. package/tests/fixtures_attended_results/standalone/files-find/fixture_java.java.json +5 -0
  204. package/tests/fixtures_attended_results/standalone/files-find/fixture_javascript.js.json +5 -0
  205. package/tests/fixtures_attended_results/standalone/files-find/fixture_kotlin.kt.json +5 -0
  206. package/tests/fixtures_attended_results/standalone/files-find/fixture_lua.lua.json +5 -0
  207. package/tests/fixtures_attended_results/standalone/files-find/fixture_perl.pl.json +5 -0
  208. package/tests/fixtures_attended_results/standalone/files-find/fixture_php.php.json +5 -0
  209. package/tests/fixtures_attended_results/standalone/files-find/fixture_python.py.json +5 -0
  210. package/tests/fixtures_attended_results/standalone/files-find/fixture_ruby.rb.json +5 -0
  211. package/tests/fixtures_attended_results/standalone/files-find/fixture_rust.rs.json +5 -0
  212. package/tests/fixtures_attended_results/standalone/files-find/fixture_scala.scala.json +5 -0
  213. package/tests/fixtures_attended_results/standalone/files-find/fixture_shell.sh.json +5 -0
  214. package/tests/fixtures_attended_results/standalone/files-find/fixture_swift.swift.json +5 -0
  215. package/tests/fixtures_attended_results/standalone/files-find/fixture_typescript.ts.json +5 -0
  216. package/tests/fixtures_attended_results/standalone/files-find/fixture_zig.zig.json +5 -0
  217. package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_c.c.json +5 -0
  218. package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_cpp.cpp.json +5 -0
  219. package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_csharp.cs.json +5 -0
  220. package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_elixir.ex.json +5 -0
  221. package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_go.go.json +5 -0
  222. package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_haskell.hs.json +5 -0
  223. package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_java.java.json +5 -0
  224. package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_javascript.js.json +5 -0
  225. package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_kotlin.kt.json +5 -0
  226. package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_lua.lua.json +5 -0
  227. package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_perl.pl.json +5 -0
  228. package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_php.php.json +5 -0
  229. package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_python.py.json +5 -0
  230. package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_ruby.rb.json +5 -0
  231. package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_rust.rs.json +5 -0
  232. package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_scala.scala.json +5 -0
  233. package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_shell.sh.json +5 -0
  234. package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_swift.swift.json +5 -0
  235. package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_typescript.ts.json +5 -0
  236. package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_zig.zig.json +5 -0
  237. package/tests/fixtures_attended_results/standalone/files-references/fixture_c.c.json +5 -0
  238. package/tests/fixtures_attended_results/standalone/files-references/fixture_cpp.cpp.json +5 -0
  239. package/tests/fixtures_attended_results/standalone/files-references/fixture_csharp.cs.json +5 -0
  240. package/tests/fixtures_attended_results/standalone/files-references/fixture_elixir.ex.json +5 -0
  241. package/tests/fixtures_attended_results/standalone/files-references/fixture_go.go.json +5 -0
  242. package/tests/fixtures_attended_results/standalone/files-references/fixture_haskell.hs.json +5 -0
  243. package/tests/fixtures_attended_results/standalone/files-references/fixture_java.java.json +5 -0
  244. package/tests/fixtures_attended_results/standalone/files-references/fixture_javascript.js.json +5 -0
  245. package/tests/fixtures_attended_results/standalone/files-references/fixture_kotlin.kt.json +5 -0
  246. package/tests/fixtures_attended_results/standalone/files-references/fixture_lua.lua.json +5 -0
  247. package/tests/fixtures_attended_results/standalone/files-references/fixture_perl.pl.json +5 -0
  248. package/tests/fixtures_attended_results/standalone/files-references/fixture_php.php.json +5 -0
  249. package/tests/fixtures_attended_results/standalone/files-references/fixture_python.py.json +5 -0
  250. package/tests/fixtures_attended_results/standalone/files-references/fixture_ruby.rb.json +5 -0
  251. package/tests/fixtures_attended_results/standalone/files-references/fixture_rust.rs.json +5 -0
  252. package/tests/fixtures_attended_results/standalone/files-references/fixture_scala.scala.json +5 -0
  253. package/tests/fixtures_attended_results/standalone/files-references/fixture_shell.sh.json +5 -0
  254. package/tests/fixtures_attended_results/standalone/files-references/fixture_swift.swift.json +5 -0
  255. package/tests/fixtures_attended_results/standalone/files-references/fixture_typescript.ts.json +5 -0
  256. package/tests/fixtures_attended_results/standalone/files-references/fixture_zig.zig.json +5 -0
  257. package/tests/fixtures_attended_results/standalone/files-tokens/fixture_c.c.json +5 -0
  258. package/tests/fixtures_attended_results/standalone/files-tokens/fixture_cpp.cpp.json +5 -0
  259. package/tests/fixtures_attended_results/standalone/files-tokens/fixture_csharp.cs.json +5 -0
  260. package/tests/fixtures_attended_results/standalone/files-tokens/fixture_elixir.ex.json +5 -0
  261. package/tests/fixtures_attended_results/standalone/files-tokens/fixture_go.go.json +5 -0
  262. package/tests/fixtures_attended_results/standalone/files-tokens/fixture_haskell.hs.json +5 -0
  263. package/tests/fixtures_attended_results/standalone/files-tokens/fixture_java.java.json +5 -0
  264. package/tests/fixtures_attended_results/standalone/files-tokens/fixture_javascript.js.json +5 -0
  265. package/tests/fixtures_attended_results/standalone/files-tokens/fixture_kotlin.kt.json +5 -0
  266. package/tests/fixtures_attended_results/standalone/files-tokens/fixture_lua.lua.json +5 -0
  267. package/tests/fixtures_attended_results/standalone/files-tokens/fixture_perl.pl.json +5 -0
  268. package/tests/fixtures_attended_results/standalone/files-tokens/fixture_php.php.json +5 -0
  269. package/tests/fixtures_attended_results/standalone/files-tokens/fixture_python.py.json +5 -0
  270. package/tests/fixtures_attended_results/standalone/files-tokens/fixture_ruby.rb.json +5 -0
  271. package/tests/fixtures_attended_results/standalone/files-tokens/fixture_rust.rs.json +5 -0
  272. package/tests/fixtures_attended_results/standalone/files-tokens/fixture_scala.scala.json +5 -0
  273. package/tests/fixtures_attended_results/standalone/files-tokens/fixture_shell.sh.json +5 -0
  274. package/tests/fixtures_attended_results/standalone/files-tokens/fixture_swift.swift.json +5 -0
  275. package/tests/fixtures_attended_results/standalone/files-tokens/fixture_typescript.ts.json +5 -0
  276. package/tests/fixtures_attended_results/standalone/files-tokens/fixture_zig.zig.json +5 -0
  277. package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_c.c.json +5 -0
  278. package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_cpp.cpp.json +5 -0
  279. package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_csharp.cs.json +5 -0
  280. package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_elixir.ex.json +5 -0
  281. package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_go.go.json +5 -0
  282. package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_haskell.hs.json +5 -0
  283. package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_java.java.json +5 -0
  284. package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_javascript.js.json +5 -0
  285. package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_kotlin.kt.json +5 -0
  286. package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_lua.lua.json +5 -0
  287. package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_perl.pl.json +5 -0
  288. package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_php.php.json +5 -0
  289. package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_python.py.json +5 -0
  290. package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_ruby.rb.json +5 -0
  291. package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_rust.rs.json +5 -0
  292. package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_scala.scala.json +5 -0
  293. package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_shell.sh.json +5 -0
  294. package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_swift.swift.json +5 -0
  295. package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_typescript.ts.json +5 -0
  296. package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_zig.zig.json +5 -0
  297. package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_c.c.json +5 -0
  298. package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_cpp.cpp.json +5 -0
  299. package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_csharp.cs.json +5 -0
  300. package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_elixir.ex.json +5 -0
  301. package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_go.go.json +5 -0
  302. package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_haskell.hs.json +5 -0
  303. package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_java.java.json +5 -0
  304. package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_javascript.js.json +5 -0
  305. package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_kotlin.kt.json +5 -0
  306. package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_lua.lua.json +5 -0
  307. package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_perl.pl.json +5 -0
  308. package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_php.php.json +5 -0
  309. package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_python.py.json +5 -0
  310. package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_ruby.rb.json +5 -0
  311. package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_rust.rs.json +5 -0
  312. package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_scala.scala.json +5 -0
  313. package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_shell.sh.json +5 -0
  314. package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_swift.swift.json +5 -0
  315. package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_typescript.ts.json +5 -0
  316. package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_zig.zig.json +5 -0
  317. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_c.c.json +5 -0
  318. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_cpp.cpp.json +5 -0
  319. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_csharp.cs.json +5 -0
  320. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_elixir.ex.json +5 -0
  321. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_go.go.json +5 -0
  322. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_haskell.hs.json +5 -0
  323. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_java.java.json +5 -0
  324. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_javascript.js.json +5 -0
  325. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_kotlin.kt.json +5 -0
  326. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_lua.lua.json +5 -0
  327. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_perl.pl.json +5 -0
  328. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_php.php.json +5 -0
  329. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_python.py.json +5 -0
  330. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_ruby.rb.json +5 -0
  331. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_rust.rs.json +5 -0
  332. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_scala.scala.json +5 -0
  333. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_shell.sh.json +5 -0
  334. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_swift.swift.json +5 -0
  335. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_typescript.ts.json +5 -0
  336. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_zig.zig.json +5 -0
  337. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_c.c.json +5 -0
  338. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_cpp.cpp.json +5 -0
  339. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_csharp.cs.json +5 -0
  340. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_elixir.ex.json +5 -0
  341. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_go.go.json +5 -0
  342. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_haskell.hs.json +5 -0
  343. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_java.java.json +5 -0
  344. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_javascript.js.json +5 -0
  345. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_kotlin.kt.json +5 -0
  346. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_lua.lua.json +5 -0
  347. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_perl.pl.json +5 -0
  348. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_php.php.json +5 -0
  349. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_python.py.json +5 -0
  350. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_ruby.rb.json +5 -0
  351. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_rust.rs.json +5 -0
  352. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_scala.scala.json +5 -0
  353. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_shell.sh.json +5 -0
  354. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_swift.swift.json +5 -0
  355. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_typescript.ts.json +5 -0
  356. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_zig.zig.json +5 -0
  357. package/tests/helpers.ts +204 -0
  358. package/tests/oracle-project.test.ts +63 -0
  359. package/tests/oracle-standalone.test.ts +48 -0
  360. package/tests/prompt-rendering.test.ts +66 -0
  361. package/tests/release-workflow.test.ts +133 -0
@@ -0,0 +1,660 @@
1
+ /**
2
+ * @file
3
+ * @brief Tracks pi-usereq extension status state and renders status-bar telemetry.
4
+ * @details Centralizes hook interception, context-usage snapshots, run timing,
5
+ * and deterministic status-bar formatting for the pi-usereq extension. Runtime
6
+ * is O(1) per event plus O(s) in configured source-path count during status
7
+ * rendering. Side effects are limited to in-memory state mutation and interval
8
+ * scheduling through exported controller helpers.
9
+ */
10
+
11
+ import type {
12
+ AgentEndEvent,
13
+ ContextUsage,
14
+ ExtensionContext,
15
+ ThemeColor,
16
+ } from "@mariozechner/pi-coding-agent";
17
+ import type { UseReqConfig } from "./config.js";
18
+ import { formatPiNotifyBeepStatus } from "./pi-notify.js";
19
+ import { formatAbsoluteGitPath, formatBasePathRelativeToGitPath, resolveRuntimeGitPath } from "./runtime-project-paths.js";
20
+
21
+ /**
22
+ * @brief Enumerates the CLI-supported theme tokens consumed by status rendering.
23
+ * @details Restricts the status formatter to documented pi theme tokens so the
24
+ * status bar remains compatible with the active CLI theme schema. Compile-time
25
+ * only and introduces no runtime cost.
26
+ */
27
+ type StatusForegroundColor = Extract<
28
+ ThemeColor,
29
+ "accent" | "warning" | "dim" | "error"
30
+ >;
31
+
32
+ /**
33
+ * @brief Describes the raw theme capabilities required for status rendering.
34
+ * @details Accepts the `ctx.ui.theme` foreground renderer plus optional helpers
35
+ * that can convert a foreground color into a background-styled fragment for the
36
+ * context-usage bar. Compile-time only and introduces no runtime cost.
37
+ */
38
+ interface RawStatusTheme {
39
+ fg: (color: StatusForegroundColor, text: string) => string;
40
+ bgFromFg?: (color: StatusForegroundColor, text: string) => string;
41
+ getFgAnsi?: (color: StatusForegroundColor) => string;
42
+ }
43
+
44
+ /**
45
+ * @brief Describes the normalized theme adapter used by status formatters.
46
+ * @details Exposes deterministic label, value, foreground, background, and
47
+ * context-cell renderers so status text generation stays independent from the
48
+ * raw theme API. Compile-time only and introduces no runtime cost.
49
+ */
50
+ interface StatusThemeAdapter {
51
+ label: (fieldName: string) => string;
52
+ value: (text: string) => string;
53
+ colorize: (color: StatusForegroundColor, text: string) => string;
54
+ backgroundize: (color: StatusForegroundColor, text: string) => string;
55
+ separator: string;
56
+ filledContextCell: string;
57
+ emptyContextCell: string;
58
+ }
59
+
60
+ /**
61
+ * @brief Describes one fixed-width context-bar overlay.
62
+ * @details Stores the literal text plus foreground and background color roles
63
+ * used when the context bar must render threshold-specific labels instead of
64
+ * block glyphs. Compile-time only and introduces no runtime cost.
65
+ */
66
+ interface ContextUsageOverlaySpec {
67
+ backgroundColor: StatusForegroundColor;
68
+ foregroundColor: StatusForegroundColor;
69
+ text: "CLEAR" | "FULL!";
70
+ }
71
+
72
+ /**
73
+ * @brief Lists every pi lifecycle hook intercepted by pi-usereq status logic.
74
+ * @details Preserves the canonical user-requested registration order so the
75
+ * extension can install wrappers for all supported resource, session, agent,
76
+ * model, tool, bash, and input hooks. Access complexity is O(1).
77
+ */
78
+ export const PI_USEREQ_STATUS_HOOK_NAMES = [
79
+ "resources_discover",
80
+ "session_start",
81
+ "session_before_switch",
82
+ "session_before_fork",
83
+ "session_before_compact",
84
+ "session_compact",
85
+ "session_shutdown",
86
+ "session_before_tree",
87
+ "session_tree",
88
+ "context",
89
+ "before_provider_request",
90
+ "before_agent_start",
91
+ "agent_start",
92
+ "agent_end",
93
+ "turn_start",
94
+ "turn_end",
95
+ "message_start",
96
+ "message_update",
97
+ "message_end",
98
+ "tool_execution_start",
99
+ "tool_execution_update",
100
+ "tool_execution_end",
101
+ "model_select",
102
+ "tool_call",
103
+ "tool_result",
104
+ "user_bash",
105
+ "input",
106
+ ] as const;
107
+
108
+ /**
109
+ * @brief Represents one hook name handled by the pi-usereq status controller.
110
+ * @details Narrows hook registration and event-update calls to the canonical
111
+ * intercepted-hook set. Compile-time only and introduces no runtime cost.
112
+ */
113
+ export type PiUsereqStatusHookName = (typeof PI_USEREQ_STATUS_HOOK_NAMES)[number];
114
+
115
+ /**
116
+ * @brief Stores the mutable runtime facts displayed by the status bar.
117
+ * @details Persists the latest context-usage snapshot, the active run start
118
+ * timestamp, and the most recent normally completed run duration. Runtime state
119
+ * is mutated in-place by controller helpers. Compile-time only and introduces
120
+ * no runtime cost.
121
+ */
122
+ export interface PiUsereqStatusState {
123
+ contextUsage: ContextUsage | undefined;
124
+ runStartTimeMs: number | undefined;
125
+ lastRunDurationMs: number | undefined;
126
+ }
127
+
128
+ /**
129
+ * @brief Stores the controller state required for event-driven status updates.
130
+ * @details Keeps the mutable status snapshot, the current configuration, the
131
+ * latest extension context used for rendering, the active-tools provider, and
132
+ * the interval handle used for live elapsed-time refreshes. Compile-time only
133
+ * and introduces no runtime cost.
134
+ */
135
+ export interface PiUsereqStatusController {
136
+ readonly getActiveTools: () => readonly string[];
137
+ config: UseReqConfig | undefined;
138
+ latestContext: ExtensionContext | undefined;
139
+ state: PiUsereqStatusState;
140
+ tickHandle: ReturnType<typeof setInterval> | undefined;
141
+ }
142
+
143
+ /**
144
+ * @brief Converts a foreground ANSI sequence into the equivalent background ANSI.
145
+ * @details Supports the standard `38;` foreground prefix emitted by pi themes.
146
+ * Returns `undefined` when the input cannot be transformed deterministically.
147
+ * Runtime is O(n) in ANSI sequence length. No external state is mutated.
148
+ * @param[in] foregroundAnsi {string} Foreground ANSI sequence.
149
+ * @return {string | undefined} Background ANSI sequence when derivable.
150
+ */
151
+ function convertForegroundAnsiToBackgroundAnsi(
152
+ foregroundAnsi: string,
153
+ ): string | undefined {
154
+ if (!foregroundAnsi.includes("\u001b[")) {
155
+ return undefined;
156
+ }
157
+ const backgroundAnsi = foregroundAnsi.replace("[38;", "[48;");
158
+ return backgroundAnsi === foregroundAnsi ? undefined : backgroundAnsi;
159
+ }
160
+
161
+ /**
162
+ * @brief Applies a foreground-derived background style to one text fragment.
163
+ * @details Prefers a theme-provided `bgFromFg` encoder for deterministic test
164
+ * rendering and falls back to ANSI conversion when the runtime theme exposes
165
+ * `getFgAnsi`. Runtime is O(n) in fragment length. No external state is
166
+ * mutated.
167
+ * @param[in] theme {RawStatusTheme} Raw theme adapter.
168
+ * @param[in] color {StatusForegroundColor} Foreground color reused as background.
169
+ * @param[in] text {string} Already-colored foreground fragment.
170
+ * @return {string} Background-decorated text fragment.
171
+ */
172
+ function applyForegroundAsBackground(
173
+ theme: RawStatusTheme,
174
+ color: StatusForegroundColor,
175
+ text: string,
176
+ ): string {
177
+ if (typeof theme.bgFromFg === "function") {
178
+ return theme.bgFromFg(color, text);
179
+ }
180
+ if (typeof theme.getFgAnsi === "function") {
181
+ const backgroundAnsi = convertForegroundAnsiToBackgroundAnsi(
182
+ theme.getFgAnsi(color),
183
+ );
184
+ if (backgroundAnsi) {
185
+ return `${backgroundAnsi}${text}\u001b[39m\u001b[49m`;
186
+ }
187
+ }
188
+ return text;
189
+ }
190
+
191
+ /**
192
+ * @brief Builds the normalized theme adapter used by pi-usereq status formatters.
193
+ * @details Precomputes label, value, foreground, background, separator, and
194
+ * context-cell renderers so status formatting remains stable across real TUI
195
+ * themes and deterministic test doubles. Runtime is O(1). No external state
196
+ * is mutated.
197
+ * @param[in] theme {RawStatusTheme} Raw theme implementation from `ctx.ui.theme`.
198
+ * @return {StatusThemeAdapter} Normalized status-theme adapter.
199
+ */
200
+ function createStatusThemeAdapter(theme: RawStatusTheme): StatusThemeAdapter {
201
+ const colorize = (color: StatusForegroundColor, text: string): string =>
202
+ theme.fg(color, text);
203
+ const backgroundize = (color: StatusForegroundColor, text: string): string =>
204
+ applyForegroundAsBackground(theme, color, text);
205
+ return {
206
+ label: (fieldName: string): string => colorize("accent", `${fieldName}:`),
207
+ value: (text: string): string => colorize("warning", text),
208
+ colorize,
209
+ backgroundize,
210
+ separator: colorize("dim", " • "),
211
+ filledContextCell: backgroundize(
212
+ "accent",
213
+ colorize("warning", "▓"),
214
+ ),
215
+ emptyContextCell: backgroundize(
216
+ "accent",
217
+ colorize("dim", "▓"),
218
+ ),
219
+ };
220
+ }
221
+
222
+ /**
223
+ * @brief Normalizes one raw context-usage snapshot.
224
+ * @details Preserves the runtime token and context-window counts, derives a
225
+ * percentage when the runtime omits it, and clamps percentages into `[0, 100]`.
226
+ * Runtime is O(1). No external state is mutated.
227
+ * @param[in] contextUsage {ContextUsage | undefined} Raw runtime snapshot.
228
+ * @return {ContextUsage | undefined} Normalized snapshot.
229
+ */
230
+ function normalizeContextUsage(
231
+ contextUsage: ContextUsage | undefined,
232
+ ): ContextUsage | undefined {
233
+ if (!contextUsage) {
234
+ return undefined;
235
+ }
236
+ const derivedPercent = contextUsage.percent ?? (
237
+ contextUsage.tokens === null || contextUsage.contextWindow <= 0
238
+ ? null
239
+ : (contextUsage.tokens / contextUsage.contextWindow) * 100
240
+ );
241
+ return {
242
+ ...contextUsage,
243
+ percent: derivedPercent === null
244
+ ? null
245
+ : Math.max(0, Math.min(100, derivedPercent)),
246
+ };
247
+ }
248
+
249
+ /**
250
+ * @brief Refreshes the stored context-usage snapshot from the active extension context.
251
+ * @details Calls `ctx.getContextUsage()` on every intercepted event so the
252
+ * controller retains the newest context-usage facts available from the pi
253
+ * runtime. Runtime is O(1). Side effect: mutates `state.contextUsage`.
254
+ * @param[in,out] state {PiUsereqStatusState} Mutable status state.
255
+ * @param[in] ctx {ExtensionContext} Active extension context.
256
+ * @return {void} No return value.
257
+ * @satisfies REQ-118, REQ-119
258
+ */
259
+ function refreshContextUsage(
260
+ state: PiUsereqStatusState,
261
+ ctx: ExtensionContext,
262
+ ): void {
263
+ state.contextUsage = normalizeContextUsage(ctx.getContextUsage());
264
+ }
265
+
266
+ /**
267
+ * @brief Counts the filled cells rendered by the 5-cell context bar.
268
+ * @details Uses ceiling semantics for positive percentages so any non-zero
269
+ * usage occupies at least one cell and zero usage occupies none. Runtime is
270
+ * O(1). No external state is mutated.
271
+ * @param[in] contextUsage {ContextUsage | undefined} Normalized context snapshot.
272
+ * @return {number} Filled-cell count in the inclusive range `[0, 5]`.
273
+ * @satisfies REQ-122
274
+ */
275
+ function countFilledContextCells(
276
+ contextUsage: ContextUsage | undefined,
277
+ ): number {
278
+ const percent = contextUsage?.percent;
279
+ if (percent === null || percent === undefined || percent <= 0) {
280
+ return 0;
281
+ }
282
+ return Math.min(5, Math.ceil((percent * 5) / 100));
283
+ }
284
+
285
+ /**
286
+ * @brief Resolves the threshold-specific context-bar overlay when required.
287
+ * @details Returns the empty-state `CLEAR` overlay when normalized context
288
+ * usage is unavailable or non-positive and returns the high-water `FULL!`
289
+ * overlay with the active theme `error` token when usage exceeds 90 percent.
290
+ * Runtime is O(1). No external state is mutated.
291
+ * @param[in] contextUsage {ContextUsage | undefined} Normalized context snapshot.
292
+ * @return {ContextUsageOverlaySpec | undefined} Overlay spec when a replacement label is required.
293
+ * @satisfies REQ-127, REQ-128
294
+ */
295
+ function resolveContextUsageOverlay(
296
+ contextUsage: ContextUsage | undefined,
297
+ ): ContextUsageOverlaySpec | undefined {
298
+ const percent = contextUsage?.percent;
299
+ if (percent === undefined || percent === null || percent <= 0) {
300
+ return {
301
+ backgroundColor: "accent",
302
+ foregroundColor: "warning",
303
+ text: "CLEAR",
304
+ };
305
+ }
306
+ if (percent > 90) {
307
+ return {
308
+ backgroundColor: "warning",
309
+ foregroundColor: "error",
310
+ text: "FULL!",
311
+ };
312
+ }
313
+ return undefined;
314
+ }
315
+
316
+ /**
317
+ * @brief Formats one threshold-specific context-bar overlay.
318
+ * @details Renders the fixed-width overlay text with the requested foreground
319
+ * color and reuses the selected bar color as the background so the bar width
320
+ * and state-specific backdrop remain preserved. Runtime is O(n) in overlay
321
+ * width. No external state is mutated.
322
+ * @param[in] theme {StatusThemeAdapter} Normalized status theme.
323
+ * @param[in] overlay {ContextUsageOverlaySpec} Overlay specification.
324
+ * @return {string} Rendered overlay text.
325
+ * @satisfies REQ-127, REQ-128
326
+ */
327
+ function formatContextUsageOverlay(
328
+ theme: StatusThemeAdapter,
329
+ overlay: ContextUsageOverlaySpec,
330
+ ): string {
331
+ return theme.backgroundize(
332
+ overlay.backgroundColor,
333
+ theme.colorize(overlay.foregroundColor, overlay.text),
334
+ );
335
+ }
336
+
337
+ /**
338
+ * @brief Formats one 5-cell context-usage bar.
339
+ * @details Renders threshold-specific overlays for empty and high-water states;
340
+ * otherwise renders filled cells with the theme `warning` token on an
341
+ * accent-derived background and unfilled cells in `dim` on the same background
342
+ * to preserve constant bar width. Runtime is O(1). No external state is
343
+ * mutated.
344
+ * @param[in] theme {StatusThemeAdapter} Normalized status theme.
345
+ * @param[in] contextUsage {ContextUsage | undefined} Normalized context snapshot.
346
+ * @return {string} Rendered 5-cell bar or overlay.
347
+ * @satisfies REQ-121, REQ-122, REQ-126, REQ-127, REQ-128
348
+ */
349
+ function formatContextUsageBar(
350
+ theme: StatusThemeAdapter,
351
+ contextUsage: ContextUsage | undefined,
352
+ ): string {
353
+ const overlay = resolveContextUsageOverlay(contextUsage);
354
+ if (overlay) {
355
+ return formatContextUsageOverlay(theme, overlay);
356
+ }
357
+ const filledCells = countFilledContextCells(contextUsage);
358
+ return Array.from({ length: 5 }, (_value, index) =>
359
+ index < filledCells ? theme.filledContextCell : theme.emptyContextCell,
360
+ ).join("");
361
+ }
362
+
363
+ /**
364
+ * @brief Formats one elapsed-duration value as `M:SS`.
365
+ * @details Floors the input to whole seconds, keeps minutes unbounded above 59,
366
+ * and zero-pads seconds to two digits. Runtime is O(1). No external state is
367
+ * mutated.
368
+ * @param[in] durationMs {number} Duration in milliseconds.
369
+ * @return {string} Duration rendered as `M:SS`.
370
+ * @satisfies REQ-125
371
+ */
372
+ function formatStatusDuration(durationMs: number): string {
373
+ const totalSeconds = Math.max(0, Math.floor(durationMs / 1000));
374
+ const minutes = Math.floor(totalSeconds / 60);
375
+ const seconds = totalSeconds % 60;
376
+ return `${minutes}:${String(seconds).padStart(2, "0")}`;
377
+ }
378
+
379
+ /**
380
+ * @brief Formats one standard status-bar field.
381
+ * @details Renders the field label in accent color and the value in warning
382
+ * color. Runtime is O(n) in combined text length. No external state is mutated.
383
+ * @param[in] theme {StatusThemeAdapter} Normalized status theme.
384
+ * @param[in] fieldName {string} Field label emitted before the colon.
385
+ * @param[in] value {string} Unstyled field value.
386
+ * @return {string} Rendered status-field fragment.
387
+ */
388
+ function formatStatusField(
389
+ theme: StatusThemeAdapter,
390
+ fieldName: string,
391
+ value: string,
392
+ ): string {
393
+ return `${theme.label(fieldName)}${theme.value(value)}`;
394
+ }
395
+
396
+ /**
397
+ * @brief Formats one pre-rendered status-bar field value.
398
+ * @details Preserves the accent-colored field label while allowing callers to
399
+ * provide a custom styled value such as the context-usage bar. Runtime is O(n)
400
+ * in combined text length. No external state is mutated.
401
+ * @param[in] theme {StatusThemeAdapter} Normalized status theme.
402
+ * @param[in] fieldName {string} Field label emitted before the colon.
403
+ * @param[in] renderedValue {string} Pre-rendered field value.
404
+ * @return {string} Rendered status-field fragment.
405
+ */
406
+ function formatRenderedStatusField(
407
+ theme: StatusThemeAdapter,
408
+ fieldName: string,
409
+ renderedValue: string,
410
+ ): string {
411
+ return `${theme.label(fieldName)}${renderedValue}`;
412
+ }
413
+
414
+ /**
415
+ * @brief Detects whether an agent run ended through abort semantics.
416
+ * @details Treats any assistant message whose `stopReason` equals `aborted` as
417
+ * an escape-triggered termination that must not overwrite the `last` timer.
418
+ * Runtime is O(n) in message count. No external state is mutated.
419
+ * @param[in] messages {AgentEndEvent["messages"]} Messages emitted by `agent_end`.
420
+ * @return {boolean} `true` when the run ended in aborted state.
421
+ * @satisfies REQ-125
422
+ */
423
+ function didAgentEndAbort(messages: AgentEndEvent["messages"]): boolean {
424
+ return messages.some((message) =>
425
+ typeof message === "object"
426
+ && message !== null
427
+ && (message as { role?: unknown }).role === "assistant"
428
+ && (message as { stopReason?: unknown }).stopReason === "aborted",
429
+ );
430
+ }
431
+
432
+ /**
433
+ * @brief Builds the full single-line pi-usereq status-bar payload.
434
+ * @details Renders git, base, docs, tests, src, tools, context, elapsed, last, beep, and sound fields in the canonical order with dim bullet separators and threshold-specific context-bar overlays. Runtime is O(s) in configured source-path count plus runtime git probing. No external state is mutated.
435
+ * @param[in] cwd {string} Runtime working directory used for git/base path derivation.
436
+ * @param[in] config {UseReqConfig} Effective project configuration.
437
+ * @param[in] activeTools {readonly string[]} Active runtime tool names.
438
+ * @param[in] theme {StatusThemeAdapter} Normalized status theme.
439
+ * @param[in] state {PiUsereqStatusState} Mutable status state snapshot.
440
+ * @param[in] nowMs {number} Current wall-clock time in milliseconds.
441
+ * @return {string} Single-line status-bar text.
442
+ * @satisfies REQ-109, REQ-112, REQ-120, REQ-121, REQ-123, REQ-124, REQ-125, REQ-126, REQ-127, REQ-128, REQ-135, REQ-136, REQ-147, REQ-148, REQ-156
443
+ */
444
+ function buildPiUsereqStatusText(
445
+ cwd: string,
446
+ config: UseReqConfig,
447
+ activeTools: readonly string[],
448
+ theme: StatusThemeAdapter,
449
+ state: PiUsereqStatusState,
450
+ nowMs: number,
451
+ ): string {
452
+ const gitPath = resolveRuntimeGitPath(cwd);
453
+ const gitText = formatAbsoluteGitPath(gitPath);
454
+ const baseText = formatBasePathRelativeToGitPath(cwd, gitPath);
455
+ const sourcePaths = config["src-dir"].join(",");
456
+ const elapsedText = state.runStartTimeMs === undefined
457
+ ? "idle"
458
+ : formatStatusDuration(nowMs - state.runStartTimeMs);
459
+ const lastText = state.lastRunDurationMs === undefined
460
+ ? "N/A"
461
+ : formatStatusDuration(state.lastRunDurationMs);
462
+ const beepText = formatPiNotifyBeepStatus(config);
463
+ const soundText = config["notify-sound"];
464
+ return [
465
+ formatStatusField(theme, "git", gitText),
466
+ formatStatusField(theme, "base", baseText),
467
+ formatStatusField(theme, "docs", config["docs-dir"]),
468
+ formatStatusField(theme, "tests", config["tests-dir"]),
469
+ formatStatusField(theme, "src", sourcePaths),
470
+ formatStatusField(theme, "tools", String(activeTools.length)),
471
+ formatRenderedStatusField(
472
+ theme,
473
+ "context",
474
+ formatContextUsageBar(theme, state.contextUsage),
475
+ ),
476
+ formatStatusField(theme, "elapsed", elapsedText),
477
+ formatStatusField(theme, "last", lastText),
478
+ formatStatusField(theme, "beep", beepText),
479
+ formatStatusField(theme, "sound", soundText),
480
+ ].join(theme.separator);
481
+ }
482
+
483
+ /**
484
+ * @brief Stops the live elapsed-time ticker when it is active.
485
+ * @details Clears the interval handle and resets the stored timer reference so
486
+ * subsequent runs can reinitialize live status refreshes deterministically.
487
+ * Runtime is O(1). Side effect: mutates `controller.tickHandle`.
488
+ * @param[in,out] controller {PiUsereqStatusController} Mutable status controller.
489
+ * @return {void} No return value.
490
+ */
491
+ function stopStatusTicker(controller: PiUsereqStatusController): void {
492
+ if (controller.tickHandle !== undefined) {
493
+ clearInterval(controller.tickHandle);
494
+ controller.tickHandle = undefined;
495
+ }
496
+ }
497
+
498
+ /**
499
+ * @brief Synchronizes the live elapsed-time ticker with the current run state.
500
+ * @details Starts a 1-second render ticker while a run is active and stops the
501
+ * ticker when the run returns to idle. Runtime is O(1). Side effects include
502
+ * interval creation, interval disposal, and footer-status mutation on timer
503
+ * ticks.
504
+ * @param[in,out] controller {PiUsereqStatusController} Mutable status controller.
505
+ * @return {void} No return value.
506
+ * @satisfies REQ-123
507
+ */
508
+ function syncPiUsereqStatusTicker(
509
+ controller: PiUsereqStatusController,
510
+ ): void {
511
+ if (controller.state.runStartTimeMs === undefined) {
512
+ stopStatusTicker(controller);
513
+ return;
514
+ }
515
+ if (controller.tickHandle !== undefined) {
516
+ return;
517
+ }
518
+ controller.tickHandle = setInterval(() => {
519
+ if (!controller.latestContext || !controller.config) {
520
+ return;
521
+ }
522
+ renderPiUsereqStatus(controller, controller.latestContext);
523
+ }, 1000);
524
+ }
525
+
526
+ /**
527
+ * @brief Creates an empty pi-usereq status controller.
528
+ * @details Initializes the mutable status snapshot, stores the active-tools
529
+ * provider used by render-time tool counting, and starts with no config, no
530
+ * context, and no live ticker. Runtime is O(1). No external state is mutated.
531
+ * @param[in] getActiveTools {() => readonly string[]} Provider for active tools.
532
+ * @return {PiUsereqStatusController} New status controller.
533
+ * @satisfies DES-010
534
+ */
535
+ export function createPiUsereqStatusController(
536
+ getActiveTools: () => readonly string[],
537
+ ): PiUsereqStatusController {
538
+ return {
539
+ getActiveTools,
540
+ config: undefined,
541
+ latestContext: undefined,
542
+ state: {
543
+ contextUsage: undefined,
544
+ runStartTimeMs: undefined,
545
+ lastRunDurationMs: undefined,
546
+ },
547
+ tickHandle: undefined,
548
+ };
549
+ }
550
+
551
+ /**
552
+ * @brief Stores the effective project configuration used by status rendering.
553
+ * @details Replaces the controller's cached configuration so later status
554
+ * renders reuse the latest docs, tests, source-path, and pi-notify values
555
+ * without reading from disk on every event. Runtime is O(1). Side effect:
556
+ * mutates `controller.config`.
557
+ * @param[in,out] controller {PiUsereqStatusController} Mutable status controller.
558
+ * @param[in] config {UseReqConfig} Effective project configuration.
559
+ * @return {void} No return value.
560
+ */
561
+ export function setPiUsereqStatusConfig(
562
+ controller: PiUsereqStatusController,
563
+ config: UseReqConfig,
564
+ ): void {
565
+ controller.config = config;
566
+ }
567
+
568
+ /**
569
+ * @brief Renders the current pi-usereq status bar into the active UI context.
570
+ * @details Updates the controller's latest context pointer and writes the
571
+ * single-line status text only when configuration is available, including any
572
+ * threshold-specific context-bar overlays. Runtime is O(s) in configured
573
+ * source-path count. Side effect: mutates `ctx.ui` status.
574
+ * @param[in,out] controller {PiUsereqStatusController} Mutable status controller.
575
+ * @param[in] ctx {ExtensionContext} Active extension context.
576
+ * @return {void} No return value.
577
+ * @satisfies REQ-120, REQ-121, REQ-123, REQ-124, REQ-125, REQ-126, REQ-127, REQ-128, REQ-135, REQ-136
578
+ */
579
+ export function renderPiUsereqStatus(
580
+ controller: PiUsereqStatusController,
581
+ ctx: ExtensionContext,
582
+ ): void {
583
+ controller.latestContext = ctx;
584
+ if (!controller.config) {
585
+ return;
586
+ }
587
+ const theme = createStatusThemeAdapter(ctx.ui.theme as RawStatusTheme);
588
+ ctx.ui.setStatus(
589
+ "pi-usereq",
590
+ buildPiUsereqStatusText(
591
+ ctx.cwd,
592
+ controller.config,
593
+ controller.getActiveTools(),
594
+ theme,
595
+ controller.state,
596
+ Date.now(),
597
+ ),
598
+ );
599
+ }
600
+
601
+ /**
602
+ * @brief Updates mutable status state for one intercepted lifecycle hook.
603
+ * @details Refreshes stored context usage on every hook, starts run timing on
604
+ * `agent_start`, captures non-aborted run duration on `agent_end`, clears live
605
+ * timing on shutdown, synchronizes the live ticker, and re-renders the status
606
+ * bar when configuration is available. Runtime is O(n) in `agent_end` message
607
+ * count and otherwise O(1). Side effects include in-memory state mutation,
608
+ * interval scheduling, and footer-status updates.
609
+ * @param[in,out] controller {PiUsereqStatusController} Mutable status controller.
610
+ * @param[in] hookName {PiUsereqStatusHookName} Intercepted hook name.
611
+ * @param[in] event {unknown} Hook payload forwarded from the wrapper.
612
+ * @param[in] ctx {ExtensionContext} Active extension context.
613
+ * @return {void} No return value.
614
+ * @satisfies REQ-117, REQ-118, REQ-119, REQ-123, REQ-124, REQ-125
615
+ */
616
+ export function updateExtensionStatus(
617
+ controller: PiUsereqStatusController,
618
+ hookName: PiUsereqStatusHookName,
619
+ event: unknown,
620
+ ctx: ExtensionContext,
621
+ ): void {
622
+ controller.latestContext = ctx;
623
+ refreshContextUsage(controller.state, ctx);
624
+ const nowMs = Date.now();
625
+
626
+ if (hookName === "agent_start") {
627
+ controller.state.runStartTimeMs = nowMs;
628
+ }
629
+
630
+ if (hookName === "agent_end" && controller.state.runStartTimeMs !== undefined) {
631
+ const durationMs = nowMs - controller.state.runStartTimeMs;
632
+ if (!didAgentEndAbort((event as AgentEndEvent).messages ?? [])) {
633
+ controller.state.lastRunDurationMs = durationMs;
634
+ }
635
+ controller.state.runStartTimeMs = undefined;
636
+ }
637
+
638
+ if (hookName === "session_shutdown") {
639
+ controller.state.runStartTimeMs = undefined;
640
+ }
641
+
642
+ syncPiUsereqStatusTicker(controller);
643
+ renderPiUsereqStatus(controller, ctx);
644
+ }
645
+
646
+ /**
647
+ * @brief Disposes the pi-usereq status controller.
648
+ * @details Stops the live ticker, clears the cached context pointer, and leaves
649
+ * the last captured status snapshot available for inspection until the
650
+ * controller object itself is discarded. Runtime is O(1). Side effects are
651
+ * limited to interval disposal and in-memory state mutation.
652
+ * @param[in,out] controller {PiUsereqStatusController} Mutable status controller.
653
+ * @return {void} No return value.
654
+ */
655
+ export function disposePiUsereqStatusController(
656
+ controller: PiUsereqStatusController,
657
+ ): void {
658
+ stopStatusTicker(controller);
659
+ controller.latestContext = undefined;
660
+ }