gsd-pi 2.18.0 → 2.20.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 (289) hide show
  1. package/README.md +5 -1
  2. package/dist/cli.js +3 -3
  3. package/dist/onboarding.d.ts +3 -1
  4. package/dist/onboarding.js +77 -3
  5. package/dist/remote-questions-config.d.ts +1 -1
  6. package/dist/resources/extensions/google-search/index.ts +164 -47
  7. package/dist/resources/extensions/gsd/auto-dashboard.ts +14 -2
  8. package/dist/resources/extensions/gsd/auto-prompts.ts +148 -39
  9. package/dist/resources/extensions/gsd/auto-worktree.ts +93 -9
  10. package/dist/resources/extensions/gsd/auto.ts +690 -39
  11. package/dist/resources/extensions/gsd/captures.ts +384 -0
  12. package/dist/resources/extensions/gsd/commands.ts +654 -36
  13. package/dist/resources/extensions/gsd/complexity-classifier.ts +322 -0
  14. package/dist/resources/extensions/gsd/context-budget.ts +243 -0
  15. package/dist/resources/extensions/gsd/context-store.ts +195 -0
  16. package/dist/resources/extensions/gsd/dashboard-overlay.ts +51 -3
  17. package/dist/resources/extensions/gsd/db-writer.ts +341 -0
  18. package/dist/resources/extensions/gsd/debug-logger.ts +178 -0
  19. package/dist/resources/extensions/gsd/dispatch-guard.ts +0 -1
  20. package/dist/resources/extensions/gsd/docs/preferences-reference.md +54 -0
  21. package/dist/resources/extensions/gsd/doctor-proactive.ts +286 -0
  22. package/dist/resources/extensions/gsd/doctor.ts +283 -2
  23. package/dist/resources/extensions/gsd/export.ts +81 -2
  24. package/dist/resources/extensions/gsd/files.ts +39 -9
  25. package/dist/resources/extensions/gsd/git-service.ts +6 -0
  26. package/dist/resources/extensions/gsd/gsd-db.ts +752 -0
  27. package/dist/resources/extensions/gsd/guided-flow.ts +26 -1
  28. package/dist/resources/extensions/gsd/history.ts +0 -1
  29. package/dist/resources/extensions/gsd/index.ts +277 -1
  30. package/dist/resources/extensions/gsd/md-importer.ts +526 -0
  31. package/dist/resources/extensions/gsd/metrics.ts +84 -0
  32. package/dist/resources/extensions/gsd/model-cost-table.ts +65 -0
  33. package/dist/resources/extensions/gsd/model-router.ts +256 -0
  34. package/dist/resources/extensions/gsd/notifications.ts +0 -1
  35. package/dist/resources/extensions/gsd/post-unit-hooks.ts +72 -2
  36. package/dist/resources/extensions/gsd/preferences.ts +198 -150
  37. package/dist/resources/extensions/gsd/prompt-loader.ts +45 -9
  38. package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -5
  39. package/dist/resources/extensions/gsd/prompts/heal-skill.md +45 -0
  40. package/dist/resources/extensions/gsd/prompts/plan-slice.md +5 -1
  41. package/dist/resources/extensions/gsd/prompts/quick-task.md +48 -0
  42. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +6 -0
  43. package/dist/resources/extensions/gsd/prompts/replan-slice.md +8 -0
  44. package/dist/resources/extensions/gsd/prompts/system.md +2 -1
  45. package/dist/resources/extensions/gsd/prompts/triage-captures.md +62 -0
  46. package/dist/resources/extensions/gsd/quick.ts +156 -0
  47. package/dist/resources/extensions/gsd/skill-discovery.ts +5 -3
  48. package/dist/resources/extensions/gsd/skill-health.ts +417 -0
  49. package/dist/resources/extensions/gsd/skill-telemetry.ts +127 -0
  50. package/dist/resources/extensions/gsd/state.ts +30 -0
  51. package/dist/resources/extensions/gsd/templates/preferences.md +1 -0
  52. package/dist/resources/extensions/gsd/tests/captures.test.ts +438 -0
  53. package/dist/resources/extensions/gsd/tests/complexity-classifier.test.ts +181 -0
  54. package/dist/resources/extensions/gsd/tests/context-budget.test.ts +283 -0
  55. package/dist/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
  56. package/dist/resources/extensions/gsd/tests/context-store.test.ts +462 -0
  57. package/dist/resources/extensions/gsd/tests/continue-here.test.ts +204 -0
  58. package/dist/resources/extensions/gsd/tests/dashboard-budget.test.ts +346 -0
  59. package/dist/resources/extensions/gsd/tests/db-writer.test.ts +602 -0
  60. package/dist/resources/extensions/gsd/tests/debug-logger.test.ts +185 -0
  61. package/dist/resources/extensions/gsd/tests/derive-state-db.test.ts +406 -0
  62. package/dist/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -1
  63. package/dist/resources/extensions/gsd/tests/dist-redirect.mjs +22 -0
  64. package/dist/resources/extensions/gsd/tests/doctor-proactive.test.ts +244 -0
  65. package/dist/resources/extensions/gsd/tests/doctor-runtime.test.ts +303 -0
  66. package/dist/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +434 -0
  67. package/dist/resources/extensions/gsd/tests/gsd-db.test.ts +353 -0
  68. package/dist/resources/extensions/gsd/tests/gsd-inspect.test.ts +125 -0
  69. package/dist/resources/extensions/gsd/tests/gsd-tools.test.ts +326 -0
  70. package/dist/resources/extensions/gsd/tests/integration-edge.test.ts +228 -0
  71. package/dist/resources/extensions/gsd/tests/integration-lifecycle.test.ts +277 -0
  72. package/dist/resources/extensions/gsd/tests/md-importer.test.ts +411 -0
  73. package/dist/resources/extensions/gsd/tests/metrics.test.ts +197 -0
  74. package/dist/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts +144 -0
  75. package/dist/resources/extensions/gsd/tests/model-cost-table.test.ts +69 -0
  76. package/dist/resources/extensions/gsd/tests/model-isolation.test.ts +99 -0
  77. package/dist/resources/extensions/gsd/tests/model-router.test.ts +167 -0
  78. package/dist/resources/extensions/gsd/tests/parsers.test.ts +40 -0
  79. package/dist/resources/extensions/gsd/tests/post-unit-hooks.test.ts +41 -1
  80. package/dist/resources/extensions/gsd/tests/preferences-git.test.ts +0 -1
  81. package/dist/resources/extensions/gsd/tests/preferences-hooks.test.ts +0 -1
  82. package/dist/resources/extensions/gsd/tests/preferences-mode.test.ts +110 -0
  83. package/dist/resources/extensions/gsd/tests/preferences-models.test.ts +0 -1
  84. package/dist/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +464 -0
  85. package/dist/resources/extensions/gsd/tests/prompt-db.test.ts +385 -0
  86. package/dist/resources/extensions/gsd/tests/remote-questions.test.ts +488 -1
  87. package/dist/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +17 -29
  88. package/dist/resources/extensions/gsd/tests/resolve-ts.mjs +2 -8
  89. package/dist/resources/extensions/gsd/tests/routing-history.test.ts +215 -62
  90. package/dist/resources/extensions/gsd/tests/skill-lifecycle.test.ts +126 -0
  91. package/dist/resources/extensions/gsd/tests/stop-auto-remote.test.ts +31 -8
  92. package/dist/resources/extensions/gsd/tests/token-savings.test.ts +366 -0
  93. package/dist/resources/extensions/gsd/tests/triage-dispatch.test.ts +224 -0
  94. package/dist/resources/extensions/gsd/tests/triage-resolution.test.ts +215 -0
  95. package/dist/resources/extensions/gsd/tests/unit-runtime.test.ts +25 -1
  96. package/dist/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +145 -0
  97. package/dist/resources/extensions/gsd/tests/visualizer-data.test.ts +290 -0
  98. package/dist/resources/extensions/gsd/tests/visualizer-overlay.test.ts +120 -0
  99. package/dist/resources/extensions/gsd/tests/visualizer-views.test.ts +478 -0
  100. package/dist/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
  101. package/dist/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
  102. package/dist/resources/extensions/gsd/tests/worktree-post-create-hook.test.ts +165 -0
  103. package/dist/resources/extensions/gsd/triage-resolution.ts +200 -0
  104. package/dist/resources/extensions/gsd/triage-ui.ts +175 -0
  105. package/dist/resources/extensions/gsd/types.ts +29 -0
  106. package/dist/resources/extensions/gsd/undo.ts +0 -1
  107. package/dist/resources/extensions/gsd/unit-runtime.ts +5 -1
  108. package/dist/resources/extensions/gsd/visualizer-data.ts +505 -0
  109. package/dist/resources/extensions/gsd/visualizer-overlay.ts +337 -0
  110. package/dist/resources/extensions/gsd/visualizer-views.ts +755 -0
  111. package/dist/resources/extensions/gsd/worktree-command.ts +18 -0
  112. package/dist/resources/extensions/gsd/worktree-manager.ts +11 -4
  113. package/dist/resources/extensions/remote-questions/config.ts +4 -2
  114. package/dist/resources/extensions/remote-questions/discord-adapter.ts +35 -4
  115. package/dist/resources/extensions/remote-questions/format.ts +166 -14
  116. package/dist/resources/extensions/remote-questions/manager.ts +14 -4
  117. package/dist/resources/extensions/remote-questions/remote-command.ts +100 -4
  118. package/dist/resources/extensions/remote-questions/slack-adapter.ts +58 -2
  119. package/dist/resources/extensions/remote-questions/telegram-adapter.ts +161 -0
  120. package/dist/resources/extensions/remote-questions/types.ts +2 -1
  121. package/dist/resources/extensions/ttsr/ttsr-manager.ts +26 -0
  122. package/dist/resources/extensions/voice/index.ts +4 -3
  123. package/package.json +1 -1
  124. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  125. package/packages/pi-coding-agent/dist/core/agent-session.js +12 -1
  126. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  127. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  128. package/packages/pi-coding-agent/dist/core/extensions/loader.js +5 -0
  129. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  130. package/packages/pi-coding-agent/dist/core/lsp/client.d.ts +6 -0
  131. package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
  132. package/packages/pi-coding-agent/dist/core/lsp/client.js +25 -0
  133. package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
  134. package/packages/pi-coding-agent/dist/core/lsp/index.d.ts +2 -0
  135. package/packages/pi-coding-agent/dist/core/lsp/index.d.ts.map +1 -1
  136. package/packages/pi-coding-agent/dist/core/lsp/index.js +106 -3
  137. package/packages/pi-coding-agent/dist/core/lsp/index.js.map +1 -1
  138. package/packages/pi-coding-agent/dist/core/lsp/lsp.md +6 -0
  139. package/packages/pi-coding-agent/dist/core/lsp/types.d.ts +35 -0
  140. package/packages/pi-coding-agent/dist/core/lsp/types.d.ts.map +1 -1
  141. package/packages/pi-coding-agent/dist/core/lsp/types.js +6 -0
  142. package/packages/pi-coding-agent/dist/core/lsp/types.js.map +1 -1
  143. package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts +3 -1
  144. package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts.map +1 -1
  145. package/packages/pi-coding-agent/dist/core/lsp/utils.js +45 -0
  146. package/packages/pi-coding-agent/dist/core/lsp/utils.js.map +1 -1
  147. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +6 -0
  148. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  149. package/packages/pi-coding-agent/dist/core/settings-manager.js +43 -11
  150. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  151. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  152. package/packages/pi-coding-agent/dist/core/system-prompt.js +7 -1
  153. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  154. package/packages/pi-coding-agent/dist/core/tools/edit.d.ts.map +1 -1
  155. package/packages/pi-coding-agent/dist/core/tools/edit.js +5 -0
  156. package/packages/pi-coding-agent/dist/core/tools/edit.js.map +1 -1
  157. package/packages/pi-coding-agent/dist/core/tools/index.d.ts +2 -0
  158. package/packages/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
  159. package/packages/pi-coding-agent/dist/core/tools/write.d.ts.map +1 -1
  160. package/packages/pi-coding-agent/dist/core/tools/write.js +5 -0
  161. package/packages/pi-coding-agent/dist/core/tools/write.js.map +1 -1
  162. package/packages/pi-coding-agent/src/core/agent-session.ts +13 -1
  163. package/packages/pi-coding-agent/src/core/extensions/loader.ts +6 -0
  164. package/packages/pi-coding-agent/src/core/lsp/client.ts +26 -0
  165. package/packages/pi-coding-agent/src/core/lsp/index.ts +157 -2
  166. package/packages/pi-coding-agent/src/core/lsp/lsp.md +6 -0
  167. package/packages/pi-coding-agent/src/core/lsp/types.ts +53 -0
  168. package/packages/pi-coding-agent/src/core/lsp/utils.ts +56 -0
  169. package/packages/pi-coding-agent/src/core/settings-manager.ts +41 -11
  170. package/packages/pi-coding-agent/src/core/system-prompt.ts +7 -1
  171. package/packages/pi-coding-agent/src/core/tools/edit.ts +3 -0
  172. package/packages/pi-coding-agent/src/core/tools/write.ts +3 -0
  173. package/src/resources/extensions/google-search/index.ts +164 -47
  174. package/src/resources/extensions/gsd/auto-dashboard.ts +14 -2
  175. package/src/resources/extensions/gsd/auto-prompts.ts +148 -39
  176. package/src/resources/extensions/gsd/auto-worktree.ts +93 -9
  177. package/src/resources/extensions/gsd/auto.ts +690 -39
  178. package/src/resources/extensions/gsd/captures.ts +384 -0
  179. package/src/resources/extensions/gsd/commands.ts +654 -36
  180. package/src/resources/extensions/gsd/complexity-classifier.ts +322 -0
  181. package/src/resources/extensions/gsd/context-budget.ts +243 -0
  182. package/src/resources/extensions/gsd/context-store.ts +195 -0
  183. package/src/resources/extensions/gsd/dashboard-overlay.ts +51 -3
  184. package/src/resources/extensions/gsd/db-writer.ts +341 -0
  185. package/src/resources/extensions/gsd/debug-logger.ts +178 -0
  186. package/src/resources/extensions/gsd/dispatch-guard.ts +0 -1
  187. package/src/resources/extensions/gsd/docs/preferences-reference.md +54 -0
  188. package/src/resources/extensions/gsd/doctor-proactive.ts +286 -0
  189. package/src/resources/extensions/gsd/doctor.ts +283 -2
  190. package/src/resources/extensions/gsd/export.ts +81 -2
  191. package/src/resources/extensions/gsd/files.ts +39 -9
  192. package/src/resources/extensions/gsd/git-service.ts +6 -0
  193. package/src/resources/extensions/gsd/gsd-db.ts +752 -0
  194. package/src/resources/extensions/gsd/guided-flow.ts +26 -1
  195. package/src/resources/extensions/gsd/history.ts +0 -1
  196. package/src/resources/extensions/gsd/index.ts +277 -1
  197. package/src/resources/extensions/gsd/md-importer.ts +526 -0
  198. package/src/resources/extensions/gsd/metrics.ts +84 -0
  199. package/src/resources/extensions/gsd/model-cost-table.ts +65 -0
  200. package/src/resources/extensions/gsd/model-router.ts +256 -0
  201. package/src/resources/extensions/gsd/notifications.ts +0 -1
  202. package/src/resources/extensions/gsd/post-unit-hooks.ts +72 -2
  203. package/src/resources/extensions/gsd/preferences.ts +198 -150
  204. package/src/resources/extensions/gsd/prompt-loader.ts +45 -9
  205. package/src/resources/extensions/gsd/prompts/execute-task.md +3 -5
  206. package/src/resources/extensions/gsd/prompts/heal-skill.md +45 -0
  207. package/src/resources/extensions/gsd/prompts/plan-slice.md +5 -1
  208. package/src/resources/extensions/gsd/prompts/quick-task.md +48 -0
  209. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +6 -0
  210. package/src/resources/extensions/gsd/prompts/replan-slice.md +8 -0
  211. package/src/resources/extensions/gsd/prompts/system.md +2 -1
  212. package/src/resources/extensions/gsd/prompts/triage-captures.md +62 -0
  213. package/src/resources/extensions/gsd/quick.ts +156 -0
  214. package/src/resources/extensions/gsd/skill-discovery.ts +5 -3
  215. package/src/resources/extensions/gsd/skill-health.ts +417 -0
  216. package/src/resources/extensions/gsd/skill-telemetry.ts +127 -0
  217. package/src/resources/extensions/gsd/state.ts +30 -0
  218. package/src/resources/extensions/gsd/templates/preferences.md +1 -0
  219. package/src/resources/extensions/gsd/tests/captures.test.ts +438 -0
  220. package/src/resources/extensions/gsd/tests/complexity-classifier.test.ts +181 -0
  221. package/src/resources/extensions/gsd/tests/context-budget.test.ts +283 -0
  222. package/src/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
  223. package/src/resources/extensions/gsd/tests/context-store.test.ts +462 -0
  224. package/src/resources/extensions/gsd/tests/continue-here.test.ts +204 -0
  225. package/src/resources/extensions/gsd/tests/dashboard-budget.test.ts +346 -0
  226. package/src/resources/extensions/gsd/tests/db-writer.test.ts +602 -0
  227. package/src/resources/extensions/gsd/tests/debug-logger.test.ts +185 -0
  228. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +406 -0
  229. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -1
  230. package/src/resources/extensions/gsd/tests/dist-redirect.mjs +22 -0
  231. package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +244 -0
  232. package/src/resources/extensions/gsd/tests/doctor-runtime.test.ts +303 -0
  233. package/src/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +434 -0
  234. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +353 -0
  235. package/src/resources/extensions/gsd/tests/gsd-inspect.test.ts +125 -0
  236. package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +326 -0
  237. package/src/resources/extensions/gsd/tests/integration-edge.test.ts +228 -0
  238. package/src/resources/extensions/gsd/tests/integration-lifecycle.test.ts +277 -0
  239. package/src/resources/extensions/gsd/tests/md-importer.test.ts +411 -0
  240. package/src/resources/extensions/gsd/tests/metrics.test.ts +197 -0
  241. package/src/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts +144 -0
  242. package/src/resources/extensions/gsd/tests/model-cost-table.test.ts +69 -0
  243. package/src/resources/extensions/gsd/tests/model-isolation.test.ts +99 -0
  244. package/src/resources/extensions/gsd/tests/model-router.test.ts +167 -0
  245. package/src/resources/extensions/gsd/tests/parsers.test.ts +40 -0
  246. package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +41 -1
  247. package/src/resources/extensions/gsd/tests/preferences-git.test.ts +0 -1
  248. package/src/resources/extensions/gsd/tests/preferences-hooks.test.ts +0 -1
  249. package/src/resources/extensions/gsd/tests/preferences-mode.test.ts +110 -0
  250. package/src/resources/extensions/gsd/tests/preferences-models.test.ts +0 -1
  251. package/src/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +464 -0
  252. package/src/resources/extensions/gsd/tests/prompt-db.test.ts +385 -0
  253. package/src/resources/extensions/gsd/tests/remote-questions.test.ts +488 -1
  254. package/src/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +17 -29
  255. package/src/resources/extensions/gsd/tests/resolve-ts.mjs +2 -8
  256. package/src/resources/extensions/gsd/tests/routing-history.test.ts +215 -62
  257. package/src/resources/extensions/gsd/tests/skill-lifecycle.test.ts +126 -0
  258. package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +31 -8
  259. package/src/resources/extensions/gsd/tests/token-savings.test.ts +366 -0
  260. package/src/resources/extensions/gsd/tests/triage-dispatch.test.ts +224 -0
  261. package/src/resources/extensions/gsd/tests/triage-resolution.test.ts +215 -0
  262. package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +25 -1
  263. package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +145 -0
  264. package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +290 -0
  265. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +120 -0
  266. package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +478 -0
  267. package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
  268. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
  269. package/src/resources/extensions/gsd/tests/worktree-post-create-hook.test.ts +165 -0
  270. package/src/resources/extensions/gsd/triage-resolution.ts +200 -0
  271. package/src/resources/extensions/gsd/triage-ui.ts +175 -0
  272. package/src/resources/extensions/gsd/types.ts +29 -0
  273. package/src/resources/extensions/gsd/undo.ts +0 -1
  274. package/src/resources/extensions/gsd/unit-runtime.ts +5 -1
  275. package/src/resources/extensions/gsd/visualizer-data.ts +505 -0
  276. package/src/resources/extensions/gsd/visualizer-overlay.ts +337 -0
  277. package/src/resources/extensions/gsd/visualizer-views.ts +755 -0
  278. package/src/resources/extensions/gsd/worktree-command.ts +18 -0
  279. package/src/resources/extensions/gsd/worktree-manager.ts +11 -4
  280. package/src/resources/extensions/remote-questions/config.ts +4 -2
  281. package/src/resources/extensions/remote-questions/discord-adapter.ts +35 -4
  282. package/src/resources/extensions/remote-questions/format.ts +166 -14
  283. package/src/resources/extensions/remote-questions/manager.ts +14 -4
  284. package/src/resources/extensions/remote-questions/remote-command.ts +100 -4
  285. package/src/resources/extensions/remote-questions/slack-adapter.ts +58 -2
  286. package/src/resources/extensions/remote-questions/telegram-adapter.ts +161 -0
  287. package/src/resources/extensions/remote-questions/types.ts +2 -1
  288. package/src/resources/extensions/ttsr/ttsr-manager.ts +26 -0
  289. package/src/resources/extensions/voice/index.ts +4 -3
