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,430 @@
1
+ /**
2
+ * @file
3
+ * @brief Implements pi-usereq terminal-beep and external sound-hook helpers.
4
+ * @details Centralizes configuration defaults, status serialization, agent-end outcome classification, terminal notification dispatch, and successful-run external sound-command execution. Runtime is O(m + c) in `agent_end` message count plus command length. Side effects include stdout writes and detached child-process spawning.
5
+ */
6
+
7
+ import { execFile, spawn } from "node:child_process";
8
+ import type { AgentEndEvent } from "@mariozechner/pi-coding-agent";
9
+ import { getInstallationPath } from "./path-context.js";
10
+ import type { UseReqConfig } from "./config.js";
11
+
12
+ /**
13
+ * @brief Enumerates supported successful-run sound levels.
14
+ * @details Defines the canonical persisted order used by config normalization, shortcut cycling, status rendering, and external command selection. Access complexity is O(1).
15
+ */
16
+ export const PI_NOTIFY_SOUND_LEVELS = ["none", "low", "mid", "high"] as const;
17
+
18
+ /**
19
+ * @brief Represents one supported successful-run sound level.
20
+ * @details Narrows configuration parsing and runtime command dispatch to the canonical four-state sound-hook domain. Compile-time only and introduces no runtime cost.
21
+ */
22
+ export type PiNotifySoundLevel = (typeof PI_NOTIFY_SOUND_LEVELS)[number];
23
+
24
+ /**
25
+ * @brief Enumerates supported prompt-end notification outcomes.
26
+ * @details Distinguishes successful completion, escape-triggered abortion, and error termination for independent beep routing. Access complexity is O(1).
27
+ */
28
+ export const PI_NOTIFY_OUTCOMES = ["end", "esc", "err"] as const;
29
+
30
+ /**
31
+ * @brief Represents one supported prompt-end notification outcome.
32
+ * @details Narrows prompt-end event classification and status serialization to the canonical three-outcome domain. Compile-time only and introduces no runtime cost.
33
+ */
34
+ export type PiNotifyOutcome = (typeof PI_NOTIFY_OUTCOMES)[number];
35
+
36
+ /**
37
+ * @brief Defines the default successful-run sound toggle shortcut.
38
+ * @details Seeds persisted configuration when no project-specific shortcut exists. Access complexity is O(1).
39
+ * @satisfies REQ-134
40
+ */
41
+ export const DEFAULT_PI_NOTIFY_SOUND_TOGGLE_SHORTCUT = "alt+s";
42
+
43
+ /**
44
+ * @brief Defines the default low-level successful-run sound command.
45
+ * @details Uses the bundled soft notification asset with a low playback volume and defers `%%INSTALLATION_PATH%%` substitution until runtime execution. Access complexity is O(1).
46
+ * @satisfies REQ-133, REQ-142
47
+ */
48
+ export const DEFAULT_PI_NOTIFY_SOUND_LOW_CMD = "paplay --volume=21845 %%INSTALLATION_PATH%%/resources/sounds/Soft-high-tech-notification-sound-effect.mp3";
49
+
50
+ /**
51
+ * @brief Defines the default mid-level successful-run sound command.
52
+ * @details Uses the bundled soft notification asset with a mid playback volume and defers `%%INSTALLATION_PATH%%` substitution until runtime execution. Access complexity is O(1).
53
+ * @satisfies REQ-133, REQ-143
54
+ */
55
+ export const DEFAULT_PI_NOTIFY_SOUND_MID_CMD = "paplay --volume=43690 %%INSTALLATION_PATH%%/resources/sounds/Soft-high-tech-notification-sound-effect.mp3";
56
+
57
+ /**
58
+ * @brief Defines the default high-level successful-run sound command.
59
+ * @details Uses the bundled soft notification asset with a high playback volume and defers `%%INSTALLATION_PATH%%` substitution until runtime execution. Access complexity is O(1).
60
+ * @satisfies REQ-133, REQ-144
61
+ */
62
+ export const DEFAULT_PI_NOTIFY_SOUND_HIGH_CMD = "paplay --volume=65535 %%INSTALLATION_PATH%%/resources/sounds/Soft-high-tech-notification-sound-effect.mp3";
63
+
64
+ /**
65
+ * @brief Describes the configuration fields consumed by pi-notify helpers.
66
+ * @details Narrows the full project config to the persisted notification and sound-hook fields used by status rendering, prompt-end routing, and shortcut toggles. Compile-time only and introduces no runtime cost.
67
+ */
68
+ export type PiNotifyConfigFields = Pick<
69
+ UseReqConfig,
70
+ | "notify-beep-on-end"
71
+ | "notify-beep-on-esc"
72
+ | "notify-beep-on-error"
73
+ | "notify-sound"
74
+ | "notify-sound-toggle-shortcut"
75
+ | "PI_NOTIFY_SOUND_LOW_CMD"
76
+ | "PI_NOTIFY_SOUND_MID_CMD"
77
+ | "PI_NOTIFY_SOUND_HIGH_CMD"
78
+ >;
79
+
80
+ /**
81
+ * @brief Normalizes one persisted sound level.
82
+ * @details Accepts only canonical `none|low|mid|high` values and falls back to `none` for missing or invalid payloads. Runtime is O(1). No external state is mutated.
83
+ * @param[in] value {unknown} Raw persisted sound-level payload.
84
+ * @return {PiNotifySoundLevel} Canonical sound level.
85
+ * @satisfies REQ-131
86
+ */
87
+ export function normalizePiNotifySoundLevel(value: unknown): PiNotifySoundLevel {
88
+ return PI_NOTIFY_SOUND_LEVELS.includes(value as PiNotifySoundLevel)
89
+ ? (value as PiNotifySoundLevel)
90
+ : "none";
91
+ }
92
+
93
+ /**
94
+ * @brief Normalizes one persisted sound toggle shortcut.
95
+ * @details Accepts any non-empty string so project config can carry raw pi shortcut syntax and falls back to the canonical default when the payload is empty or invalid. Runtime is O(n) in shortcut length. No external state is mutated.
96
+ * @param[in] value {unknown} Raw persisted shortcut payload.
97
+ * @return {string} Canonical non-empty shortcut string.
98
+ * @satisfies REQ-134
99
+ */
100
+ export function normalizePiNotifyShortcut(value: unknown): string {
101
+ return typeof value === "string" && value.trim()
102
+ ? value.trim()
103
+ : DEFAULT_PI_NOTIFY_SOUND_TOGGLE_SHORTCUT;
104
+ }
105
+
106
+ /**
107
+ * @brief Normalizes one persisted sound command string.
108
+ * @details Accepts any non-empty string so project config can override bundled commands verbatim and falls back to the supplied default when the payload is empty or invalid. Runtime is O(n) in command length. No external state is mutated.
109
+ * @param[in] value {unknown} Raw persisted command payload.
110
+ * @param[in] fallback {string} Canonical fallback command.
111
+ * @return {string} Canonical non-empty command string.
112
+ * @satisfies REQ-133
113
+ */
114
+ export function normalizePiNotifyCommand(value: unknown, fallback: string): string {
115
+ return typeof value === "string" && value.trim() ? value.trim() : fallback;
116
+ }
117
+
118
+ /**
119
+ * @brief Formats enabled terminal-beep flags for status rendering.
120
+ * @details Emits the canonical comma-ordered enabled outcome tokens `end`, `esc`, and `err`, or `none` when all prompt-end beep flags are disabled. Runtime is O(1). No external state is mutated.
121
+ * @param[in] config {PiNotifyConfigFields} Effective notification configuration.
122
+ * @return {string} Status-bar beep payload.
123
+ * @satisfies REQ-135
124
+ */
125
+ export function formatPiNotifyBeepStatus(config: PiNotifyConfigFields): string {
126
+ const enabledOutcomes: PiNotifyOutcome[] = [];
127
+ if (config["notify-beep-on-end"]) {
128
+ enabledOutcomes.push("end");
129
+ }
130
+ if (config["notify-beep-on-esc"]) {
131
+ enabledOutcomes.push("esc");
132
+ }
133
+ if (config["notify-beep-on-error"]) {
134
+ enabledOutcomes.push("err");
135
+ }
136
+ return enabledOutcomes.length > 0 ? enabledOutcomes.join(",") : "none";
137
+ }
138
+
139
+ /**
140
+ * @brief Cycles one sound level through the canonical shortcut order.
141
+ * @details Advances persisted sound state in the exact order `none -> low -> mid -> high -> none`, enabling deterministic shortcut toggling and menu reuse. Runtime is O(1). No external state is mutated.
142
+ * @param[in] currentLevel {PiNotifySoundLevel} Current persisted sound level.
143
+ * @return {PiNotifySoundLevel} Next sound level in the cycle.
144
+ * @satisfies REQ-134
145
+ */
146
+ export function cyclePiNotifySoundLevel(currentLevel: PiNotifySoundLevel): PiNotifySoundLevel {
147
+ const currentIndex = PI_NOTIFY_SOUND_LEVELS.indexOf(currentLevel);
148
+ const nextIndex = currentIndex >= 0
149
+ ? (currentIndex + 1) % PI_NOTIFY_SOUND_LEVELS.length
150
+ : 0;
151
+ return PI_NOTIFY_SOUND_LEVELS[nextIndex];
152
+ }
153
+
154
+ /**
155
+ * @brief Escapes one string for single-quoted PowerShell embedding.
156
+ * @details Doubles embedded apostrophes so the generated Windows toast script preserves literal title and body text. Runtime is O(n) in text length. No external state is mutated.
157
+ * @param[in] value {string} Raw literal text.
158
+ * @return {string} PowerShell-safe single-quoted literal content.
159
+ */
160
+ function escapePowerShellLiteral(value: string): string {
161
+ return value.replace(/'/g, "''");
162
+ }
163
+
164
+ /**
165
+ * @brief Builds the PowerShell script used for Windows toast notifications.
166
+ * @details Reuses the pi notify example contract, escapes title and body literals, and emits a one-shot script that shows a toast notification through the Windows Runtime API. Runtime is O(n) in payload length. No external state is mutated.
167
+ * @param[in] title {string} Notification title.
168
+ * @param[in] body {string} Notification body.
169
+ * @return {string} PowerShell script passed to `powershell.exe`.
170
+ */
171
+ function windowsToastScript(title: string, body: string): string {
172
+ const type = "Windows.UI.Notifications";
173
+ const manager = `[${type}.ToastNotificationManager, ${type}, ContentType = WindowsRuntime]`;
174
+ const template = `[${type}.ToastTemplateType]::ToastText02`;
175
+ const escapedTitle = escapePowerShellLiteral(title);
176
+ const escapedBody = escapePowerShellLiteral(body);
177
+ return [
178
+ `${manager} > $null`,
179
+ `$xml = [${type}.ToastNotificationManager]::GetTemplateContent(${template})`,
180
+ `$texts = $xml.GetElementsByTagName('text')`,
181
+ `$texts[0].AppendChild($xml.CreateTextNode('${escapedTitle}')) > $null`,
182
+ `$texts[1].AppendChild($xml.CreateTextNode('${escapedBody}')) > $null`,
183
+ `$toast = [${type}.ToastNotification]::new($xml)`,
184
+ `[${type}.ToastNotificationManager]::CreateToastNotifier('pi-usereq').Show($toast)`,
185
+ ].join("; ");
186
+ }
187
+
188
+ /**
189
+ * @brief Emits one OSC 777 terminal notification.
190
+ * @details Writes the Ghostty/iTerm2/WezTerm-compatible OSC 777 escape sequence directly to stdout using the supplied title and body payloads. Runtime is O(n) in payload length. Side effect: writes to stdout.
191
+ * @param[in] title {string} Notification title.
192
+ * @param[in] body {string} Notification body.
193
+ * @return {void} No return value.
194
+ * @satisfies REQ-130
195
+ */
196
+ export function notifyOSC777(title: string, body: string): void {
197
+ process.stdout.write(`\u001b]777;notify;${title};${body}\u0007`);
198
+ }
199
+
200
+ /**
201
+ * @brief Emits one Kitty OSC 99 terminal notification.
202
+ * @details Writes the two-part Kitty OSC 99 sequence that carries the title and body payloads under one notification identifier. Runtime is O(n) in payload length. Side effect: writes to stdout.
203
+ * @param[in] title {string} Notification title.
204
+ * @param[in] body {string} Notification body.
205
+ * @return {void} No return value.
206
+ * @satisfies REQ-130
207
+ */
208
+ export function notifyOSC99(title: string, body: string): void {
209
+ process.stdout.write(`\u001b]99;i=1:d=0;${title}\u001b\\`);
210
+ process.stdout.write(`\u001b]99;i=1:p=body;${body}\u001b\\`);
211
+ }
212
+
213
+ /**
214
+ * @brief Emits one OSC 9 terminal notification.
215
+ * @details Writes the single-part OSC 9 sequence commonly used by terminals that accept message-only desktop notifications. Runtime is O(n) in payload length. Side effect: writes to stdout.
216
+ * @param[in] title {string} Notification title.
217
+ * @param[in] body {string} Notification body.
218
+ * @return {void} No return value.
219
+ * @satisfies REQ-130
220
+ */
221
+ export function notifyOSC9(title: string, body: string): void {
222
+ process.stdout.write(`\u001b]9;${title}: ${body}\u0007`);
223
+ }
224
+
225
+ /**
226
+ * @brief Emits one Windows toast notification.
227
+ * @details Spawns `powershell.exe` without waiting, delegates payload rendering to `windowsToastScript(...)`, and ignores transport failures so prompt-end handling remains non-blocking. Runtime is dominated by process spawn. Side effects include child-process execution.
228
+ * @param[in] title {string} Notification title.
229
+ * @param[in] body {string} Notification body.
230
+ * @return {void} No return value.
231
+ * @satisfies REQ-130
232
+ */
233
+ export function notifyWindows(title: string, body: string): void {
234
+ void execFile(
235
+ "powershell.exe",
236
+ ["-NoProfile", "-Command", windowsToastScript(title, body)],
237
+ () => undefined,
238
+ );
239
+ }
240
+
241
+ /**
242
+ * @brief Routes one prompt-end terminal notification through the detected terminal protocol.
243
+ * @details Prefers Windows toast delivery inside Windows Terminal, Kitty OSC 99 inside Kitty, OSC 9 inside iTerm-like terminals, and OSC 777 as the fallback path. Runtime is O(n) in payload length plus optional process spawn cost. Side effects include stdout writes or child-process execution.
244
+ * @param[in] title {string} Notification title.
245
+ * @param[in] body {string} Notification body.
246
+ * @return {void} No return value.
247
+ * @satisfies REQ-130
248
+ */
249
+ export function notifyPiTerminal(title: string, body: string): void {
250
+ if (process.env.WT_SESSION) {
251
+ notifyWindows(title, body);
252
+ return;
253
+ }
254
+ if (process.env.KITTY_WINDOW_ID) {
255
+ notifyOSC99(title, body);
256
+ return;
257
+ }
258
+ if (process.env.TERM_PROGRAM === "iTerm.app" || process.env.TERM_PROGRAM === "Apple_Terminal") {
259
+ notifyOSC9(title, body);
260
+ return;
261
+ }
262
+ notifyOSC777(title, body);
263
+ }
264
+
265
+ /**
266
+ * @brief Detects whether one agent-end payload contains the requested stop reason.
267
+ * @details Scans assistant messages only so prompt-end classification remains stable even when user or tool-result messages also appear in the payload. Runtime is O(m) in message count. No external state is mutated.
268
+ * @param[in] messages {AgentEndEvent["messages"]} Agent-end message list.
269
+ * @param[in] stopReason {"aborted" | "error"} Stop reason to detect.
270
+ * @return {boolean} `true` when an assistant message carries the requested stop reason.
271
+ */
272
+ function hasAgentEndStopReason(
273
+ messages: AgentEndEvent["messages"],
274
+ stopReason: "aborted" | "error",
275
+ ): boolean {
276
+ return messages.some((message) =>
277
+ typeof message === "object"
278
+ && message !== null
279
+ && (message as { role?: unknown }).role === "assistant"
280
+ && (message as { stopReason?: unknown }).stopReason === stopReason,
281
+ );
282
+ }
283
+
284
+ /**
285
+ * @brief Classifies one agent-end payload into the canonical pi-notify outcome.
286
+ * @details Treats assistant `stopReason=error` as `err`, `stopReason=aborted` as `esc`, and every remaining terminal state as successful `end`. Runtime is O(m) in message count. No external state is mutated.
287
+ * @param[in] event {Pick<AgentEndEvent, "messages">} Agent-end payload subset.
288
+ * @return {PiNotifyOutcome} Canonical prompt-end outcome.
289
+ * @satisfies REQ-129
290
+ */
291
+ export function classifyPiNotifyOutcome(event: Pick<AgentEndEvent, "messages">): PiNotifyOutcome {
292
+ if (hasAgentEndStopReason(event.messages, "error")) {
293
+ return "err";
294
+ }
295
+ if (hasAgentEndStopReason(event.messages, "aborted")) {
296
+ return "esc";
297
+ }
298
+ return "end";
299
+ }
300
+
301
+ /**
302
+ * @brief Builds the user-visible prompt-end notification title.
303
+ * @details Keeps a stable `pi-usereq` title across outcomes so terminal notification stacks remain easy to correlate with this extension. Runtime is O(1). No external state is mutated.
304
+ * @return {string} Notification title.
305
+ */
306
+ function buildPiNotifyTitle(): string {
307
+ return "pi-usereq";
308
+ }
309
+
310
+ /**
311
+ * @brief Builds the user-visible prompt-end notification body.
312
+ * @details Maps each canonical outcome to one deterministic English phrase so downstream tests and users can distinguish success, abort, and error notifications. Runtime is O(1). No external state is mutated.
313
+ * @param[in] outcome {PiNotifyOutcome} Canonical prompt-end outcome.
314
+ * @return {string} Notification body.
315
+ */
316
+ function buildPiNotifyBody(outcome: PiNotifyOutcome): string {
317
+ switch (outcome) {
318
+ case "err":
319
+ return "Prompt ended with error";
320
+ case "esc":
321
+ return "Prompt aborted by escape";
322
+ default:
323
+ return "Prompt completed";
324
+ }
325
+ }
326
+
327
+ /**
328
+ * @brief Quotes one installation path for shell substitution.
329
+ * @details Emits POSIX single-quoted literals for `sh -lc` execution and CMD double-quoted literals for `cmd.exe /c` execution so `%%INSTALLATION_PATH%%` substitutions preserve whitespace safely. Runtime is O(n) in path length. No external state is mutated.
330
+ * @param[in] installationPath {string} Absolute extension installation path.
331
+ * @return {string} Shell-quoted installation path fragment.
332
+ */
333
+ function quotePiNotifyInstallPath(installationPath: string): string {
334
+ if (process.platform === "win32") {
335
+ return `"${installationPath.replace(/"/g, '""')}"`;
336
+ }
337
+ return `'${installationPath.replace(/'/g, `'\\''`)}'`;
338
+ }
339
+
340
+ /**
341
+ * @brief Substitutes `%%INSTALLATION_PATH%%` inside one sound command.
342
+ * @details Replaces every `%%INSTALLATION_PATH%%` token with a shell-quoted runtime installation path so bundled sound assets can be addressed safely from external commands. Runtime is O(n) in command length. No external state is mutated.
343
+ * @param[in] command {string} Raw configured sound command.
344
+ * @param[in] installationPath {string} Absolute extension installation path.
345
+ * @return {string} Runtime-ready command string.
346
+ * @satisfies REQ-133
347
+ */
348
+ export function substitutePiNotifyInstallPath(command: string, installationPath: string): string {
349
+ return command.replaceAll("%%INSTALLATION_PATH%%", quotePiNotifyInstallPath(installationPath));
350
+ }
351
+
352
+ /**
353
+ * @brief Resolves the configured command for one non-`none` sound level.
354
+ * @details Selects the matching persisted command string from config without performing runtime substitution or shell execution. Runtime is O(1). No external state is mutated.
355
+ * @param[in] config {PiNotifyConfigFields} Effective notification configuration.
356
+ * @param[in] soundLevel {Exclude<PiNotifySoundLevel, "none">} Non-disabled sound level.
357
+ * @return {string} Configured command string for the requested level.
358
+ */
359
+ function resolvePiNotifySoundCommand(
360
+ config: PiNotifyConfigFields,
361
+ soundLevel: Exclude<PiNotifySoundLevel, "none">,
362
+ ): string {
363
+ switch (soundLevel) {
364
+ case "low":
365
+ return config.PI_NOTIFY_SOUND_LOW_CMD;
366
+ case "mid":
367
+ return config.PI_NOTIFY_SOUND_MID_CMD;
368
+ case "high":
369
+ return config.PI_NOTIFY_SOUND_HIGH_CMD;
370
+ }
371
+ }
372
+
373
+ /**
374
+ * @brief Executes the configured successful-run sound command on an external shell.
375
+ * @details Resolves the runtime installation path, substitutes `%%INSTALLATION_PATH%%`, spawns the configured shell command without waiting, and ignores transport failures so prompt-end handling remains non-blocking. Runtime is dominated by process spawn. Side effects include detached child-process execution.
376
+ * @param[in] config {PiNotifyConfigFields} Effective notification configuration.
377
+ * @param[in] soundLevel {Exclude<PiNotifySoundLevel, "none">} Requested non-disabled sound level.
378
+ * @return {void} No return value.
379
+ * @satisfies REQ-132, REQ-133
380
+ */
381
+ export function runPiNotifySoundCommand(
382
+ config: PiNotifyConfigFields,
383
+ soundLevel: Exclude<PiNotifySoundLevel, "none">,
384
+ ): void {
385
+ const rawCommand = resolvePiNotifySoundCommand(config, soundLevel);
386
+ const command = substitutePiNotifyInstallPath(rawCommand, getInstallationPath());
387
+ const shell = process.platform === "win32" ? "cmd.exe" : (process.env.SHELL ?? "sh");
388
+ const shellArgs = process.platform === "win32"
389
+ ? ["/d", "/s", "/c", command]
390
+ : ["-lc", command];
391
+ const child = spawn(shell, shellArgs, {
392
+ detached: true,
393
+ stdio: "ignore",
394
+ });
395
+ child.once("error", () => undefined);
396
+ child.unref();
397
+ }
398
+
399
+ /**
400
+ * @brief Dispatches prompt-end beep and sound effects for one agent-end payload.
401
+ * @details Classifies the terminal outcome, emits the configured terminal notification only for the enabled outcome flag, and executes the configured external sound command only for successful completion with a non-disabled sound level. Runtime is O(m + c) in message count plus command length. Side effects include stdout writes and child-process spawning.
402
+ * @param[in] config {PiNotifyConfigFields} Effective notification configuration.
403
+ * @param[in] event {Pick<AgentEndEvent, "messages">} Agent-end payload subset.
404
+ * @return {void} No return value.
405
+ * @satisfies REQ-129, REQ-130, REQ-131, REQ-132, REQ-133
406
+ */
407
+ export function runPiNotifyEffects(
408
+ config: PiNotifyConfigFields,
409
+ event: Pick<AgentEndEvent, "messages">,
410
+ ): void {
411
+ const outcome = classifyPiNotifyOutcome(event);
412
+ const shouldNotify = (
413
+ (outcome === "end" && config["notify-beep-on-end"])
414
+ || (outcome === "esc" && config["notify-beep-on-esc"])
415
+ || (outcome === "err" && config["notify-beep-on-error"])
416
+ );
417
+ if (shouldNotify) {
418
+ notifyPiTerminal(buildPiNotifyTitle(), buildPiNotifyBody(outcome));
419
+ }
420
+ if (outcome !== "end") {
421
+ return;
422
+ }
423
+ if (config["notify-sound"] === "none") {
424
+ return;
425
+ }
426
+ runPiNotifySoundCommand(
427
+ config,
428
+ config["notify-sound"] as Exclude<PiNotifySoundLevel, "none">,
429
+ );
430
+ }
@@ -0,0 +1,140 @@
1
+ /**
2
+ * @file
3
+ * @brief Declares the configurable pi-usereq active-tool inventory.
4
+ * @details Provides canonical custom-tool names, supported embedded-tool names, default enablement subsets, and normalization helpers shared by configuration loading, extension startup, and test doubles. The module is side-effect free. Lookup and normalization costs are linear in configured tool count.
5
+ */
6
+
7
+ /**
8
+ * @brief Enumerates extension-owned tools configurable through `enabled-tools`.
9
+ * @details The tuple is the single source of truth for custom tool-name validation, default enablement, and active-tool filtering. Membership checks become O(1) when projected through `PI_USEREQ_CUSTOM_TOOL_SET`.
10
+ */
11
+ export const PI_USEREQ_CUSTOM_TOOL_NAMES = [
12
+ "git-path",
13
+ "get-base-path",
14
+ "files-tokens",
15
+ "files-references",
16
+ "files-compress",
17
+ "files-find",
18
+ "references",
19
+ "compress",
20
+ "find",
21
+ "tokens",
22
+ "files-static-check",
23
+ "static-check",
24
+ "git-check",
25
+ "docs-check",
26
+ "git-wt-name",
27
+ "git-wt-create",
28
+ "git-wt-delete",
29
+ ] as const;
30
+
31
+ /**
32
+ * @brief Enumerates supported embedded pi CLI tools configurable through `enabled-tools`.
33
+ * @details The tuple is constrained to embedded tools that remain independently addressable alongside the extension-owned tool inventory. Membership checks become O(1) when projected through `PI_USEREQ_EMBEDDED_TOOL_SET`.
34
+ * @satisfies REQ-063
35
+ */
36
+ export const PI_USEREQ_EMBEDDED_TOOL_NAMES = ["read", "bash", "edit", "write", "grep", "ls"] as const;
37
+
38
+ /**
39
+ * @brief Enumerates embedded pi CLI tools enabled by default.
40
+ * @details Restricts default embedded-tool activation to the read/write shell quartet required by the updated project configuration behavior. Access complexity is O(1).
41
+ * @satisfies REQ-064
42
+ */
43
+ export const PI_USEREQ_DEFAULT_EMBEDDED_TOOL_NAMES = ["read", "bash", "edit", "write"] as const;
44
+
45
+ /**
46
+ * @brief Enumerates every configurable active-tool name.
47
+ * @details Concatenates extension-owned and supported embedded-tool names into one ordered tuple consumed by configuration normalization and runtime filtering. Access complexity is O(1).
48
+ */
49
+ export const PI_USEREQ_STARTUP_TOOL_NAMES = [
50
+ ...PI_USEREQ_CUSTOM_TOOL_NAMES,
51
+ ...PI_USEREQ_EMBEDDED_TOOL_NAMES,
52
+ ] as const;
53
+
54
+ /**
55
+ * @brief Enumerates the default enabled-tool configuration.
56
+ * @details Enables every extension-owned tool except `find` plus the supported embedded read/write shell quartet while leaving discovery helpers `find`, `grep`, and `ls` disabled until explicitly configured. Access complexity is O(1).
57
+ * @satisfies REQ-064
58
+ */
59
+ export const PI_USEREQ_DEFAULT_ENABLED_TOOL_NAMES = [
60
+ "git-path",
61
+ "get-base-path",
62
+ "files-tokens",
63
+ "files-references",
64
+ "files-compress",
65
+ "files-find",
66
+ "references",
67
+ "compress",
68
+ "tokens",
69
+ "files-static-check",
70
+ "static-check",
71
+ "git-check",
72
+ "docs-check",
73
+ "git-wt-name",
74
+ "git-wt-create",
75
+ "git-wt-delete",
76
+ ...PI_USEREQ_DEFAULT_EMBEDDED_TOOL_NAMES,
77
+ ] as const;
78
+
79
+ /**
80
+ * @brief Represents one valid extension-owned configurable tool identifier.
81
+ * @details Narrows arbitrary strings to the literal union derived from `PI_USEREQ_CUSTOM_TOOL_NAMES`. The alias is compile-time only and introduces no runtime cost.
82
+ */
83
+ export type PiUsereqCustomToolName = (typeof PI_USEREQ_CUSTOM_TOOL_NAMES)[number];
84
+
85
+ /**
86
+ * @brief Represents one valid embedded configurable tool identifier.
87
+ * @details Narrows arbitrary strings to the literal union derived from `PI_USEREQ_EMBEDDED_TOOL_NAMES`. The alias is compile-time only and introduces no runtime cost.
88
+ */
89
+ export type PiUsereqEmbeddedToolName = (typeof PI_USEREQ_EMBEDDED_TOOL_NAMES)[number];
90
+
91
+ /**
92
+ * @brief Represents one valid configurable active-tool identifier.
93
+ * @details Narrows arbitrary strings to the literal union derived from `PI_USEREQ_STARTUP_TOOL_NAMES`. The alias is compile-time only and introduces no runtime cost.
94
+ */
95
+ export type PiUsereqStartupToolName = (typeof PI_USEREQ_STARTUP_TOOL_NAMES)[number];
96
+
97
+ /**
98
+ * @brief Provides O(1) membership checks for extension-owned configurable tools.
99
+ * @details Materializes the canonical custom-tool tuple as a `Set<string>` so runtime filters can discard non-extension names without repeated linear scans. Construction occurs once at module load.
100
+ */
101
+ export const PI_USEREQ_CUSTOM_TOOL_SET = new Set<string>(PI_USEREQ_CUSTOM_TOOL_NAMES);
102
+
103
+ /**
104
+ * @brief Provides O(1) membership checks for embedded configurable tools.
105
+ * @details Materializes the supported embedded-tool tuple as a `Set<string>` so runtime filters can discard unsupported builtin names without repeated linear scans. Construction occurs once at module load.
106
+ */
107
+ export const PI_USEREQ_EMBEDDED_TOOL_SET = new Set<string>(PI_USEREQ_EMBEDDED_TOOL_NAMES);
108
+
109
+ /**
110
+ * @brief Provides O(1) membership checks for all configurable active tools.
111
+ * @details Materializes the canonical configurable tuple as a `Set<string>` so config sanitizers can discard unknown tool names without repeated linear scans. Construction occurs once at module load.
112
+ */
113
+ export const PI_USEREQ_STARTUP_TOOL_SET = new Set<string>(PI_USEREQ_STARTUP_TOOL_NAMES);
114
+
115
+ /**
116
+ * @brief Tests whether one tool name belongs to the supported embedded-tool subset.
117
+ * @details Performs one set-membership probe against `PI_USEREQ_EMBEDDED_TOOL_SET`. Runtime is O(1). No external state is mutated.
118
+ * @param[in] name {string} Candidate tool name.
119
+ * @return {boolean} `true` when the name belongs to the embedded configurable-tool subset.
120
+ */
121
+ export function isPiUsereqEmbeddedToolName(name: string): name is PiUsereqEmbeddedToolName {
122
+ return PI_USEREQ_EMBEDDED_TOOL_SET.has(name);
123
+ }
124
+
125
+ /**
126
+ * @brief Normalizes a user-configured active-tool list.
127
+ * @details Returns the default enabled-tool tuple when the input is not an array. Otherwise filters to string entries, removes names outside the configurable tool set, and deduplicates while preserving first-seen order. Time complexity is O(n). No external state is mutated.
128
+ * @param[in] value {unknown} Raw configuration payload for `enabled-tools`.
129
+ * @return {PiUsereqStartupToolName[]} Deduplicated canonical tool names.
130
+ * @post Returned values are members of `PI_USEREQ_STARTUP_TOOL_NAMES` only.
131
+ * @satisfies REQ-064
132
+ */
133
+ export function normalizeEnabledPiUsereqTools(value: unknown): PiUsereqStartupToolName[] {
134
+ if (!Array.isArray(value)) {
135
+ return [...PI_USEREQ_DEFAULT_ENABLED_TOOL_NAMES];
136
+ }
137
+ const configured = value.filter((item): item is string => typeof item === "string");
138
+ const enabled = configured.filter((name): name is PiUsereqStartupToolName => PI_USEREQ_STARTUP_TOOL_SET.has(name));
139
+ return [...new Set(enabled)];
140
+ }