@@ -1,9 +1,15 @@
1
1
  import test from "node:test";
2
2
  import assert from "node:assert/strict";
3
- import { parseSlackReply, parseDiscordResponse } from "../../remote-questions/format.ts";
3
+ import { readFileSync } from "node:fs";
4
+ import { join, dirname } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { parseSlackReply, parseDiscordResponse, formatForDiscord, formatForSlack, parseSlackReactionResponse, formatForTelegram, parseTelegramResponse } from "../../remote-questions/format.ts";
4
7
  import { resolveRemoteConfig, isValidChannelId } from "../../remote-questions/config.ts";
5
8
  import { sanitizeError } from "../../remote-questions/manager.ts";
6
9
 
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = dirname(__filename);
12
+
7
13
  test("parseSlackReply handles single-number single-question answers", () => {
8
14
  const result = parseSlackReply("2", [{
9
15
  id: "choice",
@@ -88,6 +94,21 @@ test("parseDiscordResponse rejects multi-question reaction parsing", () => {
88
94
  assert.match(String(result.answers.second.user_note), /single-question prompts/i);
89
95
  });
90
96
 
97
+ test("parseSlackReactionResponse handles single-question reactions", () => {
98
+ const result = parseSlackReactionResponse(["two"], [{
99
+ id: "choice",
100
+ header: "Choice",
101
+ question: "Pick one",
102
+ allowMultiple: false,
103
+ options: [
104
+ { label: "Alpha", description: "A" },
105
+ { label: "Beta", description: "B" },
106
+ ],
107
+ }]);
108
+
109
+ assert.deepEqual(result, { answers: { choice: { answers: ["Beta"] } } });
110
+ });
111
+
91
112
  test("parseSlackReply truncates user_note longer than 500 chars", () => {
92
113
  const longText = "x".repeat(600);
93
114
  const result = parseSlackReply(longText, [{
@@ -153,3 +174,469 @@ test("sanitizeError preserves short safe messages", () => {
153
174
  assert.equal(sanitizeError("Connection refused"), "Connection refused");
154
175
  });
155
176
 
177
+
178
+ // ═══════════════════════════════════════════════════════════════════════════
179
+ // Discord Parity Tests
180
+ // ═══════════════════════════════════════════════════════════════════════════
181
+
182
+ test("formatForDiscord includes context source in footer when present", () => {
183
+ const prompt = {
184
+ id: "test-1",
185
+ channel: "discord" as const,
186
+ createdAt: Date.now(),
187
+ timeoutAt: Date.now() + 60000,
188
+ pollIntervalMs: 5000,
189
+ context: { source: "auto-mode-dispatch" },
190
+ questions: [{
191
+ id: "q1",
192
+ header: "Confirm",
193
+ question: "Proceed?",
194
+ options: [
195
+ { label: "Yes", description: "Continue" },
196
+ { label: "No", description: "Stop" },
197
+ ],
198
+ allowMultiple: false,
199
+ }],
200
+ };
201
+
202
+ const { embeds } = formatForDiscord(prompt);
203
+ assert.equal(embeds.length, 1);
204
+ assert.ok(embeds[0].footer?.text.includes("auto-mode-dispatch"), "footer should include context source");
205
+ });
206
+
207
+ test("formatForSlack includes context source when present", () => {
208
+ const blocks = formatForSlack({
209
+ id: "slack-1",
210
+ channel: "slack",
211
+ createdAt: Date.now(),
212
+ timeoutAt: Date.now() + 60000,
213
+ pollIntervalMs: 5000,
214
+ context: { source: "ask_user_questions" },
215
+ questions: [{
216
+ id: "q1",
217
+ header: "Confirm",
218
+ question: "Proceed?",
219
+ options: [
220
+ { label: "Yes", description: "Continue" },
221
+ { label: "No", description: "Stop" },
222
+ ],
223
+ allowMultiple: false,
224
+ }],
225
+ });
226
+
227
+ const sourceBlock = blocks.find((block) => block.type === "context" && block.elements?.some((el) => el.text.includes("Source:")));
228
+ assert.ok(sourceBlock, "Slack blocks should include a context source block");
229
+ });
230
+
231
+ test("formatForSlack multi-question prompts explain semicolon and newline reply format", () => {
232
+ const blocks = formatForSlack({
233
+ id: "slack-2",
234
+ channel: "slack",
235
+ createdAt: Date.now(),
236
+ timeoutAt: Date.now() + 60000,
237
+ pollIntervalMs: 5000,
238
+ questions: [
239
+ {
240
+ id: "q1",
241
+ header: "First",
242
+ question: "Pick one",
243
+ options: [
244
+ { label: "Alpha", description: "A" },
245
+ { label: "Beta", description: "B" },
246
+ ],
247
+ allowMultiple: false,
248
+ },
249
+ {
250
+ id: "q2",
251
+ header: "Second",
252
+ question: "Explain",
253
+ options: [
254
+ { label: "Gamma", description: "G" },
255
+ { label: "Delta", description: "D" },
256
+ ],
257
+ allowMultiple: false,
258
+ },
259
+ ],
260
+ });
261
+
262
+ const instructionBlock = blocks.find((block) => block.type === "context" && block.elements?.some((el) => el.text.includes("one line per question")));
263
+ assert.ok(instructionBlock, "Slack multi-question prompts should explain one-line or semicolon reply format");
264
+ });
265
+
266
+ test("formatForDiscord omits source from footer when context is absent", () => {
267
+ const prompt = {
268
+ id: "test-2",
269
+ channel: "discord" as const,
270
+ createdAt: Date.now(),
271
+ timeoutAt: Date.now() + 60000,
272
+ pollIntervalMs: 5000,
273
+ questions: [{
274
+ id: "q1",
275
+ header: "Choice",
276
+ question: "Pick one",
277
+ options: [
278
+ { label: "A", description: "Alpha" },
279
+ { label: "B", description: "Beta" },
280
+ ],
281
+ allowMultiple: false,
282
+ }],
283
+ };
284
+
285
+ const { embeds } = formatForDiscord(prompt);
286
+ assert.ok(!embeds[0].footer?.text.includes("Source:"), "footer should not include Source when context absent");
287
+ });
288
+
289
+ test("formatForDiscord multi-question footer includes question position", () => {
290
+ const prompt = {
291
+ id: "test-3",
292
+ channel: "discord" as const,
293
+ createdAt: Date.now(),
294
+ timeoutAt: Date.now() + 60000,
295
+ pollIntervalMs: 5000,
296
+ questions: [
297
+ {
298
+ id: "q1",
299
+ header: "First",
300
+ question: "Pick",
301
+ options: [{ label: "A", description: "a" }],
302
+ allowMultiple: false,
303
+ },
304
+ {
305
+ id: "q2",
306
+ header: "Second",
307
+ question: "Pick",
308
+ options: [{ label: "B", description: "b" }],
309
+ allowMultiple: false,
310
+ },
311
+ ],
312
+ };
313
+
314
+ const { embeds } = formatForDiscord(prompt);
315
+ assert.equal(embeds.length, 2);
316
+ assert.ok(embeds[0].footer?.text.includes("1/2"), "first embed footer should show 1/2");
317
+ assert.ok(embeds[1].footer?.text.includes("2/2"), "second embed footer should show 2/2");
318
+ });
319
+
320
+ test("formatForDiscord single-question generates reaction emojis", () => {
321
+ const prompt = {
322
+ id: "test-4",
323
+ channel: "discord" as const,
324
+ createdAt: Date.now(),
325
+ timeoutAt: Date.now() + 60000,
326
+ pollIntervalMs: 5000,
327
+ questions: [{
328
+ id: "q1",
329
+ header: "Pick",
330
+ question: "Choose",
331
+ options: [
332
+ { label: "A", description: "a" },
333
+ { label: "B", description: "b" },
334
+ { label: "C", description: "c" },
335
+ ],
336
+ allowMultiple: false,
337
+ }],
338
+ };
339
+
340
+ const { reactionEmojis } = formatForDiscord(prompt);
341
+ assert.equal(reactionEmojis.length, 3, "should generate 3 reaction emojis for 3 options");
342
+ assert.equal(reactionEmojis[0], "1️⃣");
343
+ assert.equal(reactionEmojis[1], "2️⃣");
344
+ assert.equal(reactionEmojis[2], "3️⃣");
345
+ });
346
+
347
+ test("formatForDiscord multi-question generates no reaction emojis", () => {
348
+ const prompt = {
349
+ id: "test-5",
350
+ channel: "discord" as const,
351
+ createdAt: Date.now(),
352
+ timeoutAt: Date.now() + 60000,
353
+ pollIntervalMs: 5000,
354
+ questions: [
355
+ {
356
+ id: "q1",
357
+ header: "First",
358
+ question: "Pick",
359
+ options: [{ label: "A", description: "a" }],
360
+ allowMultiple: false,
361
+ },
362
+ {
363
+ id: "q2",
364
+ header: "Second",
365
+ question: "Pick",
366
+ options: [{ label: "B", description: "b" }],
367
+ allowMultiple: false,
368
+ },
369
+ ],
370
+ };
371
+
372
+ const { reactionEmojis } = formatForDiscord(prompt);
373
+ assert.equal(reactionEmojis.length, 0, "multi-question should not generate reaction emojis");
374
+ });
375
+
376
+ test("parseDiscordResponse handles multi-question text reply via semicolons", () => {
377
+ const result = parseDiscordResponse([], "1;2", [
378
+ {
379
+ id: "first",
380
+ header: "First",
381
+ question: "Pick one",
382
+ allowMultiple: false,
383
+ options: [
384
+ { label: "Alpha", description: "A" },
385
+ { label: "Beta", description: "B" },
386
+ ],
387
+ },
388
+ {
389
+ id: "second",
390
+ header: "Second",
391
+ question: "Pick one",
392
+ allowMultiple: false,
393
+ options: [
394
+ { label: "Gamma", description: "G" },
395
+ { label: "Delta", description: "D" },
396
+ ],
397
+ },
398
+ ]);
399
+
400
+ assert.deepEqual(result.answers.first.answers, ["Alpha"]);
401
+ assert.deepEqual(result.answers.second.answers, ["Delta"]);
402
+ });
403
+
404
+ test("parseDiscordResponse handles multiple reactions for allowMultiple question", () => {
405
+ const result = parseDiscordResponse(
406
+ [{ emoji: "1️⃣", count: 1 }, { emoji: "3️⃣", count: 1 }],
407
+ null,
408
+ [{
409
+ id: "choice",
410
+ header: "Choice",
411
+ question: "Pick any",
412
+ allowMultiple: true,
413
+ options: [
414
+ { label: "Alpha", description: "A" },
415
+ { label: "Beta", description: "B" },
416
+ { label: "Gamma", description: "G" },
417
+ ],
418
+ }],
419
+ );
420
+
421
+ assert.deepEqual(result.answers.choice.answers, ["Alpha", "Gamma"]);
422
+ });
423
+
424
+ test("DiscordAdapter source-level: acknowledgeAnswer method exists", () => {
425
+ const adapterSrc = readFileSync(
426
+ join(__dirname, "..", "..", "remote-questions", "discord-adapter.ts"),
427
+ "utf-8",
428
+ );
429
+ assert.ok(adapterSrc.includes("async acknowledgeAnswer"), "should have acknowledgeAnswer method");
430
+ assert.ok(adapterSrc.includes("✅"), "should use checkmark emoji for acknowledgement");
431
+ });
432
+
433
+ test("SlackAdapter source-level: supports reaction polling and acknowledgement", () => {
434
+ const adapterSrc = readFileSync(
435
+ join(__dirname, "..", "..", "remote-questions", "slack-adapter.ts"),
436
+ "utf-8",
437
+ );
438
+ assert.ok(adapterSrc.includes("reactions.get"), "should poll Slack reactions");
439
+ assert.ok(adapterSrc.includes("reactions.add"), "should add Slack reactions");
440
+ assert.ok(adapterSrc.includes("async acknowledgeAnswer"), "should acknowledge Slack answers");
441
+ assert.ok(adapterSrc.includes("white_check_mark"), "should use a checkmark acknowledgement reaction");
442
+ });
443
+
444
+ test("Slack setup source-level: offers channel picker with manual fallback", () => {
445
+ const commandSrc = readFileSync(
446
+ join(__dirname, "..", "..", "remote-questions", "remote-command.ts"),
447
+ "utf-8",
448
+ );
449
+ assert.ok(commandSrc.includes("users.conversations"), "Slack setup should query Slack channels");
450
+ assert.ok(commandSrc.includes("Select a Slack channel"), "Slack setup should present a channel picker");
451
+ assert.ok(commandSrc.includes("Enter channel ID manually"), "Slack setup should preserve manual fallback");
452
+ });
453
+
454
+ test("DiscordAdapter source-level: resolves guild ID for message URLs", () => {
455
+ const adapterSrc = readFileSync(
456
+ join(__dirname, "..", "..", "remote-questions", "discord-adapter.ts"),
457
+ "utf-8",
458
+ );
459
+ assert.ok(adapterSrc.includes("guildId"), "should track guild ID");
460
+ assert.ok(adapterSrc.includes("guild_id"), "should read guild_id from channel info");
461
+ assert.ok(
462
+ adapterSrc.includes("discord.com/channels/"),
463
+ "should construct message URL with guild/channel/message format",
464
+ );
465
+ });
466
+
467
+ // ═══════════════════════════════════════════════════════════════════════════
468
+ // Telegram Tests
469
+ // ═══════════════════════════════════════════════════════════════════════════
470
+
471
+ test("formatForTelegram single-question produces inline keyboard", () => {
472
+ const prompt = {
473
+ id: "tg-1",
474
+ channel: "telegram" as const,
475
+ createdAt: Date.now(),
476
+ timeoutAt: Date.now() + 60000,
477
+ pollIntervalMs: 5000,
478
+ questions: [{
479
+ id: "q1",
480
+ header: "Confirm",
481
+ question: "Proceed?",
482
+ options: [
483
+ { label: "Yes", description: "Continue" },
484
+ { label: "No", description: "Stop" },
485
+ ],
486
+ allowMultiple: false,
487
+ }],
488
+ };
489
+
490
+ const msg = formatForTelegram(prompt);
491
+ assert.equal(msg.parse_mode, "HTML");
492
+ assert.ok(msg.text.includes("<b>GSD needs your input</b>"));
493
+ assert.ok(msg.text.includes("<b>Confirm</b>"));
494
+ assert.ok(msg.reply_markup, "single-question should have inline keyboard");
495
+ assert.equal(msg.reply_markup!.inline_keyboard.length, 2, "should have 2 button rows");
496
+ assert.equal(msg.reply_markup!.inline_keyboard[0][0].callback_data, "tg-1:0");
497
+ assert.equal(msg.reply_markup!.inline_keyboard[1][0].callback_data, "tg-1:1");
498
+ });
499
+
500
+ test("formatForTelegram multi-question omits inline keyboard", () => {
501
+ const prompt = {
502
+ id: "tg-2",
503
+ channel: "telegram" as const,
504
+ createdAt: Date.now(),
505
+ timeoutAt: Date.now() + 60000,
506
+ pollIntervalMs: 5000,
507
+ questions: [
508
+ {
509
+ id: "q1",
510
+ header: "First",
511
+ question: "Pick",
512
+ options: [{ label: "A", description: "a" }],
513
+ allowMultiple: false,
514
+ },
515
+ {
516
+ id: "q2",
517
+ header: "Second",
518
+ question: "Pick",
519
+ options: [{ label: "B", description: "b" }],
520
+ allowMultiple: false,
521
+ },
522
+ ],
523
+ };
524
+
525
+ const msg = formatForTelegram(prompt);
526
+ assert.equal(msg.reply_markup, undefined, "multi-question should not have inline keyboard");
527
+ assert.ok(msg.text.includes("1/2"), "should show question position");
528
+ assert.ok(msg.text.includes("2/2"), "should show question position");
529
+ });
530
+
531
+ test("formatForTelegram escapes HTML in user content", () => {
532
+ const prompt = {
533
+ id: "tg-3",
534
+ channel: "telegram" as const,
535
+ createdAt: Date.now(),
536
+ timeoutAt: Date.now() + 60000,
537
+ pollIntervalMs: 5000,
538
+ questions: [{
539
+ id: "q1",
540
+ header: "Test <script>",
541
+ question: "Is 5 > 3 & 2 < 4?",
542
+ options: [{ label: "<b>Yes</b>", description: "it's true" }],
543
+ allowMultiple: false,
544
+ }],
545
+ };
546
+
547
+ const msg = formatForTelegram(prompt);
548
+ assert.ok(msg.text.includes("&lt;script&gt;"), "should escape < > in header");
549
+ assert.ok(msg.text.includes("5 &gt; 3 &amp; 2 &lt; 4"), "should escape in question");
550
+ assert.ok(msg.text.includes("&lt;b&gt;Yes&lt;/b&gt;"), "should escape in option label");
551
+ });
552
+
553
+ test("parseTelegramResponse handles callback_data button press", () => {
554
+ const questions = [{
555
+ id: "choice",
556
+ header: "Pick",
557
+ question: "Choose",
558
+ allowMultiple: false,
559
+ options: [
560
+ { label: "Alpha", description: "A" },
561
+ { label: "Beta", description: "B" },
562
+ ],
563
+ }];
564
+
565
+ const result = parseTelegramResponse("prompt-123:1", null, questions, "prompt-123");
566
+ assert.deepEqual(result, { answers: { choice: { answers: ["Beta"] } } });
567
+ });
568
+
569
+ test("parseTelegramResponse handles text reply delegation", () => {
570
+ const questions = [{
571
+ id: "choice",
572
+ header: "Pick",
573
+ question: "Choose",
574
+ allowMultiple: false,
575
+ options: [
576
+ { label: "Alpha", description: "A" },
577
+ { label: "Beta", description: "B" },
578
+ ],
579
+ }];
580
+
581
+ const result = parseTelegramResponse(null, "1", questions, "prompt-123");
582
+ assert.deepEqual(result, { answers: { choice: { answers: ["Alpha"] } } });
583
+ });
584
+
585
+ test("parseTelegramResponse handles multi-question semicolons", () => {
586
+ const questions = [
587
+ {
588
+ id: "first",
589
+ header: "First",
590
+ question: "Pick",
591
+ allowMultiple: false,
592
+ options: [
593
+ { label: "Alpha", description: "A" },
594
+ { label: "Beta", description: "B" },
595
+ ],
596
+ },
597
+ {
598
+ id: "second",
599
+ header: "Second",
600
+ question: "Pick",
601
+ allowMultiple: false,
602
+ options: [
603
+ { label: "Gamma", description: "G" },
604
+ { label: "Delta", description: "D" },
605
+ ],
606
+ },
607
+ ];
608
+
609
+ const result = parseTelegramResponse(null, "2;1", questions, "prompt-123");
610
+ assert.deepEqual(result.answers.first.answers, ["Beta"]);
611
+ assert.deepEqual(result.answers.second.answers, ["Gamma"]);
612
+ });
613
+
614
+ test("isValidChannelId validates Telegram chat IDs", () => {
615
+ // Valid positive ID
616
+ assert.equal(isValidChannelId("telegram", "12345"), true);
617
+ // Valid negative group ID
618
+ assert.equal(isValidChannelId("telegram", "-1001234567890"), true);
619
+ // Too short
620
+ assert.equal(isValidChannelId("telegram", "1234"), false);
621
+ // Non-numeric
622
+ assert.equal(isValidChannelId("telegram", "abc12345"), false);
623
+ // URL injection
624
+ assert.equal(isValidChannelId("telegram", "https://evil.com"), false);
625
+ });
626
+
627
+ test("sanitizeError strips Telegram bot token patterns", () => {
628
+ const fakeToken = "1234567890:ABCdefGHIjklMNOpqrSTUvwxyz12345678";
629
+ const result = sanitizeError(`Token: ${fakeToken}`);
630
+ assert.ok(!result.includes("1234567890:ABC"), "should strip Telegram bot token");
631
+ });
632
+
633
+ test("DiscordAdapter source-level: sendPrompt sets threadUrl in ref", () => {
634
+ const adapterSrc = readFileSync(
635
+ join(__dirname, "..", "..", "remote-questions", "discord-adapter.ts"),
636
+ "utf-8",
637
+ );
638
+ assert.ok(
639
+ adapterSrc.includes("threadUrl: messageUrl"),
640
+ "sendPrompt should set threadUrl to the constructed message URL",
641
+ );
642
+ });
@@ -1,35 +1,23 @@
1
- // ESM resolve hook: .js → .ts rewriting for test environments.
2
- // Only rewrites relative imports from our own source files — not from node_modules.
3
- //
4
- // Handles two patterns:
5
- // 1. .js → .ts (pi bundler convention: source files use .js specifiers)
6
- // 2. extensionless → .ts (some source files omit extensions in relative imports)
1
+ import { fileURLToPath } from 'node:url';
7
2
 
8
- export function resolve(specifier, context, nextResolve) {
9
- const parentURL = context.parentURL || '';
10
- const isFromNodeModules = parentURL.includes('/node_modules/');
11
- const isFromPackages = parentURL.includes('/packages/');
12
-
13
- if (!isFromNodeModules && !isFromPackages && !specifier.startsWith('node:')) {
14
- // Rewrite .js → .ts
15
- if (specifier.endsWith('.js')) {
16
- const tsSpecifier = specifier.replace(/\.js$/, '.ts');
17
- try {
18
- return nextResolve(tsSpecifier, context);
19
- } catch {
20
- // fall through to default resolution
21
- }
22
- }
3
+ const ROOT = new URL("../../../../../", import.meta.url);
4
+ const PACKAGES_ROOT = fileURLToPath(new URL("packages/", ROOT));
23
5
 
24
- // Try adding .ts to extensionless relative imports
25
- if (specifier.startsWith('.') && !/\.[a-z]+$/i.test(specifier)) {
26
- try {
27
- return nextResolve(specifier + '.ts', context);
28
- } catch {
29
- // fall through to default resolution
30
- }
6
+ export function resolve(specifier, context, nextResolve) {
7
+ let tsSpecifier = specifier;
8
+ if (specifier.includes('@gsd/')) {
9
+ tsSpecifier = specifier.replace('@gsd/', PACKAGES_ROOT).replace('/dist/', '/src/');
10
+ if (tsSpecifier.includes('/packages/pi-ai') && !tsSpecifier.endsWith('.ts')) {
11
+ tsSpecifier = tsSpecifier.replace(/\/packages\/pi-ai$/, '/packages/pi-ai/src/index.ts');
12
+ } else if (!tsSpecifier.includes('/src/') && !tsSpecifier.endsWith('.ts')) {
13
+ // Fallback for other gsd packages like pi-coding-agent, pi-tui, pi-agent-core
14
+ tsSpecifier = tsSpecifier.replace(/\/packages\/([^\/]+)$/, '/packages/$1/src/index.ts');
15
+ } else if (!tsSpecifier.endsWith('.ts') && !tsSpecifier.endsWith('.js') && !tsSpecifier.endsWith('.mjs')) {
16
+ tsSpecifier += '/index.ts';
31
17
  }
18
+ } else if (specifier.endsWith('.js')) {
19
+ tsSpecifier = specifier.replace(/\.js$/, '.ts');
32
20
  }
33
21
 
34
- return nextResolve(specifier, context);
22
+ return nextResolve(tsSpecifier, context);
35
23
  }
@@ -1,11 +1,5 @@
1
- // Custom ESM resolver: rewrites .js imports to .ts for node --test with TypeScript sources.
2
- // Usage: node --import ./agent/extensions/gsd/tests/resolve-ts.mjs --test ...
3
- //
4
- // This is needed because pi extension source files use .js import specifiers
5
- // (the pi runtime bundler convention), but only .ts files exist on disk.
6
- // Node's built-in TypeScript support strips types but doesn't rewrite specifiers.
7
-
8
1
  import { register } from 'node:module';
9
2
  import { pathToFileURL } from 'node:url';
10
3
 
11
- register(new URL('./resolve-ts-hooks.mjs', import.meta.url), pathToFileURL('./'));
4
+ // Register hook to redirect imports to the dist directory
5
+ register(new URL('./dist-redirect.mjs', import.meta.url), pathToFileURL('./'